diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 11c898b..e0dd4c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: working-directory: tests/core run: CFLAGS="${{ matrix.extra_cflags }}" make run - cross-compile: + boards: runs-on: ubuntu-latest strategy: matrix: @@ -38,3 +38,18 @@ jobs: - name: Build tests working-directory: tests run: CFLAGS="${{ matrix.extra_cflags }}" make BOARD=${{ matrix.board }} + + peripheral-drivers: + runs-on: ubuntu-latest + strategy: + matrix: + source: + - src/block/sdhc_spi.c + steps: + - uses: actions/checkout@v4 + + - name: Install ARM toolchain + run: sudo apt-get update && sudo apt-get install -y gcc-arm-none-eabi + + - name: Compile ${{ matrix.source }} + run: arm-none-eabi-gcc -c -I. -Wall -Werror ${{ matrix.source }} -o /dev/null diff --git a/boards/peripheral/Makefile.inc b/boards/peripheral/Makefile.inc new file mode 100644 index 0000000..3eefb7c --- /dev/null +++ b/boards/peripheral/Makefile.inc @@ -0,0 +1,9 @@ +_PERIPHERAL_DIR := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST)))) + +BOARD_SOURCE += $(_PERIPHERAL_DIR)/peripheral.c + +ifdef PERIPHERAL_SDHC_SPI_SDCARD32GB +CFLAGS += -DPERIPHERAL_SDHC_SPI_SDCARD32GB +BOARD_SOURCE += $(_PERIPHERAL_DIR)/block/sdhc_spi_sdcard32gb.c +BOARD_SOURCE += $(WHAL_DIR)/src/block/sdhc_spi.c +endif diff --git a/boards/peripheral/block/sdhc_spi_sdcard32gb.c b/boards/peripheral/block/sdhc_spi_sdcard32gb.c new file mode 100644 index 0000000..30eeba6 --- /dev/null +++ b/boards/peripheral/block/sdhc_spi_sdcard32gb.c @@ -0,0 +1,21 @@ +#include "sdhc_spi_sdcard32gb.h" +#include +#include "board.h" + +static whal_Spi_ComCfg g_sdcardComCfg = { + .freq = 25000000, /* 25 MHz */ + .mode = WHAL_SPI_MODE_0, + .wordSz = 8, + .dataLines = 1, +}; + +whal_Block g_whalSdhcSpiSdcard32gb = { + .driver = &whal_SdhcSpi_Driver, + .cfg = &(whal_SdhcSpi_Cfg) { + .spiDev = &g_whalSpi, + .spiComCfg = &g_sdcardComCfg, + .gpioDev = &g_whalGpio, + .csPin = SPI_CS_PIN, + .timeout = &g_whalTimeout, + }, +}; diff --git a/boards/peripheral/block/sdhc_spi_sdcard32gb.h b/boards/peripheral/block/sdhc_spi_sdcard32gb.h new file mode 100644 index 0000000..4cf086a --- /dev/null +++ b/boards/peripheral/block/sdhc_spi_sdcard32gb.h @@ -0,0 +1,10 @@ +#ifndef BOARD_SDHC_SPI_SDCARD32GB_H +#define BOARD_SDHC_SPI_SDCARD32GB_H + +#include +#include +#include + +extern whal_Block g_whalSdhcSpiSdcard32gb; + +#endif /* BOARD_SDHC_SPI_SDCARD32GB_H */ diff --git a/boards/peripheral/peripheral.c b/boards/peripheral/peripheral.c new file mode 100644 index 0000000..7989c54 --- /dev/null +++ b/boards/peripheral/peripheral.c @@ -0,0 +1,19 @@ +#include "peripheral.h" + +#ifdef PERIPHERAL_SDHC_SPI_SDCARD32GB +#include "block/sdhc_spi_sdcard32gb.h" +#endif + +whal_PeripheralBlock_Cfg g_peripheralBlock[] = { +#ifdef PERIPHERAL_SDHC_SPI_SDCARD32GB + { + .name = "sdhc_spi_sdcard32gb", + .dev = &g_whalSdhcSpiSdcard32gb, + .blockSz = WHAL_SDHC_SPI_BLOCK_SZ, + .blockBuf = (uint8_t[WHAL_SDHC_SPI_BLOCK_SZ * 2]) {0}, + .blockBufSz = WHAL_SDHC_SPI_BLOCK_SZ * 2, + .erasedByte = 0x00, + }, +#endif + {0}, /* sentinel */ +}; diff --git a/boards/peripheral/peripheral.h b/boards/peripheral/peripheral.h new file mode 100644 index 0000000..e7bb2bd --- /dev/null +++ b/boards/peripheral/peripheral.h @@ -0,0 +1,21 @@ +#ifndef BOARD_PERIPHERAL_H +#define BOARD_PERIPHERAL_H + +#include +#include +#include +#include + +/* Peripheral block device test configuration */ +typedef struct { + const char *name; + whal_Block *dev; + uint8_t *blockBuf; /* Test buffer (must hold at least 2 blocks) */ + size_t blockBufSz; /* Size of blockBuf in bytes */ + size_t blockSz; /* Block size in bytes */ + uint8_t erasedByte; /* Expected byte value after erase (0x00 or 0xFF) */ +} whal_PeripheralBlock_Cfg; + +extern whal_PeripheralBlock_Cfg g_peripheralBlock[]; + +#endif /* BOARD_PERIPHERAL_H */ diff --git a/boards/pic32cz_curiosity_ultra/Makefile.inc b/boards/pic32cz_curiosity_ultra/Makefile.inc index 48c03be..303bf14 100644 --- a/boards/pic32cz_curiosity_ultra/Makefile.inc +++ b/boards/pic32cz_curiosity_ultra/Makefile.inc @@ -26,5 +26,6 @@ BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/timer.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/supply.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/flash.c) 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) diff --git a/boards/pic32cz_curiosity_ultra/board.c b/boards/pic32cz_curiosity_ultra/board.c index 58e18d7..df6b273 100644 --- a/boards/pic32cz_curiosity_ultra/board.c +++ b/boards/pic32cz_curiosity_ultra/board.c @@ -55,7 +55,7 @@ static const whal_Pic32czClock_Clk g_peripheralClocks[] = { .mclkEnablePos = 3, }, }; -#define PERIPHERAL_CLOCK_COUNT (sizeof(g_peripheralClocks) / sizeof(g_peripheralClocks[0])) +#define CLOCK_COUNT (sizeof(g_peripheralClocks) / sizeof(g_peripheralClocks[0])) /* GPIO */ whal_Gpio g_whalGpio = { @@ -168,7 +168,7 @@ whal_Error Board_Init(void) } /* Enable peripheral clocks */ - for (size_t i = 0; i < PERIPHERAL_CLOCK_COUNT; i++) { + for (size_t i = 0; i < CLOCK_COUNT; i++) { err = whal_Clock_Enable(&g_whalClock, &g_peripheralClocks[i]); if (err) return err; @@ -232,7 +232,7 @@ whal_Error Board_Deinit(void) } /* Disable peripheral clocks */ - for (size_t i = 0; i < PERIPHERAL_CLOCK_COUNT; i++) { + for (size_t i = 0; i < CLOCK_COUNT; i++) { err = whal_Clock_Disable(&g_whalClock, &g_peripheralClocks[i]); if (err) return err; diff --git a/boards/stm32wb55xx_nucleo/Makefile.inc b/boards/stm32wb55xx_nucleo/Makefile.inc index 6a1be19..955f1b0 100644 --- a/boards/stm32wb55xx_nucleo/Makefile.inc +++ b/boards/stm32wb55xx_nucleo/Makefile.inc @@ -1,7 +1,7 @@ _BOARD_DIR := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST)))) PLATFORM = stm32wb -TESTS ?= clock gpio flash timer rng crypto +TESTS ?= clock gpio flash timer rng crypto block GCC = $(GCC_PATH)arm-none-eabi-gcc LD = $(GCC_PATH)arm-none-eabi-ld @@ -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 @@ -25,7 +25,12 @@ BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/uart.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/timer.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/supply.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/flash.c) +BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/spi.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/rng.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/crypto.c) +BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/block.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/stm32wb_*.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/systick.c) + +# Peripheral devices +include $(WHAL_DIR)/boards/peripheral/Makefile.inc diff --git a/boards/stm32wb55xx_nucleo/board.c b/boards/stm32wb55xx_nucleo/board.c index cc63eaa..533fb8f 100644 --- a/boards/stm32wb55xx_nucleo/board.c +++ b/boards/stm32wb55xx_nucleo/board.c @@ -4,6 +4,7 @@ #include #include "board.h" #include +#include "peripheral.h" /* SysTick timing */ volatile uint32_t g_tick = 0; @@ -25,7 +26,7 @@ uint32_t Board_GetTick(void) } whal_Timeout g_whalTimeout = { - .timeoutTicks = 5, /* 5ms timeout */ + .timeoutTicks = 1000, /* 1s timeout */ .GetTick = Board_GetTick, }; @@ -50,26 +51,22 @@ whal_Clock g_whalClock = { static const whal_Stm32wbRcc_Clk g_flashClock = {WHAL_STM32WB55_FLASH_CLOCK}; -static const whal_Stm32wbRcc_Clk g_peripheralClocks[] = { +static const whal_Stm32wbRcc_Clk g_clocks[] = { + {WHAL_STM32WB55_GPIOA_CLOCK}, {WHAL_STM32WB55_GPIOB_CLOCK}, {WHAL_STM32WB55_UART1_CLOCK}, + {WHAL_STM32WB55_SPI1_CLOCK}, {WHAL_STM32WB55_RNG_CLOCK}, {WHAL_STM32WB55_AES1_CLOCK}, }; -#define PERIPHERAL_CLOCK_COUNT (sizeof(g_peripheralClocks) / sizeof(g_peripheralClocks[0])) +#define CLOCK_COUNT (sizeof(g_clocks) / sizeof(g_clocks[0])) /* GPIO */ -enum { - LED_PIN, - UART_TX_PIN, - UART_RX_PIN, -}; - whal_Gpio g_whalGpio = { WHAL_STM32WB55_GPIO_DEVICE, .cfg = &(whal_Stm32wbGpio_Cfg) { - .pinCfg = (whal_Stm32wbGpio_PinCfg[3]) { + .pinCfg = (whal_Stm32wbGpio_PinCfg[PIN_COUNT]) { [LED_PIN] = { /* LED */ .port = WHAL_STM32WB_GPIO_PORT_B, .pin = 5, @@ -97,8 +94,53 @@ whal_Gpio g_whalGpio = { .pull = WHAL_STM32WB_GPIO_PULL_UP, .altFn = 7, }, + [SPI_SCK_PIN] = { /* SPI1 SCK */ + .port = WHAL_STM32WB_GPIO_PORT_A, + .pin = 5, + .mode = WHAL_STM32WB_GPIO_MODE_ALTFN, + .outType = WHAL_STM32WB_GPIO_OUTTYPE_PUSHPULL, + .speed = WHAL_STM32WB_GPIO_SPEED_FAST, + .pull = WHAL_STM32WB_GPIO_PULL_NONE, + .altFn = 5, + }, + [SPI_MISO_PIN] = { /* SPI1 MISO */ + .port = WHAL_STM32WB_GPIO_PORT_A, + .pin = 6, + .mode = WHAL_STM32WB_GPIO_MODE_ALTFN, + .outType = WHAL_STM32WB_GPIO_OUTTYPE_PUSHPULL, + .speed = WHAL_STM32WB_GPIO_SPEED_FAST, + .pull = WHAL_STM32WB_GPIO_PULL_NONE, + .altFn = 5, + }, + [SPI_MOSI_PIN] = { /* SPI1 MOSI */ + .port = WHAL_STM32WB_GPIO_PORT_A, + .pin = 7, + .mode = WHAL_STM32WB_GPIO_MODE_ALTFN, + .outType = WHAL_STM32WB_GPIO_OUTTYPE_PUSHPULL, + .speed = WHAL_STM32WB_GPIO_SPEED_FAST, + .pull = WHAL_STM32WB_GPIO_PULL_NONE, + .altFn = 5, + }, + [SPI_CS_PIN] = { /* SPI CS */ + .port = WHAL_STM32WB_GPIO_PORT_A, + .pin = 4, + .mode = WHAL_STM32WB_GPIO_MODE_OUT, + .outType = WHAL_STM32WB_GPIO_OUTTYPE_PUSHPULL, + .speed = WHAL_STM32WB_GPIO_SPEED_FAST, + .pull = WHAL_STM32WB_GPIO_PULL_UP, + }, }, - .pinCount = 3, + .pinCount = PIN_COUNT, + }, +}; + +/* SPI */ +whal_Spi g_whalSpi = { + WHAL_STM32WB55_SPI1_DEVICE, + + .cfg = &(whal_Stm32wbSpi_Cfg) { + .pclk = 64000000, + .timeout = &g_whalTimeout, }, }; @@ -211,9 +253,9 @@ whal_Error Board_Init(void) return err; } - /* Enable peripheral clocks */ - for (size_t i = 0; i < PERIPHERAL_CLOCK_COUNT; i++) { - err = whal_Clock_Enable(&g_whalClock, &g_peripheralClocks[i]); + /* Enable clocks */ + for (size_t i = 0; i < CLOCK_COUNT; i++) { + err = whal_Clock_Enable(&g_whalClock, &g_clocks[i]); if (err) return err; } @@ -228,11 +270,23 @@ whal_Error Board_Init(void) return err; } + err = whal_Spi_Init(&g_whalSpi); + if (err) { + return err; + } + err = whal_Flash_Init(&g_whalFlash); if (err) { 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; @@ -280,11 +334,23 @@ 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; } + err = whal_Spi_Deinit(&g_whalSpi); + if (err) { + return err; + } + err = whal_Uart_Deinit(&g_whalUart); if (err) { return err; @@ -295,9 +361,9 @@ whal_Error Board_Deinit(void) return err; } - /* Disable peripheral clocks */ - for (size_t i = 0; i < PERIPHERAL_CLOCK_COUNT; i++) { - err = whal_Clock_Disable(&g_whalClock, &g_peripheralClocks[i]); + /* Disable clocks */ + for (size_t i = 0; i < CLOCK_COUNT; i++) { + err = whal_Clock_Disable(&g_whalClock, &g_clocks[i]); if (err) return err; } diff --git a/boards/stm32wb55xx_nucleo/board.h b/boards/stm32wb55xx_nucleo/board.h index 30093ff..a4d59ab 100644 --- a/boards/stm32wb55xx_nucleo/board.h +++ b/boards/stm32wb55xx_nucleo/board.h @@ -9,15 +9,28 @@ extern whal_Clock g_whalClock; extern whal_Gpio g_whalGpio; extern whal_Timer g_whalTimer; extern whal_Uart g_whalUart; +extern whal_Spi g_whalSpi; extern whal_Flash g_whalFlash; extern whal_Rng g_whalRng; extern whal_Crypto g_whalCrypto; +extern whal_Timeout g_whalTimeout; extern volatile uint32_t g_tick; -#define BOARD_LED_PIN 0 -#define BOARD_FLASH_TEST_ADDR 0x08080000 -#define BOARD_FLASH_SECTOR_SZ 0x1000 +enum { + LED_PIN, + UART_TX_PIN, + UART_RX_PIN, + SPI_SCK_PIN, + SPI_MISO_PIN, + SPI_MOSI_PIN, + SPI_CS_PIN, + PIN_COUNT, +}; + +#define BOARD_LED_PIN 0 +#define BOARD_FLASH_TEST_ADDR 0x08080000 +#define BOARD_FLASH_SECTOR_SZ 0x1000 enum { BOARD_CRYPTO_AES_ECB, diff --git a/docs/getting_started.md b/docs/getting_started.md index d99c9d6..11314a0 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -40,6 +40,7 @@ src/gpio/gpio.c src/uart/uart.c src/flash/flash.c src/spi/spi.c +src/block/block.c src/timer/timer.c src/rng/rng.c src/supply/supply.c diff --git a/docs/writing_a_driver.md b/docs/writing_a_driver.md index 34abbdd..9a152ad 100644 --- a/docs/writing_a_driver.md +++ b/docs/writing_a_driver.md @@ -383,54 +383,57 @@ Receive `dataSz` bytes into the provided buffer. For each byte: Header: `wolfHAL/spi/spi.h` -The SPI driver provides serial peripheral interface communication. SPI -operations take an additional `spiComCfg` parameter that carries per-transfer -communication settings. This allows the same SPI bus to communicate with -multiple devices using different modes and speeds without reinitializing the -peripheral. +The SPI driver provides serial peripheral interface communication. A +communication session is bracketed by `StartCom` / `EndCom`, which configure +the peripheral for a specific mode and speed. All transfers within a session +use the same settings, allowing the bus to be shared between devices with +different configurations by starting a new session for each device. ### Init -Configure and enable the SPI peripheral: - -- Set master mode and configure slave select management (typically software- - managed via GPIO) -- Set the data frame size (usually 8-bit) -- Do not configure mode or baud rate here — these are applied per-transfer via - `spiComCfg` +Perform one-time SPI peripheral configuration that remains fixed for the +lifetime of the device. Do not configure mode, baud rate, or data size +here — these are applied per-session via `StartCom`. The board must enable the peripheral clock before calling Init. The configuration struct should include the peripheral clock frequency so the -driver can compute baud rate prescalers during transfers. +driver can compute baud rate prescalers. ### Deinit Disable the SPI peripheral. -### SendRecv +### StartCom -Perform a full-duplex SPI transfer. This is the primary transfer function — -Send and Recv are convenience wrappers around it. +Begin a communication session. Configures the peripheral from the +platform-independent `whal_Spi_ComCfg` struct: -The `spiComCfg` parameter is an opaque pointer to a platform-specific -communication config that describes the SPI mode (CPOL/CPHA), baud rate, and -any other per-transfer settings. The driver should: +- `freq` — bus frequency in Hz +- `mode` — SPI mode (CPOL/CPHA) +- `wordSz` — word size in bits (e.g. 8) +- `dataLines` — number of data lines (1 for standard SPI) -1. Apply the communication config (disable the peripheral, update mode and - baud rate registers, re-enable) -2. Transfer `max(txLen, rxLen)` bytes. For each byte: - - Write a byte from `tx` to the transmit register (if `tx` is provided) - - Read a byte from the receive register into `rx` (if `rx` is provided) -3. Wait for the bus to go idle before returning +The driver should disable the peripheral, apply the new settings (mode, baud +rate prescaler, data size, FIFO threshold), and re-enable it. Return +`WHAL_EINVAL` if a requested setting is not supported by the hardware. -### Send +### EndCom -Transmit-only convenience wrapper. Calls SendRecv with no receive buffer. +End the current communication session by disabling the peripheral. -### Recv +### SendRecv + +Perform a full-duplex SPI transfer. This is the only transfer function — there +are no separate Send or Recv operations. + +The driver clocks `max(txLen, rxLen)` bytes: + +- When `tx` is NULL or exhausted, send `0xFF` for remaining clocks +- When `rx` is NULL or exhausted, discard received bytes +- Every TX byte produces an RX byte; always drain the RX FIFO to prevent + overflow -Receive-only convenience wrapper. Calls SendRecv, clocking out dummy bytes to -generate the SPI clock while capturing received data. +After the loop, wait for the bus to go idle before returning. --- @@ -512,6 +515,47 @@ that overlap with the requested range. --- +## Block + +Header: `wolfHAL/block/block.h` + +The block driver provides access to block-addressable storage devices such as +SD cards and eMMC. Unlike flash, block devices are addressed by block number +rather than byte address, and all operations work in units of fixed-size blocks +(e.g. 512 bytes). + +Block drivers are bus-device drivers — they call their underlying bus driver +(SPI, SDIO) to communicate with the storage device. This is the expected +exception to the no-cross-driver-calls rule. + +### Init + +Initialize the storage device. This includes any device-specific initialization +sequence required to bring the device into a usable state. The board must have +already initialized the bus driver and enabled the relevant clocks and GPIOs +before calling Init. + +### Deinit + +Release the storage device. + +### Read + +Read one or more blocks from the device into a buffer. The driver should handle +both single-block and multi-block reads based on the block count. + +### Write + +Write one or more blocks from a buffer to the device. The driver should handle +both single-block and multi-block writes, and wait for the device to finish +programming before returning. + +### Erase + +Erase a range of blocks on the device. + +--- + ## Timer Header: `wolfHAL/timer/timer.h` diff --git a/src/block/block.c b/src/block/block.c new file mode 100644 index 0000000..a161e7a --- /dev/null +++ b/src/block/block.c @@ -0,0 +1,51 @@ +#include +#include +#include + +inline whal_Error whal_Block_Init(whal_Block *blockDev) +{ + if (!blockDev || !blockDev->driver || !blockDev->driver->Init) { + return WHAL_EINVAL; + } + + return blockDev->driver->Init(blockDev); +} + +inline whal_Error whal_Block_Deinit(whal_Block *blockDev) +{ + if (!blockDev || !blockDev->driver || !blockDev->driver->Deinit) { + return WHAL_EINVAL; + } + + return blockDev->driver->Deinit(blockDev); +} + +inline whal_Error whal_Block_Read(whal_Block *blockDev, uint32_t block, + uint8_t *data, uint32_t blockCount) +{ + if (!blockDev || !blockDev->driver || !blockDev->driver->Read || !data) { + return WHAL_EINVAL; + } + + return blockDev->driver->Read(blockDev, block, data, blockCount); +} + +inline whal_Error whal_Block_Write(whal_Block *blockDev, uint32_t block, + const uint8_t *data, uint32_t blockCount) +{ + if (!blockDev || !blockDev->driver || !blockDev->driver->Write || !data) { + return WHAL_EINVAL; + } + + return blockDev->driver->Write(blockDev, block, data, blockCount); +} + +inline whal_Error whal_Block_Erase(whal_Block *blockDev, uint32_t block, + uint32_t blockCount) +{ + if (!blockDev || !blockDev->driver || !blockDev->driver->Erase) { + return WHAL_EINVAL; + } + + return blockDev->driver->Erase(blockDev, block, blockCount); +} diff --git a/src/block/sdhc_spi.c b/src/block/sdhc_spi.c new file mode 100644 index 0000000..f40b3ec --- /dev/null +++ b/src/block/sdhc_spi.c @@ -0,0 +1,486 @@ +#include +#include +#include +#include +#include +#include +#include + +/* SD SPI Commands */ +#define CMD0 0x40 /* GO_IDLE_STATE */ +#define CMD8 0x48 /* SEND_IF_COND */ +#define CMD12 0x4C /* STOP_TRANSMISSION */ +#define CMD17 0x51 /* READ_SINGLE_BLOCK */ +#define CMD18 0x52 /* READ_MULTIPLE_BLOCK */ +#define CMD24 0x58 /* WRITE_BLOCK */ +#define CMD25 0x59 /* WRITE_MULTIPLE_BLOCK */ +#define CMD32 0x60 /* ERASE_WR_BLK_START */ +#define CMD33 0x61 /* ERASE_WR_BLK_END */ +#define CMD38 0x66 /* ERASE */ +#define CMD55 0x77 /* APP_CMD */ +#define CMD58 0x7A /* READ_OCR */ +#define ACMD41 0x69 /* SD_SEND_OP_COND */ + +/* R1 Response Bits */ +#define R1_IDLE 0x01 +#define R1_ERRORS 0x7C + +/* Data Tokens */ +#define TOKEN_START_BLOCK 0xFE +#define TOKEN_START_BLOCK_MULTI 0xFC +#define TOKEN_STOP_TRAN 0xFD + +/* Data Response */ +#define DATA_RESP_MASK 0x1F +#define DATA_RESP_ACCEPTED 0x05 + +#define DUMMY 0xFF + +static whal_Error SdhcSpi_CsAssert(whal_SdhcSpi_Cfg *cfg) +{ + uint8_t dummy = DUMMY; + whal_Error err; + + err = whal_Gpio_Set(cfg->gpioDev, cfg->csPin, 0); + if (err) + return err; + + /* Dummy byte gives card time to recognize CS transition */ + return whal_Spi_SendRecv(cfg->spiDev, &dummy, 1, NULL, 0); +} + +static whal_Error SdhcSpi_CsDeassert(whal_SdhcSpi_Cfg *cfg) +{ + return whal_Gpio_Set(cfg->gpioDev, cfg->csPin, 1); +} + +/* Poll for a response byte (up to 8 attempts, skips 0xFF) */ +static whal_Error SdhcSpi_RecvResp(whal_SdhcSpi_Cfg *cfg, uint8_t *resp) +{ + whal_Error err; + int i; + uint8_t tmp; + + for (i = 0; i < 8; i++) { + err = whal_Spi_SendRecv(cfg->spiDev, NULL, 0, &tmp, 1); + if (err) + return err; + if (tmp != DUMMY) { + *resp = tmp; + return WHAL_SUCCESS; + } + } + + return WHAL_ETIMEOUT; +} + +/* Send a 6-byte command frame */ +static whal_Error SdhcSpi_SendCmd(whal_SdhcSpi_Cfg *cfg, + uint8_t cmd, uint32_t arg, + uint8_t crc) +{ + uint8_t frame[6]; + + frame[0] = cmd; + frame[1] = (uint8_t)(arg >> 24); + frame[2] = (uint8_t)(arg >> 16); + frame[3] = (uint8_t)(arg >> 8); + frame[4] = (uint8_t)(arg); + frame[5] = crc; + + return whal_Spi_SendRecv(cfg->spiDev, frame, 6, NULL, 0); +} + +/* Poll until card is no longer busy (MISO goes high) */ +static whal_Error SdhcSpi_WaitReady(whal_SdhcSpi_Cfg *cfg) +{ + uint8_t resp; + whal_Error err; + + WHAL_TIMEOUT_START(cfg->timeout); + do { + err = whal_Spi_SendRecv(cfg->spiDev, NULL, 0, &resp, 1); + if (err) + return err; + if (resp == DUMMY) + return WHAL_SUCCESS; + if (WHAL_TIMEOUT_EXPIRED(cfg->timeout)) + return WHAL_ETIMEOUT; + } while (1); +} + +/* Poll for data start token before a read */ +static whal_Error SdhcSpi_WaitDataToken(whal_SdhcSpi_Cfg *cfg, uint8_t *token) +{ + uint8_t resp; + whal_Error err; + + WHAL_TIMEOUT_START(cfg->timeout); + do { + err = whal_Spi_SendRecv(cfg->spiDev, NULL, 0, &resp, 1); + if (err) + return err; + if (resp != DUMMY) { + *token = resp; + return WHAL_SUCCESS; + } + if (WHAL_TIMEOUT_EXPIRED(cfg->timeout)) + return WHAL_ETIMEOUT; + } while (1); +} + +whal_Error whal_SdhcSpi_Init(whal_Block *blockDev) +{ + whal_SdhcSpi_Cfg *cfg; + whal_Spi_ComCfg slow; + whal_Error err; + uint8_t r1; + uint8_t buf[4]; + uint8_t dummy = DUMMY; + int i; + + if (!blockDev || !blockDev->cfg) + return WHAL_EINVAL; + + cfg = (whal_SdhcSpi_Cfg *)blockDev->cfg; + + if (!cfg->spiDev || !cfg->spiComCfg || !cfg->gpioDev) + return WHAL_EINVAL; + + slow = *cfg->spiComCfg; + slow.freq = 400000; + + /* Power-up: CS high, 80+ clock pulses */ + err = SdhcSpi_CsDeassert(cfg); + if (err) + return err; + err = whal_Spi_StartCom(cfg->spiDev, &slow); + if (err) + return err; + for (i = 0; i < 10; i++) { + err = whal_Spi_SendRecv(cfg->spiDev, &dummy, 1, NULL, 0); + if (err) + goto cleanup; + } + + /* CMD0: Enter SPI mode */ + err = SdhcSpi_CsAssert(cfg); + if (err) + goto cleanup; + err = SdhcSpi_SendCmd(cfg, CMD0, 0x00000000, 0x95); + if (!err) + err = SdhcSpi_RecvResp(cfg, &r1); + SdhcSpi_CsDeassert(cfg); + if (err) + goto cleanup; + if ((r1 & R1_ERRORS) || !(r1 & R1_IDLE)) { + err = WHAL_EHARDWARE; + goto cleanup; + } + + /* + * CMD8: SEND_IF_COND — check for SDv2. + * Arg 0x000001AA: voltage range 2.7-3.6V (0x01) + check pattern (0xAA). + * Response is R7: R1 byte followed by 4 trailing bytes: + * buf[0]: command version + * buf[1]: reserved + * buf[2]: accepted voltage (expect 0x01) + * buf[3]: check pattern echo (expect 0xAA) + */ + err = SdhcSpi_CsAssert(cfg); + if (err) + goto cleanup; + err = SdhcSpi_SendCmd(cfg, CMD8, 0x000001AA, 0x87); + if (!err) + err = SdhcSpi_RecvResp(cfg, &r1); + if (!err && (r1 & R1_IDLE)) + err = whal_Spi_SendRecv(cfg->spiDev, NULL, 0, buf, 4); + SdhcSpi_CsDeassert(cfg); + if (err) + goto cleanup; + if (!(r1 & R1_IDLE) || buf[2] != 0x01 || buf[3] != 0xAA) { + err = WHAL_EHARDWARE; + goto cleanup; + } + + /* ACMD41: Initialize card */ + WHAL_TIMEOUT_START(cfg->timeout); + do { + err = SdhcSpi_CsAssert(cfg); + if (err) + goto cleanup; + err = SdhcSpi_SendCmd(cfg, CMD55, 0x00000000, 0x01); + if (!err) + err = SdhcSpi_RecvResp(cfg, &r1); + SdhcSpi_CsDeassert(cfg); + if (err) + goto cleanup; + + err = SdhcSpi_CsAssert(cfg); + if (err) + goto cleanup; + err = SdhcSpi_SendCmd(cfg, ACMD41, 0x40000000, 0x01); + if (!err) + err = SdhcSpi_RecvResp(cfg, &r1); + SdhcSpi_CsDeassert(cfg); + if (err) + goto cleanup; + + if (r1 == 0x00) + break; + if (WHAL_TIMEOUT_EXPIRED(cfg->timeout)) { + err = WHAL_ETIMEOUT; + goto cleanup; + } + } while (1); + + /* CMD58: Read OCR, verify SDHC */ + err = SdhcSpi_CsAssert(cfg); + if (err) + goto cleanup; + err = SdhcSpi_SendCmd(cfg, CMD58, 0x00000000, 0x01); + if (!err) + err = SdhcSpi_RecvResp(cfg, &r1); + if (!err && r1 == 0x00) + err = whal_Spi_SendRecv(cfg->spiDev, NULL, 0, buf, 4); + SdhcSpi_CsDeassert(cfg); + if (err) + goto cleanup; + if (r1 != 0x00 || !(buf[0] & 0x40)) { + err = WHAL_EHARDWARE; + goto cleanup; + } + +cleanup: + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SdhcSpi_Deinit(whal_Block *blockDev) +{ + if (!blockDev || !blockDev->cfg) + return WHAL_EINVAL; + + SdhcSpi_CsDeassert((whal_SdhcSpi_Cfg *)blockDev->cfg); + return WHAL_SUCCESS; +} + +whal_Error whal_SdhcSpi_Read(whal_Block *blockDev, uint32_t block, + uint8_t *data, uint32_t blockCount) +{ + whal_SdhcSpi_Cfg *cfg; + whal_Error err; + uint8_t r1; + uint8_t token; + uint8_t crc[2]; + uint8_t dummy = DUMMY; + uint32_t i; + + if (!blockDev || !blockDev->cfg || !data || blockCount == 0) + return WHAL_EINVAL; + + cfg = (whal_SdhcSpi_Cfg *)blockDev->cfg; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + err = SdhcSpi_CsAssert(cfg); + if (err) + goto cleanup; + + /* Send read command */ + err = SdhcSpi_SendCmd(cfg, + (blockCount == 1) ? CMD17 : CMD18, + block, 0x01); + if (!err) + err = SdhcSpi_RecvResp(cfg, &r1); + if (err || r1 != 0x00) { + if (!err) err = WHAL_EHARDWARE; + goto cleanup; + } + + /* Read blocks */ + for (i = 0; i < blockCount; i++) { + err = SdhcSpi_WaitDataToken(cfg, &token); + if (err || token != TOKEN_START_BLOCK) { + if (!err) err = WHAL_EHARDWARE; + break; + } + + err = whal_Spi_SendRecv(cfg->spiDev, NULL, 0, + data + (i * WHAL_SDHC_SPI_BLOCK_SZ), + WHAL_SDHC_SPI_BLOCK_SZ); + if (err) + break; + + err = whal_Spi_SendRecv(cfg->spiDev, NULL, 0, crc, 2); + if (err) + break; + } + + /* Stop multi-block read */ + if (blockCount > 1) { + SdhcSpi_SendCmd(cfg, CMD12, 0x00000000, 0x01); + SdhcSpi_RecvResp(cfg, &r1); + whal_Spi_SendRecv(cfg->spiDev, &dummy, 1, NULL, 0); + SdhcSpi_WaitReady(cfg); + } + +cleanup: + SdhcSpi_CsDeassert(cfg); + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SdhcSpi_Write(whal_Block *blockDev, uint32_t block, + const uint8_t *data, uint32_t blockCount) +{ + whal_SdhcSpi_Cfg *cfg; + whal_Error err; + uint8_t r1; + uint8_t resp; + uint8_t token; + uint8_t crc[2] = {DUMMY, DUMMY}; + uint8_t dummy = DUMMY; + uint32_t i; + + if (!blockDev || !blockDev->cfg || !data || blockCount == 0) + return WHAL_EINVAL; + + cfg = (whal_SdhcSpi_Cfg *)blockDev->cfg; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + err = SdhcSpi_CsAssert(cfg); + if (err) + goto cleanup; + + /* Send write command */ + err = SdhcSpi_SendCmd(cfg, + (blockCount == 1) ? CMD24 : CMD25, + block, 0x01); + if (!err) + err = SdhcSpi_RecvResp(cfg, &r1); + if (err || r1 != 0x00) { + if (!err) err = WHAL_EHARDWARE; + goto cleanup; + } + + /* Write blocks */ + for (i = 0; i < blockCount; i++) { + err = whal_Spi_SendRecv(cfg->spiDev, &dummy, 1, NULL, 0); + if (err) + break; + token = (blockCount == 1) ? TOKEN_START_BLOCK : TOKEN_START_BLOCK_MULTI; + err = whal_Spi_SendRecv(cfg->spiDev, &token, 1, NULL, 0); + if (err) + break; + err = whal_Spi_SendRecv(cfg->spiDev, + data + (i * WHAL_SDHC_SPI_BLOCK_SZ), + WHAL_SDHC_SPI_BLOCK_SZ, NULL, 0); + if (err) + break; + err = whal_Spi_SendRecv(cfg->spiDev, crc, 2, NULL, 0); + if (err) + break; + + err = SdhcSpi_RecvResp(cfg, &resp); + if (err || (resp & DATA_RESP_MASK) != DATA_RESP_ACCEPTED) { + if (!err) err = WHAL_EHARDWARE; + break; + } + + err = SdhcSpi_WaitReady(cfg); + if (err) + break; + } + + /* Stop multi-block write */ + if (blockCount > 1) { + token = TOKEN_STOP_TRAN; + whal_Spi_SendRecv(cfg->spiDev, &token, 1, NULL, 0); + whal_Spi_SendRecv(cfg->spiDev, &dummy, 1, NULL, 0); + SdhcSpi_WaitReady(cfg); + } + +cleanup: + SdhcSpi_CsDeassert(cfg); + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SdhcSpi_Erase(whal_Block *blockDev, uint32_t block, + uint32_t blockCount) +{ + whal_SdhcSpi_Cfg *cfg; + whal_Error err; + uint8_t r1; + uint32_t endBlock; + + if (!blockDev || !blockDev->cfg || blockCount == 0) + return WHAL_EINVAL; + + cfg = (whal_SdhcSpi_Cfg *)blockDev->cfg; + + if (blockCount - 1u > UINT32_MAX - block) + return WHAL_EINVAL; + endBlock = block + blockCount - 1; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + /* CMD32: Set erase start block */ + err = SdhcSpi_CsAssert(cfg); + if (err) + goto cleanup; + err = SdhcSpi_SendCmd(cfg, CMD32, block, 0x01); + if (!err) + err = SdhcSpi_RecvResp(cfg, &r1); + SdhcSpi_CsDeassert(cfg); + if (err || r1 != 0x00) { + if (!err) err = WHAL_EHARDWARE; + goto cleanup; + } + + /* CMD33: Set erase end block */ + err = SdhcSpi_CsAssert(cfg); + if (err) + goto cleanup; + err = SdhcSpi_SendCmd(cfg, CMD33, endBlock, 0x01); + if (!err) + err = SdhcSpi_RecvResp(cfg, &r1); + SdhcSpi_CsDeassert(cfg); + if (err || r1 != 0x00) { + if (!err) err = WHAL_EHARDWARE; + goto cleanup; + } + + /* CMD38: Execute erase */ + err = SdhcSpi_CsAssert(cfg); + if (err) + goto cleanup; + err = SdhcSpi_SendCmd(cfg, CMD38, 0x00000000, 0x01); + if (!err) + err = SdhcSpi_RecvResp(cfg, &r1); + if (!err && r1 == 0x00) + err = SdhcSpi_WaitReady(cfg); + SdhcSpi_CsDeassert(cfg); + if (!err && r1 != 0x00) + err = WHAL_EHARDWARE; + +cleanup: + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +const whal_BlockDriver whal_SdhcSpi_Driver = { + .Init = whal_SdhcSpi_Init, + .Deinit = whal_SdhcSpi_Deinit, + .Read = whal_SdhcSpi_Read, + .Write = whal_SdhcSpi_Write, + .Erase = whal_SdhcSpi_Erase, +}; diff --git a/src/spi/spi.c b/src/spi/spi.c index e45cca4..5b28519 100644 --- a/src/spi/spi.c +++ b/src/spi/spi.c @@ -20,30 +20,29 @@ inline whal_Error whal_Spi_Deinit(whal_Spi *spiDev) return spiDev->driver->Deinit(spiDev); } -inline whal_Error whal_Spi_SendRecv(whal_Spi *spiDev, void *spiComCfg, const uint8_t *tx, size_t txLen, uint8_t *rx, size_t rxLen) +inline whal_Error whal_Spi_StartCom(whal_Spi *spiDev, whal_Spi_ComCfg *comCfg) { - if (!spiDev || !spiDev->driver || !spiDev->driver->SendRecv || !spiComCfg) { + if (!spiDev || !spiDev->driver || !spiDev->driver->StartCom || !comCfg) { return WHAL_EINVAL; } - return spiDev->driver->SendRecv(spiDev, spiComCfg, tx, txLen, rx, rxLen); + return spiDev->driver->StartCom(spiDev, comCfg); } -inline whal_Error whal_Spi_Send(whal_Spi *spiDev, void *spiComCfg, const uint8_t *data, size_t dataSz) +inline whal_Error whal_Spi_EndCom(whal_Spi *spiDev) { - if (!spiDev || !spiDev->driver || !spiDev->driver->Send || !spiComCfg || !data) { + if (!spiDev || !spiDev->driver || !spiDev->driver->EndCom) { return WHAL_EINVAL; } - return spiDev->driver->Send(spiDev, spiComCfg, data, dataSz); + return spiDev->driver->EndCom(spiDev); } -inline whal_Error whal_Spi_Recv(whal_Spi *spiDev, void *spiComCfg, uint8_t *data, size_t dataSz) +inline whal_Error whal_Spi_SendRecv(whal_Spi *spiDev, const uint8_t *tx, size_t txLen, uint8_t *rx, size_t rxLen) { - if (!spiDev || !spiDev->driver || !spiDev->driver->Recv || !spiComCfg || !data) { + if (!spiDev || !spiDev->driver || !spiDev->driver->SendRecv) { return WHAL_EINVAL; } - return spiDev->driver->Recv(spiDev, spiComCfg, data, dataSz); + return spiDev->driver->SendRecv(spiDev, tx, txLen, rx, rxLen); } - diff --git a/src/spi/stm32wb_spi.c b/src/spi/stm32wb_spi.c index e4b9a5b..b4b6839 100644 --- a/src/spi/stm32wb_spi.c +++ b/src/spi/stm32wb_spi.c @@ -60,9 +60,6 @@ #define SPI_DR_Pos 0 #define SPI_DR_Msk (WHAL_BITMASK(8) << SPI_DR_Pos) -/* 8-bit data size value for DS field */ -#define SPI_DS_8BIT 0x7 - /* * Calculate the baud rate prescaler index for a target baud rate. * @@ -84,37 +81,6 @@ static uint32_t whal_Stm32wbSpi_CalcBr(size_t pclk, uint32_t targetBaud) return 7; } -/* - * Configure SPI mode and baud rate, then enable the peripheral. - * Must be called with SPE disabled. - */ -static void whal_Stm32wbSpi_ApplyComCfg(const whal_Regmap *reg, - whal_Stm32wbSpi_Cfg *cfg, - whal_Stm32wbSpi_ComCfg *comCfg) -{ - uint32_t cpol, cpha, br; - - br = whal_Stm32wbSpi_CalcBr(cfg->pclk, comCfg->baud); - - cpol = (comCfg->mode >> 1) & 1; - cpha = comCfg->mode & 1; - - /* Disable SPE before reconfiguring */ - whal_Reg_Update(reg->base, SPI_CR1_REG, SPI_CR1_SPE_Msk, - whal_SetBits(SPI_CR1_SPE_Msk, SPI_CR1_SPE_Pos, 0)); - - /* Set mode and baud rate */ - whal_Reg_Update(reg->base, SPI_CR1_REG, - SPI_CR1_CPOL_Msk | SPI_CR1_CPHA_Msk | SPI_CR1_BR_Msk, - whal_SetBits(SPI_CR1_CPOL_Msk, SPI_CR1_CPOL_Pos, cpol) | - whal_SetBits(SPI_CR1_CPHA_Msk, SPI_CR1_CPHA_Pos, cpha) | - whal_SetBits(SPI_CR1_BR_Msk, SPI_CR1_BR_Pos, br)); - - /* Re-enable SPE */ - whal_Reg_Update(reg->base, SPI_CR1_REG, SPI_CR1_SPE_Msk, - whal_SetBits(SPI_CR1_SPE_Msk, SPI_CR1_SPE_Pos, 1)); -} - whal_Error whal_Stm32wbSpi_Init(whal_Spi *spiDev) { const whal_Regmap *reg; @@ -132,12 +98,6 @@ whal_Error whal_Stm32wbSpi_Init(whal_Spi *spiDev) whal_SetBits(SPI_CR1_SSM_Msk, SPI_CR1_SSM_Pos, 1) | whal_SetBits(SPI_CR1_SSI_Msk, SPI_CR1_SSI_Pos, 1)); - /* 8-bit data size and FIFO receive threshold for 8-bit */ - whal_Reg_Update(reg->base, SPI_CR2_REG, - SPI_CR2_DS_Msk | SPI_CR2_FRXTH_Msk, - whal_SetBits(SPI_CR2_DS_Msk, SPI_CR2_DS_Pos, SPI_DS_8BIT) | - whal_SetBits(SPI_CR2_FRXTH_Msk, SPI_CR2_FRXTH_Pos, 1)); - return WHAL_SUCCESS; } @@ -158,79 +118,129 @@ whal_Error whal_Stm32wbSpi_Deinit(whal_Spi *spiDev) return WHAL_SUCCESS; } -whal_Error whal_Stm32wbSpi_SendRecv(whal_Spi *spiDev, void *spiComCfg, const uint8_t *tx, - size_t txLen, uint8_t *rx, size_t rxLen) +whal_Error whal_Stm32wbSpi_StartCom(whal_Spi *spiDev, whal_Spi_ComCfg *comCfg) { const whal_Regmap *reg; whal_Stm32wbSpi_Cfg *cfg; - whal_Stm32wbSpi_ComCfg *comCfg; + uint32_t cpol, cpha, br, ds, frxth; + + if (!spiDev || !spiDev->cfg || !comCfg) { + return WHAL_EINVAL; + } + + /* DS field encodes word size as (bits - 1); valid range 4-16 */ + if (comCfg->wordSz < 4 || comCfg->wordSz > 16) { + return WHAL_EINVAL; + } - if (!spiDev || !spiDev->cfg || !spiComCfg) { + if (comCfg->mode > 3 || comCfg->dataLines != 1 || comCfg->freq == 0) { return WHAL_EINVAL; } reg = &spiDev->regmap; cfg = (whal_Stm32wbSpi_Cfg *)spiDev->cfg; - comCfg = (whal_Stm32wbSpi_ComCfg *)spiComCfg; - size_t totalLen = txLen > rxLen ? txLen : rxLen; - size_t d; - whal_Stm32wbSpi_ApplyComCfg(reg, cfg, comCfg); + br = whal_Stm32wbSpi_CalcBr(cfg->pclk, comCfg->freq); - for (size_t i = 0; i < totalLen; i++) { - if (txLen && tx) { - /* Wait for TX buffer empty */ - whal_Error txErr = whal_Reg_ReadPoll(reg->base, SPI_SR_REG, - SPI_SR_TXE_Msk, - SPI_SR_TXE_Msk, - cfg->timeout); - if (txErr) - return txErr; - - /* Write data or dummy byte */ - uint8_t txByte = (i >= txLen) ? tx[txLen-1] : tx[i]; - *(volatile uint8_t *)(reg->base + SPI_DR_REG) = txByte; - } + cpol = (comCfg->mode >> 1) & 1; + cpha = comCfg->mode & 1; + ds = comCfg->wordSz - 1; + frxth = (comCfg->wordSz <= 8) ? 1 : 0; - if (rxLen && rx) { - /* Wait for RX buffer not empty */ - whal_Error rxErr = whal_Reg_ReadPoll(reg->base, SPI_SR_REG, - SPI_SR_RXNE_Msk, - SPI_SR_RXNE_Msk, - cfg->timeout); - if (rxErr) - return rxErr; - - /* Read received byte */ - d = *(volatile uint8_t *)(reg->base + SPI_DR_REG); - if (i < rxLen) { - rx[i] = (uint8_t)d; - } - } - } + /* Disable SPE before reconfiguring */ + whal_Reg_Update(reg->base, SPI_CR1_REG, SPI_CR1_SPE_Msk, + whal_SetBits(SPI_CR1_SPE_Msk, SPI_CR1_SPE_Pos, 0)); - /* Wait for not busy */ - return whal_Reg_ReadPoll(reg->base, SPI_SR_REG, SPI_SR_BSY_Msk, 0, - cfg->timeout); + /* Set mode and baud rate */ + whal_Reg_Update(reg->base, SPI_CR1_REG, + SPI_CR1_CPOL_Msk | SPI_CR1_CPHA_Msk | SPI_CR1_BR_Msk, + whal_SetBits(SPI_CR1_CPOL_Msk, SPI_CR1_CPOL_Pos, cpol) | + whal_SetBits(SPI_CR1_CPHA_Msk, SPI_CR1_CPHA_Pos, cpha) | + whal_SetBits(SPI_CR1_BR_Msk, SPI_CR1_BR_Pos, br)); + + /* Set data size and FIFO receive threshold */ + whal_Reg_Update(reg->base, SPI_CR2_REG, + SPI_CR2_DS_Msk | SPI_CR2_FRXTH_Msk, + whal_SetBits(SPI_CR2_DS_Msk, SPI_CR2_DS_Pos, ds) | + whal_SetBits(SPI_CR2_FRXTH_Msk, SPI_CR2_FRXTH_Pos, frxth)); + + /* Enable SPE */ + whal_Reg_Update(reg->base, SPI_CR1_REG, SPI_CR1_SPE_Msk, + whal_SetBits(SPI_CR1_SPE_Msk, SPI_CR1_SPE_Pos, 1)); + + return WHAL_SUCCESS; } -whal_Error whal_Stm32wbSpi_Send(whal_Spi *spiDev, void *spiComCfg, const uint8_t *data, - size_t dataSz) +whal_Error whal_Stm32wbSpi_EndCom(whal_Spi *spiDev) { - return whal_Stm32wbSpi_SendRecv(spiDev, spiComCfg, data, dataSz, NULL, 0); + const whal_Regmap *reg; + + if (!spiDev || !spiDev->cfg) { + return WHAL_EINVAL; + } + + reg = &spiDev->regmap; + + /* Disable SPE */ + whal_Reg_Update(reg->base, SPI_CR1_REG, SPI_CR1_SPE_Msk, + whal_SetBits(SPI_CR1_SPE_Msk, SPI_CR1_SPE_Pos, 0)); + + return WHAL_SUCCESS; } -whal_Error whal_Stm32wbSpi_Recv(whal_Spi *spiDev, void *spiComCfg, uint8_t *data, - size_t dataSz) +whal_Error whal_Stm32wbSpi_SendRecv(whal_Spi *spiDev, + const uint8_t *tx, size_t txLen, + uint8_t *rx, size_t rxLen) { - uint8_t dummyByte = 0xFF; - return whal_Stm32wbSpi_SendRecv(spiDev, spiComCfg, &dummyByte, 1, data, dataSz); + const whal_Regmap *reg; + whal_Stm32wbSpi_Cfg *cfg; + size_t totalLen; + whal_Error err; + uint8_t txByte; + + if (!spiDev || !spiDev->cfg || (!tx && txLen) || (!rx && rxLen)) { + return WHAL_EINVAL; + } + + reg = &spiDev->regmap; + cfg = (whal_Stm32wbSpi_Cfg *)spiDev->cfg; + totalLen = txLen > rxLen ? txLen : rxLen; + + for (size_t i = 0; i < totalLen; i++) { + /* Wait for TX buffer empty */ + err = whal_Reg_ReadPoll(reg->base, SPI_SR_REG, + SPI_SR_TXE_Msk, SPI_SR_TXE_Msk, + cfg->timeout); + if (err) + return err; + + /* Write TX data, pad with 0xFF when tx is exhausted or NULL */ + txByte = (tx && i < txLen) ? tx[i] : 0xFF; + *(volatile uint8_t *)(reg->base + SPI_DR_REG) = txByte; + + /* Wait for RX byte */ + err = whal_Reg_ReadPoll(reg->base, SPI_SR_REG, + SPI_SR_RXNE_Msk, SPI_SR_RXNE_Msk, + cfg->timeout); + if (err) + return err; + + /* Store or discard received byte */ + if (rx && i < rxLen) + rx[i] = *(volatile uint8_t *)(reg->base + SPI_DR_REG); + else + (void)*(volatile uint8_t *)(reg->base + SPI_DR_REG); + } + + /* Wait for not busy */ + return whal_Reg_ReadPoll(reg->base, SPI_SR_REG, SPI_SR_BSY_Msk, 0, + cfg->timeout); } const whal_SpiDriver whal_Stm32wbSpi_Driver = { .Init = whal_Stm32wbSpi_Init, .Deinit = whal_Stm32wbSpi_Deinit, + .StartCom = whal_Stm32wbSpi_StartCom, + .EndCom = whal_Stm32wbSpi_EndCom, .SendRecv = whal_Stm32wbSpi_SendRecv, - .Send = whal_Stm32wbSpi_Send, - .Recv = whal_Stm32wbSpi_Recv, }; diff --git a/tests/Makefile b/tests/Makefile index 38a686c..c0bab79 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -3,7 +3,8 @@ WHAL_DIR = $(CURDIR)/.. BOARD ?= stm32wb55xx_nucleo BOARD_DIR = $(WHAL_DIR)/boards/$(BOARD) BUILD_DIR = build/$(BOARD) -INCLUDE = -I$(WHAL_DIR) -I. +PERIPHERAL_DIR = $(WHAL_DIR)/boards/peripheral +INCLUDE = -I$(WHAL_DIR) -I. -I$(PERIPHERAL_DIR) include $(BOARD_DIR)/Makefile.inc diff --git a/tests/block/test_block.c b/tests/block/test_block.c new file mode 100644 index 0000000..7f252ae --- /dev/null +++ b/tests/block/test_block.c @@ -0,0 +1,105 @@ +#include +#include +#include +#include "board.h" +#include "test.h" +#include "peripheral.h" + +static whal_Block *g_testBlockDev; +static uint8_t *g_testBuf; +static size_t g_testBlockSz; +static uint8_t g_testErasedByte; + +static void Test_Block_EraseBlank(void) +{ + size_t i; + + WHAL_ASSERT_EQ(whal_Block_Erase(g_testBlockDev, 0, 1), WHAL_SUCCESS); + + WHAL_ASSERT_EQ(whal_Block_Read(g_testBlockDev, 0, + g_testBuf, 1), WHAL_SUCCESS); + + for (i = 0; i < g_testBlockSz; i++) { + WHAL_ASSERT_EQ(g_testBuf[i], g_testErasedByte); + } +} + +static void Test_Block_WriteRead(void) +{ + uint8_t pattern[] = "wolfHAL"; + size_t i; + + for (i = 0; i < g_testBlockSz; i++) + g_testBuf[i] = pattern[i % sizeof(pattern)]; + + WHAL_ASSERT_EQ(whal_Block_Erase(g_testBlockDev, 0, 1), WHAL_SUCCESS); + + WHAL_ASSERT_EQ(whal_Block_Write(g_testBlockDev, 0, + g_testBuf, 1), WHAL_SUCCESS); + + for (i = 0; i < g_testBlockSz; i++) + g_testBuf[i] = 0; + + WHAL_ASSERT_EQ(whal_Block_Read(g_testBlockDev, 0, + g_testBuf, 1), WHAL_SUCCESS); + + for (i = 0; i < g_testBlockSz; i++) { + WHAL_ASSERT_EQ(g_testBuf[i], pattern[i % sizeof(pattern)]); + } +} + +static void Test_Block_MultiWriteRead(void) +{ + uint8_t patternA[] = "wolfHAL_A"; + uint8_t patternB[] = "wolfHAL_B"; + size_t i; + + /* Fill block 0 with pattern A, block 1 with pattern B */ + for (i = 0; i < g_testBlockSz; i++) + g_testBuf[i] = patternA[i % sizeof(patternA)]; + for (i = 0; i < g_testBlockSz; i++) + g_testBuf[g_testBlockSz + i] = patternB[i % sizeof(patternB)]; + + WHAL_ASSERT_EQ(whal_Block_Erase(g_testBlockDev, 0, 2), WHAL_SUCCESS); + + WHAL_ASSERT_EQ(whal_Block_Write(g_testBlockDev, 0, + g_testBuf, 2), WHAL_SUCCESS); + + for (i = 0; i < g_testBlockSz * 2; i++) + g_testBuf[i] = 0; + + WHAL_ASSERT_EQ(whal_Block_Read(g_testBlockDev, 0, + g_testBuf, 2), WHAL_SUCCESS); + + for (i = 0; i < g_testBlockSz; i++) { + WHAL_ASSERT_EQ(g_testBuf[i], + patternA[i % sizeof(patternA)]); + } + for (i = 0; i < g_testBlockSz; i++) { + WHAL_ASSERT_EQ(g_testBuf[g_testBlockSz + i], + patternB[i % sizeof(patternB)]); + } +} + +static void run_block_tests(const char *name) +{ + WHAL_TEST_SUITE_START("block"); + if (name) + whal_Test_Printf(" device: %s\n", name); + WHAL_TEST(Test_Block_EraseBlank); + WHAL_TEST(Test_Block_WriteRead); + WHAL_TEST(Test_Block_MultiWriteRead); + WHAL_TEST_SUITE_END(); +} + +void whal_Test_Block(void) +{ + /* Test peripheral block devices */ + for (size_t i = 0; g_peripheralBlock[i].dev; i++) { + g_testBlockDev = g_peripheralBlock[i].dev; + g_testBuf = g_peripheralBlock[i].blockBuf; + g_testBlockSz = g_peripheralBlock[i].blockSz; + g_testErasedByte = g_peripheralBlock[i].erasedByte; + run_block_tests(g_peripheralBlock[i].name); + } +} diff --git a/tests/core/Makefile b/tests/core/Makefile index a0be45d..c712f30 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -12,7 +12,8 @@ WHAL_SRC = $(WHAL_DIR)/src/clock/clock.c \ $(WHAL_DIR)/src/timer/timer.c \ $(WHAL_DIR)/src/spi/spi.c \ $(WHAL_DIR)/src/rng/rng.c \ - $(WHAL_DIR)/src/supply/supply.c + $(WHAL_DIR)/src/supply/supply.c \ + $(WHAL_DIR)/src/block/block.c SOURCE = $(TEST_SRC) $(WHAL_SRC) OBJECTS = $(patsubst %.c,%.o,$(SOURCE)) diff --git a/tests/core/test_dispatch.c b/tests/core/test_dispatch.c index 86a0387..16c6e3f 100644 --- a/tests/core/test_dispatch.c +++ b/tests/core/test_dispatch.c @@ -258,6 +258,94 @@ static void Test_Rng_ValidDispatch(void) WHAL_ASSERT_EQ(whal_Rng_Deinit(&dev), WHAL_SUCCESS); } +/* --- SPI dispatch tests --- */ + +static whal_Error MockSpiInit(whal_Spi *d) { (void)d; return WHAL_SUCCESS; } +static whal_Error MockSpiDeinit(whal_Spi *d) { (void)d; return WHAL_SUCCESS; } +static whal_Error MockSpiStartCom(whal_Spi *d, whal_Spi_ComCfg *c) { (void)d; (void)c; return WHAL_SUCCESS; } +static whal_Error MockSpiEndCom(whal_Spi *d) { (void)d; return WHAL_SUCCESS; } +static whal_Error MockSpiSendRecv(whal_Spi *d, const uint8_t *tx, size_t txLen, uint8_t *rx, size_t rxLen) { (void)d; (void)tx; (void)txLen; (void)rx; (void)rxLen; return WHAL_SUCCESS; } + +static const whal_SpiDriver mockSpiDriver = { + .Init = MockSpiInit, + .Deinit = MockSpiDeinit, + .StartCom = MockSpiStartCom, + .EndCom = MockSpiEndCom, + .SendRecv = MockSpiSendRecv, +}; + +static void Test_Spi_NullDev(void) +{ + whal_Spi_ComCfg comCfg = {0}; + uint8_t buf[1]; + WHAL_ASSERT_EQ(whal_Spi_Init(NULL), WHAL_EINVAL); + WHAL_ASSERT_EQ(whal_Spi_Deinit(NULL), WHAL_EINVAL); + WHAL_ASSERT_EQ(whal_Spi_StartCom(NULL, &comCfg), WHAL_EINVAL); + WHAL_ASSERT_EQ(whal_Spi_EndCom(NULL), WHAL_EINVAL); + WHAL_ASSERT_EQ(whal_Spi_SendRecv(NULL, buf, 1, buf, 1), WHAL_EINVAL); +} + +static void Test_Spi_NullDriver(void) +{ + whal_Spi dev = { .driver = NULL }; + WHAL_ASSERT_EQ(whal_Spi_Init(&dev), WHAL_EINVAL); +} + +static void Test_Spi_ValidDispatch(void) +{ + whal_Spi dev = { .driver = &mockSpiDriver }; + whal_Spi_ComCfg comCfg = {0}; + uint8_t buf[4] = {0}; + WHAL_ASSERT_EQ(whal_Spi_Init(&dev), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Spi_StartCom(&dev, &comCfg), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Spi_SendRecv(&dev, buf, sizeof(buf), buf, sizeof(buf)), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Spi_EndCom(&dev), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Spi_Deinit(&dev), WHAL_SUCCESS); +} + +/* --- Block dispatch tests --- */ + +static whal_Error MockBlockInit(whal_Block *d) { (void)d; return WHAL_SUCCESS; } +static whal_Error MockBlockDeinit(whal_Block *d) { (void)d; return WHAL_SUCCESS; } +static whal_Error MockBlockRead(whal_Block *d, uint32_t b, uint8_t *data, uint32_t c) { (void)d; (void)b; (void)data; (void)c; return WHAL_SUCCESS; } +static whal_Error MockBlockWrite(whal_Block *d, uint32_t b, const uint8_t *data, uint32_t c) { (void)d; (void)b; (void)data; (void)c; return WHAL_SUCCESS; } +static whal_Error MockBlockErase(whal_Block *d, uint32_t b, uint32_t c) { (void)d; (void)b; (void)c; return WHAL_SUCCESS; } + +static const whal_BlockDriver mockBlockDriver = { + .Init = MockBlockInit, + .Deinit = MockBlockDeinit, + .Read = MockBlockRead, + .Write = MockBlockWrite, + .Erase = MockBlockErase, +}; + +static void Test_Block_NullDev(void) +{ + WHAL_ASSERT_EQ(whal_Block_Init(NULL), WHAL_EINVAL); + WHAL_ASSERT_EQ(whal_Block_Deinit(NULL), WHAL_EINVAL); + uint8_t buf[1]; + WHAL_ASSERT_EQ(whal_Block_Read(NULL, 0, buf, 1), WHAL_EINVAL); + WHAL_ASSERT_EQ(whal_Block_Write(NULL, 0, buf, 1), WHAL_EINVAL); + WHAL_ASSERT_EQ(whal_Block_Erase(NULL, 0, 1), WHAL_EINVAL); +} + +static void Test_Block_NullDriver(void) +{ + whal_Block dev = { .driver = NULL }; + WHAL_ASSERT_EQ(whal_Block_Init(&dev), WHAL_EINVAL); +} + +static void Test_Block_ValidDispatch(void) +{ + whal_Block dev = { .driver = &mockBlockDriver }; + WHAL_ASSERT_EQ(whal_Block_Init(&dev), WHAL_SUCCESS); + uint8_t buf[4] = {0}; + WHAL_ASSERT_EQ(whal_Block_Read(&dev, 0, buf, 1), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Block_Write(&dev, 0, buf, 1), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Block_Erase(&dev, 0, 1), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Block_Deinit(&dev), WHAL_SUCCESS); +} + void whal_Test_Dispatch(void) { WHAL_TEST_SUITE_START("dispatch"); @@ -280,5 +368,11 @@ void whal_Test_Dispatch(void) WHAL_TEST(Test_Rng_NullDev); WHAL_TEST(Test_Rng_NullDriver); WHAL_TEST(Test_Rng_ValidDispatch); + WHAL_TEST(Test_Spi_NullDev); + WHAL_TEST(Test_Spi_NullDriver); + WHAL_TEST(Test_Spi_ValidDispatch); + WHAL_TEST(Test_Block_NullDev); + WHAL_TEST(Test_Block_NullDriver); + WHAL_TEST(Test_Block_ValidDispatch); WHAL_TEST_SUITE_END(); } diff --git a/tests/flash/test_flash.c b/tests/flash/test_flash.c index 48276e0..ca2da1d 100644 --- a/tests/flash/test_flash.c +++ b/tests/flash/test_flash.c @@ -3,25 +3,28 @@ #include "board.h" #include "test.h" +static whal_Flash *g_testFlashDev; +static size_t g_testFlashAddr; +static size_t g_testFlashSectorSz; + static void Test_Flash_EraseBlank(void) { uint8_t readback[8] = {0}; uint8_t erased[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - WHAL_ASSERT_EQ(whal_Flash_Unlock(&g_whalFlash, BOARD_FLASH_TEST_ADDR, BOARD_FLASH_SECTOR_SZ), WHAL_SUCCESS); - - WHAL_ASSERT_EQ(whal_Flash_Erase(&g_whalFlash, BOARD_FLASH_TEST_ADDR, - BOARD_FLASH_SECTOR_SZ), - WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Flash_Unlock(g_testFlashDev, g_testFlashAddr, + g_testFlashSectorSz), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Flash_Erase(g_testFlashDev, g_testFlashAddr, + g_testFlashSectorSz), WHAL_SUCCESS); - WHAL_ASSERT_EQ(whal_Flash_Read(&g_whalFlash, BOARD_FLASH_TEST_ADDR, - readback, sizeof(readback)), - WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Flash_Read(g_testFlashDev, g_testFlashAddr, + readback, sizeof(readback)), WHAL_SUCCESS); WHAL_ASSERT_MEM_EQ(readback, erased, sizeof(erased)); - WHAL_ASSERT_EQ(whal_Flash_Lock(&g_whalFlash, BOARD_FLASH_TEST_ADDR, BOARD_FLASH_SECTOR_SZ), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Flash_Lock(g_testFlashDev, g_testFlashAddr, + g_testFlashSectorSz), WHAL_SUCCESS); } static void Test_Flash_WriteRead(void) @@ -29,33 +32,43 @@ static void Test_Flash_WriteRead(void) uint8_t pattern[] = "wolfHAL"; uint8_t readback[sizeof(pattern)] = {0}; - WHAL_ASSERT_EQ(whal_Flash_Unlock(&g_whalFlash, BOARD_FLASH_TEST_ADDR, BOARD_FLASH_SECTOR_SZ), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Flash_Unlock(g_testFlashDev, g_testFlashAddr, + g_testFlashSectorSz), WHAL_SUCCESS); - WHAL_ASSERT_EQ(whal_Flash_Erase(&g_whalFlash, BOARD_FLASH_TEST_ADDR, - BOARD_FLASH_SECTOR_SZ), - WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Flash_Erase(g_testFlashDev, g_testFlashAddr, + g_testFlashSectorSz), WHAL_SUCCESS); whal_Error err; do { - err = whal_Flash_Write(&g_whalFlash, BOARD_FLASH_TEST_ADDR, pattern, + err = whal_Flash_Write(g_testFlashDev, g_testFlashAddr, pattern, sizeof(pattern)); } while (err == WHAL_ENOTREADY); WHAL_ASSERT_EQ(err, WHAL_SUCCESS); - - WHAL_ASSERT_EQ(whal_Flash_Read(&g_whalFlash, BOARD_FLASH_TEST_ADDR, - readback, sizeof(readback)), - WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Flash_Read(g_testFlashDev, g_testFlashAddr, + readback, sizeof(readback)), WHAL_SUCCESS); WHAL_ASSERT_MEM_EQ(pattern, readback, sizeof(pattern)); - WHAL_ASSERT_EQ(whal_Flash_Lock(&g_whalFlash, BOARD_FLASH_TEST_ADDR, BOARD_FLASH_SECTOR_SZ), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Flash_Lock(g_testFlashDev, g_testFlashAddr, + g_testFlashSectorSz), WHAL_SUCCESS); } -void whal_Test_Flash(void) +static void run_flash_tests(const char *name) { WHAL_TEST_SUITE_START("flash"); + if (name) + whal_Test_Printf(" device: %s\n", name); WHAL_TEST(Test_Flash_EraseBlank); WHAL_TEST(Test_Flash_WriteRead); WHAL_TEST_SUITE_END(); } + +void whal_Test_Flash(void) +{ + /* Test on-chip flash */ + g_testFlashDev = &g_whalFlash; + g_testFlashAddr = BOARD_FLASH_TEST_ADDR; + g_testFlashSectorSz = BOARD_FLASH_SECTOR_SZ; + run_flash_tests("on-chip"); +} diff --git a/tests/main.c b/tests/main.c index 81a24be..96de04d 100644 --- a/tests/main.c +++ b/tests/main.c @@ -42,10 +42,18 @@ void whal_Test_Ipc_Platform(void); #endif #endif +#ifdef WHAL_TEST_ENABLE_SPI_LOOPBACK +void whal_Test_Spi_Loopback(void); +#endif + #ifdef WHAL_TEST_ENABLE_CRYPTO void whal_Test_Crypto(void); #endif +#ifdef WHAL_TEST_ENABLE_BLOCK +void whal_Test_Block(void); +#endif + int g_whalTestPassed; int g_whalTestFailed; int g_whalTestSkipped; @@ -116,10 +124,18 @@ void main(void) #endif #endif +#ifdef WHAL_TEST_ENABLE_SPI_LOOPBACK + whal_Test_Spi_Loopback(); +#endif + #ifdef WHAL_TEST_ENABLE_CRYPTO whal_Test_Crypto(); #endif +#ifdef WHAL_TEST_ENABLE_BLOCK + whal_Test_Block(); +#endif + WHAL_TEST_SUMMARY(); if (g_whalTestFailed == 0) { diff --git a/tests/spi_loopback/test_spi_loopback.c b/tests/spi_loopback/test_spi_loopback.c new file mode 100644 index 0000000..26ec36b --- /dev/null +++ b/tests/spi_loopback/test_spi_loopback.c @@ -0,0 +1,81 @@ +#include +#include "board.h" +#include "test.h" + +/* + * Generic SPI loopback test. + * + * Requires MOSI wired to MISO so transmitted data is received back. + * The board must provide g_whalSpi. + */ + +static whal_Spi_ComCfg loopbackComCfg = { + .freq = 1000000, + .mode = WHAL_SPI_MODE_0, + .wordSz = 8, + .dataLines = 1, +}; + +static void Test_SpiLoopback_SendRecv(void) +{ + uint8_t tx[] = {0xDE, 0xAD, 0xBE, 0xEF}; + uint8_t rx[4] = {0}; + + WHAL_ASSERT_EQ(whal_Spi_StartCom(&g_whalSpi, &loopbackComCfg), + WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Spi_SendRecv(&g_whalSpi, + tx, sizeof(tx), rx, sizeof(rx)), + WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Spi_EndCom(&g_whalSpi), WHAL_SUCCESS); + WHAL_ASSERT_MEM_EQ(rx, tx, sizeof(tx)); +} + +static void Test_SpiLoopback_NullBufWithLen(void) +{ + uint8_t buf[1] = {0}; + + WHAL_ASSERT_EQ(whal_Spi_StartCom(&g_whalSpi, &loopbackComCfg), + WHAL_SUCCESS); + + /* NULL tx with nonzero txLen */ + WHAL_ASSERT_EQ(whal_Spi_SendRecv(&g_whalSpi, NULL, 1, buf, 1), + WHAL_EINVAL); + + /* NULL rx with nonzero rxLen */ + WHAL_ASSERT_EQ(whal_Spi_SendRecv(&g_whalSpi, buf, 1, NULL, 1), + WHAL_EINVAL); + + WHAL_ASSERT_EQ(whal_Spi_EndCom(&g_whalSpi), WHAL_SUCCESS); +} + +static void Test_SpiLoopback_SendRecvDrain(void) +{ + uint8_t tx[] = {0xDE, 0xAD, 0xBE, 0xEF}; + uint8_t rx[4] = {0}; + uint8_t expected[4] = {0xFF, 0xFF, 0xFF, 0xFF}; + + WHAL_ASSERT_EQ(whal_Spi_StartCom(&g_whalSpi, &loopbackComCfg), + WHAL_SUCCESS); + + /* Send-only: driver must drain RX FIFO internally */ + WHAL_ASSERT_EQ(whal_Spi_SendRecv(&g_whalSpi, tx, sizeof(tx), NULL, 0), + WHAL_SUCCESS); + + /* Receive-only: loopback returns 0xFF (the dummy TX byte) */ + WHAL_ASSERT_EQ(whal_Spi_SendRecv(&g_whalSpi, NULL, 0, rx, sizeof(rx)), + WHAL_SUCCESS); + + WHAL_ASSERT_EQ(whal_Spi_EndCom(&g_whalSpi), WHAL_SUCCESS); + + /* If RX wasn't drained, stale 0xDE/0xAD/0xBE/0xEF leaks here */ + WHAL_ASSERT_MEM_EQ(rx, expected, sizeof(expected)); +} + +void whal_Test_Spi_Loopback(void) +{ + WHAL_TEST_SUITE_START("spi_loopback"); + WHAL_TEST(Test_SpiLoopback_SendRecv); + WHAL_TEST(Test_SpiLoopback_NullBufWithLen); + WHAL_TEST(Test_SpiLoopback_SendRecvDrain); + WHAL_TEST_SUITE_END(); +} diff --git a/tests/test.h b/tests/test.h index e6b25b6..7e0ea1c 100644 --- a/tests/test.h +++ b/tests/test.h @@ -117,11 +117,14 @@ extern int g_whalTestCurSkipped; #define WHAL_ASSERT_EQ(a, b) \ do { \ - if ((a) != (b)) { \ - whal_Test_Printf(" ASSERT_EQ failed at line %d\n", __LINE__); \ - whal_Test_Printf(" got: %d, expected: %d\n", \ - (int)(a), (int)(b)); \ - g_whalTestCurFailed = 1; \ + int _a = (int)(a); \ + int _b = (int)(b); \ + if (_a != _b) { \ + whal_Test_Printf(" ASSERT_EQ failed at %s:%d\n", \ + __FILE__, __LINE__); \ + whal_Test_Printf(" got: %d, expected: %d\n", \ + _a, _b); \ + g_whalTestCurFailed = 1; \ return; \ } \ } while (0) @@ -129,8 +132,9 @@ extern int g_whalTestCurSkipped; #define WHAL_ASSERT_NEQ(a, b) \ do { \ if ((a) == (b)) { \ - whal_Test_Printf(" ASSERT_NEQ failed at line %d\n", __LINE__);\ - g_whalTestCurFailed = 1; \ + whal_Test_Printf(" ASSERT_NEQ failed at %s:%d\n", \ + __FILE__, __LINE__); \ + g_whalTestCurFailed = 1; \ return; \ } \ } while (0) @@ -141,9 +145,10 @@ extern int g_whalTestCurSkipped; const unsigned char *_b = (const unsigned char *)(b); \ for (size_t _i = 0; _i < (len); _i++) { \ if (_a[_i] != _b[_i]) { \ - whal_Test_Printf(" ASSERT_MEM_EQ failed at line %d, " \ - "byte offset: %d\n", __LINE__, (int)_i); \ - g_whalTestCurFailed = 1; \ + whal_Test_Printf(" ASSERT_MEM_EQ failed at " \ + "%s:%d, byte offset: %d\n", \ + __FILE__, __LINE__, (int)_i); \ + g_whalTestCurFailed = 1; \ return; \ } \ } \ diff --git a/wolfHAL/block/block.h b/wolfHAL/block/block.h new file mode 100644 index 0000000..d9611b0 --- /dev/null +++ b/wolfHAL/block/block.h @@ -0,0 +1,118 @@ +#ifndef WHAL_BLOCK_H +#define WHAL_BLOCK_H + +#include +#include +#include +#include + +/* + * @file block.h + * @brief Generic block device abstraction and driver interface. + * + * Block devices are addressed in fixed-size blocks rather than individual + * bytes. This is suitable for SD cards, eMMC, and similar storage where + * the smallest addressable unit is a block (e.g. 512 bytes or 4096 bytes). + */ + +typedef struct whal_Block whal_Block; + +/* + * @brief Driver vtable for block devices. + */ +typedef struct { + /* Bring the block device into a usable state. */ + whal_Error (*Init)(whal_Block *blockDev); + /* Release any resources owned by the block driver. */ + whal_Error (*Deinit)(whal_Block *blockDev); + /* Read blocks from the device into a buffer. */ + whal_Error (*Read)(whal_Block *blockDev, uint32_t block, uint8_t *data, uint32_t blockCount); + /* Write data to the device starting at @p block. */ + whal_Error (*Write)(whal_Block *blockDev, uint32_t block, const uint8_t *data, uint32_t blockCount); + /* Erase blocks on the device starting at @p block. */ + whal_Error (*Erase)(whal_Block *blockDev, uint32_t block, uint32_t blockCount); +} whal_BlockDriver; + +/* + * @brief Block device instance tying configuration to a driver implementation. + */ +struct whal_Block { + const whal_Regmap regmap; + const whal_BlockDriver *driver; + void *cfg; +}; + +/* + * @brief Initialize a block device and its driver. + * + * @param blockDev Block device instance to initialize. + * + * @retval WHAL_SUCCESS Driver-specific init completed. + * @retval WHAL_EINVAL Null pointer or missing driver function. + */ +#ifdef WHAL_CFG_DIRECT_CALLBACKS +#define whal_Block_Init(blockDev) ((blockDev)->driver->Init((blockDev))) +#define whal_Block_Deinit(blockDev) ((blockDev)->driver->Deinit((blockDev))) +#define whal_Block_Read(blockDev, block, data, blockCount) ((blockDev)->driver->Read((blockDev), (block), (data), (blockCount))) +#define whal_Block_Write(blockDev, block, data, blockCount) ((blockDev)->driver->Write((blockDev), (block), (data), (blockCount))) +#define whal_Block_Erase(blockDev, block, blockCount) ((blockDev)->driver->Erase((blockDev), (block), (blockCount))) +#else +/* + * @brief Initialize a block device and its driver. + * + * @param blockDev Block device instance to initialize. + * + * @retval WHAL_SUCCESS Driver-specific init completed. + * @retval WHAL_EINVAL Null pointer or missing driver function. + */ +whal_Error whal_Block_Init(whal_Block *blockDev); +/* + * @brief Deinitialize a block device. + * + * @param blockDev Block device instance to deinitialize. + * + * @retval WHAL_SUCCESS Driver-specific deinit completed. + * @retval WHAL_EINVAL Null pointer or missing driver function. + */ +whal_Error whal_Block_Deinit(whal_Block *blockDev); +/* + * @brief Read blocks from a block device into a buffer. + * + * @param blockDev Block device instance to read from. + * @param block Starting block number. + * @param data Destination buffer. + * @param blockCount Number of blocks to read. + * + * @retval WHAL_SUCCESS Read completed. + * @retval WHAL_EINVAL Null pointer or missing driver function. + */ +whal_Error whal_Block_Read(whal_Block *blockDev, uint32_t block, uint8_t *data, + uint32_t blockCount); +/* + * @brief Write data to a block device. + * + * @param blockDev Block device instance to program. + * @param block Starting block number. + * @param data Buffer containing data to write. + * @param blockCount Number of blocks to write. + * + * @retval WHAL_SUCCESS Write accepted or completed. + * @retval WHAL_EINVAL Null pointer or missing driver function. + */ +whal_Error whal_Block_Write(whal_Block *blockDev, uint32_t block, + const uint8_t *data, uint32_t blockCount); +/* + * @brief Erase blocks on a block device. + * + * @param blockDev Block device instance to erase. + * @param block Starting block number. + * @param blockCount Number of blocks to erase. + * + * @retval WHAL_SUCCESS Erase accepted or completed. + * @retval WHAL_EINVAL Null pointer or missing driver function. + */ +whal_Error whal_Block_Erase(whal_Block *blockDev, uint32_t block, + uint32_t blockCount); +#endif + +#endif /* WHAL_BLOCK_H */ diff --git a/wolfHAL/block/sdhc_spi.h b/wolfHAL/block/sdhc_spi.h new file mode 100644 index 0000000..754ce56 --- /dev/null +++ b/wolfHAL/block/sdhc_spi.h @@ -0,0 +1,46 @@ +#ifndef WHAL_SDHC_SPI_H +#define WHAL_SDHC_SPI_H + +#include +#include +#include +#include +#include +#include + +/* + * @file sdhc_spi.h + * @brief SD card driver over SPI (SDHC/SDXC). + * + * Implements the whal_Block interface for SD cards using the SD/SPI + * protocol. Supports SDHC and SDXC cards with 512-byte block addressing. + * + * The driver handles: + * - Card initialization (CMD0, CMD8, ACMD41, CMD58) + * - Single and multi-block reads (CMD17, CMD18) + * - Single and multi-block writes (CMD24, CMD25) + * - Block range erase (CMD32, CMD33, CMD38) + */ + +#define WHAL_SDHC_SPI_BLOCK_SZ 512 + +typedef struct whal_SdhcSpi_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; +} whal_SdhcSpi_Cfg; + +extern const whal_BlockDriver whal_SdhcSpi_Driver; + +whal_Error whal_SdhcSpi_Init(whal_Block *blockDev); +whal_Error whal_SdhcSpi_Deinit(whal_Block *blockDev); +whal_Error whal_SdhcSpi_Read(whal_Block *blockDev, uint32_t block, + uint8_t *data, uint32_t blockCount); +whal_Error whal_SdhcSpi_Write(whal_Block *blockDev, uint32_t block, + const uint8_t *data, uint32_t blockCount); +whal_Error whal_SdhcSpi_Erase(whal_Block *blockDev, uint32_t block, + uint32_t blockCount); + +#endif /* WHAL_SDHC_SPI_H */ diff --git a/wolfHAL/spi/spi.h b/wolfHAL/spi/spi.h index fba83e0..c53a3e4 100644 --- a/wolfHAL/spi/spi.h +++ b/wolfHAL/spi/spi.h @@ -13,6 +13,26 @@ typedef struct whal_Spi whal_Spi; +/* SPI mode (CPOL/CPHA) */ +#define WHAL_SPI_MODE_0 0 /* CPOL=0, CPHA=0 */ +#define WHAL_SPI_MODE_1 1 /* CPOL=0, CPHA=1 */ +#define WHAL_SPI_MODE_2 2 /* CPOL=1, CPHA=0 */ +#define WHAL_SPI_MODE_3 3 /* CPOL=1, CPHA=1 */ + +/* + * @brief SPI communication session parameters. + * + * Platform-independent configuration applied when starting a communication + * session via StartCom and kept in effect until EndCom. The platform driver + * translates these to hardware register values. + */ +typedef struct { + uint32_t freq; /* Bus frequency in Hz */ + uint8_t mode; /* WHAL_SPI_MODE_x */ + uint8_t wordSz; /* Word size in bits (typically 8) */ + uint8_t dataLines; /* Number of data lines (1, 2, 4, or 8) */ +} whal_Spi_ComCfg; + /* * @brief Driver vtable for SPI devices. */ @@ -21,12 +41,12 @@ typedef struct { whal_Error (*Init)(whal_Spi *spiDev); /* Deinitialize the SPI hardware. */ whal_Error (*Deinit)(whal_Spi *spiDev); + /* Begin a communication session with the given parameters. */ + whal_Error (*StartCom)(whal_Spi *spiDev, whal_Spi_ComCfg *comCfg); + /* End the current communication session. */ + whal_Error (*EndCom)(whal_Spi *spiDev); /* Perform a bidirectional transfer. */ - whal_Error (*SendRecv)(whal_Spi *spiDev, void *spiComCfg, const uint8_t *tx, size_t txLen, uint8_t *rx, size_t rxLen); - /* Transmit a buffer. */ - whal_Error (*Send)(whal_Spi *spiDev, void *spiComCfg, const uint8_t *data, size_t dataSz); - /* Receive into a buffer. */ - whal_Error (*Recv)(whal_Spi *spiDev, void *spiComCfg, uint8_t *data, size_t dataSz); + whal_Error (*SendRecv)(whal_Spi *spiDev, const uint8_t *tx, size_t txLen, uint8_t *rx, size_t rxLen); } whal_SpiDriver; /* @@ -49,12 +69,11 @@ struct whal_Spi { #ifdef WHAL_CFG_DIRECT_CALLBACKS #define whal_Spi_Init(spiDev) ((spiDev)->driver->Init((spiDev))) #define whal_Spi_Deinit(spiDev) ((spiDev)->driver->Deinit((spiDev))) -#define whal_Spi_SendRecv(spiDev, spiComCfg, tx, txLen, rx, rxLen) \ - ((spiDev)->driver->SendRecv((spiDev), (spiComCfg), (tx), (txLen), (rx), (rxLen))) -#define whal_Spi_Send(spiDev, spiComCfg, data, dataSz) \ - ((spiDev)->driver->Send((spiDev), (spiComCfg), (data), (dataSz))) -#define whal_Spi_Recv(spiDev, spiComCfg, data, dataSz) \ - ((spiDev)->driver->Recv((spiDev), (spiComCfg), (data), (dataSz))) +#define whal_Spi_StartCom(spiDev, spiComCfg) \ + ((spiDev)->driver->StartCom((spiDev), (spiComCfg))) +#define whal_Spi_EndCom(spiDev) ((spiDev)->driver->EndCom((spiDev))) +#define whal_Spi_SendRecv(spiDev, tx, txLen, rx, rxLen) \ + ((spiDev)->driver->SendRecv((spiDev), (tx), (txLen), (rx), (rxLen))) #else /* * @brief Initializes an SPI device and its driver. @@ -75,43 +94,48 @@ whal_Error whal_Spi_Init(whal_Spi *spiDev); */ whal_Error whal_Spi_Deinit(whal_Spi *spiDev); /* - * @brief Perform a bidirectional SPI transfer. + * @brief Begin a communication session with the given parameters. * - * @param spiDev Pointer to the SPI instance. - * @param spiComCfg Communication configuration (chip select, mode, speed, etc). - * @param tx Buffer of bytes to transmit (may be NULL for read-only). - * @param txLen Number of bytes to transmit. - * @param rx Buffer to receive bytes into (may be NULL for write-only). - * @param rxLen Number of bytes to receive. - * - * @retval WHAL_SUCCESS Transfer completed or queued. - * @retval WHAL_EINVAL Null pointer or driver failed to transfer. + * Configures the SPI peripheral for the specified mode, frequency, + * and word size. Must be called before any SendRecv calls. + * + * @param spiDev Pointer to the SPI instance. + * @param comCfg Per-session communication parameters. + * + * @retval WHAL_SUCCESS Communication session started. + * @retval WHAL_EINVAL Null pointer or invalid parameters. */ -whal_Error whal_Spi_SendRecv(whal_Spi *spiDev, void *spiComCfg, const uint8_t *tx, size_t txLen, uint8_t *rx, size_t rxLen); +whal_Error whal_Spi_StartCom(whal_Spi *spiDev, whal_Spi_ComCfg *comCfg); /* - * @brief Sends a buffer over SPI. + * @brief End the current communication session. + * + * Disables the SPI peripheral. Must be called after the last + * SendRecv in a session. * * @param spiDev Pointer to the SPI instance. - * @param spiComCfg Communication configuration (chip select, mode, speed, etc). - * @param data Buffer to transmit. - * @param dataSz Number of bytes to send. * - * @retval WHAL_SUCCESS Buffer was queued or transmitted. - * @retval WHAL_EINVAL Null pointer or driver failed to send. + * @retval WHAL_SUCCESS Communication session ended. + * @retval WHAL_EINVAL Null pointer or driver error. */ -whal_Error whal_Spi_Send(whal_Spi *spiDev, void *spiComCfg, const uint8_t *data, size_t dataSz); +whal_Error whal_Spi_EndCom(whal_Spi *spiDev); /* - * @brief Receives data from SPI into a buffer. + * @brief Perform a bidirectional SPI transfer. + * + * Clocks max(txLen, rxLen) bytes. When one side is shorter: + * - tx exhausted: sends 0xFF for remaining clocks. + * - rx exhausted or NULL: received bytes are discarded. + * Either tx or rx (or both) may be NULL. * * @param spiDev Pointer to the SPI instance. - * @param spiComCfg Communication configuration (chip select, mode, speed, etc). - * @param data Destination buffer. - * @param dataSz Maximum number of bytes to read. + * @param tx Buffer of bytes to transmit (NULL to send 0xFF). + * @param txLen Number of bytes in tx (0 when tx is NULL). + * @param rx Buffer to receive bytes into (NULL to discard). + * @param rxLen Number of bytes to receive (0 when rx is NULL). * - * @retval WHAL_SUCCESS Buffer was filled or receive started. - * @retval WHAL_EINVAL Null pointer or driver failed to receive. + * @retval WHAL_SUCCESS Transfer completed. + * @retval WHAL_EINVAL Null spiDev or driver failed to transfer. */ -whal_Error whal_Spi_Recv(whal_Spi *spiDev, void *spiComCfg, uint8_t *data, size_t dataSz); +whal_Error whal_Spi_SendRecv(whal_Spi *spiDev, const uint8_t *tx, size_t txLen, uint8_t *rx, size_t rxLen); #endif #endif /* WHAL_SPI_H */ diff --git a/wolfHAL/spi/stm32wb_spi.h b/wolfHAL/spi/stm32wb_spi.h index 026ea75..f5d8dfb 100644 --- a/wolfHAL/spi/stm32wb_spi.h +++ b/wolfHAL/spi/stm32wb_spi.h @@ -19,16 +19,6 @@ * - Software slave management (chip select via GPIO) */ -/* - * @brief SPI clock polarity/phase mode selection. - */ -typedef enum { - WHAL_STM32WB_SPI_MODE_0, /* CPOL=0, CPHA=0 */ - WHAL_STM32WB_SPI_MODE_1, /* CPOL=0, CPHA=1 */ - WHAL_STM32WB_SPI_MODE_2, /* CPOL=1, CPHA=0 */ - WHAL_STM32WB_SPI_MODE_3, /* CPOL=1, CPHA=1 */ -} whal_Stm32wbSpi_Mode; - /* * @brief SPI device configuration. */ @@ -37,14 +27,6 @@ typedef struct whal_Stm32wbSpi_Cfg { whal_Timeout *timeout; } whal_Stm32wbSpi_Cfg; -/* - * @brief Per-transaction SPI communication parameters. - */ -typedef struct whal_Stm32wbSpi_ComCfg { - uint32_t mode; /* SPI mode (WHAL_STM32WB_SPI_MODE_x) */ - uint32_t baud; /* Baud rate in Hz */ -} whal_Stm32wbSpi_ComCfg; - /* * @brief Driver instance for STM32 SPI peripheral. */ @@ -59,6 +41,7 @@ extern const whal_SpiDriver whal_Stm32wbSpi_Driver; * @retval WHAL_EINVAL Invalid arguments. */ whal_Error whal_Stm32wbSpi_Init(whal_Spi *spiDev); + /* * @brief Deinitialize the STM32 SPI peripheral. * @@ -68,46 +51,47 @@ whal_Error whal_Stm32wbSpi_Init(whal_Spi *spiDev); * @retval WHAL_EINVAL Invalid arguments. */ whal_Error whal_Stm32wbSpi_Deinit(whal_Spi *spiDev); + /* - * @brief Perform a full-duplex SPI transfer. + * @brief Begin a communication session on the STM32 SPI peripheral. * - * @param spiDev SPI device instance. - * @param spiComCfg Per-transfer configuration. - * @param tx Data to transmit (may be NULL). - * @param txLen Number of bytes to transmit. - * @param rx Receive buffer (may be NULL). - * @param rxLen Number of bytes to receive. + * Configures clock polarity, phase, and baud rate from comCfg, + * then enables the peripheral. * - * @retval WHAL_SUCCESS Transfer completed. + * @param spiDev SPI device instance. + * @param comCfg Per-session communication parameters. + * + * @retval WHAL_SUCCESS Communication session started. * @retval WHAL_EINVAL Invalid arguments. */ -whal_Error whal_Stm32wbSpi_SendRecv(whal_Spi *spiDev, void *spiComCfg, const uint8_t *tx, - size_t txLen, uint8_t *rx, size_t rxLen); +whal_Error whal_Stm32wbSpi_StartCom(whal_Spi *spiDev, whal_Spi_ComCfg *comCfg); + /* - * @brief Transmit a buffer over SPI. + * @brief End the current communication session on the STM32 SPI peripheral. * - * @param spiDev SPI device instance. - * @param spiComCfg Per-transfer configuration. - * @param data Buffer to transmit. - * @param dataSz Number of bytes to transmit. + * Disables SPE. * - * @retval WHAL_SUCCESS Transfer completed. + * @param spiDev SPI device instance. + * + * @retval WHAL_SUCCESS Communication session ended. * @retval WHAL_EINVAL Invalid arguments. */ -whal_Error whal_Stm32wbSpi_Send(whal_Spi *spiDev, void *spiComCfg, const uint8_t *data, - size_t dataSz); +whal_Error whal_Stm32wbSpi_EndCom(whal_Spi *spiDev); + /* - * @brief Receive a buffer over SPI. + * @brief Perform a full-duplex SPI transfer. * - * @param spiDev SPI device instance. - * @param spiComCfg Per-transfer configuration. - * @param data Receive buffer. - * @param dataSz Number of bytes to receive. + * @param spiDev SPI device instance. + * @param tx Data to transmit (may be NULL). + * @param txLen Number of bytes to transmit. + * @param rx Receive buffer (may be NULL). + * @param rxLen Number of bytes to receive. * * @retval WHAL_SUCCESS Transfer completed. * @retval WHAL_EINVAL Invalid arguments. */ -whal_Error whal_Stm32wbSpi_Recv(whal_Spi *spiDev, void *spiComCfg, uint8_t *data, - size_t dataSz); +whal_Error whal_Stm32wbSpi_SendRecv(whal_Spi *spiDev, + const uint8_t *tx, size_t txLen, + uint8_t *rx, size_t rxLen); #endif /* WHAL_STM32WB_SPI_H */ diff --git a/wolfHAL/wolfHAL.h b/wolfHAL/wolfHAL.h index f6643f7..f23cfaa 100644 --- a/wolfHAL/wolfHAL.h +++ b/wolfHAL/wolfHAL.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include