This is an automated email from Gerrit. "Sergey Matsievskiy <matsievski...@gmail.com>" just uploaded a new patch set to Gerrit, which you can find at https://review.openocd.org/c/openocd/+/8400
-- gerrit commit 6861bd1bbac352218c7a8cc45659fd38410c9059 Author: Matsievskiy S.V. <matsievski...@gmail.com> Date: Tue Jul 16 08:08:23 2024 +0300 flash/nor: add DesignWare SPI controller driver Driver for DesignWare SPI controller, found on many SoCs (see compatible list in Linux device tree bindings Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml). This implementation only supports MIPS as it was the only one available for the tests, however, adding support for other architectures should require only few adjustments. Driver relies on flash/nor/spi.h to find Flash chip info. Driver internal functions support 24bit addressing mode, but due to limitations of flash/nor/spi.h, it is not used. The repoted writing speed is about 60kb/s. Lint, sanitizer and valgraind repoted warnings were not related to the driver. Change-Id: Id3df5626ab88055f034f74f274823051dedefeb1 Signed-off-by: Matsievskiy S.V. <matsievski...@gmail.com> diff --git a/contrib/loaders/flash/dw-spi/Makefile b/contrib/loaders/flash/dw-spi/Makefile new file mode 100644 index 0000000000..a2f4cced70 --- /dev/null +++ b/contrib/loaders/flash/dw-spi/Makefile @@ -0,0 +1,33 @@ +TOOLCHAIN:=mipsel-linux-gnu- +CC:=$(TOOLCHAIN)gcc +OBJCOPY:=$(TOOLCHAIN)objcopy +CFLAGS:=-O2 -Wall -Wextra -fpic +SRC=dw-spi.c +OBJ=$(patsubst %.c, %.o,$(SRC)) + +# sparx-iv +ifeq ($(TOOLCHAIN),mipsel-linux-gnu-) + CFLAGS+= -march=24kec +endif + +all: \ + $(TOOLCHAIN)transaction.inc \ + $(TOOLCHAIN)erase.inc \ + $(TOOLCHAIN)check_fill.inc \ + $(TOOLCHAIN)program.inc \ + $(TOOLCHAIN)read.inc + +$(TOOLCHAIN)%.bin: $(OBJ) + $(OBJCOPY) --dump-section .$*=$@ $< + +%.inc: %.bin + xxd -i > $@ < $< + +.PHONY: clean +clean: + rm -rf .ccls-cache + find . \( \ + -iname "*.o" \ + -o -iname "*.bin" \ + -o -iname "*.inc" \ + \) -delete diff --git a/contrib/loaders/flash/dw-spi/dw-spi.c b/contrib/loaders/flash/dw-spi/dw-spi.c new file mode 100644 index 0000000000..2bdf115d25 --- /dev/null +++ b/contrib/loaders/flash/dw-spi/dw-spi.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "dw-spi.h" + +#include "../../../../src/flash/nor/dw-spi-helper.h" + +/** + * @brief Generic flash transaction. + * + * nCS is only asserted when TX FIFO is not empty. + * Must supply TX FIFO with data for the whole duration of transaction. + * + * @param[in] arg Function arguments. + * @retval 0 Success. Returned in argument register. + * @retval 1 Failure. Returned in argument register. + */ +__attribute__((section(".transaction"))) void +transaction(struct dw_spi_transaction_t *arg) +{ + register uint8_t *buffer_tx = (uint8_t *)arg->buffer; + register uint8_t *buffer_rx = buffer_tx; + register uint32_t size = arg->size; + register volatile uint8_t *status = (uint8_t *)arg->status_reg; + register volatile uint8_t *data = (uint8_t *)arg->data_reg; + + wait_tx_finish(status); + flush_rx(status, data); + + for (; size > 0; size--) { + wait_tx_available(status); + push_byte(data, *buffer_tx++); + if (!tx_in_progress(status)) { + RET_ERROR; + BKPT; + return; + } + if (arg->read_flag && rx_available(status)) + *buffer_rx++ = rcv_byte(data); + } + + if (arg->read_flag) { + while (buffer_rx < buffer_tx) { + wait_rx_available(status); + *buffer_rx++ = rcv_byte(data); + } + } + + RET_OK; + BKPT; +} + +/** + * @brief Check flash sectors are filled with pattern. Primary use for + * checking sector erase state. + * + * nCS is only asserted when TX FIFO is not empty. + * Must supply TX FIFO with data for the whole duration of transaction. + * + * @param[in] arg Function arguments. + * @retval 0 Success. Returned in argument register. + * @retval 1 Failure. Returned in argument register. + */ +__attribute__((section(".check_fill"))) void +check_fill(struct dw_spi_check_fill_t *arg) +{ + register uint32_t tx_size; + register uint32_t rx_size; + register uint32_t dummy_count; + register uint8_t filled; + register uint8_t *fill_status_array = + (uint8_t *)arg->fill_status_array; + register volatile uint8_t *status = (uint8_t *)arg->status_reg; + register volatile uint8_t *data = (uint8_t *)arg->data_reg; + + for (; arg->sector_count > 0; arg->sector_count--, + arg->address += arg->sector_size, + fill_status_array++) { + wait_tx_finish(status); + flush_rx(status, data); + + push_byte(data, arg->read_cmd); + if (arg->four_byte_mode) { + dummy_count = 5; + push_u32(status, data, arg->address); + } else { + dummy_count = 4; + push_u24(status, data, arg->address); + } + + for (tx_size = arg->sector_size, rx_size = arg->sector_size, + filled = 1; + tx_size > 0; tx_size--) { + wait_tx_available(status); + if (!tx_in_progress(status)) { + RET_ERROR; + BKPT; + return; + } + push_byte(data, 0); // dummy write + if (rx_available(status)) { + if (dummy_count > 0) { + rcv_byte(data); + dummy_count--; + } else { + if (rcv_byte(data) != arg->pattern) { + filled = 0; + break; + } + rx_size--; + } + } + } + if (filled) { + for (; rx_size > 0; rx_size--) { + wait_rx_available(status); + if (rcv_byte(data) != arg->pattern) { + filled = 0; + break; + } + } + } + *fill_status_array = filled; + } + + RET_OK; + BKPT; +} + +/** + * @brief Erase flash sectors. + * + * @param[in] arg Function arguments. + * @retval 0 Success. Returned in argument register. + * @retval 1 Failure. Returned in argument register. + */ +__attribute__((section(".erase"))) void +erase(struct dw_spi_erase_t *arg) +{ + register uint32_t address = arg->address; + register uint32_t count = arg->sector_count; + register volatile uint8_t *status = (uint8_t *)arg->status_reg; + register volatile uint8_t *data = (uint8_t *)arg->data_reg; + + for (; count > 0; count--, address += arg->sector_size) { + write_enable(status, data, arg->write_enable_cmd); + wait_write_enable(status, data, arg->read_status_cmd, + arg->write_enable_mask); + + erase_sector(status, data, arg->erase_sector_cmd, address, + arg->four_byte_mode); + wait_busy(status, data, arg->read_status_cmd, arg->busy_mask); + } + + RET_OK; + BKPT; +} + +/** + * @brief Flash program. + * + * @param[in] arg Function arguments. + * @retval 0 Success. Returned in argument register. + * @retval 1 Failure. Returned in argument register. + */ +__attribute__((section(".program"))) void +program(struct dw_spi_program_t *arg) +{ + register uint8_t *buffer = (uint8_t *)arg->buffer; + register uint32_t buffer_size = arg->buffer_size; + register volatile uint8_t *status = (uint8_t *)arg->status_reg; + register volatile uint8_t *data = (uint8_t *)arg->data_reg; + register uint32_t page_size; + + while (buffer_size > 0) { + write_enable(status, data, arg->write_enable_cmd); + wait_write_enable(status, data, arg->read_status_cmd, + arg->write_enable_mask); + + wait_tx_finish(status); + + push_byte(data, arg->program_cmd); + if (arg->four_byte_mode) + push_u32(status, data, arg->address); + else + push_u24(status, data, arg->address); + for (page_size = MIN(arg->page_size, buffer_size); page_size > 0; + page_size--, buffer_size--) { + wait_tx_available(status); + push_byte(data, *buffer++); + } + arg->address += arg->page_size; + wait_busy(status, data, arg->read_status_cmd, arg->busy_mask); + } + + RET_OK; + BKPT; +} + +/** + * @brief Read data from flash. + * + * nCS is only asserted when TX FIFO is not empty. + * Must supply TX FIFO with data for the whole duration of transaction. + * + * @param[in] arg Function arguments. + * @retval 0 Success. Returned in argument register. + * @retval 1 Failure. Returned in argument register. + */ +__attribute__((section(".read"))) void +read(struct dw_spi_read_t *arg) +{ + register uint32_t tx_size = arg->buffer_size; + register uint32_t rx_size = arg->buffer_size; + register uint32_t dummy_count; + register uint8_t *buffer = (uint8_t *)arg->buffer; + register volatile uint8_t *status = (uint8_t *)arg->status_reg; + register volatile uint8_t *data = (uint8_t *)arg->data_reg; + + wait_tx_finish(status); + flush_rx(status, data); + + push_byte(data, arg->read_cmd); + if (arg->four_byte_mode) { + dummy_count = 5; + push_u32(status, data, arg->address); + } else { + dummy_count = 4; + push_u24(status, data, arg->address); + } + + for (; tx_size > 0; tx_size--) { + wait_tx_available(status); + if (!tx_in_progress(status)) { + RET_ERROR; + BKPT; + return; + } + push_byte(data, 0); // dummy write + if (rx_available(status)) { + if (dummy_count > 0) { + rcv_byte(data); + dummy_count--; + } else { + *buffer++ = rcv_byte(data); + rx_size--; + } + } + } + while (rx_size > 0) { + wait_rx_available(status); + if (dummy_count > 0) { + rcv_byte(data); + dummy_count--; + } else { + *buffer++ = rcv_byte(data); + rx_size--; + } + } + + RET_OK; + BKPT; +} diff --git a/contrib/loaders/flash/dw-spi/dw-spi.h b/contrib/loaders/flash/dw-spi/dw-spi.h new file mode 100644 index 0000000000..a8552064af --- /dev/null +++ b/contrib/loaders/flash/dw-spi/dw-spi.h @@ -0,0 +1,257 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _DW_SPI_H_ +#define _DW_SPI_H_ + +#include <stdint.h> +#include <sys/param.h> + +#define BIT(x) (1 << (x)) + +#define STATUS_BUSY \ + BIT(0) ///< Set when serial transfer is in progress. Cleared when + ///< master is idle or disabled. +#define STATUS_TFNF \ + BIT(1) ///< Set when transmit FIFO has room for one or more + ///< data-word. +#define STATUS_TFE BIT(2) ///< Set when transmit FIFO is empty. +#define STATUS_RFNE \ + BIT(3) ///< Set when receive FIFO has one or more data-word. +#define STATUS_RFF BIT(4) ///< Set when receive FIFO is full. + +#define BKPT asm("sdbbp\n") + +// Use asm for return values so that code does not get optimized out. +#define RET_OK asm("addi $a0,$zero,0\n") +#define RET_ERROR asm("addi $a0,$zero,1\n") + +/** + * @brief Check transmission is currently in progress. + * + * @param[in] status Pointer to SR register. + * @retval 1 Transmission is in progress. + * @retval 0 Controller is idle or off. + */ +__attribute__((always_inline)) static inline int +tx_in_progress(volatile uint8_t *status) +{ + return (*status ^ STATUS_TFE) & (STATUS_BUSY | STATUS_TFE); +} + +/** + * @brief Wait for controller to finish previous transaction. + * + * @param[in] status Pointer to SR register. + */ +__attribute__((always_inline)) static inline void +wait_tx_finish(volatile uint8_t *status) +{ + while (tx_in_progress(status)) + ; +} + +/** + * @brief Wait for room in TX FIFO. + * + * @param[in] status Pointer to SR register. + */ +__attribute__((always_inline)) static inline void +wait_tx_available(volatile uint8_t *status) +{ + while (!(*status & STATUS_TFNF)) + ; +} + +/** + * @brief Check for data available in RX FIFO. + * + * @param[in] status Pointer to SR register. + * @retval 1 Data available. + * @retval 0 No data available. + */ +__attribute__((always_inline)) static inline int +rx_available(volatile uint8_t *status) +{ + return *status & STATUS_RFNE; +} + +/** + * @brief Wait for data in RX FIFO. + * + * @param[in] status Pointer to SR register. + */ +__attribute__((always_inline)) static inline void +wait_rx_available(volatile uint8_t *status) +{ + while (!rx_available(status)) + ; +} + +/** + * @brief Flush RX FIFO. + * + * @param[in] status Pointer to SR register. + * @param[in] data Pointer to DR register. + */ +__attribute__((always_inline)) static inline void +flush_rx(volatile uint8_t *status, volatile uint8_t *data) +{ + while (*status & STATUS_RFNE) + *data; +} + +/** + * @brief Push byte to TX FIFO. + * + * @param[in] data Pointer to DR register. + * @param[in] byte Data to push. + */ +__attribute__((always_inline)) static inline void +push_byte(volatile uint8_t *data, uint8_t byte) +{ + *data = byte; +} + +/** + * @brief Get byte from RX FIFO. + * + * @param[in] data Pointer to DR register. + * @return RX FIFO byte. + */ +__attribute__((always_inline)) static inline uint8_t +rcv_byte(volatile uint8_t *data) +{ + return *data; +} + +/** + * @brief Read chip status register. + * + * @param[in] status Pointer to SR register. + * @param[in] data Pointer to DR register. + * @param[in] read_status_cmd Read status command. + * @return Chip status. + */ +__attribute__((always_inline)) static inline uint8_t +read_status(volatile uint8_t *status, volatile uint8_t *data, + uint8_t read_status_cmd) +{ + wait_tx_finish(status); + flush_rx(status, data); + wait_tx_available(status); + push_byte(data, read_status_cmd); + push_byte(data, 0); // dummy write + wait_rx_available(status); + rcv_byte(data); // dummy read + wait_rx_available(status); + return rcv_byte(data); +} + +/** + * @brief Enable chip write. + * + * @param[in] status Pointer to SR register. + * @param[in] data Pointer to DR register. + * @param[in] write_enable_cmd Write enable command. + */ +__attribute__((always_inline)) static inline void +write_enable(volatile uint8_t *status, volatile uint8_t *data, + uint8_t write_enable_cmd) +{ + wait_tx_finish(status); + wait_tx_available(status); + push_byte(data, write_enable_cmd); +} + +__attribute__((always_inline)) static inline void +_push_u32(volatile uint8_t *status, volatile uint8_t *data, uint32_t word, + int bytes) +{ + for (register int i = bytes - 1; i >= 0; i--) { + wait_tx_available(status); + push_byte(data, (word >> (i * 8)) & 0xff); + } +} + +/** + * @brief Push 32 bit value. + * + * @param[in] status Pointer to SR register. + * @param[in] data Pointer to DR register. + * @param[in] word Data to push. + */ +__attribute__((always_inline)) static inline void +push_u32(volatile uint8_t *status, volatile uint8_t *data, uint32_t word) +{ + _push_u32(status, data, word, 4); +} + +/** + * @brief Push 24 bit value. + * + * @param[in] status Pointer to SR register. + * @param[in] data Pointer to DR register. + * @param[in] word Data to push. + */ +__attribute__((always_inline)) static inline void +push_u24(volatile uint8_t *status, volatile uint8_t *data, uint32_t word) +{ + _push_u32(status, data, word, 3); +} + +/** + * @brief Erase sector. + * + * @param[in] status Pointer to SR register. + * @param[in] data Pointer to DR register. + * @param[in] erase_sector_cmd Erase sector cmd. + * @param[in] address Sector address. + * @param[in] four_byte_mode Device is in 32 bit mode flag. + */ +__attribute__((always_inline)) static inline void +erase_sector(volatile uint8_t *status, volatile uint8_t *data, + uint8_t erase_sector_cmd, uint32_t sector_address, + uint8_t four_byte_mode) +{ + wait_tx_finish(status); + wait_tx_available(status); + push_byte(data, erase_sector_cmd); + if (four_byte_mode) + push_u32(status, data, sector_address); + else + push_u24(status, data, sector_address); +} + +/** + * @brief Wait for write enable flag. + * + * @param[in] status Pointer to SR register. + * @param[in] data Pointer to DR register. + * @param[in] read_status_cmd Read status command. + * @param[in] write_enable_mask Write enable status mask. + */ +__attribute__((always_inline)) static inline void +wait_write_enable(volatile uint8_t *status, volatile uint8_t *data, + uint8_t read_status_cmd, uint8_t write_enable_mask) +{ + while (!(read_status(status, data, read_status_cmd) & + write_enable_mask)) + ; +} + +/** + * @brief Wait while flash is busy. + * + * @param[in] status Pointer to SR register. + * @param[in] data Pointer to DR register. + * @param[in] read_status_cmd Read status command. + * @param[in] busy_mask Flash busy mask. + */ +__attribute__((always_inline)) static inline void +wait_busy(volatile uint8_t *status, volatile uint8_t *data, + uint8_t read_status_cmd, uint8_t busy_mask) +{ + while (read_status(status, data, read_status_cmd) & busy_mask) + ; +} + +#endif // _DW_SPI_H_ diff --git a/contrib/loaders/flash/dw-spi/mipsel-linux-gnu-check_fill.inc b/contrib/loaders/flash/dw-spi/mipsel-linux-gnu-check_fill.inc new file mode 100644 index 0000000000..bd07f2d79a --- /dev/null +++ b/contrib/loaders/flash/dw-spi/mipsel-linux-gnu-check_fill.inc @@ -0,0 +1,40 @@ + 0x0b, 0x00, 0x82, 0x88, 0x17, 0x00, 0x89, 0x88, 0x0f, 0x00, 0x83, 0x88, + 0x13, 0x00, 0x86, 0x88, 0x08, 0x00, 0x82, 0x98, 0x14, 0x00, 0x89, 0x98, + 0x0c, 0x00, 0x83, 0x98, 0x10, 0x00, 0x86, 0x98, 0x55, 0x00, 0x40, 0x10, + 0xf8, 0xff, 0x08, 0x24, 0x00, 0x00, 0x65, 0x90, 0x05, 0x00, 0xa5, 0x30, + 0x04, 0x00, 0xa5, 0x38, 0xfc, 0xff, 0xa0, 0x14, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x62, 0x90, 0x08, 0x00, 0x42, 0x30, 0x07, 0x00, 0x40, 0x50, + 0x19, 0x00, 0x82, 0x90, 0x00, 0x00, 0xc2, 0x90, 0x00, 0x00, 0x62, 0x90, + 0x08, 0x00, 0x42, 0x30, 0xfc, 0xff, 0x40, 0x14, 0x00, 0x00, 0x00, 0x00, + 0x19, 0x00, 0x82, 0x90, 0x00, 0x00, 0xc2, 0xa0, 0x1a, 0x00, 0x82, 0x90, + 0x4b, 0x00, 0x40, 0x10, 0x03, 0x00, 0x8a, 0x88, 0x00, 0x00, 0x8a, 0x98, + 0x18, 0x00, 0x07, 0x24, 0x00, 0x00, 0x62, 0x90, 0x02, 0x00, 0x42, 0x30, + 0xfd, 0xff, 0x40, 0x10, 0x06, 0x10, 0xea, 0x00, 0xff, 0x00, 0x42, 0x30, + 0xf8, 0xff, 0xe7, 0x24, 0x00, 0x00, 0xc2, 0xa0, 0xf8, 0xff, 0xe8, 0x14, + 0x05, 0x00, 0x0b, 0x24, 0x07, 0x00, 0x8a, 0x88, 0x04, 0x00, 0x8a, 0x98, + 0x23, 0x00, 0x40, 0x11, 0x25, 0x38, 0x40, 0x01, 0x00, 0x00, 0x62, 0x90, + 0x02, 0x00, 0x42, 0x30, 0xfd, 0xff, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x62, 0x90, 0x05, 0x00, 0x42, 0x30, 0x04, 0x00, 0x42, 0x38, + 0x40, 0x00, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa0, + 0x00, 0x00, 0x62, 0x90, 0x08, 0x00, 0x42, 0x30, 0x06, 0x00, 0x40, 0x50, + 0xff, 0xff, 0xe7, 0x24, 0x00, 0x00, 0xc2, 0x90, 0x26, 0x00, 0x60, 0x51, + 0x18, 0x00, 0x8c, 0x90, 0xff, 0xff, 0x6b, 0x25, 0xff, 0xff, 0xe7, 0x24, + 0xec, 0xff, 0xe0, 0x14, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x40, 0x51, + 0x01, 0x00, 0x05, 0x24, 0x18, 0x00, 0x87, 0x90, 0x00, 0x00, 0x62, 0x90, + 0x08, 0x00, 0x42, 0x30, 0xfd, 0xff, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xc2, 0x90, 0xff, 0x00, 0x42, 0x30, 0x04, 0x00, 0xe2, 0x14, + 0xff, 0xff, 0x4a, 0x25, 0xf7, 0xff, 0x40, 0x15, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x05, 0x24, 0x00, 0x00, 0x25, 0xa1, 0x0b, 0x00, 0x82, 0x88, + 0x01, 0x00, 0x29, 0x25, 0x08, 0x00, 0x82, 0x98, 0xff, 0xff, 0x42, 0x24, + 0x0b, 0x00, 0x82, 0xa8, 0x08, 0x00, 0x82, 0xb8, 0x03, 0x00, 0x85, 0x88, + 0x07, 0x00, 0x87, 0x88, 0x00, 0x00, 0x85, 0x98, 0x04, 0x00, 0x87, 0x98, + 0x21, 0x28, 0xa7, 0x00, 0x03, 0x00, 0x85, 0xa8, 0xad, 0xff, 0x40, 0x14, + 0x00, 0x00, 0x85, 0xb8, 0x00, 0x00, 0x04, 0x20, 0x3f, 0x00, 0x00, 0x70, + 0x08, 0x00, 0xe0, 0x03, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x42, 0x30, + 0xec, 0xff, 0x82, 0x55, 0x00, 0x00, 0x25, 0xa1, 0xd8, 0xff, 0x00, 0x10, + 0xff, 0xff, 0x4a, 0x25, 0x00, 0x00, 0x8a, 0x98, 0x10, 0x00, 0x07, 0x24, + 0x00, 0x00, 0x62, 0x90, 0x02, 0x00, 0x42, 0x30, 0xfd, 0xff, 0x40, 0x10, + 0x06, 0x10, 0xea, 0x00, 0xff, 0x00, 0x42, 0x30, 0xf8, 0xff, 0xe7, 0x24, + 0x00, 0x00, 0xc2, 0xa0, 0xf8, 0xff, 0xe8, 0x14, 0x04, 0x00, 0x0b, 0x24, + 0xb6, 0xff, 0x00, 0x10, 0x07, 0x00, 0x8a, 0x88, 0x01, 0x00, 0x04, 0x20, + 0x3f, 0x00, 0x00, 0x70, 0x08, 0x00, 0xe0, 0x03, 0x00, 0x00, 0x00, 0x00 diff --git a/contrib/loaders/flash/dw-spi/mipsel-linux-gnu-erase.inc b/contrib/loaders/flash/dw-spi/mipsel-linux-gnu-erase.inc new file mode 100644 index 0000000000..e225713cb5 --- /dev/null +++ b/contrib/loaders/flash/dw-spi/mipsel-linux-gnu-erase.inc @@ -0,0 +1,45 @@ + 0x0b, 0x00, 0x88, 0x88, 0x25, 0x28, 0x80, 0x00, 0x03, 0x00, 0x86, 0x88, + 0x0f, 0x00, 0x82, 0x88, 0x13, 0x00, 0x84, 0x88, 0x08, 0x00, 0xa8, 0x98, + 0x00, 0x00, 0xa6, 0x98, 0x0c, 0x00, 0xa2, 0x98, 0x6f, 0x00, 0x00, 0x11, + 0x10, 0x00, 0xa4, 0x98, 0xf8, 0xff, 0x07, 0x24, 0x15, 0x00, 0xa9, 0x90, + 0x00, 0x00, 0x43, 0x90, 0x05, 0x00, 0x63, 0x30, 0x04, 0x00, 0x63, 0x38, + 0xfc, 0xff, 0x60, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x90, + 0x02, 0x00, 0x63, 0x30, 0xfd, 0xff, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x89, 0xa0, 0x14, 0x00, 0xaa, 0x90, 0x17, 0x00, 0xa9, 0x90, + 0x00, 0x00, 0x43, 0x90, 0x05, 0x00, 0x63, 0x30, 0x04, 0x00, 0x63, 0x38, + 0xfc, 0xff, 0x60, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x90, + 0x08, 0x00, 0x63, 0x30, 0x06, 0x00, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x83, 0x90, 0x00, 0x00, 0x43, 0x90, 0x08, 0x00, 0x63, 0x30, + 0xfc, 0xff, 0x60, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x90, + 0x02, 0x00, 0x63, 0x30, 0xfd, 0xff, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x8a, 0xa0, 0x00, 0x00, 0x80, 0xa0, 0x00, 0x00, 0x43, 0x90, + 0x08, 0x00, 0x63, 0x30, 0xfd, 0xff, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x83, 0x90, 0x00, 0x00, 0x43, 0x90, 0x08, 0x00, 0x63, 0x30, + 0xfd, 0xff, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0x90, + 0x24, 0x18, 0x23, 0x01, 0xe0, 0xff, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x16, 0x00, 0xaa, 0x90, 0x19, 0x00, 0xa9, 0x90, 0x00, 0x00, 0x43, 0x90, + 0x05, 0x00, 0x63, 0x30, 0x04, 0x00, 0x63, 0x38, 0xfc, 0xff, 0x60, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x90, 0x02, 0x00, 0x63, 0x30, + 0xfd, 0xff, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0xa0, + 0x36, 0x00, 0x20, 0x11, 0x10, 0x00, 0x09, 0x24, 0x18, 0x00, 0x09, 0x24, + 0x00, 0x00, 0x43, 0x90, 0x02, 0x00, 0x63, 0x30, 0xfd, 0xff, 0x60, 0x10, + 0x06, 0x18, 0x26, 0x01, 0xff, 0x00, 0x63, 0x30, 0xf8, 0xff, 0x29, 0x25, + 0xf9, 0xff, 0x27, 0x15, 0x00, 0x00, 0x83, 0xa0, 0x14, 0x00, 0xaa, 0x90, + 0x18, 0x00, 0xa9, 0x90, 0x00, 0x00, 0x43, 0x90, 0x05, 0x00, 0x63, 0x30, + 0x04, 0x00, 0x63, 0x38, 0xfc, 0xff, 0x60, 0x14, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x43, 0x90, 0x08, 0x00, 0x63, 0x30, 0x06, 0x00, 0x60, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0x90, 0x00, 0x00, 0x43, 0x90, + 0x08, 0x00, 0x63, 0x30, 0xfc, 0xff, 0x60, 0x14, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x43, 0x90, 0x02, 0x00, 0x63, 0x30, 0xfd, 0xff, 0x60, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0xa0, 0x00, 0x00, 0x80, 0xa0, + 0x00, 0x00, 0x43, 0x90, 0x08, 0x00, 0x63, 0x30, 0xfd, 0xff, 0x60, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0x90, 0x00, 0x00, 0x43, 0x90, + 0x08, 0x00, 0x63, 0x30, 0xfd, 0xff, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x83, 0x90, 0x24, 0x18, 0x23, 0x01, 0xe0, 0xff, 0x60, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0xa3, 0x88, 0xff, 0xff, 0x08, 0x25, + 0x04, 0x00, 0xa3, 0x98, 0x94, 0xff, 0x00, 0x15, 0x21, 0x30, 0xc3, 0x00, + 0x00, 0x00, 0x04, 0x20, 0x3f, 0x00, 0x00, 0x70, 0x08, 0x00, 0xe0, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x90, 0x02, 0x00, 0x63, 0x30, + 0xfd, 0xff, 0x60, 0x10, 0x06, 0x18, 0x26, 0x01, 0xff, 0x00, 0x63, 0x30, + 0xf8, 0xff, 0x29, 0x25, 0xf9, 0xff, 0x27, 0x15, 0x00, 0x00, 0x83, 0xa0, + 0xcc, 0xff, 0x00, 0x10, 0x14, 0x00, 0xaa, 0x90 diff --git a/contrib/loaders/flash/dw-spi/mipsel-linux-gnu-program.inc b/contrib/loaders/flash/dw-spi/mipsel-linux-gnu-program.inc new file mode 100644 index 0000000000..ecdad3b1d5 --- /dev/null +++ b/contrib/loaders/flash/dw-spi/mipsel-linux-gnu-program.inc @@ -0,0 +1,51 @@ + 0x0f, 0x00, 0x88, 0x88, 0x25, 0x30, 0x80, 0x00, 0x0b, 0x00, 0x85, 0x88, + 0x13, 0x00, 0x82, 0x88, 0x17, 0x00, 0x84, 0x88, 0x0c, 0x00, 0xc8, 0x98, + 0x08, 0x00, 0xc5, 0x98, 0x10, 0x00, 0xc2, 0x98, 0x14, 0x00, 0xc4, 0x98, + 0x7f, 0x00, 0x00, 0x11, 0xf8, 0xff, 0x07, 0x24, 0x19, 0x00, 0xc9, 0x90, + 0x00, 0x00, 0x43, 0x90, 0x05, 0x00, 0x63, 0x30, 0x04, 0x00, 0x63, 0x38, + 0xfc, 0xff, 0x60, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x90, + 0x02, 0x00, 0x63, 0x30, 0xfd, 0xff, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x89, 0xa0, 0x18, 0x00, 0xca, 0x90, 0x1b, 0x00, 0xc9, 0x90, + 0x00, 0x00, 0x43, 0x90, 0x05, 0x00, 0x63, 0x30, 0x04, 0x00, 0x63, 0x38, + 0xfc, 0xff, 0x60, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x90, + 0x08, 0x00, 0x63, 0x30, 0x06, 0x00, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x83, 0x90, 0x00, 0x00, 0x43, 0x90, 0x08, 0x00, 0x63, 0x30, + 0xfc, 0xff, 0x60, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x90, + 0x02, 0x00, 0x63, 0x30, 0xfd, 0xff, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x8a, 0xa0, 0x00, 0x00, 0x80, 0xa0, 0x00, 0x00, 0x43, 0x90, + 0x08, 0x00, 0x63, 0x30, 0xfd, 0xff, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x83, 0x90, 0x00, 0x00, 0x43, 0x90, 0x08, 0x00, 0x63, 0x30, + 0xfd, 0xff, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0x90, + 0x24, 0x18, 0x23, 0x01, 0xe0, 0xff, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x43, 0x90, 0x05, 0x00, 0x63, 0x30, 0x04, 0x00, 0x63, 0x38, + 0xfc, 0xff, 0x60, 0x14, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0xc3, 0x90, + 0x00, 0x00, 0x83, 0xa0, 0x1d, 0x00, 0xc3, 0x90, 0x4b, 0x00, 0x60, 0x10, + 0x03, 0x00, 0xca, 0x88, 0x00, 0x00, 0xca, 0x98, 0x18, 0x00, 0x09, 0x24, + 0x00, 0x00, 0x43, 0x90, 0x02, 0x00, 0x63, 0x30, 0xfd, 0xff, 0x60, 0x10, + 0x06, 0x18, 0x2a, 0x01, 0xff, 0x00, 0x63, 0x30, 0xf8, 0xff, 0x29, 0x25, + 0xf9, 0xff, 0x27, 0x15, 0x00, 0x00, 0x83, 0xa0, 0x07, 0x00, 0xc3, 0x88, + 0x25, 0x50, 0x00, 0x01, 0x04, 0x00, 0xc3, 0x98, 0x2b, 0x48, 0x03, 0x01, + 0x0a, 0x50, 0x69, 0x00, 0x0c, 0x00, 0x40, 0x11, 0x21, 0x48, 0xaa, 0x00, + 0x00, 0x00, 0x43, 0x90, 0x02, 0x00, 0x63, 0x30, 0xfd, 0xff, 0x60, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa3, 0x90, 0x01, 0x00, 0xa5, 0x24, + 0xf9, 0xff, 0xa9, 0x14, 0x00, 0x00, 0x83, 0xa0, 0x07, 0x00, 0xc3, 0x88, + 0x23, 0x40, 0x0a, 0x01, 0x04, 0x00, 0xc3, 0x98, 0x03, 0x00, 0xc9, 0x88, + 0x00, 0x00, 0xc9, 0x98, 0x21, 0x18, 0x23, 0x01, 0x03, 0x00, 0xc3, 0xa8, + 0x00, 0x00, 0xc3, 0xb8, 0x18, 0x00, 0xca, 0x90, 0x1c, 0x00, 0xc9, 0x90, + 0x00, 0x00, 0x43, 0x90, 0x05, 0x00, 0x63, 0x30, 0x04, 0x00, 0x63, 0x38, + 0xfc, 0xff, 0x60, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x90, + 0x08, 0x00, 0x63, 0x30, 0x06, 0x00, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x83, 0x90, 0x00, 0x00, 0x43, 0x90, 0x08, 0x00, 0x63, 0x30, + 0xfc, 0xff, 0x60, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x90, + 0x02, 0x00, 0x63, 0x30, 0xfd, 0xff, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x8a, 0xa0, 0x00, 0x00, 0x80, 0xa0, 0x00, 0x00, 0x43, 0x90, + 0x08, 0x00, 0x63, 0x30, 0xfd, 0xff, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x83, 0x90, 0x00, 0x00, 0x43, 0x90, 0x08, 0x00, 0x63, 0x30, + 0xfd, 0xff, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0x90, + 0x24, 0x18, 0x23, 0x01, 0xe0, 0xff, 0x60, 0x14, 0x00, 0x00, 0x00, 0x00, + 0x84, 0xff, 0x00, 0x55, 0x19, 0x00, 0xc9, 0x90, 0x00, 0x00, 0x04, 0x20, + 0x3f, 0x00, 0x00, 0x70, 0x08, 0x00, 0xe0, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xca, 0x98, 0x10, 0x00, 0x09, 0x24, 0x00, 0x00, 0x43, 0x90, + 0x02, 0x00, 0x63, 0x30, 0xfd, 0xff, 0x60, 0x10, 0x06, 0x18, 0x2a, 0x01, + 0xff, 0x00, 0x63, 0x30, 0xf8, 0xff, 0x29, 0x25, 0xf9, 0xff, 0x27, 0x15, + 0x00, 0x00, 0x83, 0xa0, 0xb6, 0xff, 0x00, 0x10, 0x07, 0x00, 0xc3, 0x88 diff --git a/contrib/loaders/flash/dw-spi/mipsel-linux-gnu-read.inc b/contrib/loaders/flash/dw-spi/mipsel-linux-gnu-read.inc new file mode 100644 index 0000000000..e14e11e8e7 --- /dev/null +++ b/contrib/loaders/flash/dw-spi/mipsel-linux-gnu-read.inc @@ -0,0 +1,35 @@ + 0x0b, 0x00, 0x87, 0x88, 0x07, 0x00, 0x88, 0x88, 0x0f, 0x00, 0x83, 0x88, + 0x13, 0x00, 0x85, 0x88, 0x08, 0x00, 0x87, 0x98, 0x04, 0x00, 0x88, 0x98, + 0x0c, 0x00, 0x83, 0x98, 0x10, 0x00, 0x85, 0x98, 0x00, 0x00, 0x62, 0x90, + 0x05, 0x00, 0x42, 0x30, 0x04, 0x00, 0x42, 0x38, 0xfc, 0xff, 0x40, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x90, 0x08, 0x00, 0x42, 0x30, + 0x07, 0x00, 0x40, 0x50, 0x14, 0x00, 0x82, 0x90, 0x00, 0x00, 0xa2, 0x90, + 0x00, 0x00, 0x62, 0x90, 0x08, 0x00, 0x42, 0x30, 0xfc, 0xff, 0x40, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x82, 0x90, 0x00, 0x00, 0xa2, 0xa0, + 0x15, 0x00, 0x82, 0x90, 0x0f, 0x00, 0x40, 0x10, 0x03, 0x00, 0x82, 0x88, + 0x18, 0x00, 0x06, 0x24, 0xf8, 0xff, 0x09, 0x24, 0x00, 0x00, 0x82, 0x98, + 0x25, 0x20, 0x40, 0x00, 0x00, 0x00, 0x62, 0x90, 0x02, 0x00, 0x42, 0x30, + 0xfd, 0xff, 0x40, 0x10, 0x06, 0x10, 0xc4, 0x00, 0xff, 0x00, 0x42, 0x30, + 0xf8, 0xff, 0xc6, 0x24, 0xf9, 0xff, 0xc9, 0x14, 0x00, 0x00, 0xa2, 0xa0, + 0x0e, 0x00, 0x00, 0x10, 0x05, 0x00, 0x06, 0x24, 0x10, 0x00, 0x06, 0x24, + 0xf8, 0xff, 0x09, 0x24, 0x00, 0x00, 0x82, 0x98, 0x25, 0x20, 0x40, 0x00, + 0x00, 0x00, 0x62, 0x90, 0x02, 0x00, 0x42, 0x30, 0xfd, 0xff, 0x40, 0x10, + 0x06, 0x10, 0xc4, 0x00, 0xff, 0x00, 0x42, 0x30, 0xf8, 0xff, 0xc6, 0x24, + 0xf9, 0xff, 0xc9, 0x14, 0x00, 0x00, 0xa2, 0xa0, 0x04, 0x00, 0x06, 0x24, + 0x28, 0x00, 0xe0, 0x10, 0x25, 0x20, 0xe0, 0x00, 0x00, 0x00, 0x62, 0x90, + 0x02, 0x00, 0x42, 0x30, 0xfd, 0xff, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x62, 0x90, 0x05, 0x00, 0x42, 0x30, 0x04, 0x00, 0x42, 0x38, + 0x23, 0x00, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0xa0, + 0x00, 0x00, 0x62, 0x90, 0x08, 0x00, 0x42, 0x30, 0x06, 0x00, 0x40, 0x50, + 0xff, 0xff, 0x84, 0x24, 0x00, 0x00, 0xa2, 0x90, 0x10, 0x00, 0xc0, 0x50, + 0x00, 0x00, 0x02, 0xa1, 0xff, 0xff, 0xc6, 0x24, 0xff, 0xff, 0x84, 0x24, + 0xec, 0xff, 0x80, 0x14, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0xe0, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x90, 0x08, 0x00, 0x42, 0x30, + 0xfd, 0xff, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa2, 0x90, + 0x06, 0x00, 0xc0, 0x50, 0xff, 0xff, 0xe7, 0x24, 0xf8, 0xff, 0x00, 0x10, + 0xff, 0xff, 0xc6, 0x24, 0xff, 0xff, 0xe7, 0x24, 0xf0, 0xff, 0x00, 0x10, + 0x01, 0x00, 0x08, 0x25, 0x03, 0x00, 0xe0, 0x10, 0x00, 0x00, 0x02, 0xa1, + 0xf1, 0xff, 0x00, 0x10, 0x01, 0x00, 0x08, 0x25, 0x00, 0x00, 0x04, 0x20, + 0x3f, 0x00, 0x00, 0x70, 0x08, 0x00, 0xe0, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x04, 0x20, 0x3f, 0x00, 0x00, 0x70, 0x08, 0x00, 0xe0, 0x03, + 0x00, 0x00, 0x00, 0x00 diff --git a/contrib/loaders/flash/dw-spi/mipsel-linux-gnu-transaction.inc b/contrib/loaders/flash/dw-spi/mipsel-linux-gnu-transaction.inc new file mode 100644 index 0000000000..f29aa30a48 --- /dev/null +++ b/contrib/loaders/flash/dw-spi/mipsel-linux-gnu-transaction.inc @@ -0,0 +1,24 @@ + 0x03, 0x00, 0x88, 0x88, 0x07, 0x00, 0x89, 0x88, 0x0b, 0x00, 0x83, 0x88, + 0x0f, 0x00, 0x87, 0x88, 0x00, 0x00, 0x88, 0x98, 0x04, 0x00, 0x89, 0x98, + 0x08, 0x00, 0x83, 0x98, 0x0c, 0x00, 0x87, 0x98, 0x25, 0x30, 0x00, 0x01, + 0x00, 0x00, 0x62, 0x90, 0x05, 0x00, 0x42, 0x30, 0x04, 0x00, 0x42, 0x38, + 0xfc, 0xff, 0x40, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x90, + 0x08, 0x00, 0x42, 0x30, 0x06, 0x00, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xe2, 0x90, 0x00, 0x00, 0x62, 0x90, 0x08, 0x00, 0x42, 0x30, + 0xfc, 0xff, 0x40, 0x14, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x20, 0x11, + 0x00, 0x00, 0x00, 0x00, 0x21, 0x48, 0x09, 0x01, 0x00, 0x00, 0x62, 0x90, + 0x02, 0x00, 0x42, 0x30, 0xfd, 0xff, 0x40, 0x10, 0x01, 0x00, 0xc5, 0x24, + 0x00, 0x00, 0xc2, 0x90, 0x00, 0x00, 0xe2, 0xa0, 0x00, 0x00, 0x62, 0x90, + 0x05, 0x00, 0x42, 0x30, 0x04, 0x00, 0x42, 0x38, 0x1f, 0x00, 0x40, 0x10, + 0x25, 0x30, 0xa0, 0x00, 0x10, 0x00, 0x82, 0x90, 0x08, 0x00, 0x40, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x90, 0x08, 0x00, 0x42, 0x30, + 0x04, 0x00, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2, 0x90, + 0x01, 0x00, 0x08, 0x25, 0xff, 0xff, 0x02, 0xa1, 0xea, 0xff, 0xa9, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x82, 0x90, 0x0c, 0x00, 0x40, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x2b, 0x10, 0x05, 0x01, 0x09, 0x00, 0x40, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x90, 0x08, 0x00, 0x42, 0x30, + 0xfd, 0xff, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2, 0x90, + 0x01, 0x00, 0x08, 0x25, 0xf9, 0xff, 0x05, 0x15, 0xff, 0xff, 0x02, 0xa1, + 0x00, 0x00, 0x04, 0x20, 0x3f, 0x00, 0x00, 0x70, 0x08, 0x00, 0xe0, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x20, 0x3f, 0x00, 0x00, 0x70, + 0x08, 0x00, 0xe0, 0x03, 0x00, 0x00, 0x00, 0x00 diff --git a/doc/openocd.texi b/doc/openocd.texi index 7169ef08b5..6666d53c6d 100644 --- a/doc/openocd.texi +++ b/doc/openocd.texi @@ -6357,6 +6357,73 @@ flash bank $_FLASHNAME fespi 0x20000000 0 0 0 $_TARGETNAME @end example @end deffn +@deffn {Flash Driver} {dw-spi} +@cindex DesignWare SPI controller driver +@cindex DW-SPI +Driver for SPI NOR flash chips connected via DesignWare SPI Core, used +in number of MCUs. +Currently, only MIPS M4K CPU architecture is supported. +This driver requires configuring DRAM controller first, setting up +working area big enough to hold read/write buffers and switching Flash +chip to 32bit mode via Tcl commands. + +@quotation Note +If chip contains Boot controller, its 24/32bit setting must match +Flash chip. If Flash chip's reset line is not connected to JTAG adapter, +CPU reset may cause these configurations to be out of sync. +@end quotation + + +Driver's required arguments are + +@itemize +@item @var{freq} ... core frequency in Hz, used for calculating +communication speed. +@item @var{simc} ... @var{SIMC} register block absolute address. +This value is the same as for Linux's driver device tree register field. +@item @var{spi_mst} ... @var{SPI_MST} register address. When available, +it is used for switching between SPI Boot and Master controllers. This +value is the same as for Linux's driver device tree register field +second argument. +@item @var{if_owner_offset} ... offset of @var{if_owner} field inside +@var{SPI_MST} register. +@end itemize + +Driver provides shortcut arguments for MSCC @var{jaguar2} and @var{ocelot} +switch chips, which set the correct values for @var{freq}, @var{simc}, +@var{spi_mst} and @var{if_owner_offset} arguments. +Therefore, the following commands are equivalent + +@example +flash bank $_FLASHNAME dw-spi 0x40000000 0x02000000 4 4 $_TARGETNAME freq 250000000 simc 0x70101000 spi_mst 0x70000024 if_owner_offset 6 speed 2000000 +flash bank $_FLASHNAME dw-spi 0x40000000 0x02000000 4 4 $_TARGETNAME jaguar2 speed 2000000 +@end example + +The optional driver's arguments are + +@itemize +@item @var{speed} ... SPI device communication speed. Minimum speed is +limited by the clock divisor, which could be set to maximum value of +@var{0xfffe}. The default value is @var{1000000}. +@item @var{chip_select} ... Chip select pin. The default value +is @var{0}. +@item @var{bootctrl_read} ... This flag instructs driver to use Boot +controller instead of Master controller for reading. +Disabled by default. +@item @var{transaction_timeout} ... SPI transaction timeout in +seconds. The default value is @var{5}. +@item @var{program_timeout} ... program timeout in +seconds. The default value is @var{600}. +@item @var{read_timeout} ... read timeout in +seconds. The default value is @var{600}. +@item @var{erase_timeout} ... erase timeout in +seconds. The default value is @var{600}. +@item @var{erase_check_timeout} ... erase check timeout in +seconds. The default value is @var{600}. +@end itemize + +@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 afa11e7d40..1811df27c7 100644 --- a/src/flash/nor/Makefile.am +++ b/src/flash/nor/Makefile.am @@ -80,7 +80,8 @@ NOR_DRIVERS = \ %D%/w600.c \ %D%/xcf.c \ %D%/xmc1xxx.c \ - %D%/xmc4xxx.c + %D%/xmc4xxx.c \ + %D%/dw-spi.c NORHEADERS = \ %D%/core.h \ diff --git a/src/flash/nor/driver.h b/src/flash/nor/driver.h index 211661e214..b7b4779db7 100644 --- a/src/flash/nor/driver.h +++ b/src/flash/nor/driver.h @@ -309,5 +309,6 @@ extern const struct flash_driver w600_flash; extern const struct flash_driver xcf_flash; extern const struct flash_driver xmc1xxx_flash; extern const struct flash_driver xmc4xxx_flash; +extern const struct flash_driver dw_spi_flash; #endif /* OPENOCD_FLASH_NOR_DRIVER_H */ diff --git a/src/flash/nor/drivers.c b/src/flash/nor/drivers.c index 34359889a6..69a6e707dd 100644 --- a/src/flash/nor/drivers.c +++ b/src/flash/nor/drivers.c @@ -86,6 +86,7 @@ static const struct flash_driver * const flash_drivers[] = { &xmc4xxx_flash, &w600_flash, &rsl10_flash, + &dw_spi_flash, NULL, }; diff --git a/src/flash/nor/dw-spi-helper.h b/src/flash/nor/dw-spi-helper.h new file mode 100644 index 0000000000..d64b8784fe --- /dev/null +++ b/src/flash/nor/dw-spi-helper.h @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/** + * @file + * Driver for SPI NOR flash chips connected via DesignWare SPI Core. + * + * In order to avoid using stack, all helper function arguments are packed + * into single struct, passed by pointer. + * + * This file contains helper function argument structures. + */ + +#ifndef OPENOCD_FLASH_NOR_DW_SPI_HELPER_H +#define OPENOCD_FLASH_NOR_DW_SPI_HELPER_H + +#include <stdint.h> + +/** + * @brief Arguments for @transaction helper function. + */ +struct __attribute__((packed)) dw_spi_transaction_t { + uint32_t buffer; ///< Pointer to data buffer to send over SPI. Return + ///< values are stored in place of output data when + ///< @read_flag is 1. + uint32_t size; ///< Size of @buffer. + uint32_t status_reg; ///< Pointer to SR register. + uint32_t data_reg; ///< Pointer to DR register. + uint8_t read_flag; ///< When 1, store RX FIFO data to @buffer. +}; + +/** + * @brief Arguments for @check_fill helper function. + */ +struct __attribute__((packed)) dw_spi_check_fill_t { + uint32_t address; ///< Starting address. Sector aligned. + uint32_t sector_size; ///< Sector size. + uint32_t sector_count; ///< Number of sectors to check. + uint32_t status_reg; ///< Pointer to SR register. + uint32_t data_reg; ///< Pointer to DR register. + uint32_t fill_status_array; ///< Array describing sectors fill status. + ///< 1 if filled, 0 if not filled. + uint8_t pattern; ///< Fill pattern. + uint8_t read_cmd; ///< Read data cmd. + uint8_t four_byte_mode; ///< Four byte addressing mode. +}; + +/** + * @brief Arguments for @erase helper function. + */ +struct __attribute__((packed)) dw_spi_erase_t { + uint32_t address; ///< First sector address. Sector aligned. + uint32_t sector_size; ///< Sector size. + uint32_t sector_count; ///< Number of sectors to erase. + uint32_t status_reg; ///< Pointer to SR register. + uint32_t data_reg; ///< Pointer to DR register. + uint8_t read_status_cmd; ///< Read status cmd. + uint8_t write_enable_cmd; ///< Write enable cmd. + uint8_t erase_sector_cmd; ///< Erase sector cmd. + uint8_t write_enable_mask; ///< Write enable mask. + uint8_t busy_mask; ///< Busy mask. + uint8_t four_byte_mode; ///< Four byte addressing mode. +}; + +/** + * @brief Arguments for @program helper function. + */ +struct __attribute__((packed)) dw_spi_program_t { + uint32_t + address; ///< First page address. Page aligned when @buffer_size > + ///< @page_size + uint32_t page_size; ///< Page size. + uint32_t buffer; ///< Data buffer pointer. + uint32_t buffer_size; ///< Data buffer size. + uint32_t status_reg; ///< Pointer to SR register. + uint32_t data_reg; ///< Pointer to DR register. + uint8_t read_status_cmd; ///< Read status cmd. + uint8_t write_enable_cmd; ///< Write enable cmd. + uint8_t program_cmd; ///< Program cmd. + uint8_t write_enable_mask; ///< Write enable mask. + uint8_t busy_mask; ///< Busy mask. + uint8_t four_byte_mode; ///< Four byte addressing mode. +}; + +/** + * @brief Arguments for @read helper function. + */ +struct __attribute__((packed)) dw_spi_read_t { + uint32_t address; ///< First sector address. + uint32_t buffer; ///< Data buffer pointer. + uint32_t buffer_size; ///< Data buffer size. + uint32_t status_reg; ///< Pointer to SR register. + uint32_t data_reg; ///< Pointer to DR register. + uint8_t read_cmd; ///< Read data cmd. + uint8_t four_byte_mode; ///< Four byte addressing mode. +}; + +#endif /* OPENOCD_FLASH_NOR_DW_SPI_HELPER_H */ diff --git a/src/flash/nor/dw-spi.c b/src/flash/nor/dw-spi.c new file mode 100644 index 0000000000..d7ac56afb5 --- /dev/null +++ b/src/flash/nor/dw-spi.c @@ -0,0 +1,1464 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Driver for SPI NOR flash chips connected via DesignWare SPI Core. + * Controller's Linux driver is located at drivers/spi/spi-dw-mmio.c. + * DW-SPI is used in a number processors, including Jaguar2 and Ocelot MSCC + * switch chips. + * + * In MSCC devices nCS0 line is shared between Boot and Master controllers + * and cannot be controller via GPIO controller. It is asserted only when + * TX FIFO is not empty. Since JTAG is not fast enough to fill TX FIFO and + * collect data from RX FIFO at the same time even on the slowest SPI clock + * speeds, driver can only operate using functions loaded in target's + * memory. Therefore, it requires user to setup DRAM controller and provide + * work-area. + * + * This code was tested on Jaguar2 VSC7448 connected to Macronix + * MX25L25635F. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "dw-spi-helper.h" +#include "imp.h" +#include "spi.h" + +#include <helper/bits.h> +#include <target/algorithm.h> +#include <target/breakpoints.h> +#include <target/mips32.h> +#include <target/target_type.h> + +/** + * @brief Check return code of a function. + */ +#define CHECK_RET(EXP) \ + do { \ + int ret; \ + ret = (EXP); \ + if (ret) \ + return ret; \ + } while (0) + +/** + * @brief Debug logging level. + */ +#define LOG_TRACE LOG_DEBUG + +// {{{ DW-SPI defs + +/** + * @brief IP block placement map. Used for dynamic definition of the + * register map. + * + * IP block is used on different chips and placed in different locations. + * This structure defines some implementation specific variables. + */ +typedef struct { + uint32_t freq; ///< Clock frequency. + target_addr_t simc; ///< Absolute offset of SIMC register block. + target_addr_t spi_mst; ///< Absolute offset of ICPU_CFG:SPI_MST + ///< register. 0 if not available. + uint8_t si_if_owner_offset; ///< Offset of \ref si_mode_t bits in + ///< ICPU_CFG:SPI_MST. +} dw_spi_regmap_t; + +/** + * @brief Register map for Jaguar2 switch devices. + */ +static const dw_spi_regmap_t jaguar2_regmap = { + .freq = 250000000UL, + .simc = 0x70101000UL, + .spi_mst = 0x70000024UL, + .si_if_owner_offset = 6, +}; + +/** + * @brief Register map for Ocelot switch devices. + */ +static const dw_spi_regmap_t ocelot_regmap = { + .freq = 250000000UL, + .simc = 0x70101000UL, + .spi_mst = 0x70000024UL, + .si_if_owner_offset = 4, +}; + +#define IF_OWNER_WIDTH 2 + +/** + * @brief Owner of the SI interface. + */ +typedef enum { + SI_BOOT_CONTROLLER = 1, ///< Boot controller maps contents of SPI Flash + ///< to memory in read-only mode. + SI_MASTER_CONTROLLER = + 2, ///< SPI controller mode for reading/writing SPI Flash. +} si_mode_t; + +#define REG_CTRLR0 0x00 ///< General configuration register. +#define REG_SIMCEN 0x08 ///< Master controller enable register. +#define REG_SER 0x10 ///< Slave select register. +#define REG_BAUDR 0x14 ///< Baud rate configuration register. +#define REG_SR 0x28 ///< Status register. +#define REG_IMR 0x2c ///< Interrupt configuration register. +#define REG_DR 0x60 ///< Data register. + +#define REG_CTRLR0_DFS(x) ((x) & GENMASK(3, 0)) ///< Data frame size. +#define REG_CTRLR0_FRF(x) (((x) << 4) & GENMASK(5, 4)) ///< SI protocol. +#define REG_CTRLR0_SCPH(x) ((!!(x)) << 6) ///< Probe position. +#define REG_CTRLR0_SCPOL(x) ((!!(x)) << 7) ///< Probe polarity. +#define REG_CTRLR0_TMOD(x) (((x) << 8) & GENMASK(9, 8)) ///< SI mode. +#define REG_SIMCEN_SIMCEN(x) (!!(x)) ///< Controller enable. +#define REG_SER_SER(x) ((x) & GENMASK(15, 0)) ///< Slave select bitmask. +#define REG_BAUDR_SCKDV(x) ((x) & GENMASK(15, 0)) ///< Clock divisor. +// }}} + +// {{{ Driver defs +/** + * Driver private state. + */ +typedef struct { + bool probed; ///< Bank is probed. + uint32_t id; ///< Chip ID. + size_t speed; ///< Flash speed. + size_t transaction_timeout; ///< Transaction timeout in seconds. + size_t program_timeout; ///< Program timeout in seconds. + size_t read_timeout; ///< Read timeout in seconds. + size_t erase_timeout; ///< Erase timeout in seconds. + size_t erase_check_timeout; ///< Erase check timeout in seconds. + uint8_t chip_select_bitmask; ///< Chip select bitmask. + bool four_byte_mode; ///< Flash chip is in 32bit address mode. + bool read_boot_ctrl; ///< Read flash using boot controller + ///< instead of master controller. + si_mode_t saved_ctrl_mode; ///< Previously selected controller mode. + dw_spi_regmap_t regmap; ///< SI controller regmap. + const struct flash_device *spi_flash; ///< SPI flash device info. +} dw_spi_driver_t; + +#define ARG_REG \ + "r4" ///< Register used to pass args & receive return code from helper + ///< functions. +// }}} + +// {{{ DW-SPI funcs +/** + * @brief Select SI controller mode. + * + * Mode selection is skipped if %spi_mst is not configured. + * + * @param[in] bank Flash bank. + * @param[in] mode New controller mode. + * @return Command execution status. + */ +static int +ctrl_mode(const struct flash_bank *const bank, si_mode_t mode) +{ + struct target *const target = bank->target; + const dw_spi_driver_t *const driver = bank->driver_priv; + const dw_spi_regmap_t *const regmap = &driver->regmap; + uint32_t ctrl; + + if (!regmap->spi_mst) + return ERROR_OK; + + CHECK_RET(target_read_u32(target, regmap->spi_mst, &ctrl)); + ctrl &= ~GENMASK(IF_OWNER_WIDTH + driver->regmap.si_if_owner_offset, + driver->regmap.si_if_owner_offset); + ctrl |= mode << driver->regmap.si_if_owner_offset; + + return target_write_u32(target, regmap->spi_mst, ctrl); +} + +/** + * @brief Push controller mode. + * + * Mode selection is skipped if %spi_mst is not configured. + * + * @param[in] bank Flash bank. + * @param[in] mode New controller mode. + * @return Command execution status. + */ +static int +ctrl_mode_push(const struct flash_bank *const bank, si_mode_t mode) +{ + struct target *const target = bank->target; + dw_spi_driver_t *const driver = bank->driver_priv; + const dw_spi_regmap_t *const regmap = &driver->regmap; + uint32_t ctrl; + + if (!regmap->spi_mst) + return ERROR_OK; + + CHECK_RET(target_read_u32(target, regmap->spi_mst, &ctrl)); + driver->saved_ctrl_mode = + (si_mode_t)((ctrl >> driver->regmap.si_if_owner_offset) & + GENMASK(IF_OWNER_WIDTH, 0)); + + return ctrl_mode(bank, mode); +} + +/** + * @brief Push controller mode. + * + * Mode selection is skipped if %spi_mst is not configured. + * + * @param[in] bank Flash bank. + * @return Command execution status. + */ +static int +ctrl_mode_pop(const struct flash_bank *const bank) +{ + const dw_spi_driver_t *const driver = bank->driver_priv; + + return ctrl_mode(bank, driver->saved_ctrl_mode); +} + +/** + * @brief Enable master controller. + * + * Configuration of the master controller must be done when it's disabled. + * + * @param[in] bank Flash bank. + * @param[in] value New enable state. + * @return Command execution status. + */ +static int +master_ctrl_enable(const struct flash_bank *const bank, bool value) +{ + struct target *const target = bank->target; + const dw_spi_driver_t *const driver = bank->driver_priv; + const dw_spi_regmap_t *const regmap = &driver->regmap; + + return target_write_u32(target, regmap->simc + REG_SIMCEN, + REG_SIMCEN_SIMCEN(value)); +} + +/** + * @brief Configure SPI transfer mode. + * + * @param[in] bank Flash bank. + * @return Command execution status. + */ +static int +ctrl_configure_spi(const struct flash_bank *const bank) +{ + struct target *const target = bank->target; + const dw_spi_driver_t *const driver = bank->driver_priv; + const dw_spi_regmap_t *const regmap = &driver->regmap; + + // 8 bit frame; Motorolla protocol; middle lo probe; TX RX mode + const uint32_t mode = REG_CTRLR0_DFS(0x7) | REG_CTRLR0_FRF(0) | + REG_CTRLR0_SCPH(0) | REG_CTRLR0_SCPOL(0) | + REG_CTRLR0_TMOD(0); + + CHECK_RET(target_write_u32(target, regmap->simc + REG_CTRLR0, mode)); + return target_write_u32(target, regmap->simc + REG_SER, + REG_SER_SER(driver->chip_select_bitmask)); +} + +/** + * @brief Configure SPI transfer speed. + * + * @param[in] bank Flash bank. + * @param[in] speed Transfer speed. + * @return Command execution status. + */ +static int +ctrl_configure_speed(const struct flash_bank *const bank, uint32_t speed) +{ + struct target *const target = bank->target; + const dw_spi_driver_t *const driver = bank->driver_priv; + const dw_spi_regmap_t *const regmap = &driver->regmap; + + const uint16_t div = (regmap->freq / speed) & 0xfffe; + + LOG_INFO("Setting NOR controller speed to %d kHz", + regmap->freq / div / 1000); + + return target_write_u32(target, regmap->simc + REG_BAUDR, + REG_BAUDR_SCKDV(div)); +} + +/** + * @brief Disable SI interrupts. + * + * @param[in] bank Flash bank. + * @return Command execution status. + */ +static int +ctrl_disable_interrupts(const struct flash_bank *const bank) +{ + struct target *const target = bank->target; + const dw_spi_driver_t *const driver = bank->driver_priv; + const dw_spi_regmap_t *const regmap = &driver->regmap; + + return target_write_u32(target, regmap->simc + REG_IMR, 0); +} + +/** + * @brief Do data transaction. Buffer data are send and replaced with + * received data. + * + * @param[in] bank Flash bank. + * @param[in,out] buffer Data buffer. If \p read flag is set, buffer is + * filled with received data. + * @param[in] size Data size. + * @param[in] read The read flag. If set to true, read values will override \p + * buffer. + * @return Command execution status. + */ +static int +ctrl_transaction(const struct flash_bank *const bank, + uint8_t *const buffer, size_t size, bool read) +{ + struct target *const target = bank->target; + const dw_spi_driver_t *const driver = bank->driver_priv; + const dw_spi_regmap_t *const regmap = &driver->regmap; + + struct reg_param reg_param; + struct mem_param mem_param; + struct working_area *helper, *helper_args, *target_buffer; + struct dw_spi_transaction_t *helper_args_val; + struct mips32_algorithm mips32_algo; + int ret = ERROR_OK; + + static const uint8_t target_code[] = { +#include "../../../contrib/loaders/flash/dw-spi/mipsel-linux-gnu-transaction.inc" + }; + const size_t target_code_size = sizeof(target_code); + + // allocate working area, memory args and data buffer + ret = target_alloc_working_area(target, target_code_size, &helper); + ret |= target_alloc_working_area(target, + sizeof(struct dw_spi_transaction_t), + &helper_args); + ret |= target_alloc_working_area(target, size, &target_buffer); + if (ret) { + LOG_ERROR("could not allocate working area"); + return ret; + } + + // write algorithm code and buffer to working areas + ret = target_write_buffer(target, helper->address, target_code_size, + target_code); + ret |= + target_write_buffer(target, target_buffer->address, size, buffer); + if (ret) { + target_free_all_working_areas(target); + LOG_ERROR("writing to working area error"); + return ret; + } + + // prepare helper execution + mips32_algo.common_magic = MIPS32_COMMON_MAGIC; + mips32_algo.isa_mode = MIPS32_ISA_MIPS32; + + init_reg_param(®_param, ARG_REG, 32, PARAM_IN_OUT); + init_mem_param(&mem_param, helper_args->address, helper_args->size, + PARAM_OUT); + + // Set the arguments for the helper + buf_set_u32(reg_param.value, 0, 32, helper_args->address); + + helper_args_val = (struct dw_spi_transaction_t *)mem_param.value; + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->buffer, + target_buffer->address); + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->size, size); + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->status_reg, + regmap->simc + REG_SR); + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->data_reg, + regmap->simc + REG_DR); + helper_args_val->read_flag = read; + + LOG_TRACE("transaction: entry 0x%zx; args 0x%zx; buffer 0x%zx; size " + "%zd; read %d", + helper->address, helper_args->address, + target_buffer->address, size, read); + ret = target_run_algorithm(target, 1, &mem_param, 1, ®_param, + helper->address, 0, + driver->transaction_timeout * 1000, + &mips32_algo); + + if (ret) { + LOG_ERROR("flash algorithm error"); + goto cleanup; + } + + ret = buf_get_u32(reg_param.value, 0, 32); + if (ret) { + LOG_ERROR("flash transaction error"); + ret = ERROR_TARGET_ALGO_EXIT; + goto cleanup; + } + + if (read) + ret = target_read_buffer(target, target_buffer->address, size, + buffer); + +cleanup: + destroy_reg_param(®_param); + destroy_mem_param(&mem_param); + + target_free_all_working_areas(target); + + return ret; +} + +/** + * @brief Check that selected region is filled with pattern. + * + * @param[in] bank Flash bank. + * @param[in] address Starting address. Sector aligned. + * @param[in] sector_size Size of sector. + * @param[in] sector_count Number of sectors. + * @param[in] pattern Fill pattern. + * @param[in] read_cmd Flash read command. + * @param[out] buffer Filled flag array. Must hold \p sector_count number + * of entries. + * @return Command execution status. + */ +static int +ctrl_check_sectors_fill(const struct flash_bank *const bank, + uint32_t address, size_t sector_size, + size_t sector_count, uint8_t pattern, + uint8_t read_cmd, uint8_t *buffer) +{ + struct target *const target = bank->target; + const dw_spi_driver_t *const driver = bank->driver_priv; + const dw_spi_regmap_t *const regmap = &driver->regmap; + + struct reg_param reg_param; + struct mem_param mem_param; + struct working_area *helper, *helper_args, *target_buffer; + struct dw_spi_check_fill_t *helper_args_val; + struct mips32_algorithm mips32_algo; + int ret = ERROR_OK; + + static const uint8_t target_code[] = { +#include "../../../contrib/loaders/flash/dw-spi/mipsel-linux-gnu-check_fill.inc" + }; + const size_t target_code_size = sizeof(target_code); + + // allocate working area, memory args and data buffer + ret = target_alloc_working_area(target, target_code_size, &helper); + ret |= target_alloc_working_area(target, + sizeof(struct dw_spi_check_fill_t), + &helper_args); + ret |= target_alloc_working_area(target, sector_count, &target_buffer); + if (ret) { + LOG_ERROR("no working area available"); + return ret; + } + + // write algorithm code and buffer to working areas + ret = target_write_buffer(target, helper->address, target_code_size, + target_code); + if (ret) { + target_free_all_working_areas(target); + LOG_ERROR("writing to working area error"); + return ret; + } + + // prepare helper execution + mips32_algo.common_magic = MIPS32_COMMON_MAGIC; + mips32_algo.isa_mode = MIPS32_ISA_MIPS32; + + init_reg_param(®_param, ARG_REG, 32, PARAM_IN_OUT); + init_mem_param(&mem_param, helper_args->address, helper_args->size, + PARAM_OUT); + + // Set the arguments for the helper + buf_set_u32(reg_param.value, 0, 32, helper_args->address); + + helper_args_val = (struct dw_spi_check_fill_t *)mem_param.value; + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->address, + address); + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->sector_size, + sector_size); + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->sector_count, + sector_count); + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->status_reg, + regmap->simc + REG_SR); + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->data_reg, + regmap->simc + REG_DR); + target_buffer_set_u32(target, + (uint8_t *)&helper_args_val->fill_status_array, + target_buffer->address); + helper_args_val->pattern = pattern; + helper_args_val->read_cmd = read_cmd; + helper_args_val->four_byte_mode = driver->four_byte_mode; + + LOG_TRACE("fill check: entry 0x%zx; args 0x%zx; start 0x%x; size 0x%zx; " + "count %zd; fill 0x%x; buffer 0x%lx", + helper->address, helper_args->address, address, sector_size, + sector_count, pattern, target_buffer->address); + ret = target_run_algorithm(target, 1, &mem_param, 1, ®_param, + helper->address, 0, + driver->erase_check_timeout * 1000, + &mips32_algo); + + if (ret) { + LOG_ERROR("flash algorithm error"); + goto cleanup; + } + + ret = buf_get_u32(reg_param.value, 0, 32); + if (ret) { + LOG_ERROR("flash erase check error"); + ret = ERROR_TARGET_ALGO_EXIT; + goto cleanup; + } + + ret = target_read_buffer(target, target_buffer->address, sector_count, + buffer); + + if (ret) { + LOG_ERROR("target buffer read error"); + goto cleanup; + } + +cleanup: + destroy_reg_param(®_param); + destroy_mem_param(&mem_param); + + target_free_all_working_areas(target); + + return ret; +} + +/** + * @brief Write flash region. Buffer and offset must be page aligned. + * + * @param[in] bank Flash bank. + * @param[in] address Flash address offset. Must be page aligned. + * @param[in] buffer Data buffer. + * @param[in] buffer_size Buffer size. + * @param[in] page_size Size of flash page. + * @param[in] read_status_cmd Flash command to read chip status. + * @param[in] write_enable_cmd Flash command to set enable write. + * @param[in] program_cmd Flash command to program chip. + * @param[in] write_enable_mask Status byte write enable mask. + * @param[in] busy_mask Status byte write busy mask. + * @return Command execution status. + */ +static int +ctrl_program(const struct flash_bank *const bank, uint32_t address, + const uint8_t *const buffer, size_t buffer_size, + uint32_t page_size, uint8_t read_status_cmd, + uint8_t write_enable_cmd, uint8_t program_cmd, + uint8_t write_enable_mask, uint8_t busy_mask) +{ + struct target *const target = bank->target; + const dw_spi_driver_t *const driver = bank->driver_priv; + const dw_spi_regmap_t *const regmap = &driver->regmap; + + struct reg_param reg_param; + struct mem_param mem_param; + struct working_area *helper, *helper_args, *target_buffer; + struct dw_spi_program_t *helper_args_val; + struct mips32_algorithm mips32_algo; + int ret = ERROR_OK; + + static const uint8_t target_code[] = { +#include "../../../contrib/loaders/flash/dw-spi/mipsel-linux-gnu-program.inc" + }; + const size_t target_code_size = sizeof(target_code); + + // allocate working area, memory args and data buffer + ret = target_alloc_working_area(target, target_code_size, &helper); + ret |= target_alloc_working_area(target, + sizeof(struct dw_spi_program_t), + &helper_args); + ret |= target_alloc_working_area(target, buffer_size, &target_buffer); + if (ret) { + LOG_ERROR("no working area available"); + return ret; + } + + // write algorithm code and buffer to working areas + ret = target_write_buffer(target, helper->address, target_code_size, + target_code) != ERROR_OK; + ret |= target_write_buffer(target, target_buffer->address, buffer_size, + buffer) != ERROR_OK; + if (ret) { + target_free_all_working_areas(target); + LOG_ERROR("writing to working area error"); + return ret; + } + + // prepare helper execution + mips32_algo.common_magic = MIPS32_COMMON_MAGIC; + mips32_algo.isa_mode = MIPS32_ISA_MIPS32; + + init_reg_param(®_param, ARG_REG, 32, PARAM_IN_OUT); + init_mem_param(&mem_param, helper_args->address, helper_args->size, + PARAM_OUT); + + // Set the arguments for the helper + buf_set_u32(reg_param.value, 0, 32, helper_args->address); + helper_args_val = (struct dw_spi_program_t *)mem_param.value; + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->address, + address); + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->page_size, + page_size); + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->buffer, + target_buffer->address); + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->buffer_size, + buffer_size); + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->status_reg, + regmap->simc + REG_SR); + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->data_reg, + regmap->simc + REG_DR); + helper_args_val->read_status_cmd = read_status_cmd; + helper_args_val->write_enable_cmd = write_enable_cmd; + helper_args_val->program_cmd = program_cmd; + helper_args_val->write_enable_mask = write_enable_mask; + helper_args_val->busy_mask = busy_mask; + helper_args_val->four_byte_mode = driver->four_byte_mode; + + LOG_TRACE("program: entry 0x%zx; args 0x%zx, address 0x%x; buffer 0x%zx; " + "buffer_size %zd; page size 0x%x", + helper->address, helper_args->address, address, + target_buffer->address, buffer_size, page_size); + ret = target_run_algorithm(target, 1, &mem_param, 1, ®_param, + helper->address, 0, + driver->program_timeout * 1000, + &mips32_algo); + if (ret) { + LOG_ERROR("flash algorithm error"); + goto cleanup; + } + + ret = buf_get_u32(reg_param.value, 0, 32); + if (ret) { + LOG_ERROR("flash program error"); + ret = ERROR_TARGET_ALGO_EXIT; + goto cleanup; + } + +cleanup: + destroy_reg_param(®_param); + destroy_mem_param(&mem_param); + + target_free_all_working_areas(target); + + return ret; +} + +/** + * @brief Erase sectors. Offset must be sector aligned. + * + * @param[in] bank Flash bank. + * @param[in] address Flash address. Must be sector aligned. + * @param[in] sector_size Size of flash sector. + * @param[in] sector_count Number of sectors to erase. + * @param[in] read_status_cmd Flash command to read chip status. + * @param[in] write_enable_cmd Flash command to set enable write. + * @param[in] erase_sector_cmd Flash command to set erase sector. + * @param[in] write_enable_mask Status byte write enable mask. + * @param[in] busy_mask Status byte write busy mask. + * @return Command execution status. + */ +static int +ctrl_erase_sectors(const struct flash_bank *const bank, uint32_t address, + uint32_t sector_size, size_t sector_count, + uint8_t read_status_cmd, uint8_t write_enable_cmd, + uint8_t erase_sector_cmd, uint8_t write_enable_mask, + uint8_t busy_mask) +{ + struct target *const target = bank->target; + const dw_spi_driver_t *const driver = bank->driver_priv; + const dw_spi_regmap_t *const regmap = &driver->regmap; + + struct reg_param reg_param; + struct mem_param mem_param; + struct working_area *helper, *helper_args; + struct dw_spi_erase_t *helper_args_val; + struct mips32_algorithm mips32_algo; + int ret = ERROR_OK; + + static const uint8_t target_code[] = { +#include "../../../contrib/loaders/flash/dw-spi/mipsel-linux-gnu-erase.inc" + }; + const size_t target_code_size = sizeof(target_code); + + // allocate working area and memory args + ret = target_alloc_working_area(target, target_code_size, &helper); + ret |= target_alloc_working_area(target, sizeof(struct dw_spi_erase_t), + &helper_args); + if (ret) { + LOG_ERROR("no working area available"); + return ret; + } + + // write algorithm code to working area + ret = target_write_buffer(target, helper->address, target_code_size, + target_code); + if (ret) { + target_free_all_working_areas(target); + LOG_ERROR("writing to working area error"); + return ret; + } + + // prepare helper execution + mips32_algo.common_magic = MIPS32_COMMON_MAGIC; + mips32_algo.isa_mode = MIPS32_ISA_MIPS32; + + init_reg_param(®_param, ARG_REG, 32, PARAM_IN_OUT); + init_mem_param(&mem_param, helper_args->address, helper_args->size, + PARAM_OUT); + + // Set the arguments for the helper + buf_set_u32(reg_param.value, 0, 32, helper_args->address); + helper_args_val = (struct dw_spi_erase_t *)mem_param.value; + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->address, + address); + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->sector_size, + sector_size); + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->sector_count, + sector_count); + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->status_reg, + regmap->simc + REG_SR); + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->data_reg, + regmap->simc + REG_DR); + helper_args_val->read_status_cmd = read_status_cmd; + helper_args_val->write_enable_cmd = write_enable_cmd; + helper_args_val->erase_sector_cmd = erase_sector_cmd; + helper_args_val->write_enable_mask = write_enable_mask; + helper_args_val->busy_mask = busy_mask; + helper_args_val->four_byte_mode = driver->four_byte_mode; + + LOG_TRACE("erase: entry 0x%zx; args 0x%zx, offset 0x%x; sector count " + "%zd; sector " + "size 0x%x", + helper->address, helper_args->address, address, sector_count, + sector_size); + ret = target_run_algorithm(target, 1, &mem_param, 1, ®_param, + helper->address, 0, + driver->erase_timeout * 1000, &mips32_algo); + if (ret) { + LOG_ERROR("flash algorithm error"); + goto cleanup; + } + + ret = buf_get_u32(reg_param.value, 0, 32); + if (ret) { + LOG_ERROR("flash erase error"); + ret = ERROR_TARGET_ALGO_EXIT; + goto cleanup; + } + +cleanup: + destroy_reg_param(®_param); + destroy_mem_param(&mem_param); + + target_free_all_working_areas(target); + + return ret; +} + +/** + * @brief Read flash data. + * + * @param[in] bank Flash bank. + * @param[in] address Flash address. + * @param[out] buffer Data buffer. + * @param[in] buffer_size Data buffer size. + * @param[in] read_cmd Flash command to read data from flash. + * @return Command execution status. + */ +static int +ctrl_read(const struct flash_bank *const bank, uint32_t address, + uint8_t *buffer, size_t buffer_size, uint8_t read_cmd) +{ + struct target *const target = bank->target; + const dw_spi_driver_t *const driver = bank->driver_priv; + const dw_spi_regmap_t *const regmap = &driver->regmap; + + struct reg_param reg_param; + struct mem_param mem_param; + struct working_area *helper, *helper_args, *target_buffer; + struct dw_spi_read_t *helper_args_val; + struct mips32_algorithm mips32_algo; + int ret = ERROR_OK; + + static const uint8_t target_code[] = { +#include "../../../contrib/loaders/flash/dw-spi/mipsel-linux-gnu-read.inc" + }; + const size_t target_code_size = sizeof(target_code); + + // allocate working area and memory args + ret = target_alloc_working_area(target, target_code_size, &helper); + ret |= target_alloc_working_area(target, sizeof(struct dw_spi_read_t), + &helper_args); + ret |= target_alloc_working_area(target, buffer_size, &target_buffer); + if (ret) { + LOG_ERROR("no working area available"); + return ret; + } + + // write algorithm code to working area + ret = target_write_buffer(target, helper->address, target_code_size, + target_code); + if (ret) { + target_free_all_working_areas(target); + LOG_ERROR("writing to working area error"); + return ret; + } + + // prepare helper execution + mips32_algo.common_magic = MIPS32_COMMON_MAGIC; + mips32_algo.isa_mode = MIPS32_ISA_MIPS32; + + init_reg_param(®_param, ARG_REG, 32, PARAM_IN_OUT); + init_mem_param(&mem_param, helper_args->address, helper_args->size, + PARAM_OUT); + + // Set the arguments for the helper + buf_set_u32(reg_param.value, 0, 32, helper_args->address); + helper_args_val = (struct dw_spi_read_t *)mem_param.value; + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->address, + address); + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->buffer, + target_buffer->address); + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->buffer_size, + buffer_size); + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->status_reg, + regmap->simc + REG_SR); + target_buffer_set_u32(target, (uint8_t *)&helper_args_val->data_reg, + regmap->simc + REG_DR); + helper_args_val->read_cmd = read_cmd; + helper_args_val->four_byte_mode = driver->four_byte_mode; + + LOG_TRACE("read: entry 0x%zx; args 0x%zx, address 0x%x, buffer 0x%zx, " + "size 0x%zx", + helper->address, helper_args->address, address, + target_buffer->address, buffer_size); + ret = target_run_algorithm(target, 1, &mem_param, 1, ®_param, + helper->address, 0, + driver->read_timeout * 1000, &mips32_algo); + if (ret) { + LOG_ERROR("flash algorithm error"); + goto cleanup; + } + + ret = buf_get_u32(reg_param.value, 0, 32); + if (ret) { + LOG_ERROR("flash read error"); + ret = ERROR_TARGET_ALGO_EXIT; + goto cleanup; + } + ret = target_read_buffer(target, target_buffer->address, buffer_size, + buffer); + +cleanup: + destroy_reg_param(®_param); + destroy_mem_param(&mem_param); + + target_free_all_working_areas(target); + + return ret; +} +// }}} + +// {{{ Nor funcs +/** + * @brief Read device ID. + * + * @param[in] bank Flash bank handle. + * @return Command execution status. + */ +static int +read_id(const struct flash_bank *const bank) +{ + dw_spi_driver_t *const driver = bank->driver_priv; + const size_t buffer_size = 1 + 3 + 1; + uint8_t buffer[buffer_size]; + + LOG_TRACE("look for chip ID"); + memset(buffer, 0, buffer_size); + buffer[0] = SPIFLASH_READ_ID; + + CHECK_RET(ctrl_transaction(bank, buffer, buffer_size, true)); + + buffer[buffer_size - 1] = 0; + driver->id = le_to_h_u32(buffer + 1); + + return ERROR_OK; +} + +/** + * @brief Read status. + * + * @param[in] bank Flash bank handle. + * @param[out] status The status byte. + * @return Command execution status. + */ +static int +read_status(const struct flash_bank *const bank, uint8_t *const status) +{ + const int buffer_size = 2; + uint8_t buffer[buffer_size]; + + LOG_TRACE("read status"); + memset(buffer, 0, buffer_size); + buffer[0] = SPIFLASH_READ_STATUS; + + CHECK_RET(ctrl_transaction(bank, buffer, buffer_size, true)); + + *status = buffer[1]; + LOG_TRACE("read status 0x%x", *status); + + return ERROR_OK; +} + +/** + * @brief Wait for command to finish. + * + * @param[in] bank Flash bank handle. + * @param[in] timeout The timeout in ms. + * @return Command execution status. + */ +static int +wait_finish(const struct flash_bank *const bank, size_t timeout) +{ + uint8_t status; + + for (size_t count = 0; count < timeout; count++) { + CHECK_RET(read_status(bank, &status)); + if (!(status & SPIFLASH_BSY_BIT)) + return ERROR_OK; + + usleep(1000); + } + + return ERROR_TIMEOUT_REACHED; +} + +/** + * @brief Write enable. + * + * @param[in] bank Flash bank handle. + * @return Command execution status. + */ +static int +write_enable(const struct flash_bank *const bank) +{ + uint8_t status; + const int buffer_size = 1; + uint8_t buffer[buffer_size]; + + LOG_TRACE("set write enable"); + memset(buffer, 0, buffer_size); + buffer[0] = SPIFLASH_WRITE_ENABLE; + + CHECK_RET(ctrl_transaction(bank, buffer, buffer_size, false)); + CHECK_RET(read_status(bank, &status)); + + return status & SPIFLASH_WE_BIT ? ERROR_OK : ERROR_FAIL; +} + +/** + * @brief Erase chip. + * + * @param[in] bank Flash bank handle. + * @return Command execution status. + */ +static int +erase_chip(const struct flash_bank *const bank) +{ + const dw_spi_driver_t *const driver = bank->driver_priv; + const int buffer_size = 1; + uint8_t buffer[buffer_size]; + + CHECK_RET(write_enable(bank)); + LOG_TRACE("erase chip"); + + memset(buffer, 0, buffer_size); + buffer[0] = driver->spi_flash->chip_erase_cmd; + + CHECK_RET(ctrl_transaction(bank, buffer, buffer_size, false)); + CHECK_RET(wait_finish(bank, driver->erase_timeout * 1000)); + + for (size_t sector_idx = 0; sector_idx < bank->num_sectors; + sector_idx++) { + bank->sectors[sector_idx].is_erased = 1; + } + + return ERROR_OK; +} + +/** + * @brief Erase sectors. + * + * @param[in] bank Flash bank handle. + * @param[in] first The first sector to erase. + * @param[in] last The last sector to erase. + * @return Command execution status. + */ +static int +erase_sectors(const struct flash_bank *const bank, unsigned int first, + unsigned int last) +{ + const dw_spi_driver_t *const driver = bank->driver_priv; + + if (first == 0 && last >= (bank->num_sectors - 1)) { + CHECK_RET(erase_chip(bank)); + } else { + CHECK_RET(ctrl_erase_sectors(bank, bank->sectors[first].offset, + driver->spi_flash->sectorsize, + last - first + 1, SPIFLASH_READ_STATUS, + SPIFLASH_WRITE_ENABLE, + driver->spi_flash->erase_cmd, + SPIFLASH_WE_BIT, SPIFLASH_BSY_BIT)); + } + + for (size_t sector_idx = first; sector_idx <= last; sector_idx++) + bank->sectors[sector_idx].is_erased = 1; + + return ERROR_OK; +} + +/** + * @brief Flash bank blank check. + */ +static int +blank_check(struct flash_bank *bank, size_t sector_count, uint8_t pattern) +{ + const dw_spi_driver_t *const driver = bank->driver_priv; + int ret; + + uint8_t *erased = malloc(sector_count); + if (!erased) { + LOG_ERROR("Could not allocate memory"); + return ERROR_FAIL; + } + + memset(erased, 2, sector_count); // set initial erased value to unknown + for (size_t sector_idx = 0; sector_idx < sector_count; sector_idx++) { + bank->sectors[sector_idx].is_erased = + 2; // set initial erased value to unknown + } + + ret = ctrl_check_sectors_fill(bank, 0, bank->sectors[0].size, + sector_count, pattern, + driver->spi_flash->read_cmd, erased); + if (!ret) { + for (size_t sector_idx = 0; sector_idx < sector_count; + sector_idx++) { + bank->sectors[sector_idx].is_erased = erased[sector_idx]; + } + } + + free(erased); + + return ret; +} + +/** + * @brief Write buffer to flash. + * + * @param[in] bank Flash bank handle. + * @param[in] buffer Data buffer. + * @param[in] offset Data offset. + * @param[in] count Buffer size. + * @return Command execution status. + */ +static int +write_buffer(const struct flash_bank *const bank, const uint8_t *buffer, + uint32_t offset, uint32_t count) +{ + const dw_spi_driver_t *const driver = bank->driver_priv; + const size_t sector_size = driver->spi_flash->sectorsize; + const size_t page_size = driver->spi_flash->pagesize; + + // Write unaligned first sector separately as helper function does + // not handle this case well. + struct { + uint32_t address; + const uint8_t *buffer; + size_t count; + } chunks[2] = { + {.address = offset, .buffer = buffer, .count = 0}, + {.address = offset, .buffer = buffer, .count = count}, + }; + + for (size_t sector_idx = offset / sector_size; + sector_idx < ((offset + count) / sector_size + + ((offset + count) % sector_size ? 1 : 0)); + sector_idx++) { + if (bank->sectors[sector_idx].is_erased == 0) + LOG_WARNING("writing to not erased sector %zd", sector_idx); + bank->sectors[sector_idx].is_erased = 0; + } + + if (offset % page_size) { + // start is not aligned + chunks[0].count = MIN(page_size - (offset % page_size), count); + chunks[1].count -= chunks[0].count; + chunks[1].address += chunks[0].count; + chunks[1].buffer += chunks[0].count; + } + + for (size_t chunk_idx = 0; chunk_idx < ARRAY_SIZE(chunks); + chunk_idx++) { + if (chunks[chunk_idx].count > 0) + CHECK_RET(ctrl_program(bank, chunks[chunk_idx].address, + chunks[chunk_idx].buffer, + chunks[chunk_idx].count, page_size, + SPIFLASH_READ_STATUS, SPIFLASH_WRITE_ENABLE, + driver->spi_flash->pprog_cmd, + SPIFLASH_WE_BIT, SPIFLASH_BSY_BIT)); + } + + return ERROR_OK; +} + +/** + * @brief Search for SPI chip info. + * + * @param[in] bank Flash bank handle. + * @return Command execution status. + */ +static int +spiflash_search(const struct flash_bank *const bank) +{ + dw_spi_driver_t *const driver = bank->driver_priv; + size_t idx = 0; + + CHECK_RET(read_id(bank)); + + while (flash_devices[idx].name) { + if (flash_devices[idx].device_id == driver->id) { + driver->spi_flash = &flash_devices[idx]; + return ERROR_OK; + } + idx++; + } + + LOG_ERROR("Could not find chip in SPI flash table"); + return ERROR_FAIL; +} +// }}} + +// {{{ Driver funcs +FLASH_BANK_COMMAND_HANDLER(flash_bank_command) +{ + size_t speed = 1000000; + size_t transaction_timeout = 5; + size_t program_timeout = 600; + size_t read_timeout = 600; + size_t erase_check_timeout = 600; + size_t erase_timeout = 600; + uint8_t chip_select_bitmask = BIT(0); + bool read_boot_ctrl = false; + dw_spi_regmap_t regmap = {0}; + + if (CMD_ARGC < 6) + return ERROR_COMMAND_SYNTAX_ERROR; + + for (size_t idx = 6; idx < CMD_ARGC; idx++) { + if (strcmp(CMD_ARGV[idx], "jaguar2") == 0) { + // Fast config for Jaguar2 chips. + LOG_TRACE("set jaguar2"); + memcpy(®map, &jaguar2_regmap, sizeof(jaguar2_regmap)); + } else if (strcmp(CMD_ARGV[idx], "ocelot") == 0) { + // Fast config for ocelot chips. + LOG_TRACE("set ocelot"); + memcpy(®map, &ocelot_regmap, sizeof(ocelot_regmap)); + } else if (strcmp(CMD_ARGV[idx], "freq") == 0) { + regmap.freq = strtol(CMD_ARGV[++idx], NULL, 0); + LOG_TRACE("set freq=%d", regmap.freq); + } else if (strcmp(CMD_ARGV[idx], "simc") == 0) { + regmap.simc = strtol(CMD_ARGV[++idx], NULL, 0); + LOG_TRACE("set simc=0x%lx", regmap.simc); + } else if (strcmp(CMD_ARGV[idx], "spi_mst") == 0) { + regmap.spi_mst = strtol(CMD_ARGV[++idx], NULL, 0); + LOG_TRACE("set spi_mst=0x%lx", regmap.spi_mst); + } else if (strcmp(CMD_ARGV[idx], "if_owner_offset") == 0) { + regmap.si_if_owner_offset = strtol(CMD_ARGV[++idx], NULL, 0); + LOG_TRACE("set if_owner_offset=%d", regmap.si_if_owner_offset); + } else if (strcmp(CMD_ARGV[idx], "speed") == 0) { + speed = strtol(CMD_ARGV[++idx], NULL, 0); + LOG_TRACE("set speed=%ld", speed); + } else if (strcmp(CMD_ARGV[idx], "transaction_timeout") == 0) { + transaction_timeout = strtol(CMD_ARGV[++idx], NULL, 0); + LOG_TRACE("set transaction_timeout=%ld", transaction_timeout); + } else if (strcmp(CMD_ARGV[idx], "program_timeout") == 0) { + program_timeout = strtol(CMD_ARGV[++idx], NULL, 0); + LOG_TRACE("set program_timeout=%ld", program_timeout); + } else if (strcmp(CMD_ARGV[idx], "read_timeout") == 0) { + read_timeout = strtol(CMD_ARGV[++idx], NULL, 0); + LOG_TRACE("set read_timeout=%ld", read_timeout); + } else if (strcmp(CMD_ARGV[idx], "erase_timeout") == 0) { + erase_timeout = strtol(CMD_ARGV[++idx], NULL, 0); + LOG_TRACE("set erase_timeout=%ld", erase_timeout); + } else if (strcmp(CMD_ARGV[idx], "erase_check_timeout") == 0) { + erase_check_timeout = strtol(CMD_ARGV[++idx], NULL, 0); + LOG_TRACE("set erase_check_timeout=%ld", erase_check_timeout); + } else if (strcmp(CMD_ARGV[idx], "chip_select") == 0) { + chip_select_bitmask = BIT(strtol(CMD_ARGV[++idx], NULL, 0)); + LOG_TRACE("set chip_select=0x%x", chip_select_bitmask); + } else if (strcmp(CMD_ARGV[idx], "bootctrl_read") == 0) { + read_boot_ctrl = true; + LOG_TRACE("set bootctrl_read"); + } else { + LOG_WARNING("unknown argument %s", CMD_ARGV[idx]); + } + } + + if (!regmap.simc) { + LOG_ERROR("cannot use boot controller with unconfigured simc"); + return ERROR_COMMAND_SYNTAX_ERROR; + } + + bank->driver_priv = malloc(sizeof(dw_spi_driver_t)); + if (!bank->driver_priv) { + LOG_ERROR("Could not allocate memory"); + return ERROR_FAIL; + } + + dw_spi_driver_t *driver = bank->driver_priv; + memset(driver, 0, sizeof(dw_spi_driver_t)); + driver->speed = speed; + driver->transaction_timeout = transaction_timeout; + driver->program_timeout = program_timeout; + driver->read_timeout = read_timeout; + driver->erase_timeout = erase_timeout; + driver->erase_check_timeout = erase_check_timeout; + driver->chip_select_bitmask = chip_select_bitmask; + driver->four_byte_mode = true; // 24bit commands not provided by spi.h + driver->read_boot_ctrl = read_boot_ctrl; + memcpy(&driver->regmap, ®map, sizeof(regmap)); + + return ERROR_OK; +} + +static int +assert_halted(const struct flash_bank *const bank) +{ + if (bank->target->state != TARGET_HALTED) { + LOG_ERROR("target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + return ERROR_OK; +} + +/** + * @brief Flash bank probe. + */ +static int +probe(struct flash_bank *bank) +{ + dw_spi_driver_t *const driver = bank->driver_priv; + int ret_val; + + if (!driver) + return ERROR_FAIL; + + if (strcmp(bank->target->type->name, mips_m4k_target.name) != 0 || + bank->target->endianness != TARGET_LITTLE_ENDIAN) { + LOG_ERROR("currently, only little endian " + "mips_m4k target is supported"); + return ERROR_TARGET_INVALID; + } + + CHECK_RET(assert_halted(bank)); + CHECK_RET(ctrl_mode_push(bank, SI_MASTER_CONTROLLER)); + CHECK_RET(master_ctrl_enable(bank, false)); + CHECK_RET(ctrl_configure_speed(bank, driver->speed)); + CHECK_RET(ctrl_disable_interrupts(bank)); + CHECK_RET(ctrl_configure_spi(bank)); + CHECK_RET(master_ctrl_enable(bank, true)); + + ret_val = spiflash_search(bank); + + CHECK_RET(master_ctrl_enable(bank, false)); + CHECK_RET(ctrl_mode_push(bank, SI_BOOT_CONTROLLER)); + + if (ret_val) + return ret_val; + + bank->write_start_alignment = 0; + bank->write_end_alignment = 0; + + uint32_t flash_size = driver->spi_flash->size_in_bytes; + if (flash_size > bank->size) + LOG_WARNING("Probed flash size 0x%x is greater then declared 0x%x", + flash_size, bank->size); + if (flash_size < bank->size) { + LOG_ERROR("Probed flash size 0x%x is smaller then declared 0x%x", + flash_size, bank->size); + return ERROR_FLASH_BANK_INVALID; + } + bank->num_sectors = bank->size / driver->spi_flash->sectorsize; + bank->sectors = + malloc(sizeof(struct flash_sector) * bank->num_sectors); + if (!bank->sectors) { + LOG_ERROR("Could not allocate memory"); + return ERROR_FAIL; + } + + uint32_t offset = 0; + for (size_t sector_idx = 0; sector_idx < bank->num_sectors; + sector_idx++) { + bank->sectors[sector_idx].size = driver->spi_flash->sectorsize; + bank->sectors[sector_idx].offset = offset; + bank->sectors[sector_idx].is_erased = + 2; // set initial erased value to unknown + bank->sectors[sector_idx].is_protected = 0; + offset += driver->spi_flash->sectorsize; + } + + driver->probed = true; + return ERROR_OK; +} + +/** + * @brief Flash bank erase sectors. + */ +static int +erase(struct flash_bank *bank, unsigned int first, unsigned int last) +{ + CHECK_RET(assert_halted(bank)); + CHECK_RET(ctrl_mode_push(bank, SI_MASTER_CONTROLLER)); + CHECK_RET(master_ctrl_enable(bank, false)); + CHECK_RET(ctrl_configure_spi(bank)); + CHECK_RET(master_ctrl_enable(bank, true)); + CHECK_RET(erase_sectors(bank, first, last)); + CHECK_RET(master_ctrl_enable(bank, false)); + CHECK_RET(ctrl_mode_pop(bank)); + return ERROR_OK; +} + +/** + * @brief Flash bank write data. + */ +static int +dw_write(struct flash_bank *bank, const uint8_t *buffer, uint32_t offset, + uint32_t count) +{ + CHECK_RET(assert_halted(bank)); + CHECK_RET(ctrl_mode_push(bank, SI_MASTER_CONTROLLER)); + CHECK_RET(master_ctrl_enable(bank, false)); + CHECK_RET(ctrl_configure_spi(bank)); + CHECK_RET(master_ctrl_enable(bank, true)); + CHECK_RET(write_buffer(bank, buffer, offset, count)); + CHECK_RET(master_ctrl_enable(bank, false)); + CHECK_RET(ctrl_mode_pop(bank)); + return ERROR_OK; +} + +/** + * @brief Flash bank read data using master controller. + */ +static int +dw_read_master_ctrl(struct flash_bank *bank, uint8_t *buffer, + uint32_t offset, uint32_t count) +{ + dw_spi_driver_t *const driver = bank->driver_priv; + + CHECK_RET(assert_halted(bank)); + CHECK_RET(ctrl_mode_push(bank, SI_MASTER_CONTROLLER)); + CHECK_RET(master_ctrl_enable(bank, false)); + CHECK_RET(ctrl_configure_spi(bank)); + CHECK_RET(master_ctrl_enable(bank, true)); + CHECK_RET(ctrl_read(bank, offset, buffer, count, + driver->spi_flash->read_cmd)); + CHECK_RET(master_ctrl_enable(bank, false)); + CHECK_RET(ctrl_mode_pop(bank)); + return ERROR_OK; +} + +/** + * @brief Flash bank read data using boot controller. + */ +static int +dw_read_boot_ctrl(struct flash_bank *bank, uint8_t *buffer, + uint32_t offset, uint32_t count) +{ + CHECK_RET(ctrl_mode_push(bank, SI_BOOT_CONTROLLER)); + CHECK_RET(target_read_buffer(bank->target, bank->base + offset, count, + buffer)); + CHECK_RET(ctrl_mode_pop(bank)); + return ERROR_OK; +} + +/** + * @brief Flash bank read data. + */ +static int +dw_read(struct flash_bank *bank, uint8_t *buffer, uint32_t offset, + uint32_t count) +{ + dw_spi_driver_t *const driver = bank->driver_priv; + if (driver->read_boot_ctrl) + return dw_read_boot_ctrl(bank, buffer, offset, count); + else + return dw_read_master_ctrl(bank, buffer, offset, count); +} + +/** + * @brief Flash bank erase check. + */ +static int +erase_check(struct flash_bank *bank) +{ + CHECK_RET(assert_halted(bank)); + CHECK_RET(ctrl_mode_push(bank, SI_MASTER_CONTROLLER)); + CHECK_RET(master_ctrl_enable(bank, false)); + CHECK_RET(ctrl_configure_spi(bank)); + CHECK_RET(master_ctrl_enable(bank, true)); + CHECK_RET(blank_check(bank, bank->num_sectors, bank->erased_value)); + CHECK_RET(master_ctrl_enable(bank, false)); + CHECK_RET(ctrl_mode_pop(bank)); + return ERROR_OK; +} + +/** + * @brief Flash bank info. + */ +static int +info(struct flash_bank *bank, struct command_invocation *cmd) +{ + const dw_spi_driver_t *const driver = bank->driver_priv; + command_print(cmd, "model %s", driver->spi_flash->name); + command_print(cmd, "ID 0x%x", driver->id); + command_print_sameline(cmd, "size 0x%x", bank->size); + return ERROR_OK; +} + +/** + * @brief Autoprobe driver. + */ +static int +auto_probe(struct flash_bank *bank) +{ + dw_spi_driver_t *driver = bank->driver_priv; + if (!driver) + return ERROR_FAIL; + if (!driver->probed) + return probe(bank); + return ERROR_OK; +} +// }}} + +/** + * @brief DW-SPI NOR flash functions. + */ +const struct flash_driver dw_spi_flash = { + .name = "dw-spi", + .flash_bank_command = flash_bank_command, + .erase = erase, + .write = dw_write, + .read = dw_read, + .probe = probe, + .auto_probe = auto_probe, + .erase_check = erase_check, + .info = info, + .verify = default_flash_verify, + .free_driver_priv = default_flash_free_driver_priv, +}; --