]> www.infradead.org Git - users/borneoa/openocd-next.git/commitdiff
flash/nor: add DesignWare SPI controller driver
authorSergey Matsievskiy <matsievskiysv@gmail.com>
Wed, 18 Sep 2024 17:12:48 +0000 (20:12 +0300)
committerTomas Vanek <vanekt@fbl.cz>
Fri, 31 Jan 2025 03:25:53 +0000 (03:25 +0000)
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 reported writing speed
is about 60kb/s.
Lint, sanitizer and valgrind reported warnings were not related to the
driver.

Change-Id: Id3df5626ab88055f034f74f274823051dedefeb1
Signed-off-by: Sergey Matsievskiy <matsievskiysv@gmail.com>
Reviewed-on: https://review.openocd.org/c/openocd/+/8400
Tested-by: jenkins
Reviewed-by: Tomas Vanek <vanekt@fbl.cz>
14 files changed:
contrib/loaders/flash/dw-spi/Makefile [new file with mode: 0644]
contrib/loaders/flash/dw-spi/dw-spi.c [new file with mode: 0644]
contrib/loaders/flash/dw-spi/dw-spi.h [new file with mode: 0644]
contrib/loaders/flash/dw-spi/mipsel-linux-gnu-check_fill.inc [new file with mode: 0644]
contrib/loaders/flash/dw-spi/mipsel-linux-gnu-erase.inc [new file with mode: 0644]
contrib/loaders/flash/dw-spi/mipsel-linux-gnu-program.inc [new file with mode: 0644]
contrib/loaders/flash/dw-spi/mipsel-linux-gnu-read.inc [new file with mode: 0644]
contrib/loaders/flash/dw-spi/mipsel-linux-gnu-transaction.inc [new file with mode: 0644]
doc/openocd.texi
src/flash/nor/Makefile.am
src/flash/nor/driver.h
src/flash/nor/drivers.c
src/flash/nor/dw-spi-helper.h [new file with mode: 0644]
src/flash/nor/dw-spi.c [new file with mode: 0644]

diff --git a/contrib/loaders/flash/dw-spi/Makefile b/contrib/loaders/flash/dw-spi/Makefile
new file mode 100644 (file)
index 0000000..e868278
--- /dev/null
@@ -0,0 +1,35 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+TOOLCHAIN:=mipsel-linux-gnu-
+CC:=$(TOOLCHAIN)gcc
+OBJCOPY:=$(TOOLCHAIN)objcopy
+CFLAGS:=-O2 -Wall -Wextra -fpic -Wno-int-to-pointer-cast
+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 (file)
index 0000000..66b7439
--- /dev/null
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Helper functions for DesignWare SPI Core driver.
+ * These helpers are loaded into CPU and execute Flash manipulation algorithms
+ * at full CPU speed. Due to inability to control nCS pin, this is the only way
+ * to communicate with Flash chips connected via DW SPI serial interface.
+ *
+ * In order to avoid using stack, all functions used in helpers are inlined.
+ * Software breakpoints are used to terminate helpers.
+ *
+ * Pushing byte to TX FIFO does not make byte immediately available in RX FIFO
+ * and nCS is only asserted when TX FIFO is not empty. General approach is to
+ * fill TX FIFO with as many bytes as possible, at the same time reading
+ * available bytes from RX FIFO.
+ *
+ * This file contains helper functions.
+ */
+
+#include "dw-spi.h"
+
+#include "../../../../src/flash/nor/dw-spi-helper.h"
+
+/**
+ * @brief Generic flash transaction.
+ *
+ * @param[in] arg: Function arguments.
+ */
+__attribute__((section(".transaction"))) void
+transaction(struct dw_spi_transaction *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--) {
+               send_u8(status, data, *buffer_tx++);
+               if (arg->read_flag && rx_available(status))
+                       *buffer_rx++ = rcv_byte(data);
+       }
+
+       // Pushed all data to TX FIFO. Read bytes left in RX FIFO.
+       if (arg->read_flag) {
+               while (buffer_rx < buffer_tx) {
+                       wait_rx_available(status);
+                       *buffer_rx++ = rcv_byte(data);
+               }
+       }
+
+       RETURN;
+}
+
+/**
+ * @brief Check flash sectors are filled with pattern. Primary use for
+ * checking sector erase state.
+ *
+ * @param[in] arg: Function arguments.
+ */
+__attribute__((section(".check_fill"))) void
+check_fill(struct dw_spi_check_fill *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);
+
+               /*
+                * Command byte and address bytes make up for dummy_count number of
+                * bytes, that must be skipped in RX FIFO before actual data arrives.
+                */
+               send_u8(status, data, arg->read_cmd);
+               if (arg->four_byte_mode) {
+                       dummy_count = 1 + 4; // Command byte + 4 address bytes
+                       send_u32(status, data, arg->address);
+               } else {
+                       dummy_count = 1 + 3; // Command byte + 3 address bytes
+                       send_u24(status, data, arg->address);
+               }
+
+               for (tx_size = arg->sector_size, rx_size = arg->sector_size, filled = 1;
+                        tx_size > 0; tx_size--) {
+                       send_u8(status, data, 0); // Dummy write to push out read data.
+                       if (rx_available(status)) {
+                               if (dummy_count > 0) {
+                                       // Read data not arrived yet.
+                                       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;
+       }
+
+       RETURN;
+}
+
+/**
+ * @brief Erase flash sectors.
+ *
+ * @param[in] arg: Function arguments.
+ */
+__attribute__((section(".erase"))) void
+erase(struct dw_spi_erase *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);
+       }
+
+       RETURN;
+}
+
+/**
+ * @brief Flash program.
+ *
+ * @param[in] arg: Function arguments.
+ */
+__attribute__((section(".program"))) void
+program(struct dw_spi_program *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);
+
+               send_u8(status, data, arg->program_cmd);
+               if (arg->four_byte_mode)
+                       send_u32(status, data, arg->address);
+               else
+                       send_u24(status, data, arg->address);
+
+               for (page_size = MIN(arg->page_size, buffer_size); page_size > 0;
+                        page_size--, buffer_size--) {
+                       send_u8(status, data, *buffer++);
+               }
+               arg->address += arg->page_size;
+               wait_busy(status, data, arg->read_status_cmd, arg->busy_mask);
+       }
+
+       RETURN;
+}
+
+/**
+ * @brief Read data from flash.
+ *
+ * @param[in] arg: Function arguments.
+ */
+__attribute__((section(".read"))) void
+read(struct dw_spi_read *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);
+
+       /*
+        * Command byte and address bytes make up for dummy_count number of
+        * bytes, that must be skipped in RX FIFO before actual data arrives.
+        */
+       send_u8(status, data, arg->read_cmd);
+       if (arg->four_byte_mode) {
+               dummy_count = 1 + 4; // Command byte + 4 address bytes
+               send_u32(status, data, arg->address);
+       } else {
+               dummy_count = 1 + 3; // Command byte + 3 address bytes
+               send_u24(status, data, arg->address);
+       }
+
+       for (; tx_size > 0; tx_size--) {
+               send_u8(status, data, 0); // Dummy write to push out read data.
+               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) {
+                       // Read data not arrived yet.
+                       rcv_byte(data);
+                       dummy_count--;
+               } else {
+                       *buffer++ = rcv_byte(data);
+                       rx_size--;
+               }
+       }
+
+       RETURN;
+}
diff --git a/contrib/loaders/flash/dw-spi/dw-spi.h b/contrib/loaders/flash/dw-spi/dw-spi.h
new file mode 100644 (file)
index 0000000..9efa768
--- /dev/null
@@ -0,0 +1,313 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/**
+ * @file
+ * Helper functions for DesignWare SPI Core driver.
+ * These helpers are loaded into CPU and execute Flash manipulation algorithms
+ * at full CPU speed. Due to inability to control nCS pin, this is the only way
+ * to communicate with Flash chips connected via DW SPI serial interface.
+ *
+ * In order to avoid using stack, all functions used in helpers are inlined.
+ * Software breakpoints are used to terminate helpers.
+ *
+ * This file contains functions, common to helpers.
+ */
+
+#ifndef _DW_SPI_H_
+#define _DW_SPI_H_
+
+#include <stdint.h>
+#include <sys/param.h>
+
+#include "../../../../src/helper/types.h"
+
+/**
+ * @brief SI busy status bit.
+ *
+ * Set when serial transfer is in progress, cleared when master is idle or
+ * disabled.
+ */
+#define DW_SPI_STATUS_BUSY 0x01
+
+/**
+ * @brief SI TX FIFO not full status bit.
+ *
+ * Set when TX FIFO has room for one or more data-word.
+ */
+#define DW_SPI_STATUS_TFNF 0x02
+
+/**
+ * @brief SI TX FIFO empty status bit.
+ */
+#define DW_SPI_STATUS_TFE 0x04
+
+/**
+ * @brief SI RX FIFO not empty status bit.
+ */
+#define DW_SPI_STATUS_RFNE 0x08
+
+/**
+ * @brief Return from helper function.
+ */
+#define RETURN            \
+       do {                  \
+               asm("sdbbp\n\t"); \
+               return;           \
+       } while (0)
+
+/**
+ * @brief Append byte to TX FIFO.
+ *
+ * For each transferred byte, DW SPI controller receives a byte into RX FIFO.
+ * Slave data are read by pushing dummy bytes to TX FIFO.
+ *
+ * @param[in] dr: Pointer to DR register.
+ * @param[in] byte: Data to push.
+ */
+__attribute__((always_inline)) static inline void
+_send_byte(volatile uint8_t *dr, uint8_t byte)
+{
+       *dr = byte;
+}
+
+/**
+ * @brief Get byte from RX FIFO.
+ *
+ * Reading RX byte removes it from RX FIFO.
+ *
+ * @param[in] dr: Pointer to DR register.
+ * @return RX FIFO byte.
+ */
+__attribute__((always_inline)) static inline uint8_t
+rcv_byte(volatile uint8_t *dr)
+{
+       return *dr;
+}
+
+/**
+ * @brief Check transmission is currently in progress.
+ *
+ * @param[in] sr: 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 *sr)
+{
+       return (*sr ^ DW_SPI_STATUS_TFE) & (DW_SPI_STATUS_BUSY | DW_SPI_STATUS_TFE);
+}
+
+/**
+ * @brief Wait for controller to finish previous transaction.
+ *
+ * @param[in] sr: Pointer to SR register.
+ */
+__attribute__((always_inline)) static inline void
+wait_tx_finish(volatile uint8_t *sr)
+{
+       while (tx_in_progress(sr))
+               ;
+}
+
+/**
+ * @brief Wait for room in TX FIFO.
+ *
+ * @param[in] sr: Pointer to SR register.
+ */
+__attribute__((always_inline)) static inline void
+wait_tx_available(volatile uint8_t *sr)
+{
+       while (!(*sr & DW_SPI_STATUS_TFNF))
+               ;
+}
+
+/**
+ * @brief Check for data available in RX FIFO.
+ *
+ * @param[in] sr: Pointer to SR register.
+ * @retval 1: Data available.
+ * @retval 0: No data available.
+ */
+__attribute__((always_inline)) static inline int
+rx_available(volatile uint8_t *sr)
+{
+       return *sr & DW_SPI_STATUS_RFNE;
+}
+
+/**
+ * @brief Wait for data in RX FIFO.
+ *
+ * @param[in] sr: Pointer to SR register.
+ */
+__attribute__((always_inline)) static inline void
+wait_rx_available(volatile uint8_t *sr)
+{
+       while (!rx_available(sr))
+               ;
+}
+
+/**
+ * @brief Flush RX FIFO.
+ *
+ * @param[in] sr: Pointer to SR register.
+ * @param[in] dr: Pointer to DR register.
+ */
+__attribute__((always_inline)) static inline void
+flush_rx(volatile uint8_t *sr, volatile uint8_t *dr)
+{
+       while (*sr & DW_SPI_STATUS_RFNE)
+               *dr;
+}
+
+/**
+ * @brief Append variable number of bytes to TX FIFO.
+ *
+ * @param[in] sr: Pointer to SR register.
+ * @param[in] dr: Pointer to DR register.
+ * @param[in] word: Data to append.
+ * @param[in] bytes: Number of bytes to append.
+ */
+__attribute__((always_inline)) static inline void
+_send_bytes(volatile uint8_t *sr, volatile uint8_t *dr, uint32_t word,
+                       int bytes)
+{
+       for (register int i = bytes - 1; i >= 0; i--) {
+               wait_tx_available(sr);
+               _send_byte(dr, (word >> (i * 8)) & 0xff);
+       }
+}
+
+/**
+ * @brief Append 8 bit value to TX FIFO.
+ *
+ * @param[in] sr: Pointer to SR register.
+ * @param[in] dr: Pointer to DR register.
+ * @param[in] word: Data to push.
+ */
+__attribute__((always_inline)) static inline void
+send_u8(volatile uint8_t *sr, volatile uint8_t *dr, uint8_t byte)
+{
+       wait_tx_available(sr);
+       _send_byte(dr, byte);
+}
+
+/**
+ * @brief Append 24 bit value to TX FIFO.
+ *
+ * Used to send Flash addresses in 24 bit mode.
+ *
+ * @param[in] sr: Pointer to SR register.
+ * @param[in] dr: Pointer to DR register.
+ * @param[in] word: Data to push.
+ */
+__attribute__((always_inline)) static inline void
+send_u24(volatile uint8_t *sr, volatile uint8_t *dr, uint32_t word)
+{
+       _send_bytes(sr, dr, word, 3);
+}
+
+/**
+ * @brief Append 32 bit value to TX FIFO.
+ *
+ * @param[in] sr: Pointer to SR register.
+ * @param[in] dr: Pointer to DR register.
+ * @param[in] word: Data to push.
+ */
+__attribute__((always_inline)) static inline void
+send_u32(volatile uint8_t *sr, volatile uint8_t *dr, uint32_t word)
+{
+       _send_bytes(sr, dr, word, 4);
+}
+
+/**
+ * @brief Read chip status register.
+ *
+ * @param[in] sr: Pointer to SR register.
+ * @param[in] dr: Pointer to DR register.
+ * @param[in] stat_cmd: Read status command.
+ * @return Chip status.
+ */
+__attribute__((always_inline)) static inline uint8_t
+read_status(volatile uint8_t *sr, volatile uint8_t *dr, uint8_t stat_cmd)
+{
+       wait_tx_finish(sr);
+       flush_rx(sr, dr);
+       /*
+        * Don't bother with wait_tx_available() as TX FIFO is empty
+        * and we only send two bytes.
+        */
+       _send_byte(dr, stat_cmd);
+       _send_byte(dr, 0); // Dummy write to push out read data.
+       wait_rx_available(sr);
+       rcv_byte(dr); // Dummy read to skip command byte.
+       wait_rx_available(sr);
+       return rcv_byte(dr);
+}
+
+/**
+ * @brief Enable Flash chip write.
+ *
+ * @param[in] sr: Pointer to SR register.
+ * @param[in] dr: Pointer to DR register.
+ * @param[in] we_cmd: Write enable command.
+ */
+__attribute__((always_inline)) static inline void
+write_enable(volatile uint8_t *sr, volatile uint8_t *dr, uint8_t we_cmd)
+{
+       wait_tx_finish(sr);
+       _send_byte(dr, we_cmd);
+}
+
+/**
+ * @brief Erase Flash sector.
+ *
+ * @param[in] sr: Pointer to SR register.
+ * @param[in] dr: Pointer to DR register.
+ * @param[in] erase_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 *sr, volatile uint8_t *dr, uint8_t erase_cmd,
+                        uint32_t address, uint8_t four_byte_mode)
+{
+       wait_tx_finish(sr);
+       _send_byte(dr, erase_cmd);
+       if (four_byte_mode)
+               send_u32(sr, dr, address);
+       else
+               send_u24(sr, dr, address);
+}
+
+/**
+ * @brief Wait for write enable flag.
+ *
+ * @param[in] sr: Pointer to SR register.
+ * @param[in] dr: Pointer to DR register.
+ * @param[in] stat_cmd: Read status command.
+ * @param[in] we_mask: Write enable status mask.
+ */
+__attribute__((always_inline)) static inline void
+wait_write_enable(volatile uint8_t *sr, volatile uint8_t *dr, uint8_t stat_cmd,
+                                 uint8_t we_mask)
+{
+       while (!(read_status(sr, dr, stat_cmd) & we_mask))
+               ;
+}
+
+/**
+ * @brief Wait while flash is busy.
+ *
+ * @param[in] sr: Pointer to SR register.
+ * @param[in] dr: Pointer to DR register.
+ * @param[in] stat_cmd: Read status command.
+ * @param[in] busy_mask: Flash busy mask.
+ */
+__attribute__((always_inline)) static inline void
+wait_busy(volatile uint8_t *sr, volatile uint8_t *dr, uint8_t stat_cmd,
+                 uint8_t busy_mask)
+{
+       while (read_status(sr, dr, stat_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 (file)
index 0000000..94c732f
--- /dev/null
@@ -0,0 +1,39 @@
+  0x0b, 0x00, 0x82, 0x88, 0x1f, 0x00, 0x8a, 0x88, 0x0f, 0x00, 0x83, 0x88,
+  0x17, 0x00, 0x86, 0x88, 0x08, 0x00, 0x82, 0x98, 0x1c, 0x00, 0x8a, 0x98,
+  0x0c, 0x00, 0x83, 0x98, 0x14, 0x00, 0x86, 0x98, 0x25, 0x38, 0x80, 0x00,
+  0x54, 0x00, 0x40, 0x10, 0xf8, 0xff, 0x09, 0x24, 0x00, 0x00, 0x64, 0x90,
+  0x05, 0x00, 0x84, 0x30, 0x04, 0x00, 0x84, 0x38, 0xfc, 0xff, 0x80, 0x14,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x90, 0x08, 0x00, 0x42, 0x30,
+  0x07, 0x00, 0x40, 0x50, 0x25, 0x00, 0xe5, 0x90, 0x00, 0x00, 0xc2, 0x90,
+  0x00, 0x00, 0x62, 0x90, 0x08, 0x00, 0x42, 0x30, 0xfc, 0xff, 0x40, 0x14,
+  0x00, 0x00, 0x00, 0x00, 0x25, 0x00, 0xe5, 0x90, 0x00, 0x00, 0x62, 0x90,
+  0x02, 0x00, 0x42, 0x30, 0xfd, 0xff, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0xc5, 0xa0, 0x26, 0x00, 0xe2, 0x90, 0x45, 0x00, 0x40, 0x10,
+  0x03, 0x00, 0xe8, 0x88, 0x00, 0x00, 0xe8, 0x98, 0x18, 0x00, 0x05, 0x24,
+  0x00, 0x00, 0x62, 0x90, 0x02, 0x00, 0x42, 0x30, 0xfd, 0xff, 0x40, 0x10,
+  0x06, 0x10, 0xa8, 0x00, 0xff, 0x00, 0x42, 0x30, 0xf8, 0xff, 0xa5, 0x24,
+  0x00, 0x00, 0xc2, 0xa0, 0xf8, 0xff, 0xa9, 0x14, 0x05, 0x00, 0x0b, 0x24,
+  0x07, 0x00, 0xe8, 0x88, 0x04, 0x00, 0xe8, 0x98, 0x1e, 0x00, 0x00, 0x11,
+  0x25, 0x28, 0x00, 0x01, 0x00, 0x00, 0x62, 0x90, 0x02, 0x00, 0x42, 0x30,
+  0xfd, 0xff, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa0,
+  0x00, 0x00, 0x62, 0x90, 0x08, 0x00, 0x42, 0x30, 0x06, 0x00, 0x40, 0x50,
+  0xff, 0xff, 0xa5, 0x24, 0x00, 0x00, 0xc2, 0x90, 0x25, 0x00, 0x60, 0x51,
+  0x24, 0x00, 0xec, 0x90, 0xff, 0xff, 0x6b, 0x25, 0xff, 0xff, 0xa5, 0x24,
+  0xf1, 0xff, 0xa0, 0x14, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x51,
+  0x01, 0x00, 0x04, 0x24, 0x24, 0x00, 0xe5, 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, 0xa2, 0x14,
+  0xff, 0xff, 0x08, 0x25, 0xf7, 0xff, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x04, 0x24, 0x00, 0x00, 0x44, 0xa1, 0x0b, 0x00, 0xe2, 0x88,
+  0x01, 0x00, 0x4a, 0x25, 0x08, 0x00, 0xe2, 0x98, 0xff, 0xff, 0x42, 0x24,
+  0x0b, 0x00, 0xe2, 0xa8, 0x08, 0x00, 0xe2, 0xb8, 0x03, 0x00, 0xe4, 0x88,
+  0x07, 0x00, 0xe5, 0x88, 0x00, 0x00, 0xe4, 0x98, 0x04, 0x00, 0xe5, 0x98,
+  0x21, 0x20, 0x85, 0x00, 0x03, 0x00, 0xe4, 0xa8, 0xae, 0xff, 0x40, 0x14,
+  0x00, 0x00, 0xe4, 0xb8, 0x3f, 0x00, 0x00, 0x70, 0x08, 0x00, 0xe0, 0x03,
+  0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x42, 0x30, 0xed, 0xff, 0x82, 0x55,
+  0x00, 0x00, 0x44, 0xa1, 0xd9, 0xff, 0x00, 0x10, 0xff, 0xff, 0x08, 0x25,
+  0x00, 0x00, 0xe8, 0x98, 0x10, 0x00, 0x05, 0x24, 0x00, 0x00, 0x62, 0x90,
+  0x02, 0x00, 0x42, 0x30, 0xfd, 0xff, 0x40, 0x10, 0x06, 0x10, 0xa8, 0x00,
+  0xff, 0x00, 0x42, 0x30, 0xf8, 0xff, 0xa5, 0x24, 0x00, 0x00, 0xc2, 0xa0,
+  0xf8, 0xff, 0xa9, 0x14, 0x04, 0x00, 0x0b, 0x24, 0xbc, 0xff, 0x00, 0x10,
+  0x07, 0x00, 0xe8, 0x88
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 (file)
index 0000000..b72c7ea
--- /dev/null
@@ -0,0 +1,39 @@
+  0x0b, 0x00, 0x88, 0x88, 0x25, 0x28, 0x80, 0x00, 0x03, 0x00, 0x86, 0x88,
+  0x0f, 0x00, 0x82, 0x88, 0x17, 0x00, 0x84, 0x88, 0x08, 0x00, 0xa8, 0x98,
+  0x00, 0x00, 0xa6, 0x98, 0x0c, 0x00, 0xa2, 0x98, 0x5f, 0x00, 0x00, 0x11,
+  0x14, 0x00, 0xa4, 0x98, 0xf8, 0xff, 0x07, 0x24, 0x1d, 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, 0x89, 0xa0,
+  0x1c, 0x00, 0xaa, 0x90, 0x1f, 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, 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, 0xe4, 0xff, 0x60, 0x10,
+  0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0xaa, 0x90, 0x21, 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, 0x8a, 0xa0,
+  0x31, 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, 0x1c, 0x00, 0xaa, 0x90,
+  0x20, 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, 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, 0xe4, 0xff, 0x60, 0x14, 0x00, 0x00, 0x00, 0x00,
+  0x07, 0x00, 0xa3, 0x88, 0xff, 0xff, 0x08, 0x25, 0x04, 0x00, 0xa3, 0x98,
+  0xa4, 0xff, 0x00, 0x15, 0x21, 0x30, 0xc3, 0x00, 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, 0xd1, 0xff, 0x00, 0x10, 0x1c, 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 (file)
index 0000000..31500e0
--- /dev/null
@@ -0,0 +1,51 @@
+  0x13, 0x00, 0x88, 0x88, 0x25, 0x30, 0x80, 0x00, 0x0b, 0x00, 0x85, 0x88,
+  0x17, 0x00, 0x82, 0x88, 0x1f, 0x00, 0x84, 0x88, 0x10, 0x00, 0xc8, 0x98,
+  0x08, 0x00, 0xc5, 0x98, 0x14, 0x00, 0xc2, 0x98, 0x1c, 0x00, 0xc4, 0x98,
+  0x78, 0x00, 0x00, 0x11, 0xf8, 0xff, 0x07, 0x24, 0x25, 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, 0x89, 0xa0,
+  0x24, 0x00, 0xca, 0x90, 0x27, 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, 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, 0xe4, 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,
+  0x26, 0x00, 0xc9, 0x90, 0x00, 0x00, 0x43, 0x90, 0x02, 0x00, 0x63, 0x30,
+  0xfd, 0xff, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0xa0,
+  0x29, 0x00, 0xc3, 0x90, 0x47, 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, 0x00, 0x00, 0x83, 0xa0,
+  0xf8, 0xff, 0x27, 0x15, 0x25, 0x58, 0x00, 0x01, 0x07, 0x00, 0xc3, 0x88,
+  0x04, 0x00, 0xc3, 0x98, 0x2b, 0x48, 0x03, 0x01, 0x0a, 0x58, 0x69, 0x00,
+  0x0d, 0x00, 0x60, 0x11, 0x21, 0x50, 0xab, 0x00, 0x00, 0x00, 0xa9, 0x90,
+  0x01, 0x00, 0xa5, 0x24, 0x00, 0x00, 0x43, 0x90, 0x02, 0x00, 0x63, 0x30,
+  0xfd, 0xff, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0xa0,
+  0xf9, 0xff, 0xaa, 0x54, 0x00, 0x00, 0xa9, 0x90, 0x07, 0x00, 0xc3, 0x88,
+  0x23, 0x40, 0x0b, 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, 0x24, 0x00, 0xca, 0x90, 0x28, 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, 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,
+  0xe4, 0xff, 0x60, 0x14, 0x00, 0x00, 0x00, 0x00, 0x8b, 0xff, 0x00, 0x55,
+  0x25, 0x00, 0xc9, 0x90, 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,
+  0x00, 0x00, 0x83, 0xa0, 0xf8, 0xff, 0x27, 0x15, 0x25, 0x58, 0x00, 0x01,
+  0x07, 0x00, 0xc3, 0x88, 0x04, 0x00, 0xc3, 0x98, 0x2b, 0x48, 0x03, 0x01,
+  0x0a, 0x58, 0x69, 0x00, 0xbb, 0xff, 0x60, 0x15, 0x21, 0x50, 0xab, 0x00,
+  0xc6, 0xff, 0x00, 0x10, 0x03, 0x00, 0xc9, 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 (file)
index 0000000..3f18b7c
--- /dev/null
@@ -0,0 +1,33 @@
+  0x0f, 0x00, 0x87, 0x88, 0x07, 0x00, 0x88, 0x88, 0x13, 0x00, 0x83, 0x88,
+  0x1b, 0x00, 0x85, 0x88, 0x0c, 0x00, 0x87, 0x98, 0x04, 0x00, 0x88, 0x98,
+  0x10, 0x00, 0x83, 0x98, 0x18, 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, 0x20, 0x00, 0x86, 0x90, 0x00, 0x00, 0xa2, 0x90,
+  0x00, 0x00, 0x62, 0x90, 0x08, 0x00, 0x42, 0x30, 0xfc, 0xff, 0x40, 0x14,
+  0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x86, 0x90, 0x00, 0x00, 0x62, 0x90,
+  0x02, 0x00, 0x42, 0x30, 0xfd, 0xff, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0xa6, 0xa0, 0x21, 0x00, 0x82, 0x90, 0x35, 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, 0x05, 0x00, 0x06, 0x24, 0x23, 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, 0xa0, 0xa0,
+  0x00, 0x00, 0x62, 0x90, 0x08, 0x00, 0x42, 0x30, 0x06, 0x00, 0x40, 0x50,
+  0xff, 0xff, 0x84, 0x24, 0x00, 0x00, 0xa2, 0x90, 0x14, 0x00, 0xc0, 0x50,
+  0x00, 0x00, 0x02, 0xa1, 0xff, 0xff, 0xc6, 0x24, 0xff, 0xff, 0x84, 0x24,
+  0xf1, 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,
+  0x03, 0x00, 0xc0, 0x50, 0xff, 0xff, 0xe7, 0x24, 0xf8, 0xff, 0x00, 0x10,
+  0xff, 0xff, 0xc6, 0x24, 0x06, 0x00, 0xe0, 0x10, 0x00, 0x00, 0x02, 0xa1,
+  0xf4, 0xff, 0x00, 0x10, 0x01, 0x00, 0x08, 0x25, 0xff, 0xff, 0xe7, 0x24,
+  0xec, 0xff, 0x00, 0x10, 0x01, 0x00, 0x08, 0x25, 0x3f, 0x00, 0x00, 0x70,
+  0x08, 0x00, 0xe0, 0x03, 0x00, 0x00, 0x00, 0x00, 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, 0xcc, 0xff, 0x00, 0x10,
+  0x04, 0x00, 0x06, 0x24
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 (file)
index 0000000..384dbad
--- /dev/null
@@ -0,0 +1,21 @@
+  0x03, 0x00, 0x85, 0x88, 0x0b, 0x00, 0x88, 0x88, 0x0f, 0x00, 0x83, 0x88,
+  0x17, 0x00, 0x86, 0x88, 0x00, 0x00, 0x85, 0x98, 0x08, 0x00, 0x88, 0x98,
+  0x0c, 0x00, 0x83, 0x98, 0x14, 0x00, 0x86, 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,
+  0x06, 0x00, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc2, 0x90,
+  0x00, 0x00, 0x62, 0x90, 0x08, 0x00, 0x42, 0x30, 0xfc, 0xff, 0x40, 0x14,
+  0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00,
+  0x25, 0x38, 0xa0, 0x00, 0x21, 0x40, 0x05, 0x01, 0x00, 0x00, 0xa9, 0x90,
+  0x01, 0x00, 0xa5, 0x24, 0x00, 0x00, 0x62, 0x90, 0x02, 0x00, 0x42, 0x30,
+  0xfd, 0xff, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc9, 0xa0,
+  0x1c, 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, 0xc2, 0x90, 0x01, 0x00, 0xe7, 0x24,
+  0xff, 0xff, 0xe2, 0xa0, 0xef, 0xff, 0xa8, 0x54, 0x00, 0x00, 0xa9, 0x90,
+  0x1c, 0x00, 0x82, 0x90, 0x0c, 0x00, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00,
+  0x2b, 0x10, 0xe8, 0x00, 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, 0xc2, 0x90, 0x01, 0x00, 0xe7, 0x24,
+  0xf9, 0xff, 0x07, 0x15, 0xff, 0xff, 0xe2, 0xa0, 0x3f, 0x00, 0x00, 0x70,
+  0x08, 0x00, 0xe0, 0x03, 0x00, 0x00, 0x00, 0x00
index 47a6f69ebcae123206e439305157dfeff36fa606..2ec49a4c0845c8ba0e153e48d2f8ac4f1b0c88b3 100644 (file)
@@ -6421,6 +6421,75 @@ 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.
+
+The flash size is autodetected based on the table of known JEDEC IDs hardcoded
+in the OpenOCD sources. When flash size is set to @var{0}, probed Flash
+size is used.
+
+This driver requires configuring DRAM controller first, setting up a
+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
+
+
+Mandatory driver's arguments are
+
+@itemize
+@item @var{-freq} ... core frequency in Hz, used in communication speed
+calculation.
+@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. Set to @var{0} if SPI Boot controller not available.
+@item @var{-if_owner_offset} ... offset of @var{if_owner} field inside
+@var{SPI_MST} register. Set to @var{0} if SPI Boot controller not available.
+@end itemize
+
+Optional driver's arguments are
+
+@itemize
+@item @var{-speed} ... SPI device communication speed in Hz. Minimal
+speed depends on the @var{-freq} variable and has the value of
+@var{freq/0xfffe}. The default value is @var{1000000}.
+@item @var{-chip_select} ... Chip select pin. The default value
+is @var{0}.
+@item @var{-timeout} ... flash communication timeout in
+seconds. The default value is @var{600}.
+@end itemize
+
+For some SoCs there are shortcuts for mandatory arguments
+
+@itemize
+@item @var{-jaguar2} ... configuration for MSCC Jaguar2 SoC family.
+@item @var{-ocelot} ... configuration for MSCC Ocelot SoC family.
+@end itemize
+
+Driver provides shortcut arguments for MSCC @var{-jaguar2} and
+@var{-ocelot} network switch SOCs, which set the correct values for @var{-freq},
+@var{-simc}, @var{-spi_mst} and @var{-if_owner_offset} arguments.
+
+Example of equivalent configurations for Jaguar2 SoC
+
+@example
+flash bank $_FLASHNAME dw-spi 0x40000000 0x02000000 4 4 $_TARGETNAME -freq 250000000 -simc 0x70101000 -spi_mst 0x70000024 -if_owner_offset 6 -speed 3000000
+flash bank $_FLASHNAME dw-spi 0x40000000 0x02000000 4 4 $_TARGETNAME -jaguar2 -speed 3000000
+@end example
+
+@end deffn
+
 @subsection Internal Flash (Microcontrollers)
 
 @deffn {Flash Driver} {aduc702x}
index afa11e7d40313ebbf9cf09c75c90365427569632..82968776108c25fa6ba94a978c62be2fa6eaaae0 100644 (file)
@@ -26,6 +26,7 @@ NOR_DRIVERS = \
        %D%/cc26xx.c \
        %D%/cfi.c \
        %D%/dsp5680xx_flash.c \
+       %D%/dw-spi.c \
        %D%/efm32.c \
        %D%/em357.c \
        %D%/eneispif.c \
@@ -89,6 +90,7 @@ NORHEADERS = \
        %D%/cc26xx.h \
        %D%/cfi.h \
        %D%/driver.h \
+       %D%/dw-spi-helper.h \
        %D%/imp.h \
        %D%/non_cfi.h \
        %D%/ocl.h \
index 211661e214ae945809131d254890ab4ae113b8e2..852a55a112ae41a88cc7ebebefd11ba8482507a5 100644 (file)
@@ -254,6 +254,7 @@ extern const struct flash_driver cc26xx_flash;
 extern const struct flash_driver cc3220sf_flash;
 extern const struct flash_driver cfi_flash;
 extern const struct flash_driver dsp5680xx_flash;
+extern const struct flash_driver dw_spi_flash;
 extern const struct flash_driver efm32_flash;
 extern const struct flash_driver em357_flash;
 extern const struct flash_driver eneispif_flash;
index dd9995ecbaf4641fa754c1de4b6530c2bdbcd679..ce97b815042db79e1dad7d4b3c0c5aef0b690467 100644 (file)
@@ -31,6 +31,7 @@ static const struct flash_driver * const flash_drivers[] = {
        &cc26xx_flash,
        &cfi_flash,
        &dsp5680xx_flash,
+       &dw_spi_flash,
        &efm32_flash,
        &em357_flash,
        &eneispif_flash,
diff --git a/src/flash/nor/dw-spi-helper.h b/src/flash/nor/dw-spi-helper.h
new file mode 100644 (file)
index 0000000..d353755
--- /dev/null
@@ -0,0 +1,102 @@
+/* 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 a single struct, passed by pointer.
+ *
+ * Pointers are represented by 64 bit integers to make structs compatible
+ * with 64 bit targets.
+ *
+ * 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 dw_spi_transaction {
+       uint64_t buffer;
+       ///< Pointer to data buffer to send over SPI.
+       ///< Return values are stored in place of output data when
+       ///< dw_spi_transaction::read_flag is 1.
+       uint32_t size; ///< Size of dw_spi_transaction::buffer.
+       uint64_t status_reg; ///< Pointer to SR register.
+       uint64_t data_reg; ///< Pointer to DR register.
+       uint8_t read_flag;
+       ///< When 1, store RX FIFO data to dw_spi_transaction::buffer.
+} __attribute__((packed));
+
+/**
+ * @brief Arguments for check_fill helper function.
+ */
+struct dw_spi_check_fill {
+       uint32_t address; ///< Starting address. Sector aligned.
+       uint32_t sector_size; ///< Sector size.
+       uint32_t sector_count; ///< Number of sectors to check.
+       uint64_t status_reg; ///< Pointer to SR register.
+       uint64_t data_reg; ///< Pointer to DR register.
+       uint64_t fill_status_array;
+       ///< Pointer to array describing sectors fill status.
+       ///< 1 if filled, 0 if not filled.
+       uint8_t pattern; ///< Fill pattern.
+       uint8_t read_cmd; ///< Read data command.
+       uint8_t four_byte_mode; ///< Four byte addressing mode flag.
+} __attribute__((packed));
+
+/**
+ * @brief Arguments for erase helper function.
+ */
+struct dw_spi_erase {
+       uint32_t address; ///< First sector address. Sector aligned.
+       uint32_t sector_size; ///< Sector size.
+       uint32_t sector_count; ///< Number of sectors to erase.
+       uint64_t status_reg; ///< Pointer to SR register.
+       uint64_t data_reg; ///< Pointer to DR register.
+       uint8_t read_status_cmd; ///< Read status command.
+       uint8_t write_enable_cmd; ///< Write enable command.
+       uint8_t erase_sector_cmd; ///< Erase sector command.
+       uint8_t write_enable_mask; ///< Write enable mask.
+       uint8_t busy_mask; ///< Busy mask.
+       uint8_t four_byte_mode; ///< Four byte addressing mode flag.
+} __attribute__((packed));
+
+/**
+ * @brief Arguments for program helper function.
+ */
+struct dw_spi_program {
+       uint32_t address;
+       ///< First page address. Page aligned when write is crossing
+       ///< the page boundary.
+       uint32_t page_size; ///< Page size.
+       uint64_t buffer; ///< Data buffer pointer.
+       uint32_t buffer_size; ///< Size of dw_spi_program::buffer.
+       uint64_t status_reg; ///< Pointer to SR register.
+       uint64_t data_reg; ///< Pointer to DR register.
+       uint8_t read_status_cmd; ///< Read status command.
+       uint8_t write_enable_cmd; ///< Write enable command.
+       uint8_t program_cmd; ///< Program command.
+       uint8_t write_enable_mask; ///< Write enable mask.
+       uint8_t busy_mask; ///< Busy mask.
+       uint8_t four_byte_mode; ///< Four byte addressing mode flag.
+} __attribute__((packed));
+
+/**
+ * @brief Arguments for read helper function.
+ */
+struct dw_spi_read {
+       uint32_t address; ///< First sector address.
+       uint64_t buffer; ///< Data buffer pointer.
+       uint32_t buffer_size; ///< Size of dw_spi_read::buffer.
+       uint64_t status_reg; ///< Pointer to SR register.
+       uint64_t data_reg; ///< Pointer to DR register.
+       uint8_t read_cmd; ///< Read data command.
+       uint8_t four_byte_mode; ///< Four byte addressing mode flag.
+} __attribute__((packed));
+
+#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 (file)
index 0000000..e196547
--- /dev/null
@@ -0,0 +1,1608 @@
+// 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 of processors, including Microsemi Jaguar2 and
+ * Ocelot switch chips.
+ *
+ * Serial interface (SI) nCS0 pin, which is usually connected to the external
+ * flash, cannot be controlled 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 the user to set up DRAM controller and provide
+ * work-area.
+ *
+ * In Microsemi devices, serial interface pins may be governed either
+ * by Boot or Master controller. For these devices, additional configuration of
+ * spi_mst address is required to switch between the two.
+ *
+ * Currently supported devices typically have much more RAM then NOR Flash
+ * (Jaguar2 reference design has 256MB RAM and 32MB NOR Flash), so supporting
+ * work-area sizes smaller then transfer buffer seems like the unnecessary
+ * complication.
+ *
+ * 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 <helper/time_support.h>
+#include <target/algorithm.h>
+#include <target/breakpoints.h>
+#include <target/mips32.h>
+#include <target/target_type.h>
+
+/**
+ * @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.
+ */
+struct dw_spi_regmap {
+       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 bits in ICPU_CFG:SPI_MST.
+};
+
+/**
+ * @brief Register map for Jaguar2 switch devices.
+ */
+static const struct dw_spi_regmap jaguar2_regmap = {
+       .freq = 250000000UL,
+       .simc = 0x70101000UL,
+       .spi_mst = 0x70000024UL,
+       .si_if_owner_offset = 6,
+};
+
+/**
+ * @brief Register map for Ocelot switch devices.
+ */
+static const struct dw_spi_regmap ocelot_regmap = {
+       .freq = 250000000UL,
+       .simc = 0x70101000UL,
+       .spi_mst = 0x70000024UL,
+       .si_if_owner_offset = 4,
+};
+
+#define DW_SPI_IF_OWNER_WIDTH 2 ///< IF owner register field width.
+
+/**
+ * @brief Owner of the SI interface.
+ */
+enum dw_spi_si_mode {
+       DW_SPI_SI_MODE_BOOT = 1,
+       ///< Boot controller maps contents of SPI Flash to memory in read-only mode.
+       DW_SPI_SI_MODE_MASTER = 2,
+       ///< SPI controller mode for reading/writing SPI Flash.
+};
+
+#define DW_SPI_REG_CTRLR0 0x00 ///< General configuration register.
+#define DW_SPI_REG_SIMCEN 0x08 ///< Master controller enable register.
+#define DW_SPI_REG_SER 0x10 ///< Slave select register.
+#define DW_SPI_REG_BAUDR 0x14 ///< Baud rate configuration register.
+#define DW_SPI_REG_SR 0x28 ///< Status register.
+#define DW_SPI_REG_IMR 0x2c ///< Interrupt configuration register.
+#define DW_SPI_REG_DR 0x60 ///< Data register.
+
+#define DW_SPI_REG_CTRLR0_DFS(x) ((x) & GENMASK(3, 0)) ///< Data frame size.
+#define DW_SPI_REG_CTRLR0_FRF(x) (((x) << 4) & GENMASK(5, 4)) ///< SI protocol.
+#define DW_SPI_REG_CTRLR0_SCPH(x) ((!!(x)) << 6) ///< Probe position.
+#define DW_SPI_REG_CTRLR0_SCPOL(x) ((!!(x)) << 7) ///< Probe polarity.
+#define DW_SPI_REG_CTRLR0_TMOD(x) (((x) << 8) & GENMASK(9, 8)) ///< SI mode.
+#define DW_SPI_REG_SIMCEN_SIMCEN(x) (!!(x)) ///< Controller enable.
+#define DW_SPI_REG_SER_SER(x) ((x) & GENMASK(15, 0)) ///< Slave select bitmask.
+#define DW_SPI_REG_BAUDR_SCKDV(x) ((x) & GENMASK(15, 0)) ///< Clock divisor.
+
+/**
+ * @brief Driver private state.
+ */
+struct dw_spi_driver {
+       bool probed; ///< Bank is probed.
+       uint32_t id; ///< Chip ID.
+       unsigned int speed; ///< Flash speed.
+       unsigned int timeout; ///< Flash timeout in milliseconds.
+       uint8_t chip_select_bitmask; ///< Chip select bitmask.
+       bool four_byte_mode; ///< Flash chip is in 32bit address mode.
+       enum dw_spi_si_mode saved_ctrl_mode;
+       ///< Previously selected controller mode.
+       struct dw_spi_regmap regmap; ///< SI controller regmap.
+       const struct flash_device *spi_flash; ///< SPI flash device info.
+};
+
+/**
+ * @brief Register used to pass argument struct to helper functions.
+ */
+#define DW_SPI_ARG_REG "r4"
+
+/**
+ * @brief Default timeout value in ms for flash transaction jobs.
+ */
+#define DW_SPI_TIMEOUT_DEFAULT (600 * 1000)
+
+/**
+ * @brief Timeout value in ms for short flash transactions,
+ * e.g. reading flash ID and status register.
+ */
+#define DW_SPI_TIMEOUT_TRANSACTION 1000
+
+/**
+ * @brief Select SI interface owner.
+ *
+ * Mode selection is skipped if Boot controller not available on target
+ * (i.e. spi_mst command argument is not configured).
+ *
+ * @param[in] bank: Flash bank.
+ * @param[in] mode: New controller mode.
+ * @return Command execution status.
+ */
+static int
+dw_spi_ctrl_mode(const struct flash_bank *const bank, enum dw_spi_si_mode mode)
+{
+       struct target *const target = bank->target;
+       const struct dw_spi_driver *const driver = bank->driver_priv;
+       const struct dw_spi_regmap *const regmap = &driver->regmap;
+
+       if (!regmap->spi_mst)
+               return ERROR_OK;
+
+       uint32_t ctrl;
+       int ret = target_read_u32(target, regmap->spi_mst, &ctrl);
+       if (ret) {
+               LOG_ERROR("DW SPI SPI:MST register read error");
+               return ret;
+       }
+       ctrl &= ~GENMASK(DW_SPI_IF_OWNER_WIDTH + driver->regmap.si_if_owner_offset,
+                                        driver->regmap.si_if_owner_offset);
+       ctrl |= mode << driver->regmap.si_if_owner_offset;
+
+       ret = target_write_u32(target, regmap->spi_mst, ctrl);
+       if (ret)
+               LOG_ERROR("DW SPI controller mode configuration error");
+
+       return ret;
+}
+
+/**
+ * @brief Select master controller as SI interface owner.
+ *
+ * Previous interface owner is restored via dw_spi_ctrl_mode_restore() function.
+ * Mode selection is skipped if Boot controller not available on target
+ * (i.e. spi_mst command argument is not configured).
+ *
+ * @param[in] bank: Flash bank.
+ * @param[in] mode: New controller mode.
+ * @return Command execution status.
+ */
+static int
+dw_spi_ctrl_mode_configure(const struct flash_bank *const bank,
+                                                  enum dw_spi_si_mode mode)
+{
+       struct target *const target = bank->target;
+       struct dw_spi_driver *const driver = bank->driver_priv;
+       const struct dw_spi_regmap *const regmap = &driver->regmap;
+
+       if (!regmap->spi_mst)
+               return ERROR_OK;
+
+       uint32_t ctrl;
+       int ret = target_read_u32(target, regmap->spi_mst, &ctrl);
+       if (ret) {
+               LOG_ERROR("DW SPI controller mode query error");
+               return ret;
+       }
+       driver->saved_ctrl_mode =
+               (enum dw_spi_si_mode)((ctrl >> driver->regmap.si_if_owner_offset) &
+                                                         GENMASK(DW_SPI_IF_OWNER_WIDTH, 0));
+
+       return dw_spi_ctrl_mode(bank, mode);
+}
+
+/**
+ * @brief Restore SI controller mode.
+ *
+ * Restore initially configured SI controller mode. Undo configuration done by
+ * dw_spi_ctrl_mode_configure() function.
+ * Mode selection is skipped if Boot controller not available on target
+ * (i.e. spi_mst command argument is not configured).
+ *
+ * @param[in] bank: Flash bank.
+ * @return Command execution status.
+ */
+static int
+dw_spi_ctrl_mode_restore(const struct flash_bank *const bank)
+{
+       const struct dw_spi_driver *const driver = bank->driver_priv;
+
+       return dw_spi_ctrl_mode(bank, driver->saved_ctrl_mode);
+}
+
+/**
+ * @brief Enable master controller.
+ *
+ * Configuration of the master controller must be done when it is disabled.
+ *
+ * @param[in] bank: Flash bank.
+ * @param[in] value: New enable state.
+ * @return Command execution status.
+ */
+static int
+dw_spi_master_ctrl_enable(const struct flash_bank *const bank, bool value)
+{
+       struct target *const target = bank->target;
+       const struct dw_spi_driver *const driver = bank->driver_priv;
+       const struct dw_spi_regmap *const regmap = &driver->regmap;
+
+       int ret = target_write_u32(target, regmap->simc + DW_SPI_REG_SIMCEN,
+                                                          DW_SPI_REG_SIMCEN_SIMCEN(value));
+       if (ret)
+               LOG_ERROR("DW SPI master controller enable flag configuration error");
+
+       return ret;
+}
+
+/**
+ * @brief Configure SI transfer mode.
+ *
+ * @param[in] bank: Flash bank.
+ * @return Command execution status.
+ */
+static int
+dw_spi_ctrl_configure_si(const struct flash_bank *const bank)
+{
+       struct target *const target = bank->target;
+       const struct dw_spi_driver *const driver = bank->driver_priv;
+       const struct dw_spi_regmap *const regmap = &driver->regmap;
+
+       // 8 bit frame; Motorola protocol; middle lo probe; TX RX mode
+       const uint32_t mode = DW_SPI_REG_CTRLR0_DFS(0x7) |
+                                                 DW_SPI_REG_CTRLR0_FRF(0) |
+                                                 DW_SPI_REG_CTRLR0_SCPH(0) |
+                                                 DW_SPI_REG_CTRLR0_SCPOL(0) |
+                                                 DW_SPI_REG_CTRLR0_TMOD(0);
+
+       int ret = target_write_u32(target, regmap->simc + DW_SPI_REG_CTRLR0, mode);
+       if (ret) {
+               LOG_ERROR("DW SPI master controller configuration query error");
+               return ret;
+       }
+
+       ret = target_write_u32(target, regmap->simc + DW_SPI_REG_SER,
+                                                  DW_SPI_REG_SER_SER(driver->chip_select_bitmask));
+       if (ret)
+               LOG_ERROR("DW SPI slave select configuration error");
+
+       return ret;
+}
+
+/**
+ * @brief Configure SI transfer speed.
+ *
+ * @param[in] bank: Flash bank.
+ * @return Command execution status.
+ */
+static int
+dw_spi_ctrl_configure_speed(const struct flash_bank *const bank)
+{
+       struct target *const target = bank->target;
+       const struct dw_spi_driver *const driver = bank->driver_priv;
+       const struct dw_spi_regmap *const regmap = &driver->regmap;
+
+       // divisor LSB must be zero
+       const uint16_t div = MIN((regmap->freq / driver->speed), 0xfffe) & 0xfffe;
+
+       int ret = target_write_u32(target, regmap->simc + DW_SPI_REG_BAUDR,
+                                                          DW_SPI_REG_BAUDR_SCKDV(div));
+       if (ret) {
+               LOG_ERROR("DW SPI speed configuration error");
+               return ret;
+       }
+
+       unsigned int speed = regmap->freq / div;
+       LOG_DEBUG("DW SPI setting NOR controller speed to %u kHz", speed / 1000);
+
+       return ret;
+}
+
+/**
+ * @brief Disable SI master controller interrupts.
+ *
+ * @param[in] bank: Flash bank.
+ * @return Command execution status.
+ */
+static int
+dw_spi_ctrl_disable_interrupts(const struct flash_bank *const bank)
+{
+       struct target *const target = bank->target;
+       const struct dw_spi_driver *const driver = bank->driver_priv;
+       const struct dw_spi_regmap *const regmap = &driver->regmap;
+
+       int ret = target_write_u32(target, regmap->simc + DW_SPI_REG_IMR, 0);
+       if (ret)
+               LOG_ERROR("DW SPI disable interrupts error");
+
+       return ret;
+}
+
+/**
+ * @brief Do data transaction.
+ *
+ * Buffer data are sent and replaced with received data. Total buffer size
+ * is a sum of TX and RX messages. RX portion of the buffer is initially
+ * filled with dummy values, which get replaced by the message.
+ *
+ * @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: \p buffer size.
+ * @param[in] read: The read flag. If set to true, read values will override
+ * \p buffer.
+ * @return Command execution status.
+ */
+static int
+dw_spi_ctrl_transaction(const struct flash_bank *const bank,
+                                               uint8_t *const buffer, size_t size, bool read)
+{
+       struct target *const target = bank->target;
+       const struct dw_spi_driver *const driver = bank->driver_priv;
+       const struct dw_spi_regmap *const regmap = &driver->regmap;
+
+       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);
+       const size_t total_working_area_size =
+               target_code_size + sizeof(struct dw_spi_transaction) + size;
+
+       // allocate working area, memory args and data buffer
+       struct working_area *helper;
+       int ret = target_alloc_working_area(target, target_code_size, &helper);
+       if (ret) {
+               LOG_ERROR("DW SPI could not allocate working area. Need %zx",
+                                 total_working_area_size);
+               goto err_helper;
+       }
+
+       struct working_area *helper_args;
+       ret = target_alloc_working_area(target, sizeof(struct dw_spi_transaction),
+                                                                       &helper_args);
+       if (ret) {
+               LOG_ERROR("DW SPI could not allocate working area. Need %zx",
+                                 total_working_area_size);
+               goto err_helper_args;
+       }
+
+       struct working_area *target_buffer;
+       ret = target_alloc_working_area(target, size, &target_buffer);
+       if (ret) {
+               LOG_ERROR("DW SPI could not allocate working area. Need %zx",
+                                 total_working_area_size);
+               goto err_target_buffer;
+       }
+
+       // write algorithm code and buffer to working areas
+       ret = target_write_buffer(target, helper->address, target_code_size,
+                                                         target_code);
+       if (ret) {
+               LOG_ERROR("DW SPI writing to working area error");
+               goto err_write_buffer;
+       }
+
+       ret = target_write_buffer(target, target_buffer->address, size, buffer);
+       if (ret) {
+               LOG_ERROR("DW SPI writing to working area error");
+               goto err_write_buffer;
+       }
+
+       // prepare helper execution
+       struct mips32_algorithm mips32_algo = { .common_magic = MIPS32_COMMON_MAGIC,
+                                                                                       .isa_mode = MIPS32_ISA_MIPS32 };
+
+       struct reg_param reg_param;
+       init_reg_param(&reg_param, DW_SPI_ARG_REG, 32, PARAM_OUT);
+       struct mem_param mem_param;
+       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);
+
+       struct dw_spi_transaction *helper_args_val =
+               (struct dw_spi_transaction *)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 + DW_SPI_REG_SR);
+       target_buffer_set_u32(target, (uint8_t *)&helper_args_val->data_reg,
+                                                 regmap->simc + DW_SPI_REG_DR);
+       helper_args_val->read_flag = read;
+
+       ret = target_run_algorithm(target, 1, &mem_param, 1, &reg_param,
+                                                          helper->address, 0, DW_SPI_TIMEOUT_TRANSACTION,
+                                                          &mips32_algo);
+
+       if (ret) {
+               LOG_ERROR("DW SPI flash algorithm error");
+               goto cleanup;
+       }
+
+       if (read) {
+               ret = target_read_buffer(target, target_buffer->address, size, buffer);
+               if (ret)
+                       LOG_ERROR("DW SPI target buffer read error");
+       }
+
+cleanup:
+       destroy_reg_param(&reg_param);
+       destroy_mem_param(&mem_param);
+
+err_write_buffer:
+       target_free_working_area(target, target_buffer);
+err_target_buffer:
+       target_free_working_area(target, helper_args);
+err_helper_args:
+       target_free_working_area(target, helper);
+err_helper:
+
+       return ret;
+}
+
+/**
+ * @brief Check that selected region is filled with pattern.
+ *
+ * This function is used for Flash erase checking.
+ *
+ * @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
+dw_spi_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 struct dw_spi_driver *const driver = bank->driver_priv;
+       const struct dw_spi_regmap *const regmap = &driver->regmap;
+
+       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);
+       const size_t total_working_area_size =
+               target_code_size + sizeof(struct dw_spi_check_fill) + sector_count;
+
+       // allocate working area, memory args and data buffer
+       struct working_area *helper;
+       int ret = target_alloc_working_area(target, target_code_size, &helper);
+       if (ret) {
+               LOG_ERROR("DW SPI could not allocate working area. Need %zx",
+                                 total_working_area_size);
+               goto err_helper;
+       }
+
+       struct working_area *helper_args;
+       ret = target_alloc_working_area(target, sizeof(struct dw_spi_check_fill),
+                                                                       &helper_args);
+       if (ret) {
+               LOG_ERROR("DW SPI could not allocate working area. Need %zx",
+                                 total_working_area_size);
+               goto err_helper_args;
+       }
+
+       struct working_area *target_buffer;
+       ret = target_alloc_working_area(target, sector_count, &target_buffer);
+       if (ret) {
+               LOG_ERROR("DW SPI could not allocate working area. Need %zx",
+                                 total_working_area_size);
+               goto err_target_buffer;
+       }
+
+       // write algorithm code and buffer to working areas
+       ret = target_write_buffer(target, helper->address, target_code_size,
+                                                         target_code);
+       if (ret) {
+               LOG_ERROR("DW SPI writing to working area error");
+               goto err_write_buffer;
+       }
+
+       // prepare helper execution
+       struct mips32_algorithm mips32_algo = { .common_magic = MIPS32_COMMON_MAGIC,
+                                                                                       .isa_mode = MIPS32_ISA_MIPS32 };
+
+       struct reg_param reg_param;
+       init_reg_param(&reg_param, DW_SPI_ARG_REG, 32, PARAM_OUT);
+       struct mem_param mem_param;
+       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);
+
+       struct dw_spi_check_fill *helper_args_val =
+               (struct dw_spi_check_fill *)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 + DW_SPI_REG_SR);
+       target_buffer_set_u32(target, (uint8_t *)&helper_args_val->data_reg,
+                                                 regmap->simc + DW_SPI_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;
+
+       ret = target_run_algorithm(target, 1, &mem_param, 1, &reg_param,
+                                                          helper->address, 0, driver->timeout,
+                                                          &mips32_algo);
+
+       if (ret) {
+               LOG_ERROR("DW SPI flash algorithm error");
+               goto cleanup;
+       }
+
+       ret = target_read_buffer(target, target_buffer->address, sector_count,
+                                                        buffer);
+       if (ret)
+               LOG_ERROR("DW SPI target buffer read error");
+
+cleanup:
+       destroy_reg_param(&reg_param);
+       destroy_mem_param(&mem_param);
+
+err_write_buffer:
+       target_free_working_area(target, target_buffer);
+err_target_buffer:
+       target_free_working_area(target, helper_args);
+err_helper_args:
+       target_free_working_area(target, helper);
+err_helper:
+
+       return ret;
+}
+
+/**
+ * @brief Write flash region.
+ *
+ * @param[in] bank: Flash bank.
+ * @param[in] address: First page address. Page aligned when write is crossing
+ *                     the page boundary.
+ * @param[in] buffer: Data buffer.
+ * @param[in] buffer_size: \p buffer size.
+ * @param[in] page_size: Size of flash page.
+ * @param[in] stat_cmd: Flash command to read chip status.
+ * @param[in] we_cmd: Flash command to enable write.
+ * @param[in] program_cmd: Flash command to program chip.
+ * @param[in] we_mask: Status byte write enable mask.
+ * @param[in] busy_mask: Status byte write busy mask.
+ * @return Command execution status.
+ */
+static int
+dw_spi_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 stat_cmd, uint8_t we_cmd,
+                                       uint8_t program_cmd, uint8_t we_mask, uint8_t busy_mask)
+{
+       struct target *const target = bank->target;
+       const struct dw_spi_driver *const driver = bank->driver_priv;
+       const struct dw_spi_regmap *const regmap = &driver->regmap;
+
+       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);
+       const size_t total_working_area_size =
+               target_code_size + sizeof(struct dw_spi_program) + buffer_size;
+
+       // allocate working area, memory args and data buffer
+       struct working_area *helper;
+       int ret = target_alloc_working_area(target, target_code_size, &helper);
+       if (ret) {
+               LOG_ERROR("DW SPI could not allocate working area. Need %zx",
+                                 total_working_area_size);
+               goto err_helper;
+       }
+
+       struct working_area *helper_args;
+       ret = target_alloc_working_area(target, sizeof(struct dw_spi_program),
+                                                                       &helper_args);
+       if (ret) {
+               LOG_ERROR("DW SPI could not allocate working area. Need %zx",
+                                 total_working_area_size);
+               goto err_helper_args;
+       }
+
+       struct working_area *target_buffer;
+       ret = target_alloc_working_area(target, buffer_size, &target_buffer);
+       if (ret) {
+               LOG_ERROR("DW SPI could not allocate working area. Need %zx",
+                                 total_working_area_size);
+               goto err_target_buffer;
+       }
+
+       // write algorithm code and buffer to working areas
+       ret = target_write_buffer(target, helper->address, target_code_size,
+                                                         target_code);
+       if (ret) {
+               LOG_ERROR("DW SPI writing to working area error");
+               goto err_write_buffer;
+       }
+
+       ret = target_write_buffer(target, target_buffer->address, buffer_size,
+                                                         buffer);
+       if (ret) {
+               LOG_ERROR("DW SPI writing to working area error");
+               goto err_write_buffer;
+       }
+
+       // prepare helper execution
+       struct mips32_algorithm mips32_algo = { .common_magic = MIPS32_COMMON_MAGIC,
+                                                                                       .isa_mode = MIPS32_ISA_MIPS32 };
+
+       struct reg_param reg_param;
+       init_reg_param(&reg_param, DW_SPI_ARG_REG, 32, PARAM_OUT);
+       struct mem_param mem_param;
+       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);
+       struct dw_spi_program *helper_args_val =
+               (struct dw_spi_program *)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 + DW_SPI_REG_SR);
+       target_buffer_set_u32(target, (uint8_t *)&helper_args_val->data_reg,
+                                                 regmap->simc + DW_SPI_REG_DR);
+       helper_args_val->read_status_cmd = stat_cmd;
+       helper_args_val->write_enable_cmd = we_cmd;
+       helper_args_val->program_cmd = program_cmd;
+       helper_args_val->write_enable_mask = we_mask;
+       helper_args_val->busy_mask = busy_mask;
+       helper_args_val->four_byte_mode = driver->four_byte_mode;
+
+       ret = target_run_algorithm(target, 1, &mem_param, 1, &reg_param,
+                                                          helper->address, 0, driver->timeout,
+                                                          &mips32_algo);
+       if (ret)
+               LOG_ERROR("DW SPI flash algorithm error");
+
+       destroy_reg_param(&reg_param);
+       destroy_mem_param(&mem_param);
+
+err_write_buffer:
+       target_free_working_area(target, target_buffer);
+err_target_buffer:
+       target_free_working_area(target, helper_args);
+err_helper_args:
+       target_free_working_area(target, helper);
+err_helper:
+
+       return ret;
+}
+
+/**
+ * @brief Erase sectors.
+ *
+ * @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] stat_cmd: Flash command to read chip status.
+ * @param[in] we_cmd: Flash command to set enable write.
+ * @param[in] erase_sector_cmd: Flash command to set erase sector.
+ * @param[in] we_mask: Status byte write enable mask.
+ * @param[in] busy_mask: Status byte write busy mask.
+ * @return Command execution status.
+ */
+static int
+dw_spi_ctrl_erase_sectors(const struct flash_bank *const bank, uint32_t address,
+                                                 uint32_t sector_size, size_t sector_count,
+                                                 uint8_t stat_cmd, uint8_t we_cmd,
+                                                 uint8_t erase_sector_cmd, uint8_t we_mask,
+                                                 uint8_t busy_mask)
+{
+       struct target *const target = bank->target;
+       const struct dw_spi_driver *const driver = bank->driver_priv;
+       const struct dw_spi_regmap *const regmap = &driver->regmap;
+
+       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);
+       const size_t total_working_area_size =
+               target_code_size + sizeof(struct dw_spi_erase);
+
+       // allocate working area and memory args
+       struct working_area *helper;
+       int ret = target_alloc_working_area(target, target_code_size, &helper);
+       if (ret) {
+               LOG_ERROR("DW SPI could not allocate working area. Need %zx",
+                                 total_working_area_size);
+               goto err_helper;
+       }
+
+       struct working_area *helper_args;
+       ret = target_alloc_working_area(target, sizeof(struct dw_spi_erase),
+                                                                       &helper_args);
+       if (ret) {
+               LOG_ERROR("DW SPI could not allocate working area. Need %zx",
+                                 total_working_area_size);
+               goto err_helper_args;
+       }
+
+       // write algorithm code to working area
+       ret = target_write_buffer(target, helper->address, target_code_size,
+                                                         target_code);
+       if (ret) {
+               LOG_ERROR("DW SPI writing to working area error");
+               goto err_write_buffer;
+       }
+
+       // prepare helper execution
+       struct mips32_algorithm mips32_algo = { .common_magic = MIPS32_COMMON_MAGIC,
+                                                                                       .isa_mode = MIPS32_ISA_MIPS32 };
+
+       struct reg_param reg_param;
+       init_reg_param(&reg_param, DW_SPI_ARG_REG, 32, PARAM_OUT);
+       struct mem_param mem_param;
+       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);
+       struct dw_spi_erase *helper_args_val =
+               (struct dw_spi_erase *)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 + DW_SPI_REG_SR);
+       target_buffer_set_u32(target, (uint8_t *)&helper_args_val->data_reg,
+                                                 regmap->simc + DW_SPI_REG_DR);
+       helper_args_val->read_status_cmd = stat_cmd;
+       helper_args_val->write_enable_cmd = we_cmd;
+       helper_args_val->erase_sector_cmd = erase_sector_cmd;
+       helper_args_val->write_enable_mask = we_mask;
+       helper_args_val->busy_mask = busy_mask;
+       helper_args_val->four_byte_mode = driver->four_byte_mode;
+
+       ret = target_run_algorithm(target, 1, &mem_param, 1, &reg_param,
+                                                          helper->address, 0, driver->timeout,
+                                                          &mips32_algo);
+       if (ret)
+               LOG_ERROR("DW SPI flash algorithm error");
+
+       destroy_reg_param(&reg_param);
+       destroy_mem_param(&mem_param);
+
+err_write_buffer:
+       target_free_working_area(target, helper_args);
+err_helper_args:
+       target_free_working_area(target, helper);
+err_helper:
+
+       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: \p buffer size.
+ * @param[in] read_cmd: Flash command to read data from flash.
+ * @return Command execution status.
+ */
+static int
+dw_spi_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 struct dw_spi_driver *const driver = bank->driver_priv;
+       const struct dw_spi_regmap *const regmap = &driver->regmap;
+
+       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);
+       const size_t total_working_area_size =
+               target_code_size + sizeof(struct dw_spi_read) + buffer_size;
+
+       // allocate working area and memory args
+       struct working_area *helper;
+       int ret = target_alloc_working_area(target, target_code_size, &helper);
+       if (ret) {
+               LOG_ERROR("DW SPI could not allocate working area. Need %zx",
+                                 total_working_area_size);
+               goto err_helper;
+       }
+
+       struct working_area *helper_args;
+       ret = target_alloc_working_area(target, sizeof(struct dw_spi_read),
+                                                                       &helper_args);
+       if (ret) {
+               LOG_ERROR("DW SPI could not allocate working area. Need %zx",
+                                 total_working_area_size);
+               goto err_helper_args;
+       }
+
+       struct working_area *target_buffer;
+       ret = target_alloc_working_area(target, buffer_size, &target_buffer);
+       if (ret) {
+               LOG_ERROR("DW SPI could not allocate working area. Need %zx",
+                                 total_working_area_size);
+               goto err_target_buffer;
+       }
+
+       // write algorithm code to working area
+       ret = target_write_buffer(target, helper->address, target_code_size,
+                                                         target_code);
+       if (ret) {
+               LOG_ERROR("DW SPI writing to working area error");
+               goto err_write_buffer;
+       }
+
+       // prepare helper execution
+       struct mips32_algorithm mips32_algo = { .common_magic = MIPS32_COMMON_MAGIC,
+                                                                                       .isa_mode = MIPS32_ISA_MIPS32 };
+
+       struct reg_param reg_param;
+       init_reg_param(&reg_param, DW_SPI_ARG_REG, 32, PARAM_OUT);
+       struct mem_param mem_param;
+       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);
+       struct dw_spi_read *helper_args_val = (struct dw_spi_read *)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 + DW_SPI_REG_SR);
+       target_buffer_set_u32(target, (uint8_t *)&helper_args_val->data_reg,
+                                                 regmap->simc + DW_SPI_REG_DR);
+       helper_args_val->read_cmd = read_cmd;
+       helper_args_val->four_byte_mode = driver->four_byte_mode;
+
+       ret = target_run_algorithm(target, 1, &mem_param, 1, &reg_param,
+                                                          helper->address, 0, driver->timeout,
+                                                          &mips32_algo);
+       if (ret) {
+               LOG_ERROR("DW SPI flash algorithm error");
+               goto cleanup;
+       }
+
+       ret =
+               target_read_buffer(target, target_buffer->address, buffer_size, buffer);
+       if (ret)
+               LOG_ERROR("DW SPI target buffer read error");
+
+cleanup:
+       destroy_reg_param(&reg_param);
+       destroy_mem_param(&mem_param);
+
+err_write_buffer:
+       target_free_working_area(target, target_buffer);
+err_target_buffer:
+       target_free_working_area(target, helper_args);
+err_helper_args:
+       target_free_working_area(target, helper);
+err_helper:
+
+       return ret;
+}
+
+/**
+ * @brief Read Flash device ID.
+ *
+ * @param[in] bank: Flash bank handle.
+ * @return Command execution status.
+ */
+static int
+dw_spi_read_id(const struct flash_bank *const bank)
+{
+       struct dw_spi_driver *const driver = bank->driver_priv;
+
+       const size_t buffer_size = 1 + 3 + 1;
+       uint8_t buffer[buffer_size];
+
+       memset(buffer, 0, buffer_size);
+       buffer[0] = SPIFLASH_READ_ID;
+
+       int ret = dw_spi_ctrl_transaction(bank, buffer, buffer_size, true);
+       if (ret) {
+               LOG_ERROR("DW SPI flash ID read error");
+               return ret;
+       }
+
+       buffer[buffer_size - 1] = 0;
+       // use le_to_h_u32 to decode flash ID as per JEDEC SFDP
+       driver->id = le_to_h_u32(buffer + 1);
+       LOG_DEBUG("DW SPI read flash ID %" PRIx32, driver->id);
+
+       return ERROR_OK;
+}
+
+/**
+ * @brief Read Flash device status.
+ *
+ * @param[in] bank: Flash bank handle.
+ * @param[out] status: The status byte.
+ * @return Command execution status.
+ */
+static int
+dw_spi_read_status(const struct flash_bank *const bank, uint8_t *const status)
+{
+       const int buffer_size = 2;
+       uint8_t buffer[buffer_size];
+
+       memset(buffer, 0, buffer_size);
+       buffer[0] = SPIFLASH_READ_STATUS;
+
+       int ret = dw_spi_ctrl_transaction(bank, buffer, buffer_size, true);
+       if (ret) {
+               LOG_ERROR("DW SPI flash status read error");
+               return ret;
+       }
+
+       *status = buffer[1];
+
+       return ERROR_OK;
+}
+
+/**
+ * @brief Wait for Flash command to finish.
+ *
+ * @param[in] bank: Flash bank handle.
+ * @param[in] timeout: The timeout in ms.
+ * @return Command execution status.
+ */
+static int
+dw_spi_wait_finish(const struct flash_bank *const bank, unsigned int timeout)
+{
+       const int64_t end_time = timeval_ms() + timeout;
+       while (timeval_ms() <= end_time) {
+               uint8_t status;
+               int ret = dw_spi_read_status(bank, &status);
+               if (ret) {
+                       LOG_ERROR("DW SPI status query error");
+                       return ret;
+               }
+               if (!(status & SPIFLASH_BSY_BIT))
+                       return ERROR_OK;
+
+               alive_sleep(1);
+       }
+
+       LOG_ERROR("DW SPI process timeout");
+       return ERROR_TIMEOUT_REACHED;
+}
+
+/**
+ * @brief Flash device write enable.
+ *
+ * @param[in] bank: Flash bank handle.
+ * @return Command execution status.
+ */
+static int
+dw_spi_write_enable(const struct flash_bank *const bank)
+{
+       const int buffer_size = 1;
+       uint8_t buffer[buffer_size];
+
+       memset(buffer, 0, buffer_size);
+       buffer[0] = SPIFLASH_WRITE_ENABLE;
+
+       int ret = dw_spi_ctrl_transaction(bank, buffer, buffer_size, false);
+       if (ret) {
+               LOG_ERROR("DW SPI flash write enable error");
+               return ret;
+       }
+
+       uint8_t status;
+       ret = dw_spi_read_status(bank, &status);
+       if (ret)
+               return ret;
+
+       return status & SPIFLASH_WE_BIT ? ERROR_OK : ERROR_FAIL;
+}
+
+/**
+ * @brief Erase Flash chip.
+ *
+ * @param[in] bank: Flash bank handle.
+ * @return Command execution status.
+ */
+static int
+dw_spi_erase_chip(const struct flash_bank *const bank)
+{
+       const struct dw_spi_driver *const driver = bank->driver_priv;
+
+       const int buffer_size = 1;
+       uint8_t buffer[buffer_size];
+
+       int ret = dw_spi_write_enable(bank);
+       if (ret)
+               return ret;
+
+       memset(buffer, 0, buffer_size);
+       buffer[0] = driver->spi_flash->chip_erase_cmd;
+
+       ret = dw_spi_ctrl_transaction(bank, buffer, buffer_size, false);
+       if (ret) {
+               LOG_ERROR("DW SPI erase flash error");
+               return ret;
+       }
+
+       ret = dw_spi_wait_finish(bank, driver->timeout);
+       if (ret) {
+               LOG_ERROR("DW SPI erase flash timeout");
+               return ret;
+       }
+
+       return ERROR_OK;
+}
+
+/**
+ * @brief Flash device 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
+dw_spi_erase_sectors(const struct flash_bank *const bank, unsigned int first,
+                                        unsigned int last)
+{
+       const struct dw_spi_driver *const driver = bank->driver_priv;
+
+       if (first == 0 && last >= (bank->num_sectors - 1)) {
+               // full erase
+               int ret = dw_spi_erase_chip(bank);
+               if (ret)
+                       return ret;
+       } else {
+               // partial erase
+               int ret = dw_spi_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);
+               if (ret) {
+                       LOG_ERROR("DW SPI flash erase sectors error");
+                       return ret;
+               }
+       }
+
+       return ERROR_OK;
+}
+
+/**
+ * @brief Flash bank blank check.
+ */
+static int
+dw_spi_blank_check(struct flash_bank *bank, size_t sector_count,
+                                  uint8_t pattern)
+{
+       const struct dw_spi_driver *const driver = bank->driver_priv;
+
+       uint8_t *erased = malloc(sector_count);
+       if (!erased) {
+               LOG_ERROR("could not allocate memory");
+               return ERROR_FAIL;
+       }
+
+       // set initial erased value to unknown
+       memset(erased, 2, sector_count);
+       for (unsigned int sector_idx = 0; sector_idx < sector_count; sector_idx++)
+               bank->sectors[sector_idx].is_erased = 2;
+
+       int ret = dw_spi_ctrl_check_sectors_fill(bank, 0, bank->sectors[0].size,
+                                                                                        sector_count, pattern,
+                                                                                        driver->spi_flash->read_cmd,
+                                                                                        erased);
+       if (!ret) {
+               for (unsigned int sector_idx = 0; sector_idx < sector_count;
+                        sector_idx++)
+                       bank->sectors[sector_idx].is_erased = erased[sector_idx];
+       } else {
+               LOG_ERROR("DW SPI flash erase check error");
+       }
+
+       free(erased);
+
+       return ret;
+}
+
+/**
+ * @brief Write buffer to Flash.
+ *
+ * @param[in] bank: Flash bank handle.
+ * @param[in] buffer: Data buffer.
+ * @param[in] offset: Flash address offset.
+ * @param[in] count: \p buffer size.
+ * @return Command execution status.
+ */
+static int
+dw_spi_write_buffer(const struct flash_bank *const bank, const uint8_t *buffer,
+                                       uint32_t offset, uint32_t count)
+{
+       const struct dw_spi_driver *const driver = bank->driver_priv;
+       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 },
+       };
+
+       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 (unsigned int chunk_idx = 0; chunk_idx < ARRAY_SIZE(chunks);
+                chunk_idx++) {
+               if (chunks[chunk_idx].count > 0) {
+                       int ret = dw_spi_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);
+                       if (ret) {
+                               LOG_ERROR("DW SPI flash write error");
+                               return ret;
+                       }
+               }
+       }
+
+       return ERROR_OK;
+}
+
+/**
+ * @brief Search for Flash chip info.
+ *
+ * @param[in] bank: Flash bank handle.
+ * @return Command execution status.
+ */
+static int
+dw_spi_spiflash_search(const struct flash_bank *const bank)
+{
+       struct dw_spi_driver *const driver = bank->driver_priv;
+
+       int ret = dw_spi_read_id(bank);
+       if (ret)
+               return ret;
+
+       unsigned int idx = 0;
+       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("DW SPI could not find Flash with ID %" PRIx32
+                         " in SPI Flash table: either Flash device is not supported "
+                         "or communication speed is too high",
+                         driver->id);
+       return ERROR_FAIL;
+}
+
+/**
+ * @brief Handle flash bank command.
+ *
+ * @param[in] CMD_ARGC: Number of arguments.
+ * @param[in] CMD_ARGV: Command arguments.
+ * @return Command execution status.
+ */
+FLASH_BANK_COMMAND_HANDLER(dw_spi_flash_bank_command)
+{
+       unsigned int speed = 1000000;
+       unsigned int timeout = DW_SPI_TIMEOUT_DEFAULT;
+       uint8_t chip_select_bitmask = BIT(0);
+       struct dw_spi_regmap regmap = { 0 };
+
+       if (CMD_ARGC < 6)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       for (unsigned int idx = 6; idx < CMD_ARGC; idx++) {
+               if (strcmp(CMD_ARGV[idx], "-jaguar2") == 0) {
+                       // Fast config for Jaguar2 chips.
+                       memcpy(&regmap, &jaguar2_regmap, sizeof(jaguar2_regmap));
+               } else if (strcmp(CMD_ARGV[idx], "-ocelot") == 0) {
+                       // Fast config for Ocelot chips.
+                       memcpy(&regmap, &ocelot_regmap, sizeof(ocelot_regmap));
+               } else if (strcmp(CMD_ARGV[idx], "-freq") == 0) {
+                       COMMAND_PARSE_NUMBER(u32, CMD_ARGV[++idx], regmap.freq);
+               } else if (strcmp(CMD_ARGV[idx], "-simc") == 0) {
+                       COMMAND_PARSE_NUMBER(target_addr, CMD_ARGV[++idx], regmap.simc);
+               } else if (strcmp(CMD_ARGV[idx], "-spi_mst") == 0) {
+                       COMMAND_PARSE_NUMBER(target_addr, CMD_ARGV[++idx], regmap.spi_mst);
+               } else if (strcmp(CMD_ARGV[idx], "-if_owner_offset") == 0) {
+                       COMMAND_PARSE_NUMBER(u8, CMD_ARGV[++idx],
+                                                                regmap.si_if_owner_offset);
+               } else if (strcmp(CMD_ARGV[idx], "-speed") == 0) {
+                       COMMAND_PARSE_NUMBER(uint, CMD_ARGV[++idx], speed);
+               } else if (strcmp(CMD_ARGV[idx], "-timeout") == 0) {
+                       COMMAND_PARSE_NUMBER(uint, CMD_ARGV[++idx], timeout);
+                       timeout *= 1000; // convert to ms
+               } else if (strcmp(CMD_ARGV[idx], "-chip_select") == 0) {
+                       unsigned int cs_bit;
+                       COMMAND_PARSE_NUMBER(uint, CMD_ARGV[++idx], cs_bit);
+                       chip_select_bitmask = BIT(cs_bit);
+               } else {
+                       LOG_WARNING("DW SPI unknown argument %s", CMD_ARGV[idx]);
+               }
+       }
+
+       if (!regmap.simc) {
+               LOG_ERROR("DW SPI cannot use boot controller with unconfigured simc");
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+
+       bank->driver_priv = malloc(sizeof(struct dw_spi_driver));
+       if (!bank->driver_priv) {
+               LOG_ERROR("could not allocate memory");
+               return ERROR_FAIL;
+       }
+
+       struct dw_spi_driver *driver = bank->driver_priv;
+       memset(driver, 0, sizeof(struct dw_spi_driver));
+       driver->speed = speed;
+       driver->timeout = timeout;
+       driver->chip_select_bitmask = chip_select_bitmask;
+       driver->four_byte_mode = true; // 24bit commands not provided by spi.h
+       memcpy(&driver->regmap, &regmap, sizeof(regmap));
+
+       return ERROR_OK;
+}
+
+/**
+ * @brief Assert target is halted.
+ *
+ * @param[in] bank: Flash bank handle.
+ * @return Command execution status.
+ */
+static int
+dw_spi_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 Prepare master controller for transaction.
+ *
+ * @param[in] bank: Flash bank handle.
+ * @return Command execution status.
+ */
+static int
+dw_spi_master_ctrl_configure(struct flash_bank *bank)
+{
+       int ret = dw_spi_assert_halted(bank);
+       if (ret)
+               return ret;
+       ret = dw_spi_ctrl_mode_configure(bank, DW_SPI_SI_MODE_MASTER);
+       if (ret) {
+               LOG_ERROR("DW SPI switch to master controller mode error");
+               return ret;
+       }
+       ret = dw_spi_master_ctrl_enable(bank, false);
+       if (ret) {
+               LOG_ERROR("DW SPI disable master controller error");
+               return ret;
+       }
+       ret = dw_spi_ctrl_configure_speed(bank);
+       if (ret) {
+               LOG_ERROR("DW SPI speed configuration error");
+               return ret;
+       }
+       ret = dw_spi_ctrl_disable_interrupts(bank);
+       if (ret) {
+               LOG_ERROR("DW SPI disable SPI interrupts error");
+               return ret;
+       }
+       ret = dw_spi_ctrl_configure_si(bank);
+       if (ret) {
+               LOG_ERROR("DW SPI controller configuration error");
+               return ret;
+       }
+       ret = dw_spi_master_ctrl_enable(bank, true);
+       if (ret) {
+               LOG_ERROR("DW SPI enable master controller error");
+               return ret;
+       }
+
+       return ERROR_OK;
+}
+
+/**
+ * @brief Restore SI controller selection.
+ *
+ * @param[in] bank: Flash bank handle.
+ * @return Command execution status.
+ */
+static int
+dw_spi_master_ctrl_restore(struct flash_bank *bank)
+{
+       int ret = dw_spi_master_ctrl_enable(bank, false);
+       if (ret) {
+               LOG_ERROR("DW SPI disable master controller error");
+               return ret;
+       }
+       ret = dw_spi_ctrl_mode_restore(bank);
+       if (ret) {
+               LOG_ERROR("DW SPI controller restore error");
+               return ret;
+       }
+
+       return ret;
+}
+
+/**
+ * @brief Flash bank probe.
+ *
+ * @param[in] bank: Flash bank handle.
+ * @return Command execution status.
+ */
+static int
+dw_spi_probe(struct flash_bank *bank)
+{
+       struct dw_spi_driver *const driver = bank->driver_priv;
+
+       if (!driver)
+               return ERROR_FAIL;
+
+       if (strcmp(bank->target->type->name, mips_m4k_target.name) != 0 ||
+               bank->target->endianness != TARGET_LITTLE_ENDIAN) {
+               LOG_ERROR("DW SPI currently only supports "
+                                 "little endian mips_m4k target");
+               return ERROR_TARGET_INVALID;
+       }
+
+       int ret = dw_spi_master_ctrl_configure(bank);
+       if (ret)
+               return ret;
+
+       ret = dw_spi_spiflash_search(bank);
+       if (ret)
+               goto err;
+
+       bank->write_start_alignment = 0;
+       bank->write_end_alignment = 0;
+
+       uint32_t flash_size = driver->spi_flash->size_in_bytes;
+       if (!bank->size) {
+               bank->size = flash_size;
+               LOG_INFO("DW SPI probed flash size 0x%" PRIx32, flash_size);
+       } else {
+               if (flash_size > bank->size)
+                       LOG_WARNING("DW SPI probed flash size 0x%" PRIx32
+                                               " is greater then declared 0x%" PRIx32,
+                                               flash_size, bank->size);
+               if (flash_size < bank->size) {
+                       LOG_ERROR("DW SPI probed flash size 0x%" PRIx32
+                                         " is smaller then declared 0x%" PRIx32,
+                                         flash_size, bank->size);
+                       ret = ERROR_FLASH_BANK_INVALID;
+                       goto err;
+               }
+       }
+       bank->num_sectors = bank->size / driver->spi_flash->sectorsize;
+
+       // free previously allocated in case of reprobing
+       free(bank->sectors);
+
+       bank->sectors =
+               alloc_block_array(0, driver->spi_flash->sectorsize, bank->num_sectors);
+
+       if (!bank->sectors) {
+               LOG_ERROR("could not allocate memory");
+               ret = ERROR_FAIL;
+               goto err;
+       }
+
+       driver->probed = true;
+
+err:
+       dw_spi_master_ctrl_restore(bank);
+       return ret;
+}
+
+/**
+ * @brief Flash bank 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
+dw_spi_erase(struct flash_bank *bank, unsigned int first, unsigned int last)
+{
+       int ret = dw_spi_master_ctrl_configure(bank);
+       if (ret)
+               return ret;
+
+       ret = dw_spi_erase_sectors(bank, first, last);
+       dw_spi_master_ctrl_restore(bank);
+       return ret;
+}
+
+/**
+ * @brief Flash bank write data.
+ *
+ * @param[in] bank: Flash bank handle.
+ * @param[in] buffer: Data buffer.
+ * @param[in] offset: Flash address offset.
+ * @param[in] count: \p buffer size.
+ * @return Command execution status.
+ */
+static int
+dw_spi_write(struct flash_bank *bank, const uint8_t *buffer, uint32_t offset,
+                        uint32_t count)
+{
+       int ret = dw_spi_master_ctrl_configure(bank);
+       if (ret)
+               return ret;
+
+       ret = dw_spi_write_buffer(bank, buffer, offset, count);
+       dw_spi_master_ctrl_restore(bank);
+       return ret;
+}
+
+/**
+ * @brief Flash bank read data using master controller.
+ *
+ * @param[in] bank: Flash bank handle.
+ * @param[out] buffer: Data buffer.
+ * @param[in] offset: Flash address offset.
+ * @param[in] count: \p buffer size.
+ * @return Command execution status.
+ */
+static int
+dw_spi_read(struct flash_bank *bank, uint8_t *buffer, uint32_t offset,
+                       uint32_t count)
+{
+       struct dw_spi_driver *const driver = bank->driver_priv;
+
+       int ret = dw_spi_master_ctrl_configure(bank);
+       if (ret)
+               return ret;
+
+       ret = dw_spi_ctrl_read(bank, offset, buffer, count,
+                                                  driver->spi_flash->read_cmd);
+       dw_spi_master_ctrl_restore(bank);
+       return ret;
+}
+
+/**
+ * @brief Flash bank erase check.
+ *
+ * @param[in] bank: Flash bank handle.
+ * @return Command execution status.
+ */
+static int
+dw_spi_erase_check(struct flash_bank *bank)
+{
+       int ret = dw_spi_master_ctrl_configure(bank);
+       if (ret)
+               return ret;
+
+       ret = dw_spi_blank_check(bank, bank->num_sectors, bank->erased_value);
+
+       dw_spi_master_ctrl_restore(bank);
+       return ret;
+}
+
+/**
+ * @brief Flash bank info.
+ *
+ * @param[in] bank: Flash bank handle.
+ * @param[in,out] cmd Command invocation.
+ * @return Command execution status.
+ */
+static int
+dw_spi_info(struct flash_bank *bank, struct command_invocation *cmd)
+{
+       const struct dw_spi_driver *const driver = bank->driver_priv;
+       command_print(cmd, "model %s", driver->spi_flash->name);
+       command_print(cmd, "ID 0x%" PRIx32, driver->id);
+       command_print_sameline(cmd, "size 0x%" PRIx32, bank->size);
+       return ERROR_OK;
+}
+
+/**
+ * @brief Autoprobe driver.
+ *
+ * @param[in] bank: Flash bank handle.
+ * @return Command execution status.
+ */
+static int
+dw_spi_auto_probe(struct flash_bank *bank)
+{
+       struct dw_spi_driver *driver = bank->driver_priv;
+       if (!driver)
+               return ERROR_FAIL;
+       if (!driver->probed)
+               return dw_spi_probe(bank);
+       return ERROR_OK;
+}
+
+/**
+ * @brief DW-SPI NOR flash functions.
+ */
+const struct flash_driver dw_spi_flash = {
+       .name = "dw-spi",
+       .flash_bank_command = dw_spi_flash_bank_command,
+       .erase = dw_spi_erase,
+       .write = dw_spi_write,
+       .read = dw_spi_read,
+       .probe = dw_spi_probe,
+       .auto_probe = dw_spi_auto_probe,
+       .erase_check = dw_spi_erase_check,
+       .info = dw_spi_info,
+       .verify = default_flash_verify,
+       .free_driver_priv = default_flash_free_driver_priv,
+};