summary refs log tree commit diff
path: root/drivers
diff options
context:
space:
mode:
authorJoy Lee <chang.li@westberrytech.com>2022-02-12 04:26:16 +0800
committerGitHub <noreply@github.com>2022-02-11 20:26:16 +0000
commit71c0b97bced7722abaa0adefd57066cc065538b5 (patch)
tree6f50c8a53fb7a7faae949154351be658ce5144e8 /drivers
parent00cc64638c7ffa90b1acc10c420daaa3795a87ba (diff)
Added external spi flash driver. (#15419)
Diffstat (limited to 'drivers')
-rw-r--r--drivers/flash/flash_spi.c372
-rw-r--r--drivers/flash/flash_spi.h136
2 files changed, 508 insertions, 0 deletions
diff --git a/drivers/flash/flash_spi.c b/drivers/flash/flash_spi.c
new file mode 100644
index 0000000000..2fa8891e38
--- /dev/null
+++ b/drivers/flash/flash_spi.c
@@ -0,0 +1,372 @@
+/*
+Copyright (C) 2021 Westberry Technology (ChangZhou) Corp., Ltd
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <string.h>
+
+#include "util.h"
+#include "wait.h"
+#include "debug.h"
+#include "timer.h"
+#include "flash_spi.h"
+#include "spi_master.h"
+
+/*
+    The time-out time of spi flash transmission.
+*/
+#ifndef EXTERNAL_FLASH_SPI_TIMEOUT
+#    define EXTERNAL_FLASH_SPI_TIMEOUT 1000
+#endif
+
+/* ID comands */
+#define FLASH_CMD_RDID 0x9F /* RDID (Read Identification) */
+#define FLASH_CMD_RES 0xAB  /* RES (Read Electronic ID) */
+#define FLASH_CMD_REMS 0x90 /* REMS (Read Electronic & Device ID) */
+
+/* register comands */
+#define FLASH_CMD_WRSR 0x01 /* WRSR (Write Status register) */
+#define FLASH_CMD_RDSR 0x05 /* RDSR (Read Status register) */
+
+/* READ comands */
+#define FLASH_CMD_READ 0x03     /* READ (1 x I/O) */
+#define FLASH_CMD_FASTREAD 0x0B /* FAST READ (Fast read data) */
+#define FLASH_CMD_DREAD 0x3B    /* DREAD (1In/2 Out fast read) */
+
+/* Program comands */
+#define FLASH_CMD_WREN 0x06 /* WREN (Write Enable) */
+#define FLASH_CMD_WRDI 0x04 /* WRDI (Write Disable) */
+#define FLASH_CMD_PP 0x02   /* PP (page program) */
+
+/* Erase comands */
+#define FLASH_CMD_SE 0x20 /* SE (Sector Erase) */
+#define FLASH_CMD_BE 0xD8 /* BE (Block Erase) */
+#define FLASH_CMD_CE 0x60 /* CE (Chip Erase) hex code: 60 or C7 */
+
+/* Mode setting comands */
+#define FLASH_CMD_DP 0xB9  /* DP (Deep Power Down) */
+#define FLASH_CMD_RDP 0xAB /* RDP (Release form Deep Power Down) */
+
+/* Status register */
+#define FLASH_FLAG_WIP 0x01 /* Write in progress bit */
+#define FLASH_FLAG_WEL 0x02 /* Write enable latch bit */
+
+// #define DEBUG_FLASH_SPI_OUTPUT
+
+static bool spi_flash_start(void) { return spi_start(EXTERNAL_FLASH_SPI_SLAVE_SELECT_PIN, EXTERNAL_FLASH_SPI_LSBFIRST, EXTERNAL_FLASH_SPI_MODE, EXTERNAL_FLASH_SPI_CLOCK_DIVISOR); }
+
+static flash_status_t spi_flash_wait_while_busy(void) {
+    uint32_t       deadline = timer_read32() + EXTERNAL_FLASH_SPI_TIMEOUT;
+    flash_status_t response = FLASH_STATUS_SUCCESS;
+    uint8_t        retval;
+
+    do {
+        bool res = spi_flash_start();
+        if (!res) {
+            dprint("Failed to start SPI! [spi flash wait while busy]\n");
+            return FLASH_STATUS_ERROR;
+        }
+
+        spi_write(FLASH_CMD_RDSR);
+
+        retval = (uint8_t)spi_read();
+
+        spi_stop();
+
+        if (timer_read32() >= deadline) {
+            response = FLASH_STATUS_TIMEOUT;
+            break;
+        }
+    } while (retval & FLASH_FLAG_WIP);
+
+    return response;
+}
+
+static flash_status_t spi_flash_write_enable(void) {
+    bool res = spi_flash_start();
+    if (!res) {
+        dprint("Failed to start SPI! [spi flash write enable]\n");
+        return FLASH_STATUS_ERROR;
+    }
+
+    spi_write(FLASH_CMD_WREN);
+
+    spi_stop();
+
+    return FLASH_STATUS_SUCCESS;
+}
+
+static flash_status_t spi_flash_write_disable(void) {
+    bool res = spi_flash_start();
+    if (!res) {
+        dprint("Failed to start SPI! [spi flash write disable]\n");
+        return FLASH_STATUS_ERROR;
+    }
+
+    spi_write(FLASH_CMD_WRDI);
+
+    spi_stop();
+
+    return FLASH_STATUS_SUCCESS;
+}
+
+/* This function is used for read transfer, write transfer and erase transfer. */
+static flash_status_t spi_flash_transaction(uint8_t cmd, uint32_t addr, uint8_t *data, size_t len) {
+    flash_status_t response = FLASH_STATUS_SUCCESS;
+    uint8_t        buffer[EXTERNAL_FLASH_ADDRESS_SIZE + 1];
+
+    buffer[0] = cmd;
+    for (int i = 0; i < EXTERNAL_FLASH_ADDRESS_SIZE; ++i) {
+        buffer[EXTERNAL_FLASH_ADDRESS_SIZE - i] = addr & 0xFF;
+        addr >>= 8;
+    }
+
+    bool res = spi_flash_start();
+    if (!res) {
+        dprint("Failed to start SPI! [spi flash transmit]\n");
+        return FLASH_STATUS_ERROR;
+    }
+
+    response = spi_transmit(buffer, sizeof(buffer));
+
+    if ((!response) && (data != NULL)) {
+        switch (cmd) {
+            case FLASH_CMD_READ:
+                response = spi_receive(data, len);
+                break;
+            case FLASH_CMD_PP:
+                response = spi_transmit(data, len);
+                break;
+            default:
+                response = FLASH_STATUS_ERROR;
+                break;
+        }
+    }
+
+    spi_stop();
+
+    return response;
+}
+
+void flash_init(void) { spi_init(); }
+
+flash_status_t flash_erase_chip(void) {
+    flash_status_t response = FLASH_STATUS_SUCCESS;
+
+    /* Wait for the write-in-progress bit to be cleared. */
+    response = spi_flash_wait_while_busy();
+    if (response != FLASH_STATUS_SUCCESS) {
+        dprint("Failed to check WIP flag! [spi flash erase chip]\n");
+        return response;
+    }
+
+    /* Enable writes. */
+    response = spi_flash_write_enable();
+    if (response != FLASH_STATUS_SUCCESS) {
+        dprint("Failed to write-enable! [spi flash erase chip]\n");
+        return response;
+    }
+
+    /* Erase Chip. */
+    bool res = spi_flash_start();
+    if (!res) {
+        dprint("Failed to start SPI! [spi flash erase chip]\n");
+        return FLASH_STATUS_ERROR;
+    }
+    spi_write(FLASH_CMD_CE);
+    spi_stop();
+
+    /* Wait for the write-in-progress bit to be cleared.*/
+    response = spi_flash_wait_while_busy();
+    if (response != FLASH_STATUS_SUCCESS) {
+        dprint("Failed to check WIP flag! [spi flash erase chip]\n");
+        return response;
+    }
+
+    return response;
+}
+
+flash_status_t flash_erase_sector(uint32_t addr) {
+    flash_status_t response = FLASH_STATUS_SUCCESS;
+
+    /* Check that the address exceeds the limit. */
+    if ((addr + (EXTERNAL_FLASH_SECTOR_SIZE)) >= (EXTERNAL_FLASH_SIZE) || ((addr % (EXTERNAL_FLASH_SECTOR_SIZE)) != 0)) {
+        dprintf("Flash erase sector address over limit! [addr:0x%x]\n", (uint32_t)addr);
+        return FLASH_STATUS_ERROR;
+    }
+
+    /* Wait for the write-in-progress bit to be cleared. */
+    response = spi_flash_wait_while_busy();
+    if (response != FLASH_STATUS_SUCCESS) {
+        dprint("Failed to check WIP flag! [spi flash erase sector]\n");
+        return response;
+    }
+
+    /* Enable writes. */
+    response = spi_flash_write_enable();
+    if (response != FLASH_STATUS_SUCCESS) {
+        dprint("Failed to write-enable! [spi flash erase sector]\n");
+        return response;
+    }
+
+    /* Erase Sector. */
+    response = spi_flash_transaction(FLASH_CMD_SE, addr, NULL, 0);
+    if (response != FLASH_STATUS_SUCCESS) {
+        dprint("Failed to erase sector! [spi flash erase sector]\n");
+        return response;
+    }
+
+    /* Wait for the write-in-progress bit to be cleared.*/
+    response = spi_flash_wait_while_busy();
+    if (response != FLASH_STATUS_SUCCESS) {
+        dprint("Failed to check WIP flag! [spi flash erase sector]\n");
+        return response;
+    }
+
+    return response;
+}
+
+flash_status_t flash_erase_block(uint32_t addr) {
+    flash_status_t response = FLASH_STATUS_SUCCESS;
+
+    /* Check that the address exceeds the limit. */
+    if ((addr + (EXTERNAL_FLASH_BLOCK_SIZE)) >= (EXTERNAL_FLASH_SIZE) || ((addr % (EXTERNAL_FLASH_BLOCK_SIZE)) != 0)) {
+        dprintf("Flash erase block address over limit! [addr:0x%x]\n", (uint32_t)addr);
+        return FLASH_STATUS_ERROR;
+    }
+
+    /* Wait for the write-in-progress bit to be cleared. */
+    response = spi_flash_wait_while_busy();
+    if (response != FLASH_STATUS_SUCCESS) {
+        dprint("Failed to check WIP flag! [spi flash erase block]\n");
+        return response;
+    }
+
+    /* Enable writes. */
+    response = spi_flash_write_enable();
+    if (response != FLASH_STATUS_SUCCESS) {
+        dprint("Failed to write-enable! [spi flash erase block]\n");
+        return response;
+    }
+
+    /* Erase Block. */
+    response = spi_flash_transaction(FLASH_CMD_BE, addr, NULL, 0);
+    if (response != FLASH_STATUS_SUCCESS) {
+        dprint("Failed to erase block! [spi flash erase block]\n");
+        return response;
+    }
+
+    /* Wait for the write-in-progress bit to be cleared.*/
+    response = spi_flash_wait_while_busy();
+    if (response != FLASH_STATUS_SUCCESS) {
+        dprint("Failed to check WIP flag! [spi flash erase block]\n");
+        return response;
+    }
+
+    return response;
+}
+
+flash_status_t flash_read_block(uint32_t addr, void *buf, size_t len) {
+    flash_status_t response = FLASH_STATUS_SUCCESS;
+    uint8_t *      read_buf = (uint8_t *)buf;
+
+    /* Wait for the write-in-progress bit to be cleared. */
+    response = spi_flash_wait_while_busy();
+    if (response != FLASH_STATUS_SUCCESS) {
+        dprint("Failed to check WIP flag! [spi flash read block]\n");
+        memset(read_buf, 0, len);
+        return response;
+    }
+
+    /* Perform read. */
+    response = spi_flash_transaction(FLASH_CMD_READ, addr, read_buf, len);
+    if (response != FLASH_STATUS_SUCCESS) {
+        dprint("Failed to read block! [spi flash read block]\n");
+        memset(read_buf, 0, len);
+        return response;
+    }
+
+#if defined(CONSOLE_ENABLE) && defined(DEBUG_FLASH_SPI_OUTPUT)
+    dprintf("[SPI FLASH R] 0x%08lX: ", addr);
+    for (size_t i = 0; i < len; ++i) {
+        dprintf(" %02X", (int)(((uint8_t *)read_buf)[i]));
+    }
+    dprintf("\n");
+#endif  // DEBUG_FLASH_SPI_OUTPUT
+
+    return response;
+}
+
+flash_status_t flash_write_block(uint32_t addr, const void *buf, size_t len) {
+    flash_status_t response  = FLASH_STATUS_SUCCESS;
+    uint8_t *      write_buf = (uint8_t *)buf;
+
+    while (len > 0) {
+        uint32_t page_offset  = addr % EXTERNAL_FLASH_PAGE_SIZE;
+        size_t   write_length = EXTERNAL_FLASH_PAGE_SIZE - page_offset;
+        if (write_length > len) {
+            write_length = len;
+        }
+
+        /* Wait for the write-in-progress bit to be cleared. */
+        response = spi_flash_wait_while_busy();
+        if (response != FLASH_STATUS_SUCCESS) {
+            dprint("Failed to check WIP flag! [spi flash write block]\n");
+            return response;
+        }
+
+        /* Enable writes. */
+        response = spi_flash_write_enable();
+        if (response != FLASH_STATUS_SUCCESS) {
+            dprint("Failed to write-enable! [spi flash write block]\n");
+            return response;
+        }
+
+#if defined(CONSOLE_ENABLE) && defined(DEBUG_FLASH_SPI_OUTPUT)
+        dprintf("[SPI FLASH W] 0x%08lX: ", addr);
+        for (size_t i = 0; i < write_length; i++) {
+            dprintf(" %02X", (int)(uint8_t)(write_buf[i]));
+        }
+        dprintf("\n");
+#endif  // DEBUG_FLASH_SPI_OUTPUT
+
+        /* Perform the write. */
+        response = spi_flash_transaction(FLASH_CMD_PP, addr, write_buf, write_length);
+        if (response != FLASH_STATUS_SUCCESS) {
+            dprint("Failed to write block! [spi flash write block]\n");
+            return response;
+        }
+
+        write_buf += write_length;
+        addr += write_length;
+        len -= write_length;
+    }
+
+    /* Wait for the write-in-progress bit to be cleared. */
+    response = spi_flash_wait_while_busy();
+    if (response != FLASH_STATUS_SUCCESS) {
+        dprint("Failed to check WIP flag! [spi flash write block]\n");
+        return response;
+    }
+
+    /* Disable writes. */
+    response = spi_flash_write_disable();
+    if (response != FLASH_STATUS_SUCCESS) {
+        dprint("Failed to write-disable! [spi flash write block]\n");
+        return response;
+    }
+
+    return response;
+}
diff --git a/drivers/flash/flash_spi.h b/drivers/flash/flash_spi.h
new file mode 100644
index 0000000000..abe95e955e
--- /dev/null
+++ b/drivers/flash/flash_spi.h
@@ -0,0 +1,136 @@
+/*
+Copyright (C) 2021 Westberry Technology (ChangZhou) Corp., Ltd
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+/* All the following default configurations are based on MX25L4006E Nor FLASH. */
+
+/*
+    The slave select pin of the FLASH.
+    This needs to be a normal GPIO pin_t value, such as B14.
+*/
+#ifndef EXTERNAL_FLASH_SPI_SLAVE_SELECT_PIN
+#    error "No chip select pin defined -- missing EXTERNAL_FLASH_SPI_SLAVE_SELECT_PIN"
+#endif
+
+/*
+    The clock divisor for SPI to ensure that the MCU is within the
+    specifications of the FLASH chip. Generally this will be PCLK divided by
+    the intended divisor -- check your clock settings and the datasheet of
+    your FLASH.
+*/
+#ifndef EXTERNAL_FLASH_SPI_CLOCK_DIVISOR
+#    ifdef __AVR__
+#        define EXTERNAL_FLASH_SPI_CLOCK_DIVISOR 4
+#    else
+#        define EXTERNAL_FLASH_SPI_CLOCK_DIVISOR 8
+#    endif
+#endif
+
+/*
+    The SPI mode to communicate with the FLASH.
+*/
+#ifndef EXTERNAL_FLASH_SPI_MODE
+#    define EXTERNAL_FLASH_SPI_MODE 0
+#endif
+
+/*
+    Whether or not the SPI communication between the MCU and FLASH should be
+    LSB-first.
+*/
+#ifndef EXTERNAL_FLASH_SPI_LSBFIRST
+#    define EXTERNAL_FLASH_SPI_LSBFIRST false
+#endif
+
+/*
+    The Flash address size in bytes, as specified in datasheet.
+*/
+#ifndef EXTERNAL_FLASH_ADDRESS_SIZE
+#    define EXTERNAL_FLASH_ADDRESS_SIZE 3
+#endif
+
+/*
+    The page size of the FLASH in bytes, as specified in the datasheet.
+*/
+#ifndef EXTERNAL_FLASH_PAGE_SIZE
+#    define EXTERNAL_FLASH_PAGE_SIZE 256
+#endif
+
+/*
+    The sector size of the FLASH in bytes, as specified in the datasheet.
+*/
+#ifndef EXTERNAL_FLASH_SECTOR_SIZE
+#    define EXTERNAL_FLASH_SECTOR_SIZE (4 * 1024)
+#endif
+
+/*
+    The block size of the FLASH in bytes, as specified in the datasheet.
+*/
+#ifndef EXTERNAL_FLASH_BLOCK_SIZE
+#    define EXTERNAL_FLASH_BLOCK_SIZE (64 * 1024)
+#endif
+
+/*
+    The total size of the FLASH in bytes, as specified in the datasheet.
+*/
+#ifndef EXTERNAL_FLASH_SIZE
+#    define EXTERNAL_FLASH_SIZE (512 * 1024)
+#endif
+
+/*
+    The block count of the FLASH, calculated by total FLASH size and block size.
+*/
+#define EXTERNAL_FLASH_BLOCK_COUNT ((EXTERNAL_FLASH_SIZE) / (EXTERNAL_FLASH_BLOCK_SIZE))
+
+/*
+    The sector count of the FLASH, calculated by total FLASH size and sector size.
+*/
+#define EXTERNAL_FLASH_SECTOR_COUNT ((EXTERNAL_FLASH_SIZE) / (EXTERNAL_FLASH_SECTOR_SIZE))
+
+/*
+    The page count of the FLASH, calculated by total FLASH size and page size.
+*/
+#define EXTERNAL_FLASH_PAGE_COUNT ((EXTERNAL_FLASH_SIZE) / (EXTERNAL_FLASH_PAGE_SIZE))
+
+typedef int16_t flash_status_t;
+
+#define FLASH_STATUS_SUCCESS (0)
+#define FLASH_STATUS_ERROR (-1)
+#define FLASH_STATUS_TIMEOUT (-2)
+#define FLASH_STATUS_BAD_ADDRESS (-3)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+
+void flash_init(void);
+
+flash_status_t flash_erase_chip(void);
+
+flash_status_t flash_erase_block(uint32_t addr);
+
+flash_status_t flash_erase_sector(uint32_t addr);
+
+flash_status_t flash_read_block(uint32_t addr, void *buf, size_t len);
+
+flash_status_t flash_write_block(uint32_t addr, const void *buf, size_t len);
+
+#ifdef __cplusplus
+}
+#endif