]> git.sur5r.net Git - openocd/commitdiff
flash/nor: add mrvlqspi flash controller driver
authorMahavir Jain <mjain@marvell.com>
Thu, 4 Sep 2014 10:01:16 +0000 (15:31 +0530)
committerSpencer Oliver <spen@spen-soft.co.uk>
Mon, 22 Sep 2014 19:37:09 +0000 (19:37 +0000)
This patch adds support for QSPI flash controller driver for
Marvell's Wireless Microcontroller platform.
For more information please refer,
https://origin-www.marvell.com/microcontrollers/wi-fi-microcontroller-platform/

Following things have been tested on 88MC200 (Winbond W25Q80BV flash chip):
1. Flash sector level erase
2. Flash chip erase
3. Flash write in normal SPI mode
4. Flash fill (write and verify) in normal SPI mode

Change-Id: If4414ae3f77ff170b84e426a35b66c44590c5e06
Signed-off-by: Mahavir Jain <mjain@marvell.com>
Reviewed-on: http://openocd.zylin.com/2280
Tested-by: jenkins
Reviewed-by: Spencer Oliver <spen@spen-soft.co.uk>
contrib/loaders/flash/mrvlqspi_write.S [new file with mode: 0644]
src/flash/nor/Makefile.am
src/flash/nor/drivers.c
src/flash/nor/mrvlqspi.c [new file with mode: 0644]

diff --git a/contrib/loaders/flash/mrvlqspi_write.S b/contrib/loaders/flash/mrvlqspi_write.S
new file mode 100644 (file)
index 0000000..064192c
--- /dev/null
@@ -0,0 +1,232 @@
+/***************************************************************************
+ *   Copyright (C) 2014 by Mahavir Jain <mjain@marvell.com>                *
+ *                                                                         *
+ *   Adapted from (contrib/loaders/flash/lpcspifi_write.S):                *
+ *   Copyright (C) 2012 by George Harris                                   *
+ *   george@luminairecoffee.com                                            *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.           *
+ ***************************************************************************/
+
+       .text
+       .syntax unified
+       .cpu cortex-m3
+       .thumb
+       .thumb_func
+
+/*
+ * For compilation:
+ * arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -c contrib/loaders/flash/mrvlqspi_write.S
+ * arm-none-eabi-objcopy -O binary mrvlqspi_write.o code.bin
+ * Copy code.bin into mrvlqspi flash driver
+ */
+
+/*
+ * Params :
+ * r0 = workarea start, status (out)
+ * r1 = workarea end
+ * r2 = target address (offset from flash base)
+ * r3 = count (bytes)
+ * r4 = page size
+ * r5 = qspi base address
+ * Clobbered:
+ * r7 - rp
+ * r8 - wp, tmp
+ * r9 - send/receive data
+ * r10 - current page end address
+ */
+
+#define CNTL   0x0
+#define CONF   0x4
+#define DOUT   0x8
+#define DIN    0xc
+#define INSTR   0x10
+#define ADDR    0x14
+#define RDMODE  0x18
+#define HDRCNT 0x1c
+#define DINCNT  0x20
+
+#define SS_EN (1 << 0)
+#define XFER_RDY (1 << 1)
+#define RFIFO_EMPTY (1 << 4)
+#define WFIFO_EMPTY (1 << 6)
+#define WFIFO_FULL (1 << 7)
+#define FIFO_FLUSH (1 << 9)
+#define RW_EN (1 << 13)
+#define XFER_STOP (1 << 14)
+#define XFER_START (1 << 15)
+
+#define INS_WRITE_ENABLE 0x06
+#define INS_READ_STATUS 0x05
+#define INS_PAGE_PROGRAM 0x02
+
+init:
+       mov.w   r10, #0x00
+find_next_page_boundary:
+       add     r10, r4         /* Increment to the next page */
+       cmp     r10, r2
+       /* If we have not reached the next page boundary after the target address, keep going */
+       bls     find_next_page_boundary
+write_enable:
+       /* Flush read/write fifo's */
+       bl      flush_fifo
+
+       /* Instruction byte 1 */
+       movs    r8, #0x1
+       str     r8, [r5, #HDRCNT]
+
+       /* Set write enable instruction */
+       movs    r8, #INS_WRITE_ENABLE
+       str     r8, [r5, #INSTR]
+
+       movs    r9, #0x1
+       bl      start_tx
+       bl      stop_tx
+page_program:
+       /* Instruction byte 1, Addr byte 3 */
+       movs    r8, #0x31
+       str     r8, [r5, #HDRCNT]
+       /* Todo: set addr and data pin to single */
+write_address:
+       mov     r8, r2
+       str     r8, [r5, #ADDR]
+       /* Set page program instruction */
+       movs    r8, #INS_PAGE_PROGRAM
+       str     r8, [r5, #INSTR]
+       /* Start write transfer */
+       movs    r9, #0x1
+       bl      start_tx
+wait_fifo:
+       ldr     r8, [r0]        /* read the write pointer */
+       cmp     r8, #0          /* if it's zero, we're gonzo */
+       beq     exit
+       ldr     r7, [r0, #4]    /* read the read pointer */
+       cmp     r7, r8          /* wait until they are not equal */
+       beq     wait_fifo
+write:
+       ldrb    r9, [r7], #0x01 /* Load one byte from the FIFO, increment the read pointer by 1 */
+       bl      write_data      /* send the byte to the flash chip */
+
+       cmp     r7, r1          /* wrap the read pointer if it is at the end */
+       it      cs
+       addcs   r7, r0, #8      /* skip loader args */
+       str     r7, [r0, #4]    /* store the new read pointer */
+       subs    r3, r3, #1      /* decrement count */
+       cmp     r3, #0          /* Exit if we have written everything */
+       beq     write_wait
+       add     r2, #1          /* Increment flash address by 1 */
+       cmp     r10, r2         /* See if we have reached the end of a page */
+       bne     wait_fifo       /* If not, keep writing bytes */
+write_wait:
+       bl      stop_tx         /* Otherwise, end the command and keep going w/ the next page */
+       add     r10, r4         /* Move up the end-of-page address by the page size*/
+check_flash_busy:              /* Wait for the flash to finish the previous page write */
+       /* Flush read/write fifo's */
+       bl      flush_fifo
+       /* Instruction byte 1 */
+       movs    r8, #0x1
+       str     r8, [r5, #HDRCNT]
+       /* Continuous data in of status register */
+       movs    r8, #0x0
+       str     r8, [r5, #DINCNT]
+       /* Set write enable instruction */
+       movs    r8, #INS_READ_STATUS
+       str     r8, [r5, #INSTR]
+       /* Start read transfer */
+       movs    r9, #0x0
+       bl      start_tx
+wait_flash_busy:
+       bl      read_data
+       and.w   r9, r9, #0x1
+       cmp     r9, #0x0
+       bne.n   wait_flash_busy
+       bl      stop_tx
+       cmp     r3, #0
+       bne.n   write_enable    /* If it is done, start a new page write */
+       b       exit            /* All data written, exit */
+
+write_data:                    /* Send/receive 1 byte of data over QSPI */
+       ldr     r8, [r5, #CNTL]
+       lsls    r8, r8, #24
+       bmi.n   write_data
+       str     r9, [r5, #DOUT]
+       bx      lr
+
+read_data:                     /* Read 1 byte of data over QSPI */
+       ldr     r8, [r5, #CNTL]
+       lsls    r8, r8, #27
+       bmi.n   read_data
+       ldr     r9, [r5, #DIN]
+       bx      lr
+
+flush_fifo:                    /* Flush read write fifos */
+       ldr     r8, [r5, #CONF]
+       orr.w   r8, r8, #FIFO_FLUSH
+       str     r8, [r5, #CONF]
+flush_reset:
+       ldr     r8, [r5, #CONF]
+       lsls    r8, r8, #22
+       bmi.n   flush_reset
+       bx      lr
+
+start_tx:
+       ldr     r8, [r5, #CNTL]
+       orr.w   r8, r8, #SS_EN
+       str     r8, [r5, #CNTL]
+xfer_rdy:
+       ldr     r8, [r5, #CNTL]
+       lsls    r8, r8, #30
+       bpl.n   xfer_rdy
+       ldr     r8, [r5, #CONF]
+       bfi     r8, r9, #13, #1
+       orr.w   r8, r8, #XFER_START
+       str     r8, [r5, #CONF]
+       bx lr
+
+stop_tx:
+       ldr     r8, [r5, #CNTL]
+       lsls    r8, r8, #30
+       bpl.n   stop_tx
+wfifo_wait:
+       ldr     r8, [r5, #CNTL]
+       lsls    r8, r8, #25
+       bpl.n   wfifo_wait
+       ldr     r8, [r5, #CONF]
+       orr.w   r8, r8, #XFER_STOP
+       str     r8, [r5, #CONF]
+xfer_start:
+       ldr     r8, [r5, #CONF]
+       lsls    r8, r8, #16
+       bmi.n   xfer_start
+ss_disable:
+       # Disable SS_EN
+       ldr     r8, [r5, #CNTL]
+       bic.w   r8, r8, #SS_EN
+       str     r8, [r5, #CNTL]
+wait:
+       ldr     r8, [r5, #CNTL]
+       lsls    r8, r8, #30
+       bpl.n   wait
+       bx      lr
+
+error:
+       movs    r0, #0
+       str     r0, [r2, #4]    /* set rp = 0 on error */
+exit:
+       mov     r0, r6
+       bkpt    #0x00
+
+       .end
index 316814738d5e5e9518e63483a37b56bb79844fb9..bae42fd5dfcaa6f074e415a252bcb72027059375 100644 (file)
@@ -43,7 +43,8 @@ NOR_DRIVERS = \
        kinetis.c \
        mini51.c \
        nuc1x.c \
-       nrf51.c
+       nrf51.c \
+       mrvlqspi.c
 
 noinst_HEADERS = \
        core.h \
index ed631a3b0ba812e43ea6d61f7370c0653fb87e2b..8959f0cad7d11a957c08f19daa41cd003971f10e 100644 (file)
@@ -56,6 +56,7 @@ extern struct flash_driver mdr_flash;
 extern struct flash_driver mini51_flash;
 extern struct flash_driver nuc1x_flash;
 extern struct flash_driver nrf51_flash;
+extern struct flash_driver mrvlqspi_flash;
 
 /**
  * The list of built-in flash drivers.
@@ -96,6 +97,7 @@ static struct flash_driver *flash_drivers[] = {
        &mini51_flash,
        &nuc1x_flash,
        &nrf51_flash,
+       &mrvlqspi_flash,
        NULL,
 };
 
diff --git a/src/flash/nor/mrvlqspi.c b/src/flash/nor/mrvlqspi.c
new file mode 100644 (file)
index 0000000..a5cc1ca
--- /dev/null
@@ -0,0 +1,960 @@
+/***************************************************************************
+ *   Copyright (C) 2014 by Mahavir Jain <mjain@marvell.com>                *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.           *
+ *                                                                         *
+ ***************************************************************************/
+
+ /*
+  * This is QSPI flash controller driver for Marvell's Wireless
+  * Microcontroller platform.
+  *
+  * For more information please refer,
+  * https://origin-www.marvell.com/microcontrollers/wi-fi-microcontroller-platform/
+  */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "imp.h"
+#include "spi.h"
+#include <helper/binarybuffer.h>
+#include <target/algorithm.h>
+#include <target/armv7m.h>
+
+#define QSPI_R_EN (0x0)
+#define QSPI_W_EN (0x1)
+#define QSPI_SS_DISABLE (0x0)
+#define QSPI_SS_ENABLE (0x1)
+#define WRITE_DISBALE (0x0)
+#define WRITE_ENABLE (0x1)
+
+#define QSPI_TIMEOUT (1000)
+#define FIFO_FLUSH_TIMEOUT (1000)
+#define BLOCK_ERASE_TIMEOUT (1000)
+#define CHIP_ERASE_TIMEOUT (10000)
+
+#define SS_EN (1 << 0)
+#define XFER_RDY (1 << 1)
+#define RFIFO_EMPTY (1 << 4)
+#define WFIFO_EMPTY (1 << 6)
+#define WFIFO_FULL (1 << 7)
+#define FIFO_FLUSH (1 << 9)
+#define RW_EN (1 << 13)
+#define XFER_STOP (1 << 14)
+#define XFER_START (1 << 15)
+#define CONF_MASK (0x7)
+#define CONF_OFFSET (10)
+
+#define INS_WRITE_ENABLE 0x06
+#define INS_WRITE_DISABLE 0x04
+#define INS_READ_STATUS 0x05
+#define INS_PAGE_PROGRAM 0x02
+
+#define CNTL 0x0 /* QSPI_BASE + 0x0 */
+#define CONF 0x4
+#define DOUT 0x8
+#define DIN 0xc
+#define INSTR 0x10
+#define ADDR 0x14
+#define RDMODE 0x18
+#define HDRCNT 0x1c
+#define DINCNT 0x20
+
+struct mrvlqspi_flash_bank {
+       int probed;
+       uint32_t reg_base;
+       uint32_t bank_num;
+       const struct flash_device *dev;
+};
+
+static inline uint32_t mrvlqspi_get_reg(struct flash_bank *bank, uint32_t reg)
+{
+       struct mrvlqspi_flash_bank *mrvlqspi_info = bank->driver_priv;
+       return reg + mrvlqspi_info->reg_base;
+}
+
+static inline int mrvlqspi_set_din_cnt(struct flash_bank *bank, uint32_t count)
+{
+       struct target *target = bank->target;
+
+       return target_write_u32(target, mrvlqspi_get_reg(bank, DINCNT), count);
+}
+
+static inline int mrvlqspi_set_addr(struct flash_bank *bank, uint32_t addr)
+{
+       struct target *target = bank->target;
+
+       return target_write_u32(target, mrvlqspi_get_reg(bank, ADDR), addr);
+}
+
+static inline int mrvlqspi_set_instr(struct flash_bank *bank, uint32_t instr)
+{
+       struct target *target = bank->target;
+
+       return target_write_u32(target, mrvlqspi_get_reg(bank, INSTR), instr);
+}
+
+static inline int mrvlqspi_set_hdr_cnt(struct flash_bank *bank, uint32_t hdr_cnt)
+{
+       struct target *target = bank->target;
+
+       return target_write_u32(target, mrvlqspi_get_reg(bank, HDRCNT), hdr_cnt);
+}
+
+static int mrvlqspi_set_conf(struct flash_bank *bank, uint32_t conf_val)
+{
+       int retval;
+       uint32_t regval;
+       struct target *target = bank->target;
+
+       retval = target_read_u32(target,
+                       mrvlqspi_get_reg(bank, CONF), &regval);
+       if (retval != ERROR_OK)
+               return retval;
+
+       regval &= ~(CONF_MASK << CONF_OFFSET);
+       regval |= (conf_val << CONF_OFFSET);
+
+       return target_write_u32(target,
+                       mrvlqspi_get_reg(bank, CONF), regval);
+}
+
+static int mrvlqspi_set_ss_state(struct flash_bank *bank, bool state, int timeout)
+{
+       int retval;
+       uint32_t regval;
+       struct target *target = bank->target;
+
+       retval = target_read_u32(target,
+                       mrvlqspi_get_reg(bank, CNTL), &regval);
+       if (retval != ERROR_OK)
+               return retval;
+
+       if (state)
+               regval |= SS_EN;
+       else
+               regval &= ~(SS_EN);
+
+       retval = target_write_u32(target,
+                       mrvlqspi_get_reg(bank, CNTL), regval);
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* wait for xfer_ready to set */
+       for (;;) {
+               retval = target_read_u32(target,
+                               mrvlqspi_get_reg(bank, CNTL), &regval);
+               if (retval != ERROR_OK)
+                       return retval;
+               LOG_DEBUG("status: 0x%x", regval);
+               if ((regval & XFER_RDY) == XFER_RDY)
+                       break;
+               if (timeout-- <= 0) {
+                       LOG_ERROR("timed out waiting for flash");
+                       return ERROR_FAIL;
+               }
+               alive_sleep(1);
+       }
+       return ERROR_OK;
+}
+
+static int mrvlqspi_start_transfer(struct flash_bank *bank, bool rw_mode)
+{
+       int retval;
+       uint32_t regval;
+       struct target *target = bank->target;
+
+       retval = mrvlqspi_set_ss_state(bank, QSPI_SS_ENABLE, QSPI_TIMEOUT);
+       if (retval != ERROR_OK)
+               return retval;
+
+       retval = target_read_u32(target,
+                       mrvlqspi_get_reg(bank, CONF), &regval);
+       if (retval != ERROR_OK)
+               return retval;
+
+       if (rw_mode)
+               regval |= RW_EN;
+       else
+               regval &= ~(RW_EN);
+
+       regval |= XFER_START;
+
+       retval = target_write_u32(target,
+                       mrvlqspi_get_reg(bank, CONF), regval);
+       if (retval != ERROR_OK)
+               return retval;
+
+       return ERROR_OK;
+}
+
+static int mrvlqspi_stop_transfer(struct flash_bank *bank)
+{
+       int retval;
+       uint32_t regval;
+       struct target *target = bank->target;
+       int timeout = QSPI_TIMEOUT;
+
+       /* wait for xfer_ready and wfifo_empty to set */
+       for (;;) {
+               retval = target_read_u32(target,
+                               mrvlqspi_get_reg(bank, CNTL), &regval);
+               if (retval != ERROR_OK)
+                       return retval;
+               LOG_DEBUG("status: 0x%x", regval);
+               if ((regval & (XFER_RDY | WFIFO_EMPTY)) ==
+                                       (XFER_RDY | WFIFO_EMPTY))
+                       break;
+               if (timeout-- <= 0) {
+                       LOG_ERROR("timed out waiting for flash");
+                       return ERROR_FAIL;
+               }
+               alive_sleep(1);
+       }
+
+       retval = target_read_u32(target,
+                       mrvlqspi_get_reg(bank, CONF), &regval);
+       if (retval != ERROR_OK)
+               return retval;
+
+       regval |= XFER_STOP;
+
+       retval = target_write_u32(target,
+                       mrvlqspi_get_reg(bank, CONF), regval);
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* wait for xfer_start to reset */
+       for (;;) {
+               retval = target_read_u32(target,
+                               mrvlqspi_get_reg(bank, CONF), &regval);
+               if (retval != ERROR_OK)
+                       return retval;
+               LOG_DEBUG("status: 0x%x", regval);
+               if ((regval & XFER_START) == 0)
+                       break;
+               if (timeout-- <= 0) {
+                       LOG_ERROR("timed out waiting for flash");
+                       return ERROR_FAIL;
+               }
+               alive_sleep(1);
+       }
+
+       retval = mrvlqspi_set_ss_state(bank, QSPI_SS_DISABLE, QSPI_TIMEOUT);
+       if (retval != ERROR_OK)
+               return retval;
+
+       return ERROR_OK;
+}
+
+static int mrvlqspi_fifo_flush(struct flash_bank *bank, int timeout)
+{
+       int retval;
+       uint32_t val;
+       struct target *target = bank->target;
+
+       retval = target_read_u32(target,
+                       mrvlqspi_get_reg(bank, CONF), &val);
+       if (retval != ERROR_OK)
+               return retval;
+
+       val |= FIFO_FLUSH;
+
+       retval = target_write_u32(target,
+                       mrvlqspi_get_reg(bank, CONF), val);
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* wait for fifo_flush to clear */
+       for (;;) {
+               retval = target_read_u32(target,
+                               mrvlqspi_get_reg(bank, CONF), &val);
+               if (retval != ERROR_OK)
+                       return retval;
+               LOG_DEBUG("status: 0x%x", val);
+               if ((val & FIFO_FLUSH) == 0)
+                       break;
+               if (timeout-- <= 0) {
+                       LOG_ERROR("timed out waiting for flash");
+                       return ERROR_FAIL;
+               }
+               alive_sleep(1);
+       }
+       return ERROR_OK;
+}
+
+static int mrvlqspi_read_byte(struct flash_bank *bank, uint8_t *data)
+{
+       int retval;
+       uint32_t val;
+       struct target *target = bank->target;
+
+       /* wait for rfifo_empty to reset */
+       for (;;) {
+               retval = target_read_u32(target,
+                               mrvlqspi_get_reg(bank, CNTL), &val);
+               if (retval != ERROR_OK)
+                       return retval;
+               LOG_DEBUG("status: 0x%x", val);
+               if ((val & RFIFO_EMPTY) == 0)
+                       break;
+               usleep(10);
+       }
+
+       retval = target_read_u32(target,
+                       mrvlqspi_get_reg(bank, DIN), &val);
+       if (retval != ERROR_OK)
+               return retval;
+
+       *data = val & 0xFF;
+
+       return ERROR_OK;
+}
+
+static int mrvlqspi_flash_busy_status(struct flash_bank *bank, int timeout)
+{
+       uint8_t val;
+       int retval;
+
+       /* Flush read/write fifo's */
+       retval = mrvlqspi_fifo_flush(bank, FIFO_FLUSH_TIMEOUT);
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* Set instruction/addr count value */
+       retval = mrvlqspi_set_hdr_cnt(bank, 0x1);
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* Read flash status register in continuous manner */
+       retval = mrvlqspi_set_din_cnt(bank, 0x0);
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* Set instruction */
+       retval = mrvlqspi_set_instr(bank, INS_READ_STATUS);
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* Set data and addr pin length */
+       retval = mrvlqspi_set_conf(bank, 0x0);
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* Enable read mode transfer */
+       retval = mrvlqspi_start_transfer(bank, QSPI_R_EN);
+       if (retval != ERROR_OK)
+               return retval;
+
+       for (;;) {
+               retval = mrvlqspi_read_byte(bank, &val);
+               if (retval != ERROR_OK)
+                       return retval;
+               if (!(val & 0x1))
+                       break;
+               if (timeout-- <= 0) {
+                       LOG_ERROR("timed out waiting for flash");
+                       return ERROR_FAIL;
+               }
+               alive_sleep(1);
+       }
+
+       return mrvlqspi_stop_transfer(bank);
+}
+
+static int mrvlqspi_set_write_status(struct flash_bank *bank, bool mode)
+{
+       int retval;
+       uint32_t instr;
+
+       /* Flush read/write fifo's */
+       retval = mrvlqspi_fifo_flush(bank, FIFO_FLUSH_TIMEOUT);
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* Set instruction/addr count value */
+       retval = mrvlqspi_set_hdr_cnt(bank, 0x1);
+       if (retval != ERROR_OK)
+               return retval;
+
+       if (mode)
+               instr = INS_WRITE_ENABLE;
+       else
+               instr = INS_WRITE_DISABLE;
+
+       /* Set instruction */
+       retval = mrvlqspi_set_instr(bank, instr);
+       if (retval != ERROR_OK)
+               return retval;
+
+       retval = mrvlqspi_start_transfer(bank, QSPI_W_EN);
+       if (retval != ERROR_OK)
+               return retval;
+
+       retval = mrvlqspi_stop_transfer(bank);
+       if (retval != ERROR_OK)
+               return retval;
+
+       return retval;
+}
+
+static int mrvlqspi_read_id(struct flash_bank *bank, uint32_t *id)
+{
+       uint8_t id_buf[3] = {0, 0, 0};
+       int retval, i;
+
+       LOG_DEBUG("Getting ID");
+
+       /* Flush read/write fifo's */
+       retval = mrvlqspi_fifo_flush(bank, FIFO_FLUSH_TIMEOUT);
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* Set instruction/addr count value */
+       retval = mrvlqspi_set_hdr_cnt(bank, 0x1);
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* Set count for number of bytes to read */
+       retval = mrvlqspi_set_din_cnt(bank, 0x3);
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* Set instruction */
+       retval = mrvlqspi_set_instr(bank, SPIFLASH_READ_ID);
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* Set data and addr pin length */
+       retval = mrvlqspi_set_conf(bank, 0x0);
+       if (retval != ERROR_OK)
+               return retval;
+
+       retval = mrvlqspi_start_transfer(bank, QSPI_R_EN);
+       if (retval != ERROR_OK)
+               return retval;
+
+       for (i = 0; i < 3; i++) {
+               retval = mrvlqspi_read_byte(bank, &id_buf[i]);
+               if (retval != ERROR_OK)
+                       return retval;
+       }
+
+       LOG_DEBUG("ID is 0x%x 0x%x 0x%x", id_buf[0], id_buf[1], id_buf[2]);
+       retval = mrvlqspi_set_ss_state(bank, QSPI_SS_DISABLE, QSPI_TIMEOUT);
+       if (retval != ERROR_OK)
+               return retval;
+
+       *id = id_buf[2] << 16 | id_buf[1] << 8 | id_buf[0];
+       return ERROR_OK;
+}
+
+static int mrvlqspi_block_erase(struct flash_bank *bank, uint32_t offset)
+{
+       int retval;
+       struct mrvlqspi_flash_bank *mrvlqspi_info = bank->driver_priv;
+
+       /* Set flash write enable */
+       retval = mrvlqspi_set_write_status(bank, WRITE_ENABLE);
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* Set instruction/addr count value */
+       retval = mrvlqspi_set_hdr_cnt(bank, (0x1 | (0x3 << 4)));
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* Set read offset address */
+       retval = mrvlqspi_set_addr(bank, offset);
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* Set instruction */
+       retval = mrvlqspi_set_instr(bank, mrvlqspi_info->dev->erase_cmd);
+       if (retval != ERROR_OK)
+               return retval;
+
+       retval = mrvlqspi_start_transfer(bank, QSPI_W_EN);
+       if (retval != ERROR_OK)
+               return retval;
+
+       retval = mrvlqspi_stop_transfer(bank);
+       if (retval != ERROR_OK)
+               return retval;
+
+       return mrvlqspi_flash_busy_status(bank, BLOCK_ERASE_TIMEOUT);
+}
+
+static int mrvlqspi_bulk_erase(struct flash_bank *bank)
+{
+       int retval;
+       struct mrvlqspi_flash_bank *mrvlqspi_info = bank->driver_priv;
+
+       /* Set flash write enable */
+       retval = mrvlqspi_set_write_status(bank, WRITE_ENABLE);
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* Set instruction */
+       retval = mrvlqspi_set_instr(bank, mrvlqspi_info->dev->chip_erase_cmd);
+       if (retval != ERROR_OK)
+               return retval;
+
+       retval = mrvlqspi_start_transfer(bank, QSPI_W_EN);
+       if (retval != ERROR_OK)
+               return retval;
+
+       retval = mrvlqspi_stop_transfer(bank);
+       if (retval != ERROR_OK)
+               return retval;
+
+       return mrvlqspi_flash_busy_status(bank, CHIP_ERASE_TIMEOUT);
+}
+
+static int mrvlqspi_flash_erase(struct flash_bank *bank, int first, int last)
+{
+       struct target *target = bank->target;
+       struct mrvlqspi_flash_bank *mrvlqspi_info = bank->driver_priv;
+       int retval = ERROR_OK;
+       int sector;
+
+       LOG_DEBUG("erase from sector %d to sector %d", first, last);
+
+       if (target->state != TARGET_HALTED) {
+               LOG_ERROR("Target not halted");
+               return ERROR_TARGET_NOT_HALTED;
+       }
+
+       if ((first < 0) || (last < first) || (last >= bank->num_sectors)) {
+               LOG_ERROR("Flash sector invalid");
+               return ERROR_FLASH_SECTOR_INVALID;
+       }
+
+       if (!(mrvlqspi_info->probed)) {
+               LOG_ERROR("Flash bank not probed");
+               return ERROR_FLASH_BANK_NOT_PROBED;
+       }
+
+       for (sector = first; sector <= last; sector++) {
+               if (bank->sectors[sector].is_protected) {
+                       LOG_ERROR("Flash sector %d protected", sector);
+                       return ERROR_FAIL;
+               }
+       }
+
+       /* If we're erasing the entire chip and the flash supports
+        * it, use a bulk erase instead of going sector-by-sector. */
+       if (first == 0 && last == (bank->num_sectors - 1)
+               && mrvlqspi_info->dev->chip_erase_cmd !=
+                                       mrvlqspi_info->dev->erase_cmd) {
+               LOG_DEBUG("Chip supports the bulk erase command."\
+               " Will use bulk erase instead of sector-by-sector erase.");
+               retval = mrvlqspi_bulk_erase(bank);
+               if (retval == ERROR_OK) {
+                       return retval;
+               } else
+                       LOG_WARNING("Bulk flash erase failed."
+                               " Falling back to sector-by-sector erase.");
+       }
+
+       for (sector = first; sector <= last; sector++) {
+               retval = mrvlqspi_block_erase(bank,
+                               sector * mrvlqspi_info->dev->sectorsize);
+               if (retval != ERROR_OK)
+                       return retval;
+       }
+
+       return retval;
+}
+
+static int mrvlqspi_flash_write(struct flash_bank *bank, const uint8_t *buffer,
+       uint32_t offset, uint32_t count)
+{
+       struct target *target = bank->target;
+       struct mrvlqspi_flash_bank *mrvlqspi_info = bank->driver_priv;
+       int retval = ERROR_OK;
+       uint32_t page_size, fifo_size;
+       struct working_area *fifo;
+       struct reg_param reg_params[6];
+       struct armv7m_algorithm armv7m_info;
+       struct working_area *write_algorithm;
+       int sector;
+
+       LOG_DEBUG("offset=0x%08" PRIx32 " count=0x%08" PRIx32,
+               offset, count);
+
+       if (target->state != TARGET_HALTED) {
+               LOG_ERROR("Target not halted");
+               return ERROR_TARGET_NOT_HALTED;
+       }
+
+       if (offset + count > mrvlqspi_info->dev->size_in_bytes) {
+               LOG_WARNING("Writes past end of flash. Extra data discarded.");
+               count = mrvlqspi_info->dev->size_in_bytes - offset;
+       }
+
+       /* Check sector protection */
+       for (sector = 0; sector < bank->num_sectors; sector++) {
+               /* Start offset in or before this sector? */
+               /* End offset in or behind this sector? */
+               if ((offset <
+                       (bank->sectors[sector].offset + bank->sectors[sector].size))
+                       && ((offset + count - 1) >= bank->sectors[sector].offset)
+                       && bank->sectors[sector].is_protected) {
+                       LOG_ERROR("Flash sector %d protected", sector);
+                       return ERROR_FAIL;
+               }
+       }
+
+       page_size = mrvlqspi_info->dev->pagesize;
+
+       /* See contrib/loaders/flash/mrvlqspi.S for src */
+       static const uint8_t mrvlqspi_flash_write_code[] = {
+               0x4f, 0xf0, 0x00, 0x0a, 0xa2, 0x44, 0x92, 0x45,
+               0x7f, 0xf6, 0xfc, 0xaf, 0x00, 0xf0, 0x6b, 0xf8,
+               0x5f, 0xf0, 0x01, 0x08, 0xc5, 0xf8, 0x1c, 0x80,
+               0x5f, 0xf0, 0x06, 0x08, 0xc5, 0xf8, 0x10, 0x80,
+               0x5f, 0xf0, 0x01, 0x09, 0x00, 0xf0, 0x6b, 0xf8,
+               0x00, 0xf0, 0x7d, 0xf8, 0x5f, 0xf0, 0x31, 0x08,
+               0xc5, 0xf8, 0x1c, 0x80, 0x90, 0x46, 0xc5, 0xf8,
+               0x14, 0x80, 0x5f, 0xf0, 0x02, 0x08, 0xc5, 0xf8,
+               0x10, 0x80, 0x5f, 0xf0, 0x01, 0x09, 0x00, 0xf0,
+               0x5a, 0xf8, 0xd0, 0xf8, 0x00, 0x80, 0xb8, 0xf1,
+               0x00, 0x0f, 0x00, 0xf0, 0x8b, 0x80, 0x47, 0x68,
+               0x47, 0x45, 0x3f, 0xf4, 0xf6, 0xaf, 0x17, 0xf8,
+               0x01, 0x9b, 0x00, 0xf0, 0x30, 0xf8, 0x8f, 0x42,
+               0x28, 0xbf, 0x00, 0xf1, 0x08, 0x07, 0x47, 0x60,
+               0x01, 0x3b, 0x00, 0x2b, 0x00, 0xf0, 0x05, 0x80,
+               0x02, 0xf1, 0x01, 0x02, 0x92, 0x45, 0x7f, 0xf4,
+               0xe4, 0xaf, 0x00, 0xf0, 0x50, 0xf8, 0xa2, 0x44,
+               0x00, 0xf0, 0x2d, 0xf8, 0x5f, 0xf0, 0x01, 0x08,
+               0xc5, 0xf8, 0x1c, 0x80, 0x5f, 0xf0, 0x00, 0x08,
+               0xc5, 0xf8, 0x20, 0x80, 0x5f, 0xf0, 0x05, 0x08,
+               0xc5, 0xf8, 0x10, 0x80, 0x5f, 0xf0, 0x00, 0x09,
+               0x00, 0xf0, 0x29, 0xf8, 0x00, 0xf0, 0x13, 0xf8,
+               0x09, 0xf0, 0x01, 0x09, 0xb9, 0xf1, 0x00, 0x0f,
+               0xf8, 0xd1, 0x00, 0xf0, 0x34, 0xf8, 0x00, 0x2b,
+               0xa4, 0xd1, 0x00, 0xf0, 0x53, 0xb8, 0xd5, 0xf8,
+               0x00, 0x80, 0x5f, 0xea, 0x08, 0x68, 0xfa, 0xd4,
+               0xc5, 0xf8, 0x08, 0x90, 0x70, 0x47, 0xd5, 0xf8,
+               0x00, 0x80, 0x5f, 0xea, 0xc8, 0x68, 0xfa, 0xd4,
+               0xd5, 0xf8, 0x0c, 0x90, 0x70, 0x47, 0xd5, 0xf8,
+               0x04, 0x80, 0x48, 0xf4, 0x00, 0x78, 0xc5, 0xf8,
+               0x04, 0x80, 0xd5, 0xf8, 0x04, 0x80, 0x5f, 0xea,
+               0x88, 0x58, 0xfa, 0xd4, 0x70, 0x47, 0xd5, 0xf8,
+               0x00, 0x80, 0x48, 0xf0, 0x01, 0x08, 0xc5, 0xf8,
+               0x00, 0x80, 0xd5, 0xf8, 0x00, 0x80, 0x5f, 0xea,
+               0x88, 0x78, 0xfa, 0xd5, 0xd5, 0xf8, 0x04, 0x80,
+               0x69, 0xf3, 0x4d, 0x38, 0x48, 0xf4, 0x00, 0x48,
+               0xc5, 0xf8, 0x04, 0x80, 0x70, 0x47, 0xd5, 0xf8,
+               0x00, 0x80, 0x5f, 0xea, 0x88, 0x78, 0xfa, 0xd5,
+               0xd5, 0xf8, 0x00, 0x80, 0x5f, 0xea, 0x48, 0x68,
+               0xfa, 0xd5, 0xd5, 0xf8, 0x04, 0x80, 0x48, 0xf4,
+               0x80, 0x48, 0xc5, 0xf8, 0x04, 0x80, 0xd5, 0xf8,
+               0x04, 0x80, 0x5f, 0xea, 0x08, 0x48, 0xfa, 0xd4,
+               0xd5, 0xf8, 0x00, 0x80, 0x28, 0xf0, 0x01, 0x08,
+               0xc5, 0xf8, 0x00, 0x80, 0xd5, 0xf8, 0x00, 0x80,
+               0x5f, 0xea, 0x88, 0x78, 0xfa, 0xd5, 0x70, 0x47,
+               0x00, 0x20, 0x50, 0x60, 0x30, 0x46, 0x00, 0xbe
+       };
+
+       if (target_alloc_working_area(target, sizeof(mrvlqspi_flash_write_code),
+                       &write_algorithm) != ERROR_OK) {
+               LOG_ERROR("Insufficient working area. You must configure"\
+                       " a working area > %zdB in order to write to SPIFI flash.",
+                       sizeof(mrvlqspi_flash_write_code));
+               return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+       };
+
+       retval = target_write_buffer(target, write_algorithm->address,
+                       sizeof(mrvlqspi_flash_write_code),
+                       mrvlqspi_flash_write_code);
+       if (retval != ERROR_OK) {
+               target_free_working_area(target, write_algorithm);
+               return retval;
+       }
+
+       /* FIFO allocation */
+       fifo_size = target_get_working_area_avail(target);
+
+       if (fifo_size == 0) {
+               /* if we already allocated the writing code but failed to get fifo
+                * space, free the algorithm */
+               target_free_working_area(target, write_algorithm);
+
+               LOG_ERROR("Insufficient working area. Please allocate at least"\
+                       " %zdB of working area to enable flash writes.",
+                       sizeof(mrvlqspi_flash_write_code) + 1
+               );
+
+               return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+       } else if (fifo_size < page_size)
+               LOG_WARNING("Working area size is limited; flash writes may be"\
+                       " slow. Increase working area size to at least %zdB"\
+                       " to reduce write times.",
+                       (size_t)(sizeof(mrvlqspi_flash_write_code) + page_size)
+               );
+
+       if (target_alloc_working_area(target, fifo_size, &fifo) != ERROR_OK) {
+               target_free_working_area(target, write_algorithm);
+               return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+       };
+
+       armv7m_info.common_magic = ARMV7M_COMMON_MAGIC;
+       armv7m_info.core_mode = ARM_MODE_THREAD;
+
+       init_reg_param(&reg_params[0], "r0", 32, PARAM_IN_OUT); /* buffer start, status (out) */
+       init_reg_param(&reg_params[1], "r1", 32, PARAM_OUT);    /* buffer end */
+       init_reg_param(&reg_params[2], "r2", 32, PARAM_OUT);    /* target address */
+       init_reg_param(&reg_params[3], "r3", 32, PARAM_OUT);    /* count (halfword-16bit) */
+       init_reg_param(&reg_params[4], "r4", 32, PARAM_OUT);    /* page size */
+       init_reg_param(&reg_params[5], "r5", 32, PARAM_OUT);    /* qspi base address */
+
+       buf_set_u32(reg_params[0].value, 0, 32, fifo->address);
+       buf_set_u32(reg_params[1].value, 0, 32, fifo->address + fifo->size);
+       buf_set_u32(reg_params[2].value, 0, 32, offset);
+       buf_set_u32(reg_params[3].value, 0, 32, count);
+       buf_set_u32(reg_params[4].value, 0, 32, page_size);
+       buf_set_u32(reg_params[5].value, 0, 32, (uint32_t) mrvlqspi_info->reg_base);
+
+       retval = target_run_flash_async_algorithm(target, buffer, count, 1,
+                       0, NULL,
+                       6, reg_params,
+                       fifo->address, fifo->size,
+                       write_algorithm->address, 0,
+                       &armv7m_info
+       );
+
+       if (retval != ERROR_OK)
+               LOG_ERROR("Error executing flash write algorithm");
+
+       target_free_working_area(target, fifo);
+       target_free_working_area(target, write_algorithm);
+
+       destroy_reg_param(&reg_params[0]);
+       destroy_reg_param(&reg_params[1]);
+       destroy_reg_param(&reg_params[2]);
+       destroy_reg_param(&reg_params[3]);
+       destroy_reg_param(&reg_params[4]);
+       destroy_reg_param(&reg_params[5]);
+
+       return retval;
+}
+
+int mrvlqspi_flash_read(struct flash_bank *bank, uint8_t *buffer,
+                               uint32_t offset, uint32_t count)
+{
+       struct target *target = bank->target;
+       struct mrvlqspi_flash_bank *mrvlqspi_info = bank->driver_priv;
+       int retval;
+       uint32_t i;
+
+       if (target->state != TARGET_HALTED) {
+               LOG_ERROR("Target not halted");
+               return ERROR_TARGET_NOT_HALTED;
+       }
+
+       if (!(mrvlqspi_info->probed)) {
+               LOG_ERROR("Flash bank not probed");
+               return ERROR_FLASH_BANK_NOT_PROBED;
+       }
+
+       /* Flush read/write fifo's */
+       retval = mrvlqspi_fifo_flush(bank, FIFO_FLUSH_TIMEOUT);
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* Set instruction/addr count value */
+       retval = mrvlqspi_set_hdr_cnt(bank, (0x1 | (0x3 << 4)));
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* Set count for number of bytes to read */
+       retval = mrvlqspi_set_din_cnt(bank, count);
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* Set read address */
+       retval = mrvlqspi_set_addr(bank, offset);
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* Set instruction */
+       retval = mrvlqspi_set_instr(bank, SPIFLASH_READ);
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* Set data and addr pin length */
+       retval = mrvlqspi_set_conf(bank, 0x0);
+       if (retval != ERROR_OK)
+               return retval;
+
+       retval = mrvlqspi_start_transfer(bank, QSPI_R_EN);
+       if (retval != ERROR_OK)
+               return retval;
+
+       for (i = 0; i < count; i++) {
+               retval = mrvlqspi_read_byte(bank, &buffer[i]);
+               if (retval != ERROR_OK)
+                       return retval;
+       }
+
+       retval = mrvlqspi_set_ss_state(bank, QSPI_SS_DISABLE, QSPI_TIMEOUT);
+       if (retval != ERROR_OK)
+               return retval;
+
+       return ERROR_OK;
+}
+
+static int mrvlqspi_probe(struct flash_bank *bank)
+{
+       struct target *target = bank->target;
+       struct mrvlqspi_flash_bank *mrvlqspi_info = bank->driver_priv;
+       uint32_t id = 0;
+       int retval;
+       struct flash_sector *sectors;
+
+       /* If we've already probed, we should be fine to skip this time. */
+       if (mrvlqspi_info->probed)
+               return ERROR_OK;
+
+       if (target->state != TARGET_HALTED) {
+               LOG_ERROR("Target not halted");
+               return ERROR_TARGET_NOT_HALTED;
+       }
+
+       mrvlqspi_info->probed = 0;
+       mrvlqspi_info->bank_num = bank->bank_number;
+
+       /* Read flash JEDEC ID */
+       retval = mrvlqspi_read_id(bank, &id);
+       if (retval != ERROR_OK)
+               return retval;
+
+       mrvlqspi_info->dev = NULL;
+       for (const struct flash_device *p = flash_devices; p->name ; p++)
+               if (p->device_id == id) {
+                       mrvlqspi_info->dev = p;
+                       break;
+               }
+
+       if (!mrvlqspi_info->dev) {
+               LOG_ERROR("Unknown flash device ID 0x%08x", id);
+               return ERROR_FAIL;
+       }
+
+       LOG_INFO("Found flash device \'%s\' ID 0x%08x",
+               mrvlqspi_info->dev->name, mrvlqspi_info->dev->device_id);
+
+       /* Set correct size value */
+       bank->size = mrvlqspi_info->dev->size_in_bytes;
+
+       /* create and fill sectors array */
+       bank->num_sectors = mrvlqspi_info->dev->size_in_bytes /
+                                       mrvlqspi_info->dev->sectorsize;
+       sectors = malloc(sizeof(struct flash_sector) * bank->num_sectors);
+       if (sectors == NULL) {
+               LOG_ERROR("not enough memory");
+               return ERROR_FAIL;
+       }
+
+       for (int sector = 0; sector < bank->num_sectors; sector++) {
+               sectors[sector].offset =
+                               sector * mrvlqspi_info->dev->sectorsize;
+               sectors[sector].size = mrvlqspi_info->dev->sectorsize;
+               sectors[sector].is_erased = -1;
+               sectors[sector].is_protected = 0;
+       }
+
+       bank->sectors = sectors;
+       mrvlqspi_info->probed = 1;
+
+       return ERROR_OK;
+}
+
+static int mrvlqspi_auto_probe(struct flash_bank *bank)
+{
+       struct mrvlqspi_flash_bank *mrvlqspi_info = bank->driver_priv;
+       if (mrvlqspi_info->probed)
+               return ERROR_OK;
+       return mrvlqspi_probe(bank);
+}
+
+static int mrvlqspi_flash_erase_check(struct flash_bank *bank)
+{
+       /* Not implemented yet */
+       return ERROR_OK;
+}
+
+static int mrvlqspi_protect_check(struct flash_bank *bank)
+{
+       /* Not implemented yet */
+       return ERROR_OK;
+}
+
+int mrvlqspi_get_info(struct flash_bank *bank, char *buf, int buf_size)
+{
+       struct mrvlqspi_flash_bank *mrvlqspi_info = bank->driver_priv;
+
+       if (!(mrvlqspi_info->probed)) {
+               snprintf(buf, buf_size,
+                       "\nQSPI flash bank not probed yet\n");
+               return ERROR_OK;
+       }
+
+       snprintf(buf, buf_size, "\nQSPI flash information:\n"
+               "  Device \'%s\' ID 0x%08x\n",
+               mrvlqspi_info->dev->name, mrvlqspi_info->dev->device_id);
+
+       return ERROR_OK;
+}
+
+FLASH_BANK_COMMAND_HANDLER(mrvlqspi_flash_bank_command)
+{
+       struct mrvlqspi_flash_bank *mrvlqspi_info;
+
+       if (CMD_ARGC < 7)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       mrvlqspi_info = malloc(sizeof(struct mrvlqspi_flash_bank));
+       if (mrvlqspi_info == NULL) {
+               LOG_ERROR("not enough memory");
+               return ERROR_FAIL;
+       }
+
+       /* Get QSPI controller register map base address */
+       COMMAND_PARSE_NUMBER(u32, CMD_ARGV[6], mrvlqspi_info->reg_base);
+       bank->driver_priv = mrvlqspi_info;
+       mrvlqspi_info->probed = 0;
+
+       return ERROR_OK;
+}
+
+struct flash_driver mrvlqspi_flash = {
+       .name = "mrvlqspi",
+       .flash_bank_command = mrvlqspi_flash_bank_command,
+       .erase = mrvlqspi_flash_erase,
+       .protect = NULL,
+       .write = mrvlqspi_flash_write,
+       .read = mrvlqspi_flash_read,
+       .probe = mrvlqspi_probe,
+       .auto_probe = mrvlqspi_auto_probe,
+       .erase_check = mrvlqspi_flash_erase_check,
+       .protect_check = mrvlqspi_protect_check,
+       .info = mrvlqspi_get_info,
+};