From: Tim Newsome Date: Tue, 1 Jan 2019 20:37:34 +0000 (-0800) Subject: Add flash support for SiFive's Freedom E platforms X-Git-Tag: v0.11.0-rc1~879 X-Git-Url: https://review.openocd.org/gitweb?p=openocd.git;a=commitdiff_plain;h=31ea7037b3df7e567a9eb34d28947d118c4cada1 Add flash support for SiFive's Freedom E platforms Valgrind and Clang Static Analyzer have no complaints about this change. Change-Id: I7757615ec52448372bdc57729cdf97c7016d97e8 Signed-off-by: Tim Newsome Reviewed-on: http://openocd.zylin.com/4656 Tested-by: jenkins Reviewed-by: Philipp Guehring Reviewed-by: Tomas Vanek --- diff --git a/README b/README index 30443d37c7..00e83bbf4a 100644 --- a/README +++ b/README @@ -123,12 +123,12 @@ EJTAG, NDS32, XScale, Intel Quark. Flash drivers ------------- -ADUC702x, AT91SAM, ATH79, AVR, CFI, DSP5680xx, EFM32, EM357, eSi-TSMC, -FM3, FM4, Kinetis, LPC8xx/LPC1xxx/LPC2xxx/LPC541xx, LPC2900, LPCSPIFI, -Marvell QSPI, Milandr, NIIET, NuMicro, PIC32mx, PSoC4, PSoC5LP, SiM3x, -Stellaris, STM32, STMSMI, STR7x, STR9x, nRF51; NAND controllers of -AT91SAM9, LPC3180, LPC32xx, i.MX31, MXC, NUC910, Orion/Kirkwood, -S3C24xx, S3C6400, XMC1xxx, XMC4xxx. +ADUC702x, AT91SAM, ATH79, AVR, CFI, DSP5680xx, EFM32, EM357, eSi-TSMC, FM3, +FM4, Freedom E SPI, Kinetis, LPC8xx/LPC1xxx/LPC2xxx/LPC541xx, LPC2900, +LPCSPIFI, Marvell QSPI, Milandr, NIIET, NuMicro, PIC32mx, PSoC4, PSoC5LP, +SiM3x, Stellaris, STM32, STMSMI, STR7x, STR9x, nRF51; NAND controllers of +AT91SAM9, LPC3180, LPC32xx, i.MX31, MXC, NUC910, Orion/Kirkwood, S3C24xx, +S3C6400, XMC1xxx, XMC4xxx. ================== diff --git a/contrib/loaders/flash/fespi/Makefile b/contrib/loaders/flash/fespi/Makefile new file mode 100644 index 0000000000..4d2ab51d76 --- /dev/null +++ b/contrib/loaders/flash/fespi/Makefile @@ -0,0 +1,28 @@ +BIN2C = ../../../../src/helper/bin2char.sh + +CROSS_COMPILE ?= riscv64-unknown-elf- + +CC=$(CROSS_COMPILE)gcc +OBJCOPY=$(CROSS_COMPILE)objcopy +OBJDUMP=$(CROSS_COMPILE)objdump + +CFLAGS = -march=rv32i -mabi=ilp32 -x assembler-with-cpp -nostdlib -nostartfiles + +all: fespi.inc + +.PHONY: clean + +%.elf: %.S + $(CC) $(CFLAGS) $< -o $@ + +%.lst: %.elf + $(OBJDUMP) -S $< > $@ + +%.bin: %.elf + $(OBJCOPY) -Obinary $< $@ + +%.inc: %.bin + $(BIN2C) < $< > $@ + +clean: + -rm -f *.elf *.lst *.bin *.inc diff --git a/contrib/loaders/flash/fespi/fespi.S b/contrib/loaders/flash/fespi/fespi.S new file mode 100644 index 0000000000..d68e65ef81 --- /dev/null +++ b/contrib/loaders/flash/fespi/fespi.S @@ -0,0 +1,99 @@ +#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 diff --git a/contrib/loaders/flash/fespi/fespi.inc b/contrib/loaders/flash/fespi/fespi.inc new file mode 100644 index 0000000000..768bdc5981 --- /dev/null +++ b/contrib/loaders/flash/fespi/fespi.inc @@ -0,0 +1,15 @@ +/* Autogenerated with ../../../../src/helper/bin2char.sh */ +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, diff --git a/doc/openocd.texi b/doc/openocd.texi index 8cc3f4ad53..bede1c8c9e 100644 --- a/doc/openocd.texi +++ b/doc/openocd.texi @@ -5365,6 +5365,17 @@ flash bank flash2 ath79 0x20000000 0 0 0 $_TARGETNAME cs2 @end deffn +@deffn {Flash Driver} fespi +@cindex Freedom E SPI +@cindex fespi + +SiFive's Freedom E SPI controller, used in HiFive and other boards. + +@example +flash bank $_FLASHNAME fespi 0x20000000 0 0 0 $_TARGETNAME +@end example +@end deffn + @subsection Internal Flash (Microcontrollers) @deffn {Flash Driver} aduc702x diff --git a/src/flash/nor/Makefile.am b/src/flash/nor/Makefile.am index 7c353c4adb..bbdc312b5e 100644 --- a/src/flash/nor/Makefile.am +++ b/src/flash/nor/Makefile.am @@ -27,6 +27,7 @@ NOR_DRIVERS = \ %D%/em357.c \ %D%/esirisc_flash.c \ %D%/faux.c \ + %D%/fespi.c \ %D%/fm3.c \ %D%/fm4.c \ %D%/jtagspi.c \ diff --git a/src/flash/nor/drivers.c b/src/flash/nor/drivers.c index 1c456ad59c..d1bbb2771e 100644 --- a/src/flash/nor/drivers.c +++ b/src/flash/nor/drivers.c @@ -42,6 +42,7 @@ extern struct flash_driver esirisc_flash; extern struct flash_driver faux_flash; extern struct flash_driver fm3_flash; extern struct flash_driver fm4_flash; +extern struct flash_driver fespi_flash; extern struct flash_driver jtagspi_flash; extern struct flash_driver kinetis_flash; extern struct flash_driver kinetis_ke_flash; @@ -109,6 +110,7 @@ static struct flash_driver *flash_drivers[] = { &faux_flash, &fm3_flash, &fm4_flash, + &fespi_flash, &jtagspi_flash, &kinetis_flash, &kinetis_ke_flash, diff --git a/src/flash/nor/fespi.c b/src/flash/nor/fespi.c new file mode 100644 index 0000000000..a07972edd5 --- /dev/null +++ b/src/flash/nor/fespi.c @@ -0,0 +1,1053 @@ +/*************************************************************************** + * 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 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) + + +struct fespi_flash_bank { + int probed; + target_addr_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%" TARGET_PRIxADDR, + fespi_info->ctrl_base); + } + + return ERROR_OK; +} + +static int fespi_read_reg(struct flash_bank *bank, uint32_t *value, target_addr_t address) +{ + struct target *target = bank->target; + struct fespi_flash_bank *fespi_info = bank->driver_priv; + + int result = target_read_u32(target, fespi_info->ctrl_base + address, value); + if (result != ERROR_OK) { + LOG_ERROR("fespi_read_reg() error at 0x%" TARGET_PRIxADDR, + fespi_info->ctrl_base + address); + return result; + } + return ERROR_OK; +} + +static int fespi_write_reg(struct flash_bank *bank, target_addr_t address, uint32_t value) +{ \ + struct target *target = bank->target; + struct fespi_flash_bank *fespi_info = bank->driver_priv; + + int result = target_write_u32(target, fespi_info->ctrl_base + address, value); + if (result != ERROR_OK) { + LOG_ERROR("fespi_write_reg() error writing 0x%x to 0x%" TARGET_PRIxADDR, + value, fespi_info->ctrl_base + address); + return result; + } + return ERROR_OK; +} + +static int fespi_disable_hw_mode(struct flash_bank *bank) +{ + uint32_t fctrl; + if (fespi_read_reg(bank, &fctrl, FESPI_REG_FCTRL) != ERROR_OK) + return ERROR_FAIL; + return fespi_write_reg(bank, FESPI_REG_FCTRL, fctrl & ~FESPI_FCTRL_EN); +} + +static int fespi_enable_hw_mode(struct flash_bank *bank) +{ + uint32_t fctrl; + if (fespi_read_reg(bank, &fctrl, FESPI_REG_FCTRL) != ERROR_OK) + return ERROR_FAIL; + return fespi_write_reg(bank, FESPI_REG_FCTRL, fctrl | FESPI_FCTRL_EN); +} + +static int fespi_set_dir(struct flash_bank *bank, bool dir) +{ + uint32_t fmt; + if (fespi_read_reg(bank, &fmt, FESPI_REG_FMT) != ERROR_OK) + return ERROR_FAIL; + + return fespi_write_reg(bank, FESPI_REG_FMT, + (fmt & ~(FESPI_FMT_DIR(0xFFFFFFFF))) | FESPI_FMT_DIR(dir)); +} + +static int fespi_txwm_wait(struct flash_bank *bank) +{ + int64_t start = timeval_ms(); + + while (1) { + uint32_t ip; + if (fespi_read_reg(bank, &ip, FESPI_REG_IP) != ERROR_OK) + return ERROR_FAIL; + if (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) +{ + int64_t start = timeval_ms(); + + while (1) { + uint32_t txfifo; + if (fespi_read_reg(bank, &txfifo, FESPI_REG_TXFIFO) != ERROR_OK) + return ERROR_FAIL; + if (!(txfifo >> 31)) + break; + int64_t now = timeval_ms(); + if (now - start > 1000) { + LOG_ERROR("txfifo stayed negative."); + return ERROR_TARGET_TIMEOUT; + } + } + + return fespi_write_reg(bank, FESPI_REG_TXFIFO, in); +} + +static int fespi_rx(struct flash_bank *bank, uint8_t *out) +{ + int64_t start = timeval_ms(); + uint32_t value; + + while (1) { + if (fespi_read_reg(bank, &value, FESPI_REG_RXFIFO) != ERROR_OK) + return ERROR_FAIL; + if (!(value >> 31)) + 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) +{ + int64_t endtime; + + fespi_set_dir(bank, FESPI_DIR_RX); + + if (fespi_write_reg(bank, FESPI_REG_CSMODE, FESPI_CSMODE_HOLD) != ERROR_OK) + return ERROR_FAIL; + 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) { + if (fespi_write_reg(bank, FESPI_REG_CSMODE, FESPI_CSMODE_AUTO) != ERROR_OK) + return ERROR_FAIL; + 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 fespi_flash_bank *fespi_info = bank->driver_priv; + 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; + + if (fespi_write_reg(bank, FESPI_REG_CSMODE, FESPI_CSMODE_HOLD) != ERROR_OK) + return ERROR_FAIL; + 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; + if (fespi_write_reg(bank, FESPI_REG_CSMODE, FESPI_CSMODE_AUTO) != ERROR_OK) + return ERROR_FAIL; + + 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; + 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; + } + } + + if (fespi_info->dev->erase_cmd == 0x00) + return ERROR_FLASH_OPER_UNSUPPORTED; + + if (fespi_write_reg(bank, FESPI_REG_TXCTRL, FESPI_TXWM(1)) != ERROR_OK) + return ERROR_FAIL; + retval = fespi_txwm_wait(bank); + if (retval != ERROR_OK) { + LOG_ERROR("WM Didn't go high before attempting."); + return retval; + } + + /* Disable Hardware accesses*/ + if (fespi_disable_hw_mode(bank) != ERROR_OK) + return ERROR_FAIL; + + /* poll WIP */ + retval = fespi_wip(bank, FESPI_PROBE_TIMEOUT); + if (retval != ERROR_OK) + goto done; + + for (sector = first; sector <= last; sector++) { + retval = fespi_erase_sector(bank, sector); + if (retval != ERROR_OK) + goto done; + keep_alive(); + } + + /* Switch to HW mode before return to prompt */ +done: + if (fespi_enable_hw_mode(bank) != ERROR_OK) + return ERROR_FAIL; + 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) +{ + uint32_t ii; + + if (offset & 0xFF000000) { + LOG_ERROR("FESPI interface does not support greater than 3B addressing, can't write to offset 0x%x", + offset); + return ERROR_FAIL; + } + + /* TODO!!! assert that len < page size */ + + fespi_tx(bank, SPIFLASH_WRITE_ENABLE); + fespi_txwm_wait(bank); + + if (fespi_write_reg(bank, FESPI_REG_CSMODE, FESPI_CSMODE_HOLD) != ERROR_OK) + return ERROR_FAIL; + + 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); + + if (fespi_write_reg(bank, FESPI_REG_CSMODE, FESPI_CSMODE_AUTO) != ERROR_OK) + return ERROR_FAIL; + + keep_alive(); + + return ERROR_OK; +} + +static const uint8_t algorithm_bin[] = { +#include "../../../contrib/loaders/flash/fespi/fespi.inc" +}; +#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(void) +{ + struct algorithm_steps *as = calloc(1, sizeof(struct algorithm_steps)); + as->size = 8; + as->steps = malloc(as->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->steps); + 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_step(struct algorithm_steps *as, uint8_t *step) +{ + if (as->used == as->size) { + as->size *= 2; + as->steps = realloc(as->steps, sizeof(as->steps[0]) * as->size); + LOG_DEBUG("Increased size to 0x%x", as->size); + } + as->steps[as->used] = step; + as->used++; +} + +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); + uint8_t *step = malloc(step_count + 2); + step[0] = STEP_TX; + step[1] = step_count; + memcpy(step + 2, data, step_count); + as_add_step(as, step); + 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) +{ + uint8_t *step = malloc(3); + step[0] = STEP_WRITE_REG; + step[1] = offset; + step[2] = data; + as_add_step(as, step); +} + +static void as_add_txwm_wait(struct algorithm_steps *as) +{ + uint8_t *step = malloc(1); + step[0] = STEP_TXWM_WAIT; + as_add_step(as, step); +} + +static void as_add_wip_wait(struct algorithm_steps *as) +{ + uint8_t *step = malloc(1); + step[0] = STEP_WIP_WAIT; + as_add_step(as, step); +} + +static void as_add_set_dir(struct algorithm_steps *as, bool dir) +{ + uint8_t *step = malloc(2); + step[0] = STEP_SET_DIR; + step[1] = FESPI_FMT_DIR(dir); + as_add_step(as, step); +} + +/* 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) +{ + if (chip_offset & 0xFF000000) { + LOG_ERROR("FESPI interface does not support greater than 3B addressing, can't write to offset 0x%x", + chip_offset); + return ERROR_FAIL; + } + + 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; + 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); + + int retval = ERROR_OK; + while (!as_empty(as)) { + keep_alive(); + uint8_t *data_buf = malloc(data_wa->size); + unsigned bytes = as_compile(as, data_buf, data_wa->size); + retval = target_write_buffer(target, data_wa->address, bytes, + data_buf); + free(data_buf); + if (retval != ERROR_OK) { + LOG_ERROR("Failed to write data to 0x%" TARGET_PRIxADDR ": %d", + data_wa->address, retval); + goto exit; + } + + 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); + goto exit; + } + } + +exit: + destroy_reg_param(®_params[1]); + destroy_reg_param(®_params[0]); + return retval; +} + +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 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; + } + + /* If no valid page_size, use reasonable default. */ + page_size = fespi_info->dev->pagesize ? + fespi_info->dev->pagesize : SPIFLASH_DEF_PAGESIZE; + + fespi_txwm_wait(bank); + + /* Disable Hardware accesses*/ + if (fespi_disable_hw_mode(bank) != ERROR_OK) + return ERROR_FAIL; + + struct algorithm_steps *as = as_new(); + + /* poll WIP */ + retval = fespi_wip(bank, FESPI_PROBE_TIMEOUT); + if (retval != ERROR_OK) + goto err; + + page_offset = offset % page_size; + /* central part, aligned words */ + while (count > 0) { + /* clip block at page boundary */ + if (page_offset + count > page_size) + cur_count = page_size - page_offset; + else + 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; + + page_offset = 0; + buffer += cur_count; + offset += cur_count; + count -= cur_count; + } + + 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 */ + if (fespi_enable_hw_mode(bank) != ERROR_OK) + return ERROR_FAIL; + 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; + int retval; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + fespi_txwm_wait(bank); + + /* 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" */ + if (fespi_write_reg(bank, FESPI_REG_CSMODE, FESPI_CSMODE_HOLD) != ERROR_OK) + return ERROR_FAIL; + + 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); + + if (fespi_write_reg(bank, FESPI_REG_CSMODE, FESPI_CSMODE_AUTO) != ERROR_OK) + return ERROR_FAIL; + + 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; + struct flash_sector *sectors; + uint32_t id = 0; /* silence uninitialized warning */ + const struct fespi_target *target_device; + int retval; + uint32_t sectorsize; + + 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%" TARGET_PRIxADDR + " with ctrl at 0x%x", fespi_info->ctrl_base, bank->base); + } + + /* read and decode flash ID; returns in SW mode */ + if (fespi_write_reg(bank, FESPI_REG_TXCTRL, FESPI_TXWM(1)) != ERROR_OK) + return ERROR_FAIL; + fespi_set_dir(bank, FESPI_DIR_TX); + + /* Disable Hardware accesses*/ + if (fespi_disable_hw_mode(bank) != ERROR_OK) + return ERROR_FAIL; + + retval = fespi_read_flash_id(bank, &id); + + if (fespi_enable_hw_mode(bank) != ERROR_OK) + return ERROR_FAIL; + 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; + + if (bank->size <= (1UL << 16)) + LOG_WARNING("device needs 2-byte addresses - not implemented"); + if (bank->size > (1UL << 24)) + LOG_WARNING("device needs paging or 4-byte addresses - not implemented"); + + /* if no sectors, treat whole bank as single sector */ + sectorsize = fespi_info->dev->sectorsize ? + fespi_info->dev->sectorsize : fespi_info->dev->size_in_bytes; + + /* create and fill sectors array */ + bank->num_sectors = fespi_info->dev->size_in_bytes / 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 * sectorsize; + sectors[sector].size = sectorsize; + sectors[sector].is_erased = -1; + sectors[sector].is_protected = 0; + } + + 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, + .free_driver_priv = default_flash_free_driver_priv +};