Files
bs_explorer/modules/spi_flash/spi_flash.c
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

203 lines
5.9 KiB
C

#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;
}