diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e0dd4c4..98e857e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,6 +45,7 @@ jobs: matrix: source: - src/block/sdhc_spi.c + - src/flash/spi_nor.c steps: - uses: actions/checkout@v4 diff --git a/README.md b/README.md index 2aea179..1341e78 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ tests/ Test framework and test suites - [Tests](tests/README.md) — Test framework and test suites - [Writing a Driver](docs/writing_a_driver.md) — How to implement a driver for a new platform - [Adding a Board](docs/adding_a_board.md) — How to add a new board configuration +- [Adding a Peripheral](docs/adding_a_peripheral.md) — How to add an external peripheral device - [Adding an Example](docs/adding_an_example.md) — How to add a new example application - [Adding a Test](docs/adding_a_test.md) — How to add hardware tests diff --git a/boards/peripheral/Makefile.inc b/boards/peripheral/Makefile.inc index 3eefb7c..d6889b4 100644 --- a/boards/peripheral/Makefile.inc +++ b/boards/peripheral/Makefile.inc @@ -7,3 +7,9 @@ CFLAGS += -DPERIPHERAL_SDHC_SPI_SDCARD32GB BOARD_SOURCE += $(_PERIPHERAL_DIR)/block/sdhc_spi_sdcard32gb.c BOARD_SOURCE += $(WHAL_DIR)/src/block/sdhc_spi.c endif + +ifdef PERIPHERAL_SPI_NOR_W25Q64 +CFLAGS += -DPERIPHERAL_SPI_NOR_W25Q64 +BOARD_SOURCE += $(_PERIPHERAL_DIR)/flash/spi_nor_w25q64.c +BOARD_SOURCE += $(WHAL_DIR)/src/flash/spi_nor.c +endif diff --git a/boards/peripheral/flash/spi_nor_w25q64.c b/boards/peripheral/flash/spi_nor_w25q64.c new file mode 100644 index 0000000..530917a --- /dev/null +++ b/boards/peripheral/flash/spi_nor_w25q64.c @@ -0,0 +1,35 @@ +#include "spi_nor_w25q64.h" +#include +#include "board.h" + +/* + * Winbond W25Q64 — 64 Mbit (8 MB) SPI-NOR Flash + * + * - Page size: 256 bytes + * - Sector size: 4 KB (smallest erasable unit) + * - Capacity: 8,388,608 bytes (8 MB) + * - SPI modes 0 and 3, up to 104 MHz (standard read up to 50 MHz) + */ + +#define W25Q64_PAGE_SZ 256 +#define W25Q64_CAPACITY (8 * 1024 * 1024) /* 8 MB */ + +static whal_Spi_ComCfg g_w25q64ComCfg = { + .freq = 25000000, /* 25 MHz */ + .mode = WHAL_SPI_MODE_0, + .wordSz = 8, + .dataLines = 1, +}; + +whal_Flash g_whalSpiNorW25q64 = { + .driver = &whal_SpiNor_Driver, + .cfg = &(whal_SpiNor_Cfg) { + .spiDev = &g_whalSpi, + .spiComCfg = &g_w25q64ComCfg, + .gpioDev = &g_whalGpio, + .csPin = SPI_CS_PIN, + .timeout = &g_whalTimeout, + .pageSz = W25Q64_PAGE_SZ, + .capacity = W25Q64_CAPACITY, + }, +}; diff --git a/boards/peripheral/flash/spi_nor_w25q64.h b/boards/peripheral/flash/spi_nor_w25q64.h new file mode 100644 index 0000000..dcb41e4 --- /dev/null +++ b/boards/peripheral/flash/spi_nor_w25q64.h @@ -0,0 +1,10 @@ +#ifndef BOARD_SPI_NOR_W25Q64_H +#define BOARD_SPI_NOR_W25Q64_H + +#include +#include +#include + +extern whal_Flash g_whalSpiNorW25q64; + +#endif /* BOARD_SPI_NOR_W25Q64_H */ diff --git a/boards/peripheral/peripheral.c b/boards/peripheral/peripheral.c index 7989c54..2ef6d5f 100644 --- a/boards/peripheral/peripheral.c +++ b/boards/peripheral/peripheral.c @@ -4,6 +4,10 @@ #include "block/sdhc_spi_sdcard32gb.h" #endif +#ifdef PERIPHERAL_SPI_NOR_W25Q64 +#include "flash/spi_nor_w25q64.h" +#endif + whal_PeripheralBlock_Cfg g_peripheralBlock[] = { #ifdef PERIPHERAL_SDHC_SPI_SDCARD32GB { @@ -17,3 +21,52 @@ whal_PeripheralBlock_Cfg g_peripheralBlock[] = { #endif {0}, /* sentinel */ }; + +whal_PeripheralFlash_Cfg g_peripheralFlash[] = { +#ifdef PERIPHERAL_SPI_NOR_W25Q64 + { + .name = "spi_nor_w25q64", + .dev = &g_whalSpiNorW25q64, + .sectorSz = 4096, + }, +#endif + {0}, /* sentinel */ +}; + +whal_Error Peripheral_Init(void) +{ + whal_Error err; + + for (size_t i = 0; g_peripheralBlock[i].dev; i++) { + err = whal_Block_Init(g_peripheralBlock[i].dev); + if (err) + return err; + } + + for (size_t i = 0; g_peripheralFlash[i].dev; i++) { + err = whal_Flash_Init(g_peripheralFlash[i].dev); + if (err) + return err; + } + + return WHAL_SUCCESS; +} + +whal_Error Peripheral_Deinit(void) +{ + whal_Error err; + + for (size_t i = 0; g_peripheralFlash[i].dev; i++) { + err = whal_Flash_Deinit(g_peripheralFlash[i].dev); + if (err) + return err; + } + + for (size_t i = 0; g_peripheralBlock[i].dev; i++) { + err = whal_Block_Deinit(g_peripheralBlock[i].dev); + if (err) + return err; + } + + return WHAL_SUCCESS; +} diff --git a/boards/peripheral/peripheral.h b/boards/peripheral/peripheral.h index e7bb2bd..48b7d1b 100644 --- a/boards/peripheral/peripheral.h +++ b/boards/peripheral/peripheral.h @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -16,6 +17,17 @@ typedef struct { uint8_t erasedByte; /* Expected byte value after erase (0x00 or 0xFF) */ } whal_PeripheralBlock_Cfg; +/* Peripheral flash device test configuration */ +typedef struct { + const char *name; + whal_Flash *dev; + size_t sectorSz; /* Sector (erase) size in bytes */ +} whal_PeripheralFlash_Cfg; + extern whal_PeripheralBlock_Cfg g_peripheralBlock[]; +extern whal_PeripheralFlash_Cfg g_peripheralFlash[]; + +whal_Error Peripheral_Init(void); +whal_Error Peripheral_Deinit(void); #endif /* BOARD_PERIPHERAL_H */ diff --git a/boards/pic32cz_curiosity_ultra/Makefile.inc b/boards/pic32cz_curiosity_ultra/Makefile.inc index 303bf14..eac73f6 100644 --- a/boards/pic32cz_curiosity_ultra/Makefile.inc +++ b/boards/pic32cz_curiosity_ultra/Makefile.inc @@ -14,7 +14,7 @@ LDFLAGS = --omagic -static LINKER_SCRIPT ?= $(_BOARD_DIR)/linker.ld -INCLUDE += -I$(_BOARD_DIR) +INCLUDE += -I$(_BOARD_DIR) -I$(WHAL_DIR)/boards/peripheral BOARD_SOURCE = $(_BOARD_DIR)/ivt.c BOARD_SOURCE += $(_BOARD_DIR)/board.c @@ -29,3 +29,6 @@ BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/rng.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/block.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/pic32cz_*.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/systick.c) + +# Peripheral devices +include $(WHAL_DIR)/boards/peripheral/Makefile.inc diff --git a/boards/pic32cz_curiosity_ultra/board.c b/boards/pic32cz_curiosity_ultra/board.c index df6b273..c66d209 100644 --- a/boards/pic32cz_curiosity_ultra/board.c +++ b/boards/pic32cz_curiosity_ultra/board.c @@ -4,6 +4,7 @@ #include #include "board.h" #include +#include "peripheral.h" /* Supply */ static whal_Supply g_whalSupply = { @@ -199,6 +200,11 @@ whal_Error Board_Init(void) return err; } + err = Peripheral_Init(); + if (err) { + return err; + } + return WHAL_SUCCESS; } @@ -206,6 +212,11 @@ whal_Error Board_Deinit(void) { whal_Error err; + err = Peripheral_Deinit(); + if (err) { + return err; + } + err = whal_Timer_Stop(&g_whalTimer); if (err) { return err; diff --git a/boards/stm32wb55xx_nucleo/board.c b/boards/stm32wb55xx_nucleo/board.c index 533fb8f..6adf75d 100644 --- a/boards/stm32wb55xx_nucleo/board.c +++ b/boards/stm32wb55xx_nucleo/board.c @@ -280,13 +280,6 @@ whal_Error Board_Init(void) return err; } - /* Initialize peripheral block devices */ - for (size_t i = 0; g_peripheralBlock[i].dev; i++) { - err = whal_Block_Init(g_peripheralBlock[i].dev); - if (err) - return err; - } - err = whal_Rng_Init(&g_whalRng); if (err) { return err; @@ -307,6 +300,11 @@ whal_Error Board_Init(void) return err; } + err = Peripheral_Init(); + if (err) { + return err; + } + return WHAL_SUCCESS; } @@ -314,6 +312,11 @@ whal_Error Board_Deinit(void) { whal_Error err; + err = Peripheral_Deinit(); + if (err) { + return err; + } + err = whal_Timer_Stop(&g_whalTimer); if (err) { return err; @@ -334,13 +337,6 @@ whal_Error Board_Deinit(void) return err; } - /* Deinitialize peripheral block devices */ - for (size_t i = 0; g_peripheralBlock[i].dev; i++) { - err = whal_Block_Deinit(g_peripheralBlock[i].dev); - if (err) - return err; - } - err = whal_Flash_Deinit(&g_whalFlash); if (err) { return err; diff --git a/boards/stm32wb55xx_nucleo/linker.ld b/boards/stm32wb55xx_nucleo/linker.ld index b98b596..37ee955 100644 --- a/boards/stm32wb55xx_nucleo/linker.ld +++ b/boards/stm32wb55xx_nucleo/linker.ld @@ -54,9 +54,8 @@ _Min_Stack_Size = 0x500; /* required amount of stack */ /* Specify the memory areas */ MEMORY { -FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K -BOOTLOADER_MAGIC (rx) : ORIGIN = 0x20000004, LENGTH = 0x4 -RAM1 (xrw) : ORIGIN = 0x20000008, LENGTH = 0x1ffc +FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K +RAM1 (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00030000 } /* Define output sections */ diff --git a/docs/adding_a_board.md b/docs/adding_a_board.md index 08da55a..7a93ddf 100644 --- a/docs/adding_a_board.md +++ b/docs/adding_a_board.md @@ -49,6 +49,7 @@ on failure. ```c #include "board.h" #include +#include "peripheral.h" static whal_MyplatformGpio_PinCfg pinCfg[] = { /* ... */ }; @@ -99,6 +100,10 @@ whal_Error Board_Init(void) if (err) return err; + err = Peripheral_Init(); + if (err) + return err; + return WHAL_SUCCESS; } @@ -106,6 +111,10 @@ whal_Error Board_Deinit(void) { whal_Error err; + err = Peripheral_Deinit(); + if (err) + return err; + whal_Timer_Stop(&g_whalTimer); whal_Timer_Deinit(&g_whalTimer); whal_Uart_Deinit(&g_whalUart); @@ -141,12 +150,41 @@ LDFLAGS = -mcpu=cortex-m4 -mthumb -nostdlib -lgcc LINKER_SCRIPT = $(_BOARD_DIR)/linker.ld +INCLUDE += -I$(_BOARD_DIR) -I$(WHAL_DIR)/boards/peripheral + BOARD_SOURCE = $(_BOARD_DIR)/board.c BOARD_SOURCE += $(_BOARD_DIR)/ivt.c BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/myplatform_*.c) BOARD_SOURCE += $(WHAL_DIR)/src/timer/systick.c + +# Peripheral devices +include $(WHAL_DIR)/boards/peripheral/Makefile.inc ``` +## Peripheral Devices + +Boards support optional external peripheral devices (e.g., SPI-NOR flash, SD +cards) through the peripheral system in `boards/peripheral/`. To enable this: + +1. Include `peripheral.h` in `board.c` and add the peripheral include path + (`-I$(WHAL_DIR)/boards/peripheral`) in `Makefile.inc`. + +2. Include `boards/peripheral/Makefile.inc` at the end of the board's + `Makefile.inc`. This conditionally compiles peripheral drivers based on + build-time flags (e.g., `PERIPHERAL_SPI_NOR_W25Q64=1`). + +3. Call `Peripheral_Init()` at the end of `Board_Init()` and + `Peripheral_Deinit()` at the top of `Board_Deinit()`. These functions + iterate the peripheral registry arrays and initialize/deinitialize all + enabled peripheral devices. + +`Peripheral_Init()` and `Peripheral_Deinit()` are safe to call even when no +peripherals are enabled — the registry arrays will be empty and the functions +return immediately. + +See [Adding a Peripheral](adding_a_peripheral.md) for details on how to add +new peripheral devices to the registry. + ### linker.ld Linker script defining the memory layout for your board's MCU. Must define diff --git a/docs/adding_a_peripheral.md b/docs/adding_a_peripheral.md new file mode 100644 index 0000000..94e5336 --- /dev/null +++ b/docs/adding_a_peripheral.md @@ -0,0 +1,169 @@ +# Adding a Peripheral + +This guide covers how to add an external peripheral device to the wolfHAL +peripheral system. Peripherals are bus-attached devices (e.g., SPI-NOR flash, +SD cards) that live in `boards/peripheral/` and are opt-in at build time. + +## Overview + +A peripheral consists of three parts: + +1. A **device configuration file** that instantiates the device with + board-specific parameters (SPI bus, CS pin, clock speed, etc.) +2. An **entry in the peripheral registry** (`peripheral.c`) so that board init + and tests can discover the device +3. A **Makefile.inc entry** to conditionally compile the peripheral and its + driver source + +## File Layout + +Peripherals are organized by device type under `boards/peripheral/`: + +``` +boards/peripheral/ + peripheral.h # Registry structs and extern arrays + peripheral.c # Registry arrays (g_peripheralBlock[], g_peripheralFlash[]) + Makefile.inc # Conditional build rules + block/ + sdhc_spi_sdcard32gb.h + sdhc_spi_sdcard32gb.c + flash/ + spi_nor_w25q64.h + spi_nor_w25q64.c +``` + +## Step 1: Create the Device Configuration + +Create a header and source file for your device under the appropriate type +directory. + +### Header + +Declare the global device instance: + +```c +#ifndef BOARD_SPI_NOR_W25Q64_H +#define BOARD_SPI_NOR_W25Q64_H + +#include +#include +#include + +extern whal_Flash g_whalSpiNorW25q64; + +#endif +``` + +### Source + +Define the device instance with its configuration. The device references board +globals (`g_whalSpi`, `g_whalGpio`, `SPI_CS_PIN`, `g_whalTimeout`) from +`board.h`: + +```c +#include "spi_nor_w25q64.h" +#include +#include "board.h" + +#define W25Q64_PAGE_SZ 256 +#define W25Q64_CAPACITY (8 * 1024 * 1024) + +static whal_Spi_ComCfg g_w25q64ComCfg = { + .freq = 25000000, + .mode = WHAL_SPI_MODE_0, + .wordSz = 8, + .dataLines = 1, +}; + +whal_Flash g_whalSpiNorW25q64 = { + .driver = &whal_SpiNor_Driver, + .cfg = &(whal_SpiNor_Cfg) { + .spiDev = &g_whalSpi, + .spiComCfg = &g_w25q64ComCfg, + .gpioDev = &g_whalGpio, + .csPin = SPI_CS_PIN, + .timeout = &g_whalTimeout, + .pageSz = W25Q64_PAGE_SZ, + .capacity = W25Q64_CAPACITY, + }, +}; +``` + +## Step 2: Register in peripheral.c + +Add a conditional include and an entry in the appropriate registry array. + +In `peripheral.c`: + +```c +#ifdef PERIPHERAL_SPI_NOR_W25Q64 +#include "flash/spi_nor_w25q64.h" +#endif +``` + +And add an entry to the matching array (before the sentinel): + +```c +whal_PeripheralFlash_Cfg g_peripheralFlash[] = { +#ifdef PERIPHERAL_SPI_NOR_W25Q64 + { + .name = "spi_nor_w25q64", + .dev = &g_whalSpiNorW25q64, + .sectorSz = 4096, + }, +#endif + {0}, /* sentinel */ +}; +``` + +The registry structs are defined in `peripheral.h`: + +- `whal_PeripheralBlock_Cfg` for block devices (`g_peripheralBlock[]`) +- `whal_PeripheralFlash_Cfg` for flash devices (`g_peripheralFlash[]`) + +Each array is terminated by a zero sentinel so that board init and test code +can iterate without knowing the count. + +## Step 3: Add Build Rules + +In `boards/peripheral/Makefile.inc`, add a conditional block for your +peripheral: + +```makefile +ifdef PERIPHERAL_SPI_NOR_W25Q64 +CFLAGS += -DPERIPHERAL_SPI_NOR_W25Q64 +BOARD_SOURCE += $(_PERIPHERAL_DIR)/flash/spi_nor_w25q64.c +BOARD_SOURCE += $(WHAL_DIR)/src/flash/spi_nor.c +endif +``` + +This compiles both the peripheral configuration and the underlying driver +source when the flag is set. + +## Building + +Enable the peripheral by setting its flag when building: + +``` +make BOARD=stm32wb55xx_nucleo PERIPHERAL_SPI_NOR_W25Q64=1 +``` + +Multiple peripherals can be enabled simultaneously: + +``` +make BOARD=stm32wb55xx_nucleo PERIPHERAL_SPI_NOR_W25Q64=1 PERIPHERAL_SDHC_SPI_SDCARD32GB=1 +``` + +## Testing + +Peripheral devices are automatically picked up by their matching test suite. +Flash peripherals are tested by the `flash` test, and block peripherals by the +`block` test. See [Adding a Test](adding_a_test.md) for details on the test +framework. + +## Naming Convention + +- Flag: `PERIPHERAL__` (e.g., `PERIPHERAL_SPI_NOR_W25Q64`) +- Directory: `boards/peripheral//` (e.g., `flash/`, `block/`) +- Files: `_.h` and `_.c` +- Global instance: `g_whal` (e.g., `g_whalSpiNorW25q64`) diff --git a/docs/writing_a_driver.md b/docs/writing_a_driver.md index 9a152ad..4910924 100644 --- a/docs/writing_a_driver.md +++ b/docs/writing_a_driver.md @@ -511,7 +511,9 @@ Erase a flash region. Flash erase operates at sector/page granularity - Validate that the region is unlocked before erasing The `addr` does not need to be page-aligned — the driver should erase all pages -that overlap with the requested range. +that overlap with the requested range. Bus-device flash drivers (e.g., SPI-NOR) +may enforce stricter alignment requirements where the underlying hardware +requires aligned erase addresses. --- diff --git a/src/flash/spi_nor.c b/src/flash/spi_nor.c new file mode 100644 index 0000000..76ddc7f --- /dev/null +++ b/src/flash/spi_nor.c @@ -0,0 +1,1595 @@ +#include +#include +#include +#include +#include +#include +#include + +/* SPI-NOR JEDEC Standard Commands */ +#define CMD_WRITE_ENABLE 0x06 +#define CMD_WRITE_DISABLE 0x04 +#define CMD_READ_SR1 0x05 /* Read Status Register 1 */ +#define CMD_WRITE_SR 0x01 /* Write Status Register */ +#define CMD_READ_DATA 0x03 +#define CMD_FAST_READ 0x0B +#define CMD_PAGE_PROGRAM 0x02 +#define CMD_SECTOR_ERASE 0x20 /* 4 KB sector erase */ +#define CMD_BLOCK_ERASE32 0x52 /* 32 KB block erase */ +#define CMD_BLOCK_ERASE64 0xD8 /* 64 KB block erase */ +#define CMD_CHIP_ERASE 0xC7 + +#define ERASE_SZ_4K (4 * 1024) +#define ERASE_SZ_32K (32 * 1024) +#define ERASE_SZ_64K (64 * 1024) +#define EXREG_BANK_SZ (16 * 1024 * 1024) /* 16 MB per EAR bank */ +#define CMD_READ_JEDEC_ID 0x9F +#define CMD_RELEASE_PD 0xAB /* Release from Deep Power-Down */ +#define CMD_ENTER_4B_MODE 0xB7 + +/* Dedicated 4-byte address commands */ +#define CMD_READ_DATA_4B 0x13 +#define CMD_FAST_READ_4B 0x0C +#define CMD_PAGE_PROGRAM_4B 0x12 +#define CMD_SECTOR_ERASE_4B 0x21 /* 4 KB sector erase */ +#define CMD_BLOCK_ERASE64_4B 0xDC /* 64 KB block erase */ + +/* Extended Address Register */ +#define CMD_WRITE_EAR 0xC5 +#define CMD_READ_EAR 0xC8 + +/* Status Register 1 Bits */ +#define SR1_WIP 0x01 /* Write In Progress */ +#define SR1_WEL 0x02 /* Write Enable Latch */ +#define SR1_BP0 0x04 /* Block Protect bit 0 */ +#define SR1_BP1 0x08 /* Block Protect bit 1 */ +#define SR1_BP2 0x10 /* Block Protect bit 2 */ +#define SR1_BP_MASK (SR1_BP0 | SR1_BP1 | SR1_BP2) + +#define DUMMY 0xFF + +static whal_Error SpiNor_CsAssert(whal_SpiNor_Cfg *cfg) +{ + return whal_Gpio_Set(cfg->gpioDev, cfg->csPin, 0); +} + +static whal_Error SpiNor_CsDeassert(whal_SpiNor_Cfg *cfg) +{ + return whal_Gpio_Set(cfg->gpioDev, cfg->csPin, 1); +} + +/* Send a 1-byte command with no data phase */ +static whal_Error SpiNor_Cmd(whal_SpiNor_Cfg *cfg, uint8_t cmd) +{ + whal_Error err; + + err = SpiNor_CsAssert(cfg); + if (err) + return err; + err = whal_Spi_SendRecv(cfg->spiDev, &cmd, 1, NULL, 0); + SpiNor_CsDeassert(cfg); + return err; +} + +/* Read Status Register 1 */ +static whal_Error SpiNor_ReadSR1(whal_SpiNor_Cfg *cfg, uint8_t *sr) +{ + uint8_t cmd = CMD_READ_SR1; + whal_Error err; + + err = SpiNor_CsAssert(cfg); + if (err) + return err; + err = whal_Spi_SendRecv(cfg->spiDev, &cmd, 1, NULL, 0); + if (!err) + err = whal_Spi_SendRecv(cfg->spiDev, NULL, 0, sr, 1); + SpiNor_CsDeassert(cfg); + return err; +} + +/* Poll until WIP bit clears */ +static whal_Error SpiNor_WaitReady(whal_SpiNor_Cfg *cfg) +{ + uint8_t sr; + whal_Error err; + + WHAL_TIMEOUT_START(cfg->timeout); + do { + err = SpiNor_ReadSR1(cfg, &sr); + if (err) + return err; + if (!(sr & SR1_WIP)) + return WHAL_SUCCESS; + if (WHAL_TIMEOUT_EXPIRED(cfg->timeout)) + return WHAL_ETIMEOUT; + } while (1); +} + +/* Issue Write Enable (WREN) and verify WEL is set */ +static whal_Error SpiNor_WriteEnable(whal_SpiNor_Cfg *cfg) +{ + uint8_t sr; + whal_Error err; + + err = SpiNor_Cmd(cfg, CMD_WRITE_ENABLE); + if (err) + return err; + + err = SpiNor_ReadSR1(cfg, &sr); + if (err) + return err; + if (!(sr & SR1_WEL)) + return WHAL_EHARDWARE; + + return WHAL_SUCCESS; +} + +/* Build a command + 3-byte address frame */ +static void SpiNor_BuildCmdAddr(uint8_t *frame, uint8_t cmd, size_t addr) +{ + frame[0] = cmd; + frame[1] = (uint8_t)(addr >> 16); + frame[2] = (uint8_t)(addr >> 8); + frame[3] = (uint8_t)(addr); +} + +/* Build a command + 4-byte address frame */ +static void SpiNor_BuildCmdAddr4b(uint8_t *frame, uint8_t cmd, size_t addr) +{ + frame[0] = cmd; + frame[1] = (uint8_t)(addr >> 24); + frame[2] = (uint8_t)(addr >> 16); + frame[3] = (uint8_t)(addr >> 8); + frame[4] = (uint8_t)(addr); +} + +whal_Error whal_SpiNor_Init(whal_Flash *flashDev) +{ + whal_SpiNor_Cfg *cfg; + uint8_t jedec[3]; + uint8_t cmd; + whal_Error err; + + if (!flashDev || !flashDev->cfg) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (!cfg->spiDev || !cfg->spiComCfg || !cfg->gpioDev) + return WHAL_EINVAL; + if (cfg->pageSz == 0 || cfg->capacity == 0) + return WHAL_EINVAL; + + /* Ensure CS is deasserted */ + err = SpiNor_CsDeassert(cfg); + if (err) + return err; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + /* Wake device in case it was left in Deep Power-Down */ + err = SpiNor_Cmd(cfg, CMD_RELEASE_PD); + if (err) + goto cleanup; + + /* Wait for any in-progress operation from a previous session */ + err = SpiNor_WaitReady(cfg); + if (err) + goto cleanup; + + /* Read JEDEC ID to verify a device is present */ + cmd = CMD_READ_JEDEC_ID; + err = SpiNor_CsAssert(cfg); + if (err) + goto cleanup; + err = whal_Spi_SendRecv(cfg->spiDev, &cmd, 1, NULL, 0); + if (!err) + err = whal_Spi_SendRecv(cfg->spiDev, NULL, 0, jedec, 3); + SpiNor_CsDeassert(cfg); + if (err) + goto cleanup; + + /* A manufacturer ID of 0x00 or 0xFF indicates no device */ + if (jedec[0] == 0x00 || jedec[0] == 0xFF) { + err = WHAL_EHARDWARE; + goto cleanup; + } + +cleanup: + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor_Deinit(whal_Flash *flashDev) +{ + if (!flashDev || !flashDev->cfg) + return WHAL_EINVAL; + + return SpiNor_CsDeassert((whal_SpiNor_Cfg *)flashDev->cfg); +} + +whal_Error whal_SpiNor_Lock(whal_Flash *flashDev, size_t addr, size_t len) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[2]; + whal_Error err; + + (void)addr; + (void)len; + + if (!flashDev || !flashDev->cfg) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + err = SpiNor_WaitReady(cfg); + if (err) + goto cleanup; + + /* Set all block protect bits to lock entire device */ + err = SpiNor_WriteEnable(cfg); + if (err) + goto cleanup; + + frame[0] = CMD_WRITE_SR; + frame[1] = SR1_BP_MASK; + err = SpiNor_CsAssert(cfg); + if (err) + goto cleanup; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 2, NULL, 0); + SpiNor_CsDeassert(cfg); + if (!err) + err = SpiNor_WaitReady(cfg); + +cleanup: + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor_Unlock(whal_Flash *flashDev, size_t addr, size_t len) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[2]; + whal_Error err; + + (void)addr; + (void)len; + + if (!flashDev || !flashDev->cfg) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + err = SpiNor_WaitReady(cfg); + if (err) + goto cleanup; + + /* Clear all block protect bits to unlock entire device */ + err = SpiNor_WriteEnable(cfg); + if (err) + goto cleanup; + + frame[0] = CMD_WRITE_SR; + frame[1] = 0x00; + err = SpiNor_CsAssert(cfg); + if (err) + goto cleanup; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 2, NULL, 0); + SpiNor_CsDeassert(cfg); + if (!err) + err = SpiNor_WaitReady(cfg); + +cleanup: + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor3b_Read(whal_Flash *flashDev, size_t addr, uint8_t *data, + size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[4]; + whal_Error err; + + if (!flashDev || !flashDev->cfg || !data || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr & ~0xFFFFFF || addr >= cfg->capacity || + dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + err = SpiNor_WaitReady(cfg); + if (err) + goto cleanup; + + SpiNor_BuildCmdAddr(frame, CMD_READ_DATA, addr); + err = SpiNor_CsAssert(cfg); + if (err) + goto cleanup; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 4, NULL, 0); + if (!err) + err = whal_Spi_SendRecv(cfg->spiDev, NULL, 0, data, dataSz); + SpiNor_CsDeassert(cfg); + +cleanup: + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor3b_Write(whal_Flash *flashDev, size_t addr, + const uint8_t *data, size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[4]; + whal_Error err; + size_t chunk; + size_t offset; + size_t pageRemaining; + + if (!flashDev || !flashDev->cfg || !data || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr & ~0xFFFFFF || addr >= cfg->capacity || + dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + /* + * Page Program cannot cross page boundaries. Split writes so each + * transaction stays within a single page. + */ + offset = 0; + while (offset < dataSz) { + pageRemaining = cfg->pageSz - ((addr + offset) & (cfg->pageSz - 1)); + chunk = dataSz - offset; + if (chunk > pageRemaining) + chunk = pageRemaining; + + err = SpiNor_WaitReady(cfg); + if (err) + break; + + err = SpiNor_WriteEnable(cfg); + if (err) + break; + + SpiNor_BuildCmdAddr(frame, CMD_PAGE_PROGRAM, addr + offset); + err = SpiNor_CsAssert(cfg); + if (err) + break; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 4, NULL, 0); + if (!err) + err = whal_Spi_SendRecv(cfg->spiDev, data + offset, chunk, + NULL, 0); + SpiNor_CsDeassert(cfg); + if (err) + break; + + offset += chunk; + } + + /* Wait for final page program to complete */ + if (!err) + err = SpiNor_WaitReady(cfg); + + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor3b_Erase4k(whal_Flash *flashDev, size_t addr, + size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[4]; + whal_Error err; + size_t end; + size_t cur; + + if (!flashDev || !flashDev->cfg || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr & ~0xFFFFFF || addr >= cfg->capacity || + dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + if ((addr | dataSz) & (ERASE_SZ_4K - 1)) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + end = addr + dataSz; + cur = addr; + + while (cur < end) { + err = SpiNor_WaitReady(cfg); + if (err) + break; + + err = SpiNor_WriteEnable(cfg); + if (err) + break; + + SpiNor_BuildCmdAddr(frame, CMD_SECTOR_ERASE, cur); + err = SpiNor_CsAssert(cfg); + if (err) + break; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 4, NULL, 0); + SpiNor_CsDeassert(cfg); + if (err) + break; + + cur += ERASE_SZ_4K; + } + + if (!err) + err = SpiNor_WaitReady(cfg); + + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor3b_Erase32k(whal_Flash *flashDev, size_t addr, + size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[4]; + whal_Error err; + size_t end; + size_t cur; + + if (!flashDev || !flashDev->cfg || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr & ~0xFFFFFF || addr >= cfg->capacity || + dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + if ((addr | dataSz) & (ERASE_SZ_32K - 1)) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + end = addr + dataSz; + cur = addr; + + while (cur < end) { + err = SpiNor_WaitReady(cfg); + if (err) + break; + + err = SpiNor_WriteEnable(cfg); + if (err) + break; + + SpiNor_BuildCmdAddr(frame, CMD_BLOCK_ERASE32, cur); + err = SpiNor_CsAssert(cfg); + if (err) + break; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 4, NULL, 0); + SpiNor_CsDeassert(cfg); + if (err) + break; + + cur += ERASE_SZ_32K; + } + + if (!err) + err = SpiNor_WaitReady(cfg); + + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor3b_Erase64k(whal_Flash *flashDev, size_t addr, + size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[4]; + whal_Error err; + size_t end; + size_t cur; + + if (!flashDev || !flashDev->cfg || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr & ~0xFFFFFF || addr >= cfg->capacity || + dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + if ((addr | dataSz) & (ERASE_SZ_64K - 1)) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + end = addr + dataSz; + cur = addr; + + while (cur < end) { + err = SpiNor_WaitReady(cfg); + if (err) + break; + + err = SpiNor_WriteEnable(cfg); + if (err) + break; + + SpiNor_BuildCmdAddr(frame, CMD_BLOCK_ERASE64, cur); + err = SpiNor_CsAssert(cfg); + if (err) + break; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 4, NULL, 0); + SpiNor_CsDeassert(cfg); + if (err) + break; + + cur += ERASE_SZ_64K; + } + + if (!err) + err = SpiNor_WaitReady(cfg); + + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor_EraseChip(whal_Flash *flashDev, size_t addr, + size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + whal_Error err; + + (void)addr; + (void)dataSz; + + if (!flashDev || !flashDev->cfg) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + err = SpiNor_WaitReady(cfg); + if (!err) + err = SpiNor_WriteEnable(cfg); + if (!err) + err = SpiNor_Cmd(cfg, CMD_CHIP_ERASE); + if (!err) + err = SpiNor_WaitReady(cfg); + + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +/* --- Fast Read (3-byte addr) --- */ + +whal_Error whal_SpiNor3b_ReadFast(whal_Flash *flashDev, size_t addr, + uint8_t *data, size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[5]; + whal_Error err; + + if (!flashDev || !flashDev->cfg || !data || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr & ~0xFFFFFF || addr >= cfg->capacity || + dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + err = SpiNor_WaitReady(cfg); + if (err) + goto cleanup; + + SpiNor_BuildCmdAddr(frame, CMD_FAST_READ, addr); + frame[4] = DUMMY; + err = SpiNor_CsAssert(cfg); + if (err) + goto cleanup; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 5, NULL, 0); + if (!err) + err = whal_Spi_SendRecv(cfg->spiDev, NULL, 0, data, dataSz); + SpiNor_CsDeassert(cfg); + +cleanup: + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +/* --- Dedicated 4-byte address commands --- */ + +whal_Error whal_SpiNor4bMode_Init(whal_Flash *flashDev) +{ + whal_SpiNor_Cfg *cfg; + whal_Error err; + + err = whal_SpiNor_Init(flashDev); + if (err) + return err; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + err = SpiNor_Cmd(cfg, CMD_ENTER_4B_MODE); + + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor4b_Read(whal_Flash *flashDev, size_t addr, + uint8_t *data, size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[5]; + whal_Error err; + + if (!flashDev || !flashDev->cfg || !data || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr >= cfg->capacity || dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + err = SpiNor_WaitReady(cfg); + if (err) + goto cleanup; + + SpiNor_BuildCmdAddr4b(frame, CMD_READ_DATA_4B, addr); + err = SpiNor_CsAssert(cfg); + if (err) + goto cleanup; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 5, NULL, 0); + if (!err) + err = whal_Spi_SendRecv(cfg->spiDev, NULL, 0, data, dataSz); + SpiNor_CsDeassert(cfg); + +cleanup: + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor4b_ReadFast(whal_Flash *flashDev, size_t addr, + uint8_t *data, size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[6]; + whal_Error err; + + if (!flashDev || !flashDev->cfg || !data || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr >= cfg->capacity || dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + err = SpiNor_WaitReady(cfg); + if (err) + goto cleanup; + + SpiNor_BuildCmdAddr4b(frame, CMD_FAST_READ_4B, addr); + frame[5] = DUMMY; + err = SpiNor_CsAssert(cfg); + if (err) + goto cleanup; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 6, NULL, 0); + if (!err) + err = whal_Spi_SendRecv(cfg->spiDev, NULL, 0, data, dataSz); + SpiNor_CsDeassert(cfg); + +cleanup: + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor4b_Write(whal_Flash *flashDev, size_t addr, + const uint8_t *data, size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[5]; + whal_Error err; + size_t chunk; + size_t offset; + size_t pageRemaining; + + if (!flashDev || !flashDev->cfg || !data || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr >= cfg->capacity || dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + offset = 0; + while (offset < dataSz) { + pageRemaining = cfg->pageSz - ((addr + offset) & (cfg->pageSz - 1)); + chunk = dataSz - offset; + if (chunk > pageRemaining) + chunk = pageRemaining; + + err = SpiNor_WaitReady(cfg); + if (err) + break; + + err = SpiNor_WriteEnable(cfg); + if (err) + break; + + SpiNor_BuildCmdAddr4b(frame, CMD_PAGE_PROGRAM_4B, addr + offset); + err = SpiNor_CsAssert(cfg); + if (err) + break; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 5, NULL, 0); + if (!err) + err = whal_Spi_SendRecv(cfg->spiDev, data + offset, chunk, + NULL, 0); + SpiNor_CsDeassert(cfg); + if (err) + break; + + offset += chunk; + } + + if (!err) + err = SpiNor_WaitReady(cfg); + + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor4b_Erase4k(whal_Flash *flashDev, size_t addr, + size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[5]; + whal_Error err; + size_t end; + size_t cur; + + if (!flashDev || !flashDev->cfg || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr >= cfg->capacity || dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + if ((addr | dataSz) & (ERASE_SZ_4K - 1)) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + end = addr + dataSz; + cur = addr; + + while (cur < end) { + err = SpiNor_WaitReady(cfg); + if (err) + break; + + err = SpiNor_WriteEnable(cfg); + if (err) + break; + + SpiNor_BuildCmdAddr4b(frame, CMD_SECTOR_ERASE_4B, cur); + err = SpiNor_CsAssert(cfg); + if (err) + break; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 5, NULL, 0); + SpiNor_CsDeassert(cfg); + if (err) + break; + + cur += ERASE_SZ_4K; + } + + if (!err) + err = SpiNor_WaitReady(cfg); + + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor4b_Erase64k(whal_Flash *flashDev, size_t addr, + size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[5]; + whal_Error err; + size_t end; + size_t cur; + + if (!flashDev || !flashDev->cfg || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr >= cfg->capacity || dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + if ((addr | dataSz) & (ERASE_SZ_64K - 1)) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + end = addr + dataSz; + cur = addr; + + while (cur < end) { + err = SpiNor_WaitReady(cfg); + if (err) + break; + + err = SpiNor_WriteEnable(cfg); + if (err) + break; + + SpiNor_BuildCmdAddr4b(frame, CMD_BLOCK_ERASE64_4B, cur); + err = SpiNor_CsAssert(cfg); + if (err) + break; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 5, NULL, 0); + SpiNor_CsDeassert(cfg); + if (err) + break; + + cur += ERASE_SZ_64K; + } + + if (!err) + err = SpiNor_WaitReady(cfg); + + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +/* --- 4-byte address mode (0xB7) variants --- */ + +whal_Error whal_SpiNor4bMode_Read(whal_Flash *flashDev, size_t addr, + uint8_t *data, size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[5]; + whal_Error err; + + if (!flashDev || !flashDev->cfg || !data || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr >= cfg->capacity || dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + err = SpiNor_WaitReady(cfg); + if (err) + goto cleanup; + + SpiNor_BuildCmdAddr4b(frame, CMD_READ_DATA, addr); + err = SpiNor_CsAssert(cfg); + if (err) + goto cleanup; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 5, NULL, 0); + if (!err) + err = whal_Spi_SendRecv(cfg->spiDev, NULL, 0, data, dataSz); + SpiNor_CsDeassert(cfg); + +cleanup: + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor4bMode_ReadFast(whal_Flash *flashDev, size_t addr, + uint8_t *data, size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[6]; + whal_Error err; + + if (!flashDev || !flashDev->cfg || !data || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr >= cfg->capacity || dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + err = SpiNor_WaitReady(cfg); + if (err) + goto cleanup; + + SpiNor_BuildCmdAddr4b(frame, CMD_FAST_READ, addr); + frame[5] = DUMMY; + err = SpiNor_CsAssert(cfg); + if (err) + goto cleanup; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 6, NULL, 0); + if (!err) + err = whal_Spi_SendRecv(cfg->spiDev, NULL, 0, data, dataSz); + SpiNor_CsDeassert(cfg); + +cleanup: + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor4bMode_Write(whal_Flash *flashDev, size_t addr, + const uint8_t *data, size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[5]; + whal_Error err; + size_t chunk; + size_t offset; + size_t pageRemaining; + + if (!flashDev || !flashDev->cfg || !data || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr >= cfg->capacity || dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + offset = 0; + while (offset < dataSz) { + pageRemaining = cfg->pageSz - ((addr + offset) & (cfg->pageSz - 1)); + chunk = dataSz - offset; + if (chunk > pageRemaining) + chunk = pageRemaining; + + err = SpiNor_WaitReady(cfg); + if (err) + break; + + err = SpiNor_WriteEnable(cfg); + if (err) + break; + + SpiNor_BuildCmdAddr4b(frame, CMD_PAGE_PROGRAM, addr + offset); + err = SpiNor_CsAssert(cfg); + if (err) + break; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 5, NULL, 0); + if (!err) + err = whal_Spi_SendRecv(cfg->spiDev, data + offset, chunk, + NULL, 0); + SpiNor_CsDeassert(cfg); + if (err) + break; + + offset += chunk; + } + + if (!err) + err = SpiNor_WaitReady(cfg); + + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor4bMode_Erase4k(whal_Flash *flashDev, size_t addr, + size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[5]; + whal_Error err; + size_t end; + size_t cur; + + if (!flashDev || !flashDev->cfg || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr >= cfg->capacity || dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + if ((addr | dataSz) & (ERASE_SZ_4K - 1)) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + end = addr + dataSz; + cur = addr; + + while (cur < end) { + err = SpiNor_WaitReady(cfg); + if (err) + break; + + err = SpiNor_WriteEnable(cfg); + if (err) + break; + + SpiNor_BuildCmdAddr4b(frame, CMD_SECTOR_ERASE, cur); + err = SpiNor_CsAssert(cfg); + if (err) + break; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 5, NULL, 0); + SpiNor_CsDeassert(cfg); + if (err) + break; + + cur += ERASE_SZ_4K; + } + + if (!err) + err = SpiNor_WaitReady(cfg); + + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor4bMode_Erase32k(whal_Flash *flashDev, size_t addr, + size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[5]; + whal_Error err; + size_t end; + size_t cur; + + if (!flashDev || !flashDev->cfg || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr >= cfg->capacity || dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + if ((addr | dataSz) & (ERASE_SZ_32K - 1)) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + end = addr + dataSz; + cur = addr; + + while (cur < end) { + err = SpiNor_WaitReady(cfg); + if (err) + break; + + err = SpiNor_WriteEnable(cfg); + if (err) + break; + + SpiNor_BuildCmdAddr4b(frame, CMD_BLOCK_ERASE32, cur); + err = SpiNor_CsAssert(cfg); + if (err) + break; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 5, NULL, 0); + SpiNor_CsDeassert(cfg); + if (err) + break; + + cur += ERASE_SZ_32K; + } + + if (!err) + err = SpiNor_WaitReady(cfg); + + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor4bMode_Erase64k(whal_Flash *flashDev, size_t addr, + size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[5]; + whal_Error err; + size_t end; + size_t cur; + + if (!flashDev || !flashDev->cfg || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr >= cfg->capacity || dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + if ((addr | dataSz) & (ERASE_SZ_64K - 1)) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + end = addr + dataSz; + cur = addr; + + while (cur < end) { + err = SpiNor_WaitReady(cfg); + if (err) + break; + + err = SpiNor_WriteEnable(cfg); + if (err) + break; + + SpiNor_BuildCmdAddr4b(frame, CMD_BLOCK_ERASE64, cur); + err = SpiNor_CsAssert(cfg); + if (err) + break; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 5, NULL, 0); + SpiNor_CsDeassert(cfg); + if (err) + break; + + cur += ERASE_SZ_64K; + } + + if (!err) + err = SpiNor_WaitReady(cfg); + + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +/* --- Extended Address Register variants --- */ + +/* + * Set the extended address register to select the 16 MB bank containing + * the given address. + */ +static whal_Error SpiNor_SetEAR(whal_SpiNor_Cfg *cfg, size_t addr) +{ + uint8_t frame[2]; + whal_Error err; + + err = SpiNor_WriteEnable(cfg); + if (err) + return err; + + frame[0] = CMD_WRITE_EAR; + frame[1] = (uint8_t)(addr >> 24); + err = SpiNor_CsAssert(cfg); + if (err) + return err; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 2, NULL, 0); + SpiNor_CsDeassert(cfg); + return err; +} + +whal_Error whal_SpiNor4bExReg_Read(whal_Flash *flashDev, size_t addr, + uint8_t *data, size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[4]; + whal_Error err; + size_t offset; + size_t chunk; + size_t bankRemaining; + + if (!flashDev || !flashDev->cfg || !data || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr >= cfg->capacity || dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + offset = 0; + while (offset < dataSz) { + bankRemaining = EXREG_BANK_SZ - + ((addr + offset) & (EXREG_BANK_SZ - 1)); + chunk = dataSz - offset; + if (chunk > bankRemaining) + chunk = bankRemaining; + + err = SpiNor_WaitReady(cfg); + if (err) + break; + + err = SpiNor_SetEAR(cfg, addr + offset); + if (err) + break; + + SpiNor_BuildCmdAddr(frame, CMD_READ_DATA, addr + offset); + err = SpiNor_CsAssert(cfg); + if (err) + break; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 4, NULL, 0); + if (!err) + err = whal_Spi_SendRecv(cfg->spiDev, NULL, 0, data + offset, + chunk); + SpiNor_CsDeassert(cfg); + if (err) + break; + + offset += chunk; + } + + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor4bExReg_ReadFast(whal_Flash *flashDev, size_t addr, + uint8_t *data, size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[5]; + whal_Error err; + size_t offset; + size_t chunk; + size_t bankRemaining; + + if (!flashDev || !flashDev->cfg || !data || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr >= cfg->capacity || dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + offset = 0; + while (offset < dataSz) { + bankRemaining = EXREG_BANK_SZ - + ((addr + offset) & (EXREG_BANK_SZ - 1)); + chunk = dataSz - offset; + if (chunk > bankRemaining) + chunk = bankRemaining; + + err = SpiNor_WaitReady(cfg); + if (err) + break; + + err = SpiNor_SetEAR(cfg, addr + offset); + if (err) + break; + + SpiNor_BuildCmdAddr(frame, CMD_FAST_READ, addr + offset); + frame[4] = DUMMY; + err = SpiNor_CsAssert(cfg); + if (err) + break; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 5, NULL, 0); + if (!err) + err = whal_Spi_SendRecv(cfg->spiDev, NULL, 0, data + offset, + chunk); + SpiNor_CsDeassert(cfg); + if (err) + break; + + offset += chunk; + } + + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor4bExReg_Write(whal_Flash *flashDev, size_t addr, + const uint8_t *data, size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[4]; + whal_Error err; + size_t chunk; + size_t offset; + size_t pageRemaining; + + if (!flashDev || !flashDev->cfg || !data || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr >= cfg->capacity || dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + offset = 0; + while (offset < dataSz) { + pageRemaining = cfg->pageSz - ((addr + offset) & (cfg->pageSz - 1)); + chunk = dataSz - offset; + if (chunk > pageRemaining) + chunk = pageRemaining; + + err = SpiNor_WaitReady(cfg); + if (err) + break; + + err = SpiNor_SetEAR(cfg, addr + offset); + if (err) + break; + + err = SpiNor_WriteEnable(cfg); + if (err) + break; + + SpiNor_BuildCmdAddr(frame, CMD_PAGE_PROGRAM, addr + offset); + err = SpiNor_CsAssert(cfg); + if (err) + break; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 4, NULL, 0); + if (!err) + err = whal_Spi_SendRecv(cfg->spiDev, data + offset, chunk, + NULL, 0); + SpiNor_CsDeassert(cfg); + if (err) + break; + + offset += chunk; + } + + if (!err) + err = SpiNor_WaitReady(cfg); + + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor4bExReg_Erase4k(whal_Flash *flashDev, size_t addr, + size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[4]; + whal_Error err; + size_t end; + size_t cur; + + if (!flashDev || !flashDev->cfg || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr >= cfg->capacity || dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + if ((addr | dataSz) & (ERASE_SZ_4K - 1)) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + end = addr + dataSz; + cur = addr; + + while (cur < end) { + err = SpiNor_WaitReady(cfg); + if (err) + break; + + err = SpiNor_SetEAR(cfg, cur); + if (err) + break; + + err = SpiNor_WriteEnable(cfg); + if (err) + break; + + SpiNor_BuildCmdAddr(frame, CMD_SECTOR_ERASE, cur); + err = SpiNor_CsAssert(cfg); + if (err) + break; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 4, NULL, 0); + SpiNor_CsDeassert(cfg); + if (err) + break; + + cur += ERASE_SZ_4K; + } + + if (!err) + err = SpiNor_WaitReady(cfg); + + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor4bExReg_Erase32k(whal_Flash *flashDev, size_t addr, + size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[4]; + whal_Error err; + size_t end; + size_t cur; + + if (!flashDev || !flashDev->cfg || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr >= cfg->capacity || dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + if ((addr | dataSz) & (ERASE_SZ_32K - 1)) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + end = addr + dataSz; + cur = addr; + + while (cur < end) { + err = SpiNor_WaitReady(cfg); + if (err) + break; + + err = SpiNor_SetEAR(cfg, cur); + if (err) + break; + + err = SpiNor_WriteEnable(cfg); + if (err) + break; + + SpiNor_BuildCmdAddr(frame, CMD_BLOCK_ERASE32, cur); + err = SpiNor_CsAssert(cfg); + if (err) + break; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 4, NULL, 0); + SpiNor_CsDeassert(cfg); + if (err) + break; + + cur += ERASE_SZ_32K; + } + + if (!err) + err = SpiNor_WaitReady(cfg); + + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SpiNor4bExReg_Erase64k(whal_Flash *flashDev, size_t addr, + size_t dataSz) +{ + whal_SpiNor_Cfg *cfg; + uint8_t frame[4]; + whal_Error err; + size_t end; + size_t cur; + + if (!flashDev || !flashDev->cfg || dataSz == 0) + return WHAL_EINVAL; + + cfg = (whal_SpiNor_Cfg *)flashDev->cfg; + + if (addr >= cfg->capacity || dataSz > cfg->capacity - addr) + return WHAL_EINVAL; + + if ((addr | dataSz) & (ERASE_SZ_64K - 1)) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + end = addr + dataSz; + cur = addr; + + while (cur < end) { + err = SpiNor_WaitReady(cfg); + if (err) + break; + + err = SpiNor_SetEAR(cfg, cur); + if (err) + break; + + err = SpiNor_WriteEnable(cfg); + if (err) + break; + + SpiNor_BuildCmdAddr(frame, CMD_BLOCK_ERASE64, cur); + err = SpiNor_CsAssert(cfg); + if (err) + break; + err = whal_Spi_SendRecv(cfg->spiDev, frame, 4, NULL, 0); + SpiNor_CsDeassert(cfg); + if (err) + break; + + cur += ERASE_SZ_64K; + } + + if (!err) + err = SpiNor_WaitReady(cfg); + + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +const whal_FlashDriver whal_SpiNor_Driver = { + .Init = whal_SpiNor_Init, + .Deinit = whal_SpiNor_Deinit, + .Lock = whal_SpiNor_Lock, + .Unlock = whal_SpiNor_Unlock, + .Read = whal_SpiNor3b_Read, + .Write = whal_SpiNor3b_Write, + .Erase = whal_SpiNor3b_Erase4k, +}; diff --git a/tests/README.md b/tests/README.md index da63bf0..ef70790 100644 --- a/tests/README.md +++ b/tests/README.md @@ -46,6 +46,19 @@ Each device directory contains: Board support (device instances, linker scripts, etc.) lives in the top-level `boards/` directory. See [boards/README.md](../boards/README.md) for details. +### Peripheral Devices + +External peripheral drivers (SPI-NOR flash, SD cards, etc.) are opt-in. Enable +them by setting the corresponding variable when building: + +``` +make BOARD=stm32wb55xx_nucleo PERIPHERAL_SPI_NOR_W25Q64=1 +make BOARD=stm32wb55xx_nucleo PERIPHERAL_SDHC_SPI_SDCARD32GB=1 +``` + +Peripheral devices are automatically tested by their matching test suite (e.g., +`flash` tests iterate all entries in `g_peripheralFlash[]`). + ## Core Tests Host-side unit tests (bitops, dispatch, endian) live in `core/` and build with diff --git a/tests/flash/test_flash.c b/tests/flash/test_flash.c index ca2da1d..a13c641 100644 --- a/tests/flash/test_flash.c +++ b/tests/flash/test_flash.c @@ -2,6 +2,7 @@ #include #include "board.h" #include "test.h" +#include "peripheral.h" static whal_Flash *g_testFlashDev; static size_t g_testFlashAddr; @@ -71,4 +72,12 @@ void whal_Test_Flash(void) g_testFlashAddr = BOARD_FLASH_TEST_ADDR; g_testFlashSectorSz = BOARD_FLASH_SECTOR_SZ; run_flash_tests("on-chip"); + + /* Test peripheral flash devices */ + for (size_t i = 0; g_peripheralFlash[i].dev; i++) { + g_testFlashDev = g_peripheralFlash[i].dev; + g_testFlashAddr = 0; + g_testFlashSectorSz = g_peripheralFlash[i].sectorSz; + run_flash_tests(g_peripheralFlash[i].name); + } } diff --git a/wolfHAL/flash/spi_nor.h b/wolfHAL/flash/spi_nor.h new file mode 100644 index 0000000..00c31a0 --- /dev/null +++ b/wolfHAL/flash/spi_nor.h @@ -0,0 +1,555 @@ +#ifndef WHAL_SPI_NOR_H +#define WHAL_SPI_NOR_H + +#include +#include +#include +#include +#include +#include + +/* + * @file spi_nor.h + * @brief SPI-NOR flash driver. + * + * Implements the whal_Flash interface for SPI-NOR flash devices using + * standard JEDEC commands. + * + * The driver provides composable function variants for four addressing modes: + * + * - **3b** — Standard 3-byte (24-bit) addressing. Supports up to 16 MB. + * - **4b** — Dedicated 4-byte address opcodes (0x13, 0x12, 0x21, 0xDC). + * Does not change global chip state. No 32 KB erase available. + * - **4bMode** — Enter 4-Byte Address Mode (0xB7). Standard opcodes use + * 4-byte addresses. All erase sizes available. Persists until exit or + * power cycle. + * - **4bExReg** — Extended Address Register (0xC5/0xC8). Sets a bank + * register for the upper address bits. Standard opcodes address within + * the selected 16 MB bank. All erase sizes available. + * + * Users compose a whal_FlashDriver vtable from these functions to match + * their specific flash part. Unused functions are stripped by the linker. + * + * The driver requires a SPI bus device and a GPIO pin for chip select. + * Page size and total capacity are configurable to support a range of + * SPI-NOR parts (W25Qxx, MX25L, AT25SF, S25FL, etc.). + */ + +/* + * @brief SPI-NOR device configuration. + */ +typedef struct whal_SpiNor_Cfg { + whal_Spi *spiDev; /* SPI bus device */ + whal_Spi_ComCfg *spiComCfg; /* SPI session config for StartCom */ + whal_Gpio *gpioDev; /* GPIO device for chip select */ + size_t csPin; /* GPIO pin index for chip select */ + whal_Timeout *timeout; + size_t pageSz; /* Page program size in bytes (typically 256) */ + size_t capacity; /* Total flash capacity in bytes */ +} whal_SpiNor_Cfg; + +/* + * @brief Default driver vtable using 3-byte addressing with 4 KB erase. + */ +extern const whal_FlashDriver whal_SpiNor_Driver; + +/* -------------------------------------------------------------------- */ +/* Init / Deinit */ +/* -------------------------------------------------------------------- */ + +/* + * @brief Initialize a SPI-NOR device in 3-byte address mode. + * + * Releases the device from Deep Power-Down, waits for any in-progress + * operation, and reads the JEDEC ID to verify a device is present. + * + * @param flashDev Flash device instance. + * + * @retval WHAL_SUCCESS Device detected and ready. + * @retval WHAL_EINVAL Null pointer or invalid configuration. + * @retval WHAL_EHARDWARE No device detected (JEDEC ID 0x00 or 0xFF). + * @retval WHAL_ETIMEOUT Device did not become ready. + */ +whal_Error whal_SpiNor_Init(whal_Flash *flashDev); + +/* + * @brief Initialize a SPI-NOR device and enter 4-byte address mode. + * + * Performs standard initialization via whal_SpiNor_Init(), then sends the + * Enter 4-Byte Address Mode command (0xB7). After this call, all 4bMode + * variant functions can be used. The device remains in 4-byte mode until + * power cycle or an explicit exit command. + * + * @param flashDev Flash device instance. + * + * @retval WHAL_SUCCESS Device initialized and 4-byte mode command sent. + * @retval WHAL_EINVAL Null pointer or invalid configuration. + * @retval WHAL_EHARDWARE No device detected. + */ +whal_Error whal_SpiNor4bMode_Init(whal_Flash *flashDev); + +/* + * @brief Deinitialize a SPI-NOR device. + * + * Deasserts chip select. Does not send any commands to the device. + * + * @param flashDev Flash device instance. + * + * @retval WHAL_SUCCESS Deinit completed. + * @retval WHAL_EINVAL Null pointer. + */ +whal_Error whal_SpiNor_Deinit(whal_Flash *flashDev); + +/* -------------------------------------------------------------------- */ +/* Lock / Unlock */ +/* -------------------------------------------------------------------- */ + +/* + * @brief Lock the entire device by setting all block protect bits. + * + * Writes SR1_BP0|BP1|BP2 to Status Register 1. The @p addr and @p len + * parameters are ignored — the entire device is locked. + * + * @param flashDev Flash device instance. + * @param addr Ignored. + * @param len Ignored. + * + * @retval WHAL_SUCCESS Block protect bits set. + * @retval WHAL_EINVAL Null pointer. + * @retval WHAL_EHARDWARE Write enable failed. + */ +whal_Error whal_SpiNor_Lock(whal_Flash *flashDev, size_t addr, size_t len); + +/* + * @brief Unlock the entire device by clearing all block protect bits. + * + * Writes 0x00 to Status Register 1. The @p addr and @p len parameters + * are ignored — the entire device is unlocked. + * + * @param flashDev Flash device instance. + * @param addr Ignored. + * @param len Ignored. + * + * @retval WHAL_SUCCESS Block protect bits cleared. + * @retval WHAL_EINVAL Null pointer. + * @retval WHAL_EHARDWARE Write enable failed. + */ +whal_Error whal_SpiNor_Unlock(whal_Flash *flashDev, size_t addr, size_t len); + +/* -------------------------------------------------------------------- */ +/* Read — 3-byte address */ +/* -------------------------------------------------------------------- */ + +/* + * @brief Read data using the Read command (0x03) with 3-byte address. + * + * Standard read, limited to approximately 50 MHz clock speed. + * + * @param flashDev Flash device instance. + * @param addr Byte address to start reading from. + * @param data Destination buffer. + * @param dataSz Number of bytes to read. + * + * @retval WHAL_SUCCESS Read completed. + * @retval WHAL_EINVAL Null pointer, zero size, or out of bounds. + */ +whal_Error whal_SpiNor3b_Read(whal_Flash *flashDev, size_t addr, uint8_t *data, size_t dataSz); + +/* + * @brief Read data using the Fast Read command (0x0B) with 3-byte address. + * + * Inserts one dummy byte after the address. Supports clock speeds up to + * the device maximum (typically 104 MHz). + * + * @param flashDev Flash device instance. + * @param addr Byte address to start reading from. + * @param data Destination buffer. + * @param dataSz Number of bytes to read. + * + * @retval WHAL_SUCCESS Read completed. + * @retval WHAL_EINVAL Null pointer, zero size, or out of bounds. + */ +whal_Error whal_SpiNor3b_ReadFast(whal_Flash *flashDev, size_t addr, uint8_t *data, size_t dataSz); + +/* -------------------------------------------------------------------- */ +/* Read — dedicated 4-byte address commands */ +/* -------------------------------------------------------------------- */ + +/* + * @brief Read data using the dedicated 4-byte Read command (0x13). + * + * Uses a device-specific opcode that carries a 4-byte address. Does not + * require entering 4-byte address mode. Limited to approximately 50 MHz. + * + * @param flashDev Flash device instance. + * @param addr Byte address to start reading from (up to 32 bits). + * @param data Destination buffer. + * @param dataSz Number of bytes to read. + * + * @retval WHAL_SUCCESS Read completed. + * @retval WHAL_EINVAL Null pointer, zero size, or out of bounds. + */ +whal_Error whal_SpiNor4b_Read(whal_Flash *flashDev, size_t addr, uint8_t *data, size_t dataSz); + +/* + * @brief Read data using the dedicated 4-byte Fast Read command (0x0C). + * + * Uses a device-specific opcode with 4-byte address and one dummy byte. + * Supports full clock speed without entering 4-byte address mode. + * + * @param flashDev Flash device instance. + * @param addr Byte address to start reading from (up to 32 bits). + * @param data Destination buffer. + * @param dataSz Number of bytes to read. + * + * @retval WHAL_SUCCESS Read completed. + * @retval WHAL_EINVAL Null pointer, zero size, or out of bounds. + */ +whal_Error whal_SpiNor4b_ReadFast(whal_Flash *flashDev, size_t addr, uint8_t *data, size_t dataSz); + +/* -------------------------------------------------------------------- */ +/* Read — 4-byte address mode (after 0xB7) */ +/* -------------------------------------------------------------------- */ + +/* + * @brief Read data using Read (0x03) in 4-byte address mode. + * + * Requires prior call to whal_SpiNor4bMode_Init(). Uses the standard + * opcode with a 4-byte address frame. + * + * @param flashDev Flash device instance. + * @param addr Byte address to start reading from (up to 32 bits). + * @param data Destination buffer. + * @param dataSz Number of bytes to read. + * + * @retval WHAL_SUCCESS Read completed. + * @retval WHAL_EINVAL Null pointer, zero size, or out of bounds. + */ +whal_Error whal_SpiNor4bMode_Read(whal_Flash *flashDev, size_t addr, uint8_t *data, size_t dataSz); + +/* + * @brief Read data using Fast Read (0x0B) in 4-byte address mode. + * + * Requires prior call to whal_SpiNor4bMode_Init(). Uses the standard + * opcode with a 4-byte address frame and one dummy byte. + * + * @param flashDev Flash device instance. + * @param addr Byte address to start reading from (up to 32 bits). + * @param data Destination buffer. + * @param dataSz Number of bytes to read. + * + * @retval WHAL_SUCCESS Read completed. + * @retval WHAL_EINVAL Null pointer, zero size, or out of bounds. + */ +whal_Error whal_SpiNor4bMode_ReadFast(whal_Flash *flashDev, size_t addr, uint8_t *data, size_t dataSz); + +/* -------------------------------------------------------------------- */ +/* Read — extended address register (bank select) */ +/* -------------------------------------------------------------------- */ + +/* + * @brief Read data using Read (0x03) with extended address register. + * + * Sets the extended address register (0xC5) to select the 16 MB bank + * containing @p addr, then issues a standard 3-byte Read. Supports + * addresses beyond 16 MB on parts that lack 4-byte address commands. + * + * @param flashDev Flash device instance. + * @param addr Byte address to start reading from (up to 32 bits). + * @param data Destination buffer. + * @param dataSz Number of bytes to read. + * + * @retval WHAL_SUCCESS Read completed. + * @retval WHAL_EINVAL Null pointer, zero size, or out of bounds. + */ +whal_Error whal_SpiNor4bExReg_Read(whal_Flash *flashDev, size_t addr, uint8_t *data, size_t dataSz); + +/* + * @brief Read data using Fast Read (0x0B) with extended address register. + * + * Sets the extended address register then issues a standard 3-byte Fast + * Read with one dummy byte. + * + * @param flashDev Flash device instance. + * @param addr Byte address to start reading from (up to 32 bits). + * @param data Destination buffer. + * @param dataSz Number of bytes to read. + * + * @retval WHAL_SUCCESS Read completed. + * @retval WHAL_EINVAL Null pointer, zero size, or out of bounds. + */ +whal_Error whal_SpiNor4bExReg_ReadFast(whal_Flash *flashDev, size_t addr, uint8_t *data, size_t dataSz); + +/* -------------------------------------------------------------------- */ +/* Write — 3-byte address */ +/* -------------------------------------------------------------------- */ + +/* + * @brief Program data using Page Program (0x02) with 3-byte address. + * + * Automatically splits writes at page boundaries. Each page program is + * preceded by a Write Enable and followed by a WIP poll. + * + * @param flashDev Flash device instance. + * @param addr Byte address to start writing at. + * @param data Source buffer. + * @param dataSz Number of bytes to write. + * + * @retval WHAL_SUCCESS Write completed. + * @retval WHAL_EINVAL Null pointer, zero size, or out of bounds. + */ +whal_Error whal_SpiNor3b_Write(whal_Flash *flashDev, size_t addr, const uint8_t *data, size_t dataSz); + +/* -------------------------------------------------------------------- */ +/* Write — dedicated 4-byte address command */ +/* -------------------------------------------------------------------- */ + +/* + * @brief Program data using the dedicated 4-byte Page Program (0x12). + * + * Same behavior as whal_SpiNor3b_Write() but uses the dedicated 4-byte + * opcode. Does not require entering 4-byte address mode. + * + * @param flashDev Flash device instance. + * @param addr Byte address to start writing at (up to 32 bits). + * @param data Source buffer. + * @param dataSz Number of bytes to write. + * + * @retval WHAL_SUCCESS Write completed. + * @retval WHAL_EINVAL Null pointer, zero size, or out of bounds. + */ +whal_Error whal_SpiNor4b_Write(whal_Flash *flashDev, size_t addr, const uint8_t *data, size_t dataSz); + +/* -------------------------------------------------------------------- */ +/* Write — 4-byte address mode (after 0xB7) */ +/* -------------------------------------------------------------------- */ + +/* + * @brief Program data using Page Program (0x02) in 4-byte address mode. + * + * Requires prior call to whal_SpiNor4bMode_Init(). Uses the standard + * opcode with a 4-byte address frame. + * + * @param flashDev Flash device instance. + * @param addr Byte address to start writing at (up to 32 bits). + * @param data Source buffer. + * @param dataSz Number of bytes to write. + * + * @retval WHAL_SUCCESS Write completed. + * @retval WHAL_EINVAL Null pointer, zero size, or out of bounds. + */ +whal_Error whal_SpiNor4bMode_Write(whal_Flash *flashDev, size_t addr, const uint8_t *data, size_t dataSz); + +/* -------------------------------------------------------------------- */ +/* Write — extended address register */ +/* -------------------------------------------------------------------- */ + +/* + * @brief Program data using Page Program (0x02) with extended address register. + * + * Sets the extended address register before each page program to select + * the correct 16 MB bank. Re-sets the register on every page in case + * the write crosses a bank boundary. + * + * @param flashDev Flash device instance. + * @param addr Byte address to start writing at (up to 32 bits). + * @param data Source buffer. + * @param dataSz Number of bytes to write. + * + * @retval WHAL_SUCCESS Write completed. + * @retval WHAL_EINVAL Null pointer, zero size, or out of bounds. + */ +whal_Error whal_SpiNor4bExReg_Write(whal_Flash *flashDev, size_t addr, const uint8_t *data, size_t dataSz); + +/* -------------------------------------------------------------------- */ +/* Erase — 3-byte address */ +/* -------------------------------------------------------------------- */ + +/* + * @brief Erase using 4 KB Sector Erase (0x20) with 3-byte address. + * + * Address must be 4 KB aligned. + * + * @param flashDev Flash device instance. + * @param addr 4 KB-aligned byte address. + * @param dataSz Number of bytes to erase. + * + * @retval WHAL_SUCCESS Erase completed. + * @retval WHAL_EINVAL Null pointer, zero size, out of bounds, or unaligned. + */ +whal_Error whal_SpiNor3b_Erase4k(whal_Flash *flashDev, size_t addr, size_t dataSz); + +/* + * @brief Erase using 32 KB Block Erase (0x52) with 3-byte address. + * + * Address must be 32 KB aligned. + * + * @param flashDev Flash device instance. + * @param addr 32 KB-aligned byte address. + * @param dataSz Number of bytes to erase. + * + * @retval WHAL_SUCCESS Erase completed. + * @retval WHAL_EINVAL Null pointer, zero size, out of bounds, or unaligned. + */ +whal_Error whal_SpiNor3b_Erase32k(whal_Flash *flashDev, size_t addr, size_t dataSz); + +/* + * @brief Erase using 64 KB Block Erase (0xD8) with 3-byte address. + * + * Address must be 64 KB aligned. + * + * @param flashDev Flash device instance. + * @param addr 64 KB-aligned byte address. + * @param dataSz Number of bytes to erase. + * + * @retval WHAL_SUCCESS Erase completed. + * @retval WHAL_EINVAL Null pointer, zero size, out of bounds, or unaligned. + */ +whal_Error whal_SpiNor3b_Erase64k(whal_Flash *flashDev, size_t addr, size_t dataSz); + +/* + * @brief Erase the entire device using Chip Erase (0xC7). + * + * The @p addr and @p dataSz parameters are ignored. + * + * @param flashDev Flash device instance. + * @param addr Ignored. + * @param dataSz Ignored. + * + * @retval WHAL_SUCCESS Chip erase completed. + * @retval WHAL_EINVAL Null pointer. + */ +whal_Error whal_SpiNor_EraseChip(whal_Flash *flashDev, size_t addr, size_t dataSz); + +/* -------------------------------------------------------------------- */ +/* Erase — dedicated 4-byte address commands */ +/* -------------------------------------------------------------------- */ + +/* + * @brief Erase using dedicated 4-byte Sector Erase (0x21). + * + * Address must be 4 KB aligned. No 32 KB variant exists in the dedicated + * 4-byte command set. + * + * @param flashDev Flash device instance. + * @param addr 4 KB-aligned byte address (up to 32 bits). + * @param dataSz Number of bytes to erase. + * + * @retval WHAL_SUCCESS Erase completed. + * @retval WHAL_EINVAL Null pointer, zero size, out of bounds, or unaligned. + */ +whal_Error whal_SpiNor4b_Erase4k(whal_Flash *flashDev, size_t addr, size_t dataSz); + +/* + * @brief Erase using dedicated 4-byte Block Erase (0xDC). + * + * Address must be 64 KB aligned. + * + * @param flashDev Flash device instance. + * @param addr 64 KB-aligned byte address (up to 32 bits). + * @param dataSz Number of bytes to erase. + * + * @retval WHAL_SUCCESS Erase completed. + * @retval WHAL_EINVAL Null pointer, zero size, out of bounds, or unaligned. + */ +whal_Error whal_SpiNor4b_Erase64k(whal_Flash *flashDev, size_t addr, size_t dataSz); + +/* -------------------------------------------------------------------- */ +/* Erase — 4-byte address mode (after 0xB7) */ +/* -------------------------------------------------------------------- */ + +/* + * @brief Erase using Sector Erase (0x20) in 4-byte address mode. + * + * Requires prior call to whal_SpiNor4bMode_Init(). Address must be + * 4 KB aligned. + * + * @param flashDev Flash device instance. + * @param addr 4 KB-aligned byte address (up to 32 bits). + * @param dataSz Number of bytes to erase. + * + * @retval WHAL_SUCCESS Erase completed. + * @retval WHAL_EINVAL Null pointer, zero size, out of bounds, or unaligned. + */ +whal_Error whal_SpiNor4bMode_Erase4k(whal_Flash *flashDev, size_t addr, size_t dataSz); + +/* + * @brief Erase using Block Erase (0x52) in 4-byte address mode. + * + * Requires prior call to whal_SpiNor4bMode_Init(). Address must be + * 32 KB aligned. + * + * @param flashDev Flash device instance. + * @param addr 32 KB-aligned byte address (up to 32 bits). + * @param dataSz Number of bytes to erase. + * + * @retval WHAL_SUCCESS Erase completed. + * @retval WHAL_EINVAL Null pointer, zero size, out of bounds, or unaligned. + */ +whal_Error whal_SpiNor4bMode_Erase32k(whal_Flash *flashDev, size_t addr, size_t dataSz); + +/* + * @brief Erase using Block Erase (0xD8) in 4-byte address mode. + * + * Requires prior call to whal_SpiNor4bMode_Init(). Address must be + * 64 KB aligned. + * + * @param flashDev Flash device instance. + * @param addr 64 KB-aligned byte address (up to 32 bits). + * @param dataSz Number of bytes to erase. + * + * @retval WHAL_SUCCESS Erase completed. + * @retval WHAL_EINVAL Null pointer, zero size, out of bounds, or unaligned. + */ +whal_Error whal_SpiNor4bMode_Erase64k(whal_Flash *flashDev, size_t addr, size_t dataSz); + +/* -------------------------------------------------------------------- */ +/* Erase — extended address register */ +/* -------------------------------------------------------------------- */ + +/* + * @brief Erase using Sector Erase (0x20) with extended address register. + * + * Sets the extended address register before each erase to select the + * correct 16 MB bank. Address must be 4 KB aligned. + * + * @param flashDev Flash device instance. + * @param addr 4 KB-aligned byte address (up to 32 bits). + * @param dataSz Number of bytes to erase. + * + * @retval WHAL_SUCCESS Erase completed. + * @retval WHAL_EINVAL Null pointer, zero size, out of bounds, or unaligned. + */ +whal_Error whal_SpiNor4bExReg_Erase4k(whal_Flash *flashDev, size_t addr, size_t dataSz); + +/* + * @brief Erase using Block Erase (0x52) with extended address register. + * + * Sets the extended address register before each erase. Address must be + * 32 KB aligned. + * + * @param flashDev Flash device instance. + * @param addr 32 KB-aligned byte address (up to 32 bits). + * @param dataSz Number of bytes to erase. + * + * @retval WHAL_SUCCESS Erase completed. + * @retval WHAL_EINVAL Null pointer, zero size, out of bounds, or unaligned. + */ +whal_Error whal_SpiNor4bExReg_Erase32k(whal_Flash *flashDev, size_t addr, size_t dataSz); + +/* + * @brief Erase using Block Erase (0xD8) with extended address register. + * + * Sets the extended address register before each erase. Address must be + * 64 KB aligned. + * + * @param flashDev Flash device instance. + * @param addr 64 KB-aligned byte address (up to 32 bits). + * @param dataSz Number of bytes to erase. + * + * @retval WHAL_SUCCESS Erase completed. + * @retval WHAL_EINVAL Null pointer, zero size, out of bounds, or unaligned. + */ +whal_Error whal_SpiNor4bExReg_Erase64k(whal_Flash *flashDev, size_t addr, size_t dataSz); + +#endif /* WHAL_SPI_NOR_H */