Files
bs_explorer/modules/spi_flash/spi_flash.h
François c4afe877ce spi_flash: add generic SPI NOR flash layer (Phase 3)
Transport-agnostic (xfer callback) layer with a JEDEC-ID chip database
and detect/read/erase_sector/program_page/program/verify. Standard
opcode set, 3- and 4-byte addressing (4-byte command opcodes for parts
over 16 MB). Seeded with the KCU105's MT25QU256 plus common
Winbond/Macronix/ISSI/Micron parts.

detect + read validated on the KCU105 over the proxy. erase/program are
implemented but not yet hardware-tested (destructive on a config flash).
2026-05-24 00:13:29 +02:00

80 lines
3.4 KiB
C

#ifndef _SPI_FLASH_H
#define _SPI_FLASH_H
/*
* Generic SPI NOR flash layer (Phase 3).
*
* Transport-agnostic: it issues SPI commands through an `xfer` callback
* (one CS-framed transaction), so it works over the BSCAN proxy, the
* EXTEST bit-bang, or anything else that can clock bytes in and out.
*
* Standard JEDEC command set (RDID/READ/WREN/RDSR/PP/SE). Chips are
* matched by JEDEC ID against a small built-in database; 3- and 4-byte
* addressing are both supported (parts over 16 MB use the 4-byte
* command opcodes, which are stateless regardless of the part's
* address-mode latch).
*/
#include <stddef.h>
#include <stdint.h>
/* One CS-framed SPI transaction: clock out `txlen` MOSI bytes, then
* read `rxlen` MISO bytes into `rx`. Either length may be 0. Returns 0
* on success, <0 on error. */
typedef int (*spi_flash_xfer_fn)(void *ctx,
const uint8_t *tx, size_t txlen,
uint8_t *rx, size_t rxlen);
typedef struct {
const char *name;
uint8_t mfg_id; /* JEDEC byte 0 (manufacturer) */
uint16_t dev_id; /* JEDEC bytes 1..2 ((b1 << 8) | b2) */
uint32_t size_bytes; /* total capacity */
uint32_t page_size; /* program granularity (typically 256) */
uint32_t sector_size; /* erase granularity (typically 4096) */
uint8_t addr_bytes; /* address width: 3 or 4 */
uint8_t read_cmd; /* READ opcode (no dummy) */
uint8_t pp_cmd; /* PAGE PROGRAM opcode */
uint8_t erase_cmd; /* sector/subsector ERASE opcode */
} spi_flash_chip;
typedef struct {
spi_flash_xfer_fn xfer; /* transport */
void *ctx; /* opaque, passed to xfer */
const spi_flash_chip *chip; /* matched part, set by detect */
uint8_t jedec[3]; /* raw JEDEC ID from last detect */
} spi_flash;
/* Read the JEDEC ID (0x9F) and match the database.
* Returns 0 if a known chip matched (sf->chip set), 1 if the ID was
* read but is not in the database (sf->chip left NULL, sf->jedec set),
* <0 on transport error. */
int spi_flash_detect(spi_flash *sf);
/* Read `len` bytes starting at `addr` into `buf`. Returns 0 / <0. */
int spi_flash_read(spi_flash *sf, uint32_t addr, uint8_t *buf, size_t len);
/* Erase the sector containing `addr` (WREN + ERASE + poll WIP). */
int spi_flash_erase_sector(spi_flash *sf, uint32_t addr);
/* Program at most one page at `addr` (`len` <= page_size and must not
* cross a page boundary): WREN + PP + poll WIP. */
int spi_flash_program_page(spi_flash *sf, uint32_t addr,
const uint8_t *data, size_t len);
/* Program `len` bytes from `addr`, splitting on page boundaries. The
* target range must already be erased. */
int spi_flash_program(spi_flash *sf, uint32_t addr,
const uint8_t *data, size_t len);
/* Read back and compare. Returns 0 if identical, 1 on first mismatch
* (and reports its offset via *mismatch_off if non-NULL), <0 on error. */
int spi_flash_verify(spi_flash *sf, uint32_t addr,
const uint8_t *data, size_t len, uint32_t *mismatch_off);
/* Built-in database access. */
int spi_flash_chip_count(void);
const spi_flash_chip *spi_flash_chip_by_index(int i);
#endif