/*************************************************************************** * Copyright (C) 2010 by Antonio Borneo * * Modified by Megan Wachs from the original stmsmi.c * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ /* The Freedom E SPI controller is a SPI bus controller * specifically designed for SPI Flash Memories on Freedom E platforms. * * Two working modes are available: * - SW mode: the SPI is controlled by SW. Any custom commands can be sent * on the bus. Writes are only possible in this mode. * - HW mode: Memory content is directly * accessible in CPU memory space. CPU can read, write and execute memory * content. */ /* ATTENTION: * To have flash memory mapped in CPU memory space, the controller * must have "HW mode" enabled. * 1) The command "reset init" has to initialize the controller and put * it in HW mode (this is actually the default out of reset for Freedom E systems). * 2) every command in this file have to return to prompt in HW mode. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "imp.h" #include "spi.h" #include #include #include #include "target/riscv/riscv.h" /* Register offsets */ #define FESPI_REG_SCKDIV 0x00 #define FESPI_REG_SCKMODE 0x04 #define FESPI_REG_CSID 0x10 #define FESPI_REG_CSDEF 0x14 #define FESPI_REG_CSMODE 0x18 #define FESPI_REG_DCSSCK 0x28 #define FESPI_REG_DSCKCS 0x2a #define FESPI_REG_DINTERCS 0x2c #define FESPI_REG_DINTERXFR 0x2e #define FESPI_REG_FMT 0x40 #define FESPI_REG_TXFIFO 0x48 #define FESPI_REG_RXFIFO 0x4c #define FESPI_REG_TXCTRL 0x50 #define FESPI_REG_RXCTRL 0x54 #define FESPI_REG_FCTRL 0x60 #define FESPI_REG_FFMT 0x64 #define FESPI_REG_IE 0x70 #define FESPI_REG_IP 0x74 /* Fields */ #define FESPI_SCK_POL 0x1 #define FESPI_SCK_PHA 0x2 #define FESPI_FMT_PROTO(x) ((x) & 0x3) #define FESPI_FMT_ENDIAN(x) (((x) & 0x1) << 2) #define FESPI_FMT_DIR(x) (((x) & 0x1) << 3) #define FESPI_FMT_LEN(x) (((x) & 0xf) << 16) /* TXCTRL register */ #define FESPI_TXWM(x) ((x) & 0xffff) /* RXCTRL register */ #define FESPI_RXWM(x) ((x) & 0xffff) #define FESPI_IP_TXWM 0x1 #define FESPI_IP_RXWM 0x2 #define FESPI_FCTRL_EN 0x1 #define FESPI_INSN_CMD_EN 0x1 #define FESPI_INSN_ADDR_LEN(x) (((x) & 0x7) << 1) #define FESPI_INSN_PAD_CNT(x) (((x) & 0xf) << 4) #define FESPI_INSN_CMD_PROTO(x) (((x) & 0x3) << 8) #define FESPI_INSN_ADDR_PROTO(x) (((x) & 0x3) << 10) #define FESPI_INSN_DATA_PROTO(x) (((x) & 0x3) << 12) #define FESPI_INSN_CMD_CODE(x) (((x) & 0xff) << 16) #define FESPI_INSN_PAD_CODE(x) (((x) & 0xff) << 24) /* Values */ #define FESPI_CSMODE_AUTO 0 #define FESPI_CSMODE_HOLD 2 #define FESPI_CSMODE_OFF 3 #define FESPI_DIR_RX 0 #define FESPI_DIR_TX 1 #define FESPI_PROTO_S 0 #define FESPI_PROTO_D 1 #define FESPI_PROTO_Q 2 #define FESPI_ENDIAN_MSB 0 #define FESPI_ENDIAN_LSB 1 /* Timeout in ms */ #define FESPI_CMD_TIMEOUT (100) #define FESPI_PROBE_TIMEOUT (100) #define FESPI_MAX_TIMEOUT (3000) #define FESPI_READ_REG(a) (_FESPI_READ_REG(a)) #define _FESPI_READ_REG(a) \ { \ int __a; \ uint32_t __v; \ \ __a = target_read_u32(target, ctrl_base + (a), &__v); \ if (__a != ERROR_OK) { \ LOG_ERROR("FESPI_READ_REG error"); \ return __a; \ } \ __v; \ } #define FESPI_WRITE_REG(a, v) \ { \ int __r; \ \ __r = target_write_u32(target, ctrl_base + (a), (v)); \ if (__r != ERROR_OK) { \ LOG_ERROR("FESPI_WRITE_REG error"); \ return __r; \ } \ } #define FESPI_DISABLE_HW_MODE() FESPI_WRITE_REG(FESPI_REG_FCTRL, \ FESPI_READ_REG(FESPI_REG_FCTRL) & ~FESPI_FCTRL_EN) #define FESPI_ENABLE_HW_MODE() FESPI_WRITE_REG(FESPI_REG_FCTRL, \ FESPI_READ_REG(FESPI_REG_FCTRL) | FESPI_FCTRL_EN) struct fespi_flash_bank { int probed; uint32_t ctrl_base; const struct flash_device *dev; }; struct fespi_target { char *name; uint32_t tap_idcode; uint32_t ctrl_base; }; /* TODO !!! What is the right naming convention here? */ static const struct fespi_target target_devices[] = { /* name, tap_idcode, ctrl_base */ { "Freedom E300 SPI Flash", 0x10e31913 , 0x10014000 }, { NULL, 0, 0 } }; FLASH_BANK_COMMAND_HANDLER(fespi_flash_bank_command) { struct fespi_flash_bank *fespi_info; LOG_DEBUG("%s", __func__); if (CMD_ARGC < 6) return ERROR_COMMAND_SYNTAX_ERROR; fespi_info = malloc(sizeof(struct fespi_flash_bank)); if (fespi_info == NULL) { LOG_ERROR("not enough memory"); return ERROR_FAIL; } bank->driver_priv = fespi_info; fespi_info->probed = 0; fespi_info->ctrl_base = 0; if (CMD_ARGC >= 7) { int temp; COMMAND_PARSE_NUMBER(int, CMD_ARGV[6], temp); fespi_info->ctrl_base = (uint32_t) temp; LOG_DEBUG("ASSUMING FESPI device at ctrl_base = 0x%x", fespi_info->ctrl_base); } return ERROR_OK; } static int fespi_set_dir(struct flash_bank *bank, bool dir) { struct target *target = bank->target; struct fespi_flash_bank *fespi_info = bank->driver_priv; uint32_t ctrl_base = fespi_info->ctrl_base; FESPI_WRITE_REG(FESPI_REG_FMT, (FESPI_READ_REG(FESPI_REG_FMT) & ~(FESPI_FMT_DIR(0xFFFFFFFF))) | FESPI_FMT_DIR(dir)); return ERROR_OK; } static int fespi_txwm_wait(struct flash_bank *bank) { struct target *target = bank->target; struct fespi_flash_bank *fespi_info = bank->driver_priv; uint32_t ctrl_base = fespi_info->ctrl_base; int64_t start = timeval_ms(); while (1) { if (FESPI_READ_REG(FESPI_REG_IP) & FESPI_IP_TXWM) break; int64_t now = timeval_ms(); if (now - start > 1000) { LOG_ERROR("ip.txwm didn't get set."); return ERROR_TARGET_TIMEOUT; } } return ERROR_OK; } static int fespi_tx(struct flash_bank *bank, uint8_t in) { struct target *target = bank->target; struct fespi_flash_bank *fespi_info = bank->driver_priv; uint32_t ctrl_base = fespi_info->ctrl_base; int64_t start = timeval_ms(); while (1) { if ((int32_t) FESPI_READ_REG(FESPI_REG_TXFIFO) >= 0) break; int64_t now = timeval_ms(); if (now - start > 1000) { LOG_ERROR("txfifo stayed negative."); return ERROR_TARGET_TIMEOUT; } } FESPI_WRITE_REG(FESPI_REG_TXFIFO, in); return ERROR_OK; } static int fespi_rx(struct flash_bank *bank, uint8_t *out) { struct target *target = bank->target; struct fespi_flash_bank *fespi_info = bank->driver_priv; uint32_t ctrl_base = fespi_info->ctrl_base; int64_t start = timeval_ms(); int32_t value; while (1) { value = (int32_t) FESPI_READ_REG(FESPI_REG_RXFIFO); if (value >= 0) break; int64_t now = timeval_ms(); if (now - start > 1000) { LOG_ERROR("rxfifo didn't go positive (value=0x%x).", value); return ERROR_TARGET_TIMEOUT; } } if (out) *out = value & 0xff; return ERROR_OK; } /* TODO!!! Why don't we need to call this after writing? */ static int fespi_wip(struct flash_bank *bank, int timeout) { struct target *target = bank->target; struct fespi_flash_bank *fespi_info = bank->driver_priv; uint32_t ctrl_base = fespi_info->ctrl_base; int64_t endtime; fespi_set_dir(bank, FESPI_DIR_RX); FESPI_WRITE_REG(FESPI_REG_CSMODE, FESPI_CSMODE_HOLD); endtime = timeval_ms() + timeout; fespi_tx(bank, SPIFLASH_READ_STATUS); if (fespi_rx(bank, NULL) != ERROR_OK) return ERROR_FAIL; do { alive_sleep(1); fespi_tx(bank, 0); uint8_t rx; if (fespi_rx(bank, &rx) != ERROR_OK) return ERROR_FAIL; if ((rx & SPIFLASH_BSY_BIT) == 0) { FESPI_WRITE_REG(FESPI_REG_CSMODE, FESPI_CSMODE_AUTO); fespi_set_dir(bank, FESPI_DIR_TX); return ERROR_OK; } } while (timeval_ms() < endtime); LOG_ERROR("timeout"); return ERROR_FAIL; } static int fespi_erase_sector(struct flash_bank *bank, int sector) { struct target *target = bank->target; struct fespi_flash_bank *fespi_info = bank->driver_priv; uint32_t ctrl_base = fespi_info->ctrl_base; int retval; retval = fespi_tx(bank, SPIFLASH_WRITE_ENABLE); if (retval != ERROR_OK) return retval; retval = fespi_txwm_wait(bank); if (retval != ERROR_OK) return retval; FESPI_WRITE_REG(FESPI_REG_CSMODE, FESPI_CSMODE_HOLD); retval = fespi_tx(bank, fespi_info->dev->erase_cmd); if (retval != ERROR_OK) return retval; sector = bank->sectors[sector].offset; retval = fespi_tx(bank, sector >> 16); if (retval != ERROR_OK) return retval; retval = fespi_tx(bank, sector >> 8); if (retval != ERROR_OK) return retval; retval = fespi_tx(bank, sector); if (retval != ERROR_OK) return retval; retval = fespi_txwm_wait(bank); if (retval != ERROR_OK) return retval; FESPI_WRITE_REG(FESPI_REG_CSMODE, FESPI_CSMODE_AUTO); retval = fespi_wip(bank, FESPI_MAX_TIMEOUT); if (retval != ERROR_OK) return retval; return ERROR_OK; } static int fespi_erase(struct flash_bank *bank, int first, int last) { struct target *target = bank->target; struct fespi_flash_bank *fespi_info = bank->driver_priv; uint32_t ctrl_base = fespi_info->ctrl_base; int retval = ERROR_OK; int sector; LOG_DEBUG("%s: from sector %d to sector %d", __func__, first, last); if (target->state != TARGET_HALTED) { LOG_ERROR("Target not halted"); return ERROR_TARGET_NOT_HALTED; } if ((first < 0) || (last < first) || (last >= bank->num_sectors)) { LOG_ERROR("Flash sector invalid"); return ERROR_FLASH_SECTOR_INVALID; } if (!(fespi_info->probed)) { LOG_ERROR("Flash bank not probed"); return ERROR_FLASH_BANK_NOT_PROBED; } for (sector = first; sector <= last; sector++) { if (bank->sectors[sector].is_protected) { LOG_ERROR("Flash sector %d protected", sector); return ERROR_FAIL; } } FESPI_WRITE_REG(FESPI_REG_TXCTRL, FESPI_TXWM(1)); retval = fespi_txwm_wait(bank); if (retval != ERROR_OK) { LOG_ERROR("WM Didn't go high before attempting."); return retval; } /* Disable Hardware accesses*/ FESPI_DISABLE_HW_MODE(); /* poll WIP */ retval = fespi_wip(bank, FESPI_PROBE_TIMEOUT); if (retval != ERROR_OK) return retval; for (sector = first; sector <= last; sector++) { retval = fespi_erase_sector(bank, sector); if (retval != ERROR_OK) break; keep_alive(); } /* Switch to HW mode before return to prompt */ FESPI_ENABLE_HW_MODE(); return retval; } static int fespi_protect(struct flash_bank *bank, int set, int first, int last) { int sector; for (sector = first; sector <= last; sector++) bank->sectors[sector].is_protected = set; return ERROR_OK; } static int slow_fespi_write_buffer(struct flash_bank *bank, const uint8_t *buffer, uint32_t offset, uint32_t len) { struct target *target = bank->target; struct fespi_flash_bank *fespi_info = bank->driver_priv; uint32_t ctrl_base = fespi_info->ctrl_base; uint32_t ii; /* TODO!!! assert that len < page size */ fespi_tx(bank, SPIFLASH_WRITE_ENABLE); fespi_txwm_wait(bank); FESPI_WRITE_REG(FESPI_REG_CSMODE, FESPI_CSMODE_HOLD); fespi_tx(bank, SPIFLASH_PAGE_PROGRAM); fespi_tx(bank, offset >> 16); fespi_tx(bank, offset >> 8); fespi_tx(bank, offset); for (ii = 0; ii < len; ii++) fespi_tx(bank, buffer[ii]); fespi_txwm_wait(bank); FESPI_WRITE_REG(FESPI_REG_CSMODE, FESPI_CSMODE_AUTO); keep_alive(); return ERROR_OK; } /* * Here's the source for the algorithm. * You can turn it into the array below using: sed -n '/ALGO_START$/,/ALGO_END/ p' fespi.c | \ riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -x \ assembler-with-cpp - -nostdlib -nostartfiles -o tmp.o && \ riscv64-unknown-elf-objcopy -O binary tmp.o algorithm.bin && \ xxd -i algorithm.bin // ALGO_START #define SPIFLASH_READ_STATUS 0x05 // Read Status Register #define SPIFLASH_BSY_BIT 0x00000001 // WIP Bit of SPI SR on SMI SR // Register offsets #define FESPI_REG_FMT 0x40 #define FESPI_REG_TXFIFO 0x48 #define FESPI_REG_RXFIFO 0x4c #define FESPI_REG_IP 0x74 // Fields #define FESPI_IP_TXWM 0x1 #define FESPI_FMT_DIR(x) (((x) & 0x1) << 3) // To enter, jump to the start of command_table (ie. offset 0). // a0 - FESPI base address // a1 - start address of buffer // The buffer contains a "program" in byte sequences. The first byte in a // sequence determines the operation. Some operation will read more data from // the program, while some will not. The operation byte is the offset into // command_table, so eg. 4 means exit, 8 means transmit, and so on. .global _start _start: command_table: j main // 0 ebreak // 4 j tx // 8 j txwm_wait // 12 j write_reg // 16 j wip_wait // 20 j set_dir // 24 // Execute the program. main: lbu t0, 0(a1) addi a1, a1, 1 la t1, command_table add t0, t0, t1 jr t0 // Read 1 byte the contains the number of bytes to transmit. Then read those // bytes from the program and transmit them one by one. tx: lbu t1, 0(a1) // read number of bytes to transmit addi a1, a1, 1 1: lw t0, FESPI_REG_TXFIFO(a0) // wait for FIFO clear bltz t0, 1b lbu t0, 0(a1) // Load byte to write sw t0, FESPI_REG_TXFIFO(a0) addi a1, a1, 1 addi t1, t1, -1 bgtz t1, 1b j main // Wait until TXWM is set. txwm_wait: 1: lw t0, FESPI_REG_IP(a0) andi t0, t0, FESPI_IP_TXWM beqz t0, 1b j main // Read 1 byte that contains the offset of the register to write, and 1 byte // that contains the data to write. write_reg: lbu t0, 0(a1) // read register to write add t0, t0, a0 lbu t1, 1(a1) // read value to write addi a1, a1, 2 sw t1, 0(t0) j main wip_wait: li a2, SPIFLASH_READ_STATUS jal txrx_byte // discard first result 1: li a2, 0 jal txrx_byte andi t0, a2, SPIFLASH_BSY_BIT bnez t0, 1b j main txrx_byte: // transmit the byte in a2, receive a bit into a2 lw t0, FESPI_REG_TXFIFO(a0) // wait for FIFO clear bltz t0, txrx_byte sw a2, FESPI_REG_TXFIFO(a0) 1: lw a2, FESPI_REG_RXFIFO(a0) bltz a2, 1b ret set_dir: lw t0, FESPI_REG_FMT(a0) li t1, ~(FESPI_FMT_DIR(0xFFFFFFFF)) and t0, t0, t1 lbu t1, 0(a1) // read value to OR in addi a1, a1, 1 or t0, t0, t1 sw t0, FESPI_REG_FMT(a0) j main // ALGO_END */ static const uint8_t algorithm_bin[] = { 0x6f, 0x00, 0xc0, 0x01, 0x73, 0x00, 0x10, 0x00, 0x6f, 0x00, 0xc0, 0x02, 0x6f, 0x00, 0x00, 0x05, 0x6f, 0x00, 0xc0, 0x05, 0x6f, 0x00, 0x00, 0x07, 0x6f, 0x00, 0x00, 0x0a, 0x83, 0xc2, 0x05, 0x00, 0x93, 0x85, 0x15, 0x00, 0x17, 0x03, 0x00, 0x00, 0x13, 0x03, 0xc3, 0xfd, 0xb3, 0x82, 0x62, 0x00, 0x67, 0x80, 0x02, 0x00, 0x03, 0xc3, 0x05, 0x00, 0x93, 0x85, 0x15, 0x00, 0x83, 0x22, 0x85, 0x04, 0xe3, 0xce, 0x02, 0xfe, 0x83, 0xc2, 0x05, 0x00, 0x23, 0x24, 0x55, 0x04, 0x93, 0x85, 0x15, 0x00, 0x13, 0x03, 0xf3, 0xff, 0xe3, 0x44, 0x60, 0xfe, 0x6f, 0xf0, 0x5f, 0xfc, 0x83, 0x22, 0x45, 0x07, 0x93, 0xf2, 0x12, 0x00, 0xe3, 0x8c, 0x02, 0xfe, 0x6f, 0xf0, 0x5f, 0xfb, 0x83, 0xc2, 0x05, 0x00, 0xb3, 0x82, 0xa2, 0x00, 0x03, 0xc3, 0x15, 0x00, 0x93, 0x85, 0x25, 0x00, 0x23, 0xa0, 0x62, 0x00, 0x6f, 0xf0, 0xdf, 0xf9, 0x13, 0x06, 0x50, 0x00, 0xef, 0x00, 0x80, 0x01, 0x13, 0x06, 0x00, 0x00, 0xef, 0x00, 0x00, 0x01, 0x93, 0x72, 0x16, 0x00, 0xe3, 0x9a, 0x02, 0xfe, 0x6f, 0xf0, 0x1f, 0xf8, 0x83, 0x22, 0x85, 0x04, 0xe3, 0xce, 0x02, 0xfe, 0x23, 0x24, 0xc5, 0x04, 0x03, 0x26, 0xc5, 0x04, 0xe3, 0x4e, 0x06, 0xfe, 0x67, 0x80, 0x00, 0x00, 0x83, 0x22, 0x05, 0x04, 0x13, 0x03, 0x70, 0xff, 0xb3, 0xf2, 0x62, 0x00, 0x03, 0xc3, 0x05, 0x00, 0x93, 0x85, 0x15, 0x00, 0xb3, 0xe2, 0x62, 0x00, 0x23, 0x20, 0x55, 0x04, 0x6f, 0xf0, 0x9f, 0xf4 }; #define STEP_EXIT 4 #define STEP_TX 8 #define STEP_TXWM_WAIT 12 #define STEP_WRITE_REG 16 #define STEP_WIP_WAIT 20 #define STEP_SET_DIR 24 #define STEP_NOP 0xff struct algorithm_steps { unsigned size; unsigned used; uint8_t **steps; }; static struct algorithm_steps *as_new(unsigned size) { struct algorithm_steps *as = calloc(1, sizeof(struct algorithm_steps)); as->size = size; as->steps = calloc(size, sizeof(as->steps[0])); return as; } static struct algorithm_steps *as_delete(struct algorithm_steps *as) { for (unsigned step = 0; step < as->used; step++) { free(as->steps[step]); as->steps[step] = NULL; } free(as); return NULL; } static int as_empty(struct algorithm_steps *as) { for (unsigned s = 0; s < as->used; s++) { if (as->steps[s][0] != STEP_NOP) return 0; } return 1; } /* Return size of compiled program. */ static unsigned as_compile(struct algorithm_steps *as, uint8_t *target, unsigned target_size) { unsigned offset = 0; bool finish_early = false; for (unsigned s = 0; s < as->used && !finish_early; s++) { unsigned bytes_left = target_size - offset; switch (as->steps[s][0]) { case STEP_NOP: break; case STEP_TX: { unsigned size = as->steps[s][1]; if (size + 3 > bytes_left) { finish_early = true; break; } memcpy(target + offset, as->steps[s], size + 2); offset += size + 2; break; } case STEP_WRITE_REG: if (4 > bytes_left) { finish_early = true; break; } memcpy(target + offset, as->steps[s], 3); offset += 3; break; case STEP_SET_DIR: if (3 > bytes_left) { finish_early = true; break; } memcpy(target + offset, as->steps[s], 2); offset += 2; break; case STEP_TXWM_WAIT: case STEP_WIP_WAIT: if (2 > bytes_left) { finish_early = true; break; } memcpy(target + offset, as->steps[s], 1); offset += 1; break; default: assert(0); } if (!finish_early) as->steps[s][0] = STEP_NOP; } assert(offset + 1 <= target_size); target[offset++] = STEP_EXIT; LOG_DEBUG("%d-byte program:", offset); for (unsigned i = 0; i < offset;) { char buf[80]; for (unsigned x = 0; i < offset && x < 16; x++, i++) sprintf(buf + x*3, "%02x ", target[i]); LOG_DEBUG("%s", buf); } return offset; } static void as_add_tx(struct algorithm_steps *as, unsigned count, const uint8_t *data) { LOG_DEBUG("count=%d", count); while (count > 0) { unsigned step_count = MIN(count, 255); assert(as->used < as->size); as->steps[as->used] = malloc(step_count + 2); as->steps[as->used][0] = STEP_TX; as->steps[as->used][1] = step_count; memcpy(as->steps[as->used] + 2, data, step_count); as->used++; data += step_count; count -= step_count; } } static void as_add_tx1(struct algorithm_steps *as, uint8_t byte) { uint8_t data[1]; data[0] = byte; as_add_tx(as, 1, data); } static void as_add_write_reg(struct algorithm_steps *as, uint8_t offset, uint8_t data) { assert(as->used < as->size); as->steps[as->used] = malloc(3); as->steps[as->used][0] = STEP_WRITE_REG; as->steps[as->used][1] = offset; as->steps[as->used][2] = data; as->used++; } static void as_add_txwm_wait(struct algorithm_steps *as) { assert(as->used < as->size); as->steps[as->used] = malloc(1); as->steps[as->used][0] = STEP_TXWM_WAIT; as->used++; } static void as_add_wip_wait(struct algorithm_steps *as) { assert(as->used < as->size); as->steps[as->used] = malloc(1); as->steps[as->used][0] = STEP_WIP_WAIT; as->used++; } static void as_add_set_dir(struct algorithm_steps *as, bool dir) { assert(as->used < as->size); as->steps[as->used] = malloc(2); as->steps[as->used][0] = STEP_SET_DIR; as->steps[as->used][1] = FESPI_FMT_DIR(dir); as->used++; } /* This should write something less than or equal to a page.*/ static int steps_add_buffer_write(struct algorithm_steps *as, const uint8_t *buffer, uint32_t chip_offset, uint32_t len) { as_add_tx1(as, SPIFLASH_WRITE_ENABLE); as_add_txwm_wait(as); as_add_write_reg(as, FESPI_REG_CSMODE, FESPI_CSMODE_HOLD); uint8_t setup[] = { SPIFLASH_PAGE_PROGRAM, chip_offset >> 16, chip_offset >> 8, chip_offset, }; as_add_tx(as, sizeof(setup), setup); as_add_tx(as, len, buffer); as_add_txwm_wait(as); as_add_write_reg(as, FESPI_REG_CSMODE, FESPI_CSMODE_AUTO); /* fespi_wip() */ as_add_set_dir(as, FESPI_DIR_RX); as_add_write_reg(as, FESPI_REG_CSMODE, FESPI_CSMODE_HOLD); as_add_wip_wait(as); as_add_write_reg(as, FESPI_REG_CSMODE, FESPI_CSMODE_AUTO); as_add_set_dir(as, FESPI_DIR_TX); return ERROR_OK; } static int steps_execute(struct algorithm_steps *as, struct flash_bank *bank, struct working_area *algorithm_wa, struct working_area *data_wa) { struct target *target = bank->target; struct fespi_flash_bank *fespi_info = bank->driver_priv; uint32_t ctrl_base = fespi_info->ctrl_base; uint8_t *data_buf = malloc(data_wa->size); int xlen = riscv_xlen(target); struct reg_param reg_params[2]; init_reg_param(®_params[0], "a0", xlen, PARAM_OUT); init_reg_param(®_params[1], "a1", xlen, PARAM_OUT); buf_set_u64(reg_params[0].value, 0, xlen, ctrl_base); buf_set_u64(reg_params[1].value, 0, xlen, data_wa->address); while (!as_empty(as)) { keep_alive(); unsigned bytes = as_compile(as, data_buf, data_wa->size); int retval = target_write_buffer(target, data_wa->address, bytes, data_buf); if (retval != ERROR_OK) { LOG_ERROR("Failed to write data to 0x%" TARGET_PRIxADDR ": %d", data_wa->address, retval); return retval; } retval = target_run_algorithm(target, 0, NULL, 2, reg_params, algorithm_wa->address, algorithm_wa->address + 4, 10000, NULL); if (retval != ERROR_OK) { LOG_ERROR("Failed to execute algorithm at 0x%" TARGET_PRIxADDR ": %d", algorithm_wa->address, retval); return retval; } } return ERROR_OK; } static int fespi_write(struct flash_bank *bank, const uint8_t *buffer, uint32_t offset, uint32_t count) { struct target *target = bank->target; struct fespi_flash_bank *fespi_info = bank->driver_priv; uint32_t ctrl_base = fespi_info->ctrl_base; uint32_t cur_count, page_size, page_offset; int sector; int retval = ERROR_OK; LOG_DEBUG("%s: offset=0x%08" PRIx32 " count=0x%08" PRIx32, __func__, offset, count); if (target->state != TARGET_HALTED) { LOG_ERROR("Target not halted"); return ERROR_TARGET_NOT_HALTED; } if (offset + count > fespi_info->dev->size_in_bytes) { LOG_WARNING("Write past end of flash. Extra data discarded."); count = fespi_info->dev->size_in_bytes - offset; } /* Check sector protection */ for (sector = 0; sector < bank->num_sectors; sector++) { /* Start offset in or before this sector? */ /* End offset in or behind this sector? */ if ((offset < (bank->sectors[sector].offset + bank->sectors[sector].size)) && ((offset + count - 1) >= bank->sectors[sector].offset) && bank->sectors[sector].is_protected) { LOG_ERROR("Flash sector %d protected", sector); return ERROR_FAIL; } } struct working_area *algorithm_wa; if (target_alloc_working_area(target, sizeof(algorithm_bin), &algorithm_wa) != ERROR_OK) { LOG_WARNING("Couldn't allocate %zd-byte working area.", sizeof(algorithm_bin)); algorithm_wa = NULL; } else { retval = target_write_buffer(target, algorithm_wa->address, sizeof(algorithm_bin), algorithm_bin); if (retval != ERROR_OK) { LOG_ERROR("Failed to write code to 0x%" TARGET_PRIxADDR ": %d", algorithm_wa->address, retval); target_free_working_area(target, algorithm_wa); algorithm_wa = NULL; } } struct working_area *data_wa = NULL; unsigned data_wa_size = 2 * count; while (1) { if (data_wa_size < 128) { LOG_WARNING("Couldn't allocate data working area."); target_free_working_area(target, algorithm_wa); algorithm_wa = NULL; } if (target_alloc_working_area_try(target, data_wa_size, &data_wa) == ERROR_OK) { break; } data_wa_size /= 2; } page_size = fespi_info->dev->pagesize; fespi_txwm_wait(bank); /* Disable Hardware accesses*/ FESPI_DISABLE_HW_MODE(); /* poll WIP */ retval = fespi_wip(bank, FESPI_PROBE_TIMEOUT); if (retval != ERROR_OK) return retval; struct algorithm_steps *as = as_new(count / 4); /* unaligned buffer head */ if (count > 0 && (offset & 3) != 0) { cur_count = 4 - (offset & 3); if (cur_count > count) cur_count = count; if (algorithm_wa) retval = steps_add_buffer_write(as, buffer, offset, cur_count); else retval = slow_fespi_write_buffer(bank, buffer, offset, cur_count); if (retval != ERROR_OK) goto err; offset += cur_count; buffer += cur_count; count -= cur_count; } page_offset = offset % page_size; /* central part, aligned words */ while (count >= 4) { /* clip block at page boundary */ if (page_offset + count > page_size) cur_count = page_size - page_offset; else cur_count = count & ~3; if (algorithm_wa) retval = steps_add_buffer_write(as, buffer, offset, cur_count); else retval = slow_fespi_write_buffer(bank, buffer, offset, cur_count); if (retval != ERROR_OK) goto err; page_offset = 0; buffer += cur_count; offset += cur_count; count -= cur_count; } /* buffer tail */ if (count > 0) { if (algorithm_wa) retval = steps_add_buffer_write(as, buffer, offset, count); else retval = slow_fespi_write_buffer(bank, buffer, offset, count); if (retval != ERROR_OK) goto err; } if (algorithm_wa) retval = steps_execute(as, bank, algorithm_wa, data_wa); err: if (algorithm_wa) { target_free_working_area(target, data_wa); target_free_working_area(target, algorithm_wa); } as_delete(as); /* Switch to HW mode before return to prompt */ FESPI_ENABLE_HW_MODE(); return retval; } /* Return ID of flash device */ /* On exit, SW mode is kept */ static int fespi_read_flash_id(struct flash_bank *bank, uint32_t *id) { struct target *target = bank->target; struct fespi_flash_bank *fespi_info = bank->driver_priv; uint32_t ctrl_base = fespi_info->ctrl_base; int retval; if (target->state != TARGET_HALTED) { LOG_ERROR("Target not halted"); return ERROR_TARGET_NOT_HALTED; } fespi_txwm_wait(bank); /* Disable Hardware accesses*/ FESPI_DISABLE_HW_MODE(); /* poll WIP */ retval = fespi_wip(bank, FESPI_PROBE_TIMEOUT); if (retval != ERROR_OK) return retval; fespi_set_dir(bank, FESPI_DIR_RX); /* Send SPI command "read ID" */ FESPI_WRITE_REG(FESPI_REG_CSMODE, FESPI_CSMODE_HOLD); fespi_tx(bank, SPIFLASH_READ_ID); /* Send dummy bytes to actually read the ID.*/ fespi_tx(bank, 0); fespi_tx(bank, 0); fespi_tx(bank, 0); /* read ID from Receive Register */ *id = 0; if (fespi_rx(bank, NULL) != ERROR_OK) return ERROR_FAIL; uint8_t rx; if (fespi_rx(bank, &rx) != ERROR_OK) return ERROR_FAIL; *id = rx; if (fespi_rx(bank, &rx) != ERROR_OK) return ERROR_FAIL; *id |= (rx << 8); if (fespi_rx(bank, &rx) != ERROR_OK) return ERROR_FAIL; *id |= (rx << 16); FESPI_WRITE_REG(FESPI_REG_CSMODE, FESPI_CSMODE_AUTO); fespi_set_dir(bank, FESPI_DIR_TX); return ERROR_OK; } static int fespi_probe(struct flash_bank *bank) { struct target *target = bank->target; struct fespi_flash_bank *fespi_info = bank->driver_priv; uint32_t ctrl_base; struct flash_sector *sectors; uint32_t id = 0; /* silence uninitialized warning */ const struct fespi_target *target_device; int retval; if (fespi_info->probed) free(bank->sectors); fespi_info->probed = 0; if (fespi_info->ctrl_base == 0) { for (target_device = target_devices ; target_device->name ; ++target_device) if (target_device->tap_idcode == target->tap->idcode) break; if (!target_device->name) { LOG_ERROR("Device ID 0x%" PRIx32 " is not known as FESPI capable", target->tap->idcode); return ERROR_FAIL; } fespi_info->ctrl_base = target_device->ctrl_base; LOG_DEBUG("Valid FESPI on device %s at address 0x%" PRIx32, target_device->name, bank->base); } else { LOG_DEBUG("Assuming FESPI as specified at address 0x%x with ctrl at 0x%x", fespi_info->ctrl_base, bank->base); } ctrl_base = fespi_info->ctrl_base; /* read and decode flash ID; returns in SW mode */ FESPI_WRITE_REG(FESPI_REG_TXCTRL, FESPI_TXWM(1)); fespi_set_dir(bank, FESPI_DIR_TX); retval = fespi_read_flash_id(bank, &id); FESPI_ENABLE_HW_MODE(); if (retval != ERROR_OK) return retval; fespi_info->dev = NULL; for (const struct flash_device *p = flash_devices; p->name ; p++) if (p->device_id == id) { fespi_info->dev = p; break; } if (!fespi_info->dev) { LOG_ERROR("Unknown flash device (ID 0x%08" PRIx32 ")", id); return ERROR_FAIL; } LOG_INFO("Found flash device \'%s\' (ID 0x%08" PRIx32 ")", fespi_info->dev->name, fespi_info->dev->device_id); /* Set correct size value */ bank->size = fespi_info->dev->size_in_bytes; /* create and fill sectors array */ bank->num_sectors = fespi_info->dev->size_in_bytes / fespi_info->dev->sectorsize; sectors = malloc(sizeof(struct flash_sector) * bank->num_sectors); if (sectors == NULL) { LOG_ERROR("not enough memory"); return ERROR_FAIL; } for (int sector = 0; sector < bank->num_sectors; sector++) { sectors[sector].offset = sector * fespi_info->dev->sectorsize; sectors[sector].size = fespi_info->dev->sectorsize; sectors[sector].is_erased = -1; sectors[sector].is_protected = 1; } bank->sectors = sectors; fespi_info->probed = 1; return ERROR_OK; } static int fespi_auto_probe(struct flash_bank *bank) { struct fespi_flash_bank *fespi_info = bank->driver_priv; if (fespi_info->probed) return ERROR_OK; return fespi_probe(bank); } static int fespi_protect_check(struct flash_bank *bank) { /* Nothing to do. Protection is only handled in SW. */ return ERROR_OK; } static int get_fespi_info(struct flash_bank *bank, char *buf, int buf_size) { struct fespi_flash_bank *fespi_info = bank->driver_priv; if (!(fespi_info->probed)) { snprintf(buf, buf_size, "\nFESPI flash bank not probed yet\n"); return ERROR_OK; } snprintf(buf, buf_size, "\nFESPI flash information:\n" " Device \'%s\' (ID 0x%08" PRIx32 ")\n", fespi_info->dev->name, fespi_info->dev->device_id); return ERROR_OK; } struct flash_driver fespi_flash = { .name = "fespi", .flash_bank_command = fespi_flash_bank_command, .erase = fespi_erase, .protect = fespi_protect, .write = fespi_write, .read = default_flash_read, .probe = fespi_probe, .auto_probe = fespi_auto_probe, .erase_check = default_flash_blank_check, .protect_check = fespi_protect_check, .info = get_fespi_info };