diff options
Diffstat (limited to 'drivers/flash/flash_spi.c')
| -rw-r--r-- | drivers/flash/flash_spi.c | 372 | 
1 files changed, 372 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; +} | 
