From 447fb25324f1e9112523ef78825f8dadf3c7ddb7 Mon Sep 17 00:00:00 2001 From: Mahavir Jain Date: Thu, 4 Sep 2014 15:31:16 +0530 Subject: [PATCH] flash/nor: add mrvlqspi flash controller driver 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 Reviewed-on: http://openocd.zylin.com/2280 Tested-by: jenkins Reviewed-by: Spencer Oliver --- contrib/loaders/flash/mrvlqspi_write.S | 232 ++++++ src/flash/nor/Makefile.am | 3 +- src/flash/nor/drivers.c | 2 + src/flash/nor/mrvlqspi.c | 960 +++++++++++++++++++++++++ 4 files changed, 1196 insertions(+), 1 deletion(-) create mode 100644 contrib/loaders/flash/mrvlqspi_write.S create mode 100644 src/flash/nor/mrvlqspi.c diff --git a/contrib/loaders/flash/mrvlqspi_write.S b/contrib/loaders/flash/mrvlqspi_write.S new file mode 100644 index 0000000000..064192c9c1 --- /dev/null +++ b/contrib/loaders/flash/mrvlqspi_write.S @@ -0,0 +1,232 @@ +/*************************************************************************** + * Copyright (C) 2014 by Mahavir Jain * + * * + * 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 diff --git a/src/flash/nor/Makefile.am b/src/flash/nor/Makefile.am index 316814738d..bae42fd5df 100644 --- a/src/flash/nor/Makefile.am +++ b/src/flash/nor/Makefile.am @@ -43,7 +43,8 @@ NOR_DRIVERS = \ kinetis.c \ mini51.c \ nuc1x.c \ - nrf51.c + nrf51.c \ + mrvlqspi.c noinst_HEADERS = \ core.h \ diff --git a/src/flash/nor/drivers.c b/src/flash/nor/drivers.c index ed631a3b0b..8959f0cad7 100644 --- a/src/flash/nor/drivers.c +++ b/src/flash/nor/drivers.c @@ -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 index 0000000000..a5cc1ca025 --- /dev/null +++ b/src/flash/nor/mrvlqspi.c @@ -0,0 +1,960 @@ +/*************************************************************************** + * Copyright (C) 2014 by Mahavir Jain * + * * + * 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 +#include +#include + +#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), ®val); + 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), ®val); + 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), ®val); + 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), ®val); + 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), ®val); + 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), ®val); + 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), ®val); + 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(®_params[0], "r0", 32, PARAM_IN_OUT); /* buffer start, status (out) */ + init_reg_param(®_params[1], "r1", 32, PARAM_OUT); /* buffer end */ + init_reg_param(®_params[2], "r2", 32, PARAM_OUT); /* target address */ + init_reg_param(®_params[3], "r3", 32, PARAM_OUT); /* count (halfword-16bit) */ + init_reg_param(®_params[4], "r4", 32, PARAM_OUT); /* page size */ + init_reg_param(®_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(®_params[0]); + destroy_reg_param(®_params[1]); + destroy_reg_param(®_params[2]); + destroy_reg_param(®_params[3]); + destroy_reg_param(®_params[4]); + destroy_reg_param(®_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, +}; -- 2.30.2