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).
This commit is contained in:
7
modules/spi_flash/CMakeLists.txt
Normal file
7
modules/spi_flash/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
set(SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
|
|
||||||
|
file(GLOB_RECURSE ALL_SOURCES "*.c")
|
||||||
|
|
||||||
|
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)
|
||||||
|
|
||||||
|
add_library(spi_flash ${ALL_SOURCES})
|
||||||
202
modules/spi_flash/spi_flash.c
Normal file
202
modules/spi_flash/spi_flash.c
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "spi_flash.h"
|
||||||
|
|
||||||
|
/* Standard JEDEC SPI NOR opcodes shared by all parts. */
|
||||||
|
#define CMD_RDID 0x9F /* read JEDEC ID */
|
||||||
|
#define CMD_WREN 0x06 /* write enable */
|
||||||
|
#define CMD_RDSR 0x05 /* read status register */
|
||||||
|
#define SR_WIP 0x01 /* status: write-in-progress */
|
||||||
|
|
||||||
|
/* Largest single transport transaction for read/verify, to bound the
|
||||||
|
* buffers the transport has to allocate. */
|
||||||
|
#define SPI_FLASH_CHUNK 4096u
|
||||||
|
|
||||||
|
/* Safety cap on status polling so a stuck WIP can't hang forever. */
|
||||||
|
#define WIP_POLL_MAX 5000000
|
||||||
|
|
||||||
|
/* --- Chip database ------------------------------------------------- */
|
||||||
|
|
||||||
|
static const spi_flash_chip chip_db[] = {
|
||||||
|
/* Micron MT25QU256 — KCU105 config flash, 1.8 V, 32 MB, 4-byte addr.
|
||||||
|
* 4-byte command opcodes (READ4B/PP4B/SUBSEC-ERASE4B) so the full
|
||||||
|
* range is reachable regardless of the address-mode latch. */
|
||||||
|
{ "Micron MT25QU256", 0x20, 0xBB19, 32u * 1024 * 1024, 256, 4096, 4, 0x13, 0x12, 0x21 },
|
||||||
|
{ "Micron MT25QL256", 0x20, 0xBA19, 32u * 1024 * 1024, 256, 4096, 4, 0x13, 0x12, 0x21 },
|
||||||
|
/* 16 MB, 3-byte parts. */
|
||||||
|
{ "Winbond W25Q128", 0xEF, 0x4018, 16u * 1024 * 1024, 256, 4096, 3, 0x03, 0x02, 0x20 },
|
||||||
|
{ "Macronix MX25L128",0xC2, 0x2018, 16u * 1024 * 1024, 256, 4096, 3, 0x03, 0x02, 0x20 },
|
||||||
|
{ "ISSI IS25LP128", 0x9D, 0x6018, 16u * 1024 * 1024, 256, 4096, 3, 0x03, 0x02, 0x20 },
|
||||||
|
};
|
||||||
|
|
||||||
|
#define CHIP_DB_LEN ((int)(sizeof(chip_db) / sizeof(chip_db[0])))
|
||||||
|
|
||||||
|
int spi_flash_chip_count(void)
|
||||||
|
{
|
||||||
|
return CHIP_DB_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
const spi_flash_chip *spi_flash_chip_by_index(int i)
|
||||||
|
{
|
||||||
|
if (i < 0 || i >= CHIP_DB_LEN) return NULL;
|
||||||
|
return &chip_db[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Helpers ------------------------------------------------------- */
|
||||||
|
|
||||||
|
static int put_addr(uint8_t *b, uint32_t addr, int addr_bytes)
|
||||||
|
{
|
||||||
|
if (addr_bytes == 4) {
|
||||||
|
b[0] = (uint8_t)(addr >> 24);
|
||||||
|
b[1] = (uint8_t)(addr >> 16);
|
||||||
|
b[2] = (uint8_t)(addr >> 8);
|
||||||
|
b[3] = (uint8_t)(addr);
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
b[0] = (uint8_t)(addr >> 16);
|
||||||
|
b[1] = (uint8_t)(addr >> 8);
|
||||||
|
b[2] = (uint8_t)(addr);
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int write_enable(spi_flash *sf)
|
||||||
|
{
|
||||||
|
uint8_t cmd = CMD_WREN;
|
||||||
|
return sf->xfer(sf->ctx, &cmd, 1, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Poll the status register until write-in-progress clears. */
|
||||||
|
static int wait_wip(spi_flash *sf)
|
||||||
|
{
|
||||||
|
long tries;
|
||||||
|
for (tries = 0; tries < WIP_POLL_MAX; tries++) {
|
||||||
|
uint8_t cmd = CMD_RDSR, st = 0;
|
||||||
|
if (sf->xfer(sf->ctx, &cmd, 1, &st, 1) < 0) return -1;
|
||||||
|
if (!(st & SR_WIP)) return 0;
|
||||||
|
}
|
||||||
|
return -1; /* timeout */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Operations ---------------------------------------------------- */
|
||||||
|
|
||||||
|
int spi_flash_detect(spi_flash *sf)
|
||||||
|
{
|
||||||
|
uint8_t cmd = CMD_RDID;
|
||||||
|
uint8_t id[3] = {0, 0, 0};
|
||||||
|
uint16_t dev;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!sf || !sf->xfer) return -1;
|
||||||
|
if (sf->xfer(sf->ctx, &cmd, 1, id, 3) < 0) return -1;
|
||||||
|
|
||||||
|
memcpy(sf->jedec, id, 3);
|
||||||
|
sf->chip = NULL;
|
||||||
|
|
||||||
|
dev = (uint16_t)((id[1] << 8) | id[2]);
|
||||||
|
for (i = 0; i < CHIP_DB_LEN; i++) {
|
||||||
|
if (chip_db[i].mfg_id == id[0] && chip_db[i].dev_id == dev) {
|
||||||
|
sf->chip = &chip_db[i];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1; /* read OK but unknown part */
|
||||||
|
}
|
||||||
|
|
||||||
|
int spi_flash_read(spi_flash *sf, uint32_t addr, uint8_t *buf, size_t len)
|
||||||
|
{
|
||||||
|
uint8_t cmd[1 + 4];
|
||||||
|
int n;
|
||||||
|
|
||||||
|
if (!sf || !sf->chip || !buf) return -1;
|
||||||
|
|
||||||
|
while (len) {
|
||||||
|
size_t chunk = (len > SPI_FLASH_CHUNK) ? SPI_FLASH_CHUNK : len;
|
||||||
|
cmd[0] = sf->chip->read_cmd;
|
||||||
|
n = 1 + put_addr(&cmd[1], addr, sf->chip->addr_bytes);
|
||||||
|
if (sf->xfer(sf->ctx, cmd, (size_t)n, buf, chunk) < 0) return -1;
|
||||||
|
addr += (uint32_t)chunk;
|
||||||
|
buf += chunk;
|
||||||
|
len -= chunk;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int spi_flash_erase_sector(spi_flash *sf, uint32_t addr)
|
||||||
|
{
|
||||||
|
uint8_t cmd[1 + 4];
|
||||||
|
int n;
|
||||||
|
|
||||||
|
if (!sf || !sf->chip) return -1;
|
||||||
|
|
||||||
|
addr -= (addr % sf->chip->sector_size); /* align to sector base */
|
||||||
|
|
||||||
|
if (write_enable(sf) < 0) return -1;
|
||||||
|
cmd[0] = sf->chip->erase_cmd;
|
||||||
|
n = 1 + put_addr(&cmd[1], addr, sf->chip->addr_bytes);
|
||||||
|
if (sf->xfer(sf->ctx, cmd, (size_t)n, NULL, 0) < 0) return -1;
|
||||||
|
return wait_wip(sf);
|
||||||
|
}
|
||||||
|
|
||||||
|
int spi_flash_program_page(spi_flash *sf, uint32_t addr,
|
||||||
|
const uint8_t *data, size_t len)
|
||||||
|
{
|
||||||
|
uint8_t cmd[1 + 4 + 256];
|
||||||
|
int n;
|
||||||
|
|
||||||
|
if (!sf || !sf->chip || !data) return -1;
|
||||||
|
if (len == 0) return 0;
|
||||||
|
if (len > sf->chip->page_size || len > 256) return -1;
|
||||||
|
/* must not cross a page boundary */
|
||||||
|
if ((addr % sf->chip->page_size) + len > sf->chip->page_size) return -1;
|
||||||
|
|
||||||
|
if (write_enable(sf) < 0) return -1;
|
||||||
|
cmd[0] = sf->chip->pp_cmd;
|
||||||
|
n = 1 + put_addr(&cmd[1], addr, sf->chip->addr_bytes);
|
||||||
|
memcpy(&cmd[n], data, len);
|
||||||
|
n += (int)len;
|
||||||
|
if (sf->xfer(sf->ctx, cmd, (size_t)n, NULL, 0) < 0) return -1;
|
||||||
|
return wait_wip(sf);
|
||||||
|
}
|
||||||
|
|
||||||
|
int spi_flash_program(spi_flash *sf, uint32_t addr,
|
||||||
|
const uint8_t *data, size_t len)
|
||||||
|
{
|
||||||
|
if (!sf || !sf->chip || !data) return -1;
|
||||||
|
|
||||||
|
while (len) {
|
||||||
|
uint32_t page = sf->chip->page_size;
|
||||||
|
uint32_t off = addr % page;
|
||||||
|
size_t chunk = page - off;
|
||||||
|
if (chunk > len) chunk = len;
|
||||||
|
if (spi_flash_program_page(sf, addr, data, chunk) < 0) return -1;
|
||||||
|
addr += (uint32_t)chunk;
|
||||||
|
data += chunk;
|
||||||
|
len -= chunk;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int spi_flash_verify(spi_flash *sf, uint32_t addr,
|
||||||
|
const uint8_t *data, size_t len, uint32_t *mismatch_off)
|
||||||
|
{
|
||||||
|
uint8_t tmp[SPI_FLASH_CHUNK];
|
||||||
|
uint32_t done = 0;
|
||||||
|
|
||||||
|
if (!sf || !sf->chip || !data) return -1;
|
||||||
|
|
||||||
|
while (len) {
|
||||||
|
size_t chunk = (len > sizeof(tmp)) ? sizeof(tmp) : len;
|
||||||
|
size_t i;
|
||||||
|
if (spi_flash_read(sf, addr, tmp, chunk) < 0) return -1;
|
||||||
|
for (i = 0; i < chunk; i++) {
|
||||||
|
if (tmp[i] != data[done + i]) {
|
||||||
|
if (mismatch_off) *mismatch_off = done + (uint32_t)i;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addr += (uint32_t)chunk;
|
||||||
|
done += (uint32_t)chunk;
|
||||||
|
len -= chunk;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
79
modules/spi_flash/spi_flash.h
Normal file
79
modules/spi_flash/spi_flash.h
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#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
|
||||||
Reference in New Issue
Block a user