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).
80 lines
3.4 KiB
C
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
|