This is an automated email from Gerrit.

"Sergey Matsievskiy <matsievski...@gmail.com>" just uploaded a new patch set to 
Gerrit, which you can find at https://review.openocd.org/c/openocd/+/8400

-- gerrit

commit 6861bd1bbac352218c7a8cc45659fd38410c9059
Author: Matsievskiy S.V. <matsievski...@gmail.com>
Date:   Tue Jul 16 08:08:23 2024 +0300

    flash/nor: add DesignWare SPI controller driver
    
    Driver for DesignWare SPI controller, found on many SoCs (see compatible
    list in Linux device tree bindings
    Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml). This
    implementation only supports MIPS as it was the only one available for the
    tests, however, adding support for other architectures should require only
    few adjustments. Driver relies on flash/nor/spi.h to find Flash chip info.
    Driver internal functions support 24bit addressing mode, but due to
    limitations of flash/nor/spi.h, it is not used. The repoted writing speed
    is about 60kb/s.
    Lint, sanitizer and valgraind repoted warnings were not related to the
    driver.
    
    Change-Id: Id3df5626ab88055f034f74f274823051dedefeb1
    Signed-off-by: Matsievskiy S.V. <matsievski...@gmail.com>

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

-- 

Reply via email to