From 7bc3eef8cc262e12b0f823ba4c92cf97ca3dc1fa Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Tue, 2 Jul 2024 10:16:41 +1000 Subject: [PATCH] SPI flash API cleanup, add async erase capability. (#23894) --- builddefs/common_features.mk | 9 +- drivers/flash/flash.h | 126 ++++++++++++++++++ drivers/flash/flash_spi.c | 89 +++++++------ drivers/flash/flash_spi.h | 31 +---- .../wear_leveling/wear_leveling_flash_spi.c | 4 +- 5 files changed, 182 insertions(+), 77 deletions(-) create mode 100644 drivers/flash/flash.h diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk index 68f9a1dd08a..498614dd264 100644 --- a/builddefs/common_features.mk +++ b/builddefs/common_features.mk @@ -282,18 +282,17 @@ ifneq ($(strip $(WEAR_LEVELING_DRIVER)),none) endif endif -VALID_FLASH_DRIVER_TYPES := spi +VALID_FLASH_DRIVER_TYPES := spi custom FLASH_DRIVER ?= none ifneq ($(strip $(FLASH_DRIVER)), none) ifeq ($(filter $(FLASH_DRIVER),$(VALID_FLASH_DRIVER_TYPES)),) $(call CATASTROPHIC_ERROR,Invalid FLASH_DRIVER,FLASH_DRIVER="$(FLASH_DRIVER)" is not a valid flash driver) else - OPT_DEFS += -DFLASH_ENABLE + OPT_DEFS += -DFLASH_ENABLE -DFLASH_DRIVER -DFLASH_DRIVER_$(strip $(shell echo $(FLASH_DRIVER) | tr '[:lower:]' '[:upper:]')) + COMMON_VPATH += $(DRIVER_PATH)/flash ifeq ($(strip $(FLASH_DRIVER)),spi) - SPI_DRIVER_REQUIRED = yes - OPT_DEFS += -DFLASH_DRIVER -DFLASH_SPI - COMMON_VPATH += $(DRIVER_PATH)/flash SRC += flash_spi.c + SPI_DRIVER_REQUIRED = yes endif endif endif diff --git a/drivers/flash/flash.h b/drivers/flash/flash.h new file mode 100644 index 00000000000..4d624751398 --- /dev/null +++ b/drivers/flash/flash.h @@ -0,0 +1,126 @@ +// Copyright 2024 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +/** + * @brief The status of a flash operation. + */ +enum { + FLASH_STATUS_SUCCESS = 0, //< The operation completed successfully. + FLASH_STATUS_ERROR = -1, //< An error occurred during the operation. + FLASH_STATUS_TIMEOUT = -2, //< The operation timed out. + FLASH_STATUS_BAD_ADDRESS = -3, //< The address is out of bounds. + FLASH_STATUS_BUSY = -4, //< The flash is busy. +}; + +/** + * @brief The status of a flash operation. + */ +typedef int16_t flash_status_t; + +/** + * @brief Initializes the flash driver. + * + * This function initializes the flash driver and prepares it for use. + * It should be called before any other flash-related functions are used. + */ +void flash_init(void); + +/** + * @brief Checks if the flash is busy. + * + * This function checks if the flash is currently busy with an operation. + * + * @return FLASH_STATUS_SUCCESS if the flash is not busy, FLASH_STATUS_BUSY if the flash is busy, or FLASH_STATUS_ERROR if an error occurred. + */ +flash_status_t flash_is_busy(void); + +/** + * @brief Initiates a chip erase operation. + * + * This function does not wait for the flash to become ready. + * + * @return FLASH_STATUS_SUCCESS if the erase command was successfully sent, FLASH_STATUS_TIMEOUT if the flash is busy, or FLASH_STATUS_ERROR if an error occurred. + */ +flash_status_t flash_begin_erase_chip(void); + +/** + * @brief Waits for the chip erase operation to complete. + * + * This function waits for the chip erase operation to complete. + * + * @return FLASH_STATUS_SUCCESS if the chip erase operation completed successfully, FLASH_STATUS_TIMEOUT if the flash was still busy, or FLASH_STATUS_ERROR if an error occurred. + */ +flash_status_t flash_wait_erase_chip(void); + +/** + * @brief Erases the entire flash memory chip. + * + * This function initiates an erase operation to erase the entire flash memory chip. + * It waits for the operation to complete. + * + * @return FLASH_STATUS_SUCCESS if the erase was successfully executed, FLASH_STATUS_TIMEOUT if the flash is busy, or FLASH_STATUS_ERROR if an error occurred. + */ +flash_status_t flash_erase_chip(void); + +/** + * @brief Erases a block of flash memory. + * + * This function initiates an erase operation to erase a block of flash memory. + * It waits for the operation to complete. + * + * @param addr The address of the block to erase. + * + * @return FLASH_STATUS_SUCCESS if the erase was successfully executed, FLASH_STATUS_TIMEOUT if the flash is busy, or FLASH_STATUS_ERROR if an error occurred. + */ +flash_status_t flash_erase_block(uint32_t addr); + +/** + * @brief Erases a sector of flash memory. + * + * This function initiates an erase operation to erase a sector of flash memory. + * It waits for the operation to complete. + * + * @param addr The address of the sector to erase. + * + * @return FLASH_STATUS_SUCCESS if the erase was successfully executed, FLASH_STATUS_TIMEOUT if the flash is busy, or FLASH_STATUS_ERROR if an error occurred. + */ +flash_status_t flash_erase_sector(uint32_t addr); + +/** + * @brief Reads a range of flash memory. + * + * This function reads a range of flash memory into a buffer. + * + * @param addr The address of the range to read. + * @param buf A pointer to the buffer to read the range into. + * @param len The length of the range to read. + * + * @return FLASH_STATUS_SUCCESS if the range was successfully read, FLASH_STATUS_BAD_ADDRESS if the address is out of bounds, FLASH_STATUS_TIMEOUT if the flash is busy, or FLASH_STATUS_ERROR if an error occurred. + */ +flash_status_t flash_read_range(uint32_t addr, void *buf, size_t len); + +/** + * @brief Writes a range of flash memory. + * + * This function writes a range of flash memory from a buffer. + * + * @param addr The address of the range to write. + * @param buf A pointer to the buffer to write to the range. + * @param len The length of the range to write. + * + * @return FLASH_STATUS_SUCCESS if the range was successfully written, FLASH_STATUS_BAD_ADDRESS if the address is out of bounds, FLASH_STATUS_TIMEOUT if the flash is busy, or FLASH_STATUS_ERROR if an error occurred. + */ +flash_status_t flash_write_range(uint32_t addr, const void *buf, size_t len); + +#ifdef __cplusplus +} +#endif diff --git a/drivers/flash/flash_spi.c b/drivers/flash/flash_spi.c index 0c0eb8a99e5..7226773ff41 100644 --- a/drivers/flash/flash_spi.c +++ b/drivers/flash/flash_spi.c @@ -1,22 +1,10 @@ -/* -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 . -*/ +// Copyright 2021 Westberry Technology (ChangZhou) Corp., Ltd +// Copyright 2024 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later #include +#include "flash.h" #include "util.h" #include "wait.h" #include "debug.h" @@ -69,33 +57,43 @@ 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; +static flash_status_t spi_flash_wait_while_busy_multiplier(int multiplier) { flash_status_t response = FLASH_STATUS_SUCCESS; - uint8_t retval; - + uint32_t deadline = timer_read32() + ((EXTERNAL_FLASH_SPI_TIMEOUT)*multiplier); 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); + response = flash_is_busy(); + } while (response == FLASH_STATUS_BUSY); return response; } +static flash_status_t spi_flash_wait_while_busy(void) { + return spi_flash_wait_while_busy_multiplier(1); +} + +flash_status_t flash_is_busy(void) { + 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); + spi_status_t status = spi_read(); + spi_stop(); + + if (status < 0) { + return status; + } + + uint8_t sr = (uint8_t)status; + return (sr & FLASH_FLAG_WIP) ? FLASH_STATUS_BUSY : FLASH_STATUS_SUCCESS; +} + static flash_status_t spi_flash_write_enable(void) { bool res = spi_flash_start(); if (!res) { @@ -104,7 +102,6 @@ static flash_status_t spi_flash_write_enable(void) { } spi_write(FLASH_CMD_WREN); - spi_stop(); return FLASH_STATUS_SUCCESS; @@ -118,7 +115,6 @@ static flash_status_t spi_flash_write_disable(void) { } spi_write(FLASH_CMD_WRDI); - spi_stop(); return FLASH_STATUS_SUCCESS; @@ -166,7 +162,7 @@ void flash_init(void) { spi_init(); } -flash_status_t flash_erase_chip(void) { +flash_status_t flash_begin_erase_chip(void) { flash_status_t response = FLASH_STATUS_SUCCESS; /* Wait for the write-in-progress bit to be cleared. */ @@ -191,17 +187,28 @@ flash_status_t flash_erase_chip(void) { } spi_write(FLASH_CMD_CE); spi_stop(); + return FLASH_STATUS_SUCCESS; +} - /* Wait for the write-in-progress bit to be cleared.*/ - response = spi_flash_wait_while_busy(); +flash_status_t flash_wait_erase_chip(void) { + flash_status_t response = spi_flash_wait_while_busy_multiplier(250); // Chip erase can take a long time, wait 250x the usual timeout 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_chip(void) { + flash_status_t response = flash_begin_erase_chip(); + if (response != FLASH_STATUS_SUCCESS) { + dprint("Failed to begin erase chip! [spi flash erase chip]\n"); + return response; + } + + return flash_wait_erase_chip(); +} + flash_status_t flash_erase_sector(uint32_t addr) { flash_status_t response = FLASH_STATUS_SUCCESS; @@ -282,7 +289,7 @@ flash_status_t flash_erase_block(uint32_t addr) { return response; } -flash_status_t flash_read_block(uint32_t addr, void *buf, size_t len) { +flash_status_t flash_read_range(uint32_t addr, void *buf, size_t len) { flash_status_t response = FLASH_STATUS_SUCCESS; uint8_t * read_buf = (uint8_t *)buf; @@ -313,7 +320,7 @@ flash_status_t flash_read_block(uint32_t addr, void *buf, size_t len) { return response; } -flash_status_t flash_write_block(uint32_t addr, const void *buf, size_t len) { +flash_status_t flash_write_range(uint32_t addr, const void *buf, size_t len) { flash_status_t response = FLASH_STATUS_SUCCESS; uint8_t * write_buf = (uint8_t *)buf; diff --git a/drivers/flash/flash_spi.h b/drivers/flash/flash_spi.h index 87460fc210e..7a979daf0f7 100644 --- a/drivers/flash/flash_spi.h +++ b/drivers/flash/flash_spi.h @@ -17,6 +17,8 @@ along with this program. If not, see . #pragma once +#include "flash.h" + /* All the following default configurations are based on MX25L4006E Nor FLASH. */ /* @@ -105,32 +107,3 @@ along with this program. If not, see . 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 - -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 diff --git a/drivers/wear_leveling/wear_leveling_flash_spi.c b/drivers/wear_leveling/wear_leveling_flash_spi.c index 6191f8bf095..304aed1641f 100644 --- a/drivers/wear_leveling/wear_leveling_flash_spi.c +++ b/drivers/wear_leveling/wear_leveling_flash_spi.c @@ -58,7 +58,7 @@ bool backing_store_read(uint32_t address, backing_store_int_t *value) { bool backing_store_read_bulk(uint32_t address, backing_store_int_t *values, size_t item_count) { bs_dprintf("Read "); uint32_t offset = (WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_OFFSET) * (EXTERNAL_FLASH_BLOCK_SIZE) + address; - flash_status_t status = flash_read_block(offset, values, sizeof(backing_store_int_t) * item_count); + flash_status_t status = flash_read_range(offset, values, sizeof(backing_store_int_t) * item_count); if (status == FLASH_STATUS_SUCCESS) { for (size_t i = 0; i < item_count; ++i) { values[i] = ~values[i]; @@ -88,7 +88,7 @@ bool backing_store_write_bulk(uint32_t address, backing_store_int_t *values, siz } // Write out the block - if (flash_write_block(offset, temp, sizeof(backing_store_int_t) * this_loop) != FLASH_STATUS_SUCCESS) { + if (flash_write_range(offset, temp, sizeof(backing_store_int_t) * this_loop) != FLASH_STATUS_SUCCESS) { return false; }