Browse Source

spiflash.[ch]: a (hopefully) platform-independent SPI flash writer

SPI flash writer with support for gzipped input. To the maximum level
possible have tried to abstract out any access to the SPI bus and data
input. zlib is used to handle gzipped input.
H. Peter Anvin 3 years ago
parent
commit
9c1891e562

+ 3 - 3
fpga/max80.qpf

@@ -19,15 +19,15 @@
 #
 # Quartus Prime
 # Version 21.1.0 Build 842 10/21/2021 SJ Lite Edition
-# Date created = 00:09:47  February 06, 2022
+# Date created = 00:12:52  February 06, 2022
 #
 # -------------------------------------------------------------------------- #
 
 QUARTUS_VERSION = "21.1"
-DATE = "00:09:47  February 06, 2022"
+DATE = "00:12:52  February 06, 2022"
 
 # Revisions
 
-PROJECT_REVISION = "v2"
 PROJECT_REVISION = "v1"
+PROJECT_REVISION = "v2"
 PROJECT_REVISION = "v2boot"

+ 2 - 2
fpga/output/sram.mif

@@ -1751,8 +1751,8 @@ CONTENT BEGIN
 06D0 : 6546206E;
 06D1 : 36202062;
 06D2 : 3A303020;
-06D3 : 343A3930;
-06D4 : 53502034;
+06D3 : 353A3231;
+06D4 : 53502030;
 06D5 : 30322054;
 06D6 : 003232;
 [06D7..1FFF] : 00;

BIN
fpga/output/v1.jic


BIN
fpga/output/v1.rbf.gz


BIN
fpga/output/v1.rpd.gz


BIN
fpga/output/v1.sof


BIN
fpga/output/v1.svf.gz


BIN
fpga/output/v1.xsvf.gz


BIN
fpga/output/v2.jic


BIN
fpga/output/v2.rbf.gz


BIN
fpga/output/v2.rpd.gz


BIN
fpga/output/v2.sof


BIN
fpga/output/v2.svf.gz


BIN
fpga/output/v2.xsvf.gz


+ 2 - 2
rv32/Makefile

@@ -43,7 +43,7 @@ ROMOBJS  = $(ROMS:.rom=.o)
 
 max80.elf: head.o dummy.o die.o main.o system.o \
 	  ioregsa.o irqasm.o irqtable.o spurious_irq.o sbrk.o \
-	  console.o rtc.o romcopy.o \
+	  console.o rtc.o romcopy.o spiflash.o \
 	  sdcard.o diskcache.o \
 	  abcmem.o abcio.o abcdisk.o abcrtc.o abcpun80.o \
 	  memset.o memcpy.o \
@@ -54,7 +54,7 @@ max80.elf: head.o dummy.o die.o main.o system.o \
 
 testimg.elf: head.o dummy.o die.o test/main.o test/system.o \
 	  ioregsa.o irqasm.o irqtable.o spurious_irq.o sbrk.o \
-	  console.o rtc.o romcopy.o \
+	  console.o rtc.o romcopy.o spiflash.o \
 	  sdcard.o diskcache.o \
 	  abcmem.o abcio.o abcdisk.o abcrtc.o abcpun80.o \
 	  memset.o memcpy.o \

+ 1 - 1
rv32/checksum.h

@@ -1,4 +1,4 @@
 #ifndef CHECKSUM_H
 #define CHECKSUM_H
-#define SDRAM_SUM 0x70614e7c
+#define SDRAM_SUM 0xecd81ce9
 #endif

+ 531 - 0
rv32/spiflash.c

@@ -0,0 +1,531 @@
+#include "compiler.h"
+#include "zlib.h"
+#include "spiflash.h"
+
+struct spz_stream;
+typedef struct spz_stream spz_stream;
+struct spz_stream {
+    z_stream zs;
+    const struct spiflash *flash;
+    int (*read_data)(spz_stream *); /* Routine to get more data */
+    int (*end_data)(z_stream *);    /* Termination routine for zlib */
+    int err;			    /* Error code to return */
+    bool eoi;			    /* Reached end of input */
+    uint8_t *optr;		    /* Output data pointer into obuf */
+    /* Note: available output data ends at zs->next_out */
+    uint8_t *ibuf;		    /* Input buffer if compressed */
+    uint8_t *obuf;		    /* Output buffer */
+    uint8_t *vbuf;		    /* Readback/verify buffer */
+};
+
+static int spiflash_read_data(spz_stream *spz)
+{
+    int rv;
+
+    if (spz->eoi)
+	return 0;
+
+    spz->zs.next_in  = NULL;
+    spz->zs.avail_in = 0;
+
+    rv = spz->flash->ops->read_data(spz->flash->cookie,
+				    spz->ibuf, SPIFLASH_BLOCK_SIZE);
+    if (rv != SPIFLASH_BLOCK_SIZE)
+	spz->eoi = true;
+
+    if (rv < 0) {
+	if (!spz->err)
+	    spz->err = rv;
+	return 0;
+    }
+
+    spz->zs.next_in  = spz->ibuf;
+    spz->zs.avail_in = rv;
+
+    return rv;
+}
+
+static int read_data_raw(spz_stream *spz)
+{
+    int rlen;
+
+    if (spz->eoi)
+	return Z_STREAM_END;
+
+    rlen = spiflash_read_data(spz);
+    if (rlen < 0)
+	return rlen;		/* Error! */
+
+    spz->optr         = spz->ibuf;
+    spz->zs.next_out  = spz->ibuf + rlen;
+    spz->zs.avail_out = SPIFLASH_BLOCK_SIZE - rlen;
+
+    return spz->eoi ? Z_STREAM_END : Z_OK;
+}
+
+static int read_data_inflate(spz_stream *spz)
+{
+    int rv = Z_STREAM_END;
+
+    spz->optr = spz->zs.next_out = spz->obuf;
+    spz->zs.avail_out = SPIFLASH_BLOCK_SIZE;
+
+    while (spz->zs.avail_out) {
+	if (!spz->zs.avail_in && !spz->eoi) {
+	    int rlen = spiflash_read_data(spz);
+	    spz->zs.next_in  = spz->ibuf;
+	    spz->zs.avail_in = rlen;
+	}
+
+	rv = inflate(&spz->zs, Z_SYNC_FLUSH);
+
+	if (rv == Z_OK || (rv == Z_BUF_ERROR && !spz->eoi))
+	    continue;
+
+	if (rv != Z_STREAM_END) {
+	    if (!spz->err)
+		spz->err = rv >= 0 ? Z_STREAM_ERROR : rv;
+	}
+	break;
+    }
+
+    if (rv != Z_OK) {
+	/* Z_STREAM_END or error */
+	inflateEnd(&spz->zs);
+    }
+
+    return rv;
+}
+
+static void *spz_malloc(spz_stream *spz, size_t bytes)
+{
+    void *p = malloc(bytes);
+    if (!p && !spz->err) {
+	spz->err = Z_MEM_ERROR;
+    }
+    return p;
+}
+
+int spiflash_data_init(spz_stream *spz, const struct spiflash *flash)
+{
+    int rlen;
+    int rv = Z_OK;
+    uint8_t *rdbuf = NULL;
+
+    memset(spz, 0, sizeof *spz);
+    spz->flash = flash;
+
+    spz->ibuf = spz_malloc(spz, SPIFLASH_BLOCK_SIZE);
+    if (!spz->ibuf)
+	goto err;
+
+    spz->vbuf = spz_malloc(spz, SPIFLASH_BLOCK_SIZE);
+    if (!spz->vbuf)
+	goto err;
+
+    rlen = spiflash_read_data(spz);
+
+    if (rlen >= 14 && !memcmp(spz->ibuf, "\37\213\10", 3)) {
+	/* It is a gzip file */
+	spz->zs.next_in   = spz->ibuf;
+	spz->zs.avail_in  = rlen;
+	spz->read_data    = read_data_inflate;
+
+	spz->obuf = spz_malloc(spz, SPIFLASH_BLOCK_SIZE);
+	if (!spz->obuf)
+	    goto err;
+
+	rv = inflateInit2(&spz->zs, 16 + 15);	/* gzip, max window size */
+	if (rv != Z_OK) {
+	    spz->err = rv;
+	    goto err;
+	}
+	spz->end_data = inflateEnd;
+    } else {
+	/* Assume it is a raw binary; input buffer is output buffer */
+	spz->optr         = spz->ibuf;
+	spz->zs.next_out  = spz->ibuf + rlen;
+	spz->zs.avail_out = SPIFLASH_BLOCK_SIZE - rlen;
+	rv = spz->eoi ? Z_STREAM_END : Z_OK;
+    }
+
+err:
+    return spz->err ? spz->err : rv;
+ }
+
+static void spiflash_data_cleanup(spz_stream *spz)
+{
+    if (!spz)
+	return;
+
+    if (spz->end_data)
+	spz->end_data(&spz->zs);
+    if (spz->vbuf)
+	free(spz->vbuf);
+    if (spz->obuf)
+	free(spz->obuf);
+    if (spz->ibuf)
+	free(spz->ibuf);
+}
+
+/*
+ * Set up a command header with an address according to the SPI
+ * addressing mode. Returns a pointer to the first byte past the
+ * address.
+ */
+static void *spiflash_setup_addrcmd(const struct spiflash *flash,
+				    uint32_t addr,
+				    uint8_t cmd24, uint8_t cmd32,
+				    void *cmdbuf)
+{
+    enum spiflash_addr_mode mode = flash->param->addr;
+    uint8_t *cmd = cmdbuf;
+
+    if (!mode)
+	mode = addr < (1 << 24) ? SPIFLASH_ADDR_24BIT : SPIFLASH_ADDR_32BIT;
+
+    if (mode == SPIFLASH_ADDR_24BIT) {
+	addr <<= 8;
+	*cmd++ = cmd24;
+    } else {
+	*cmd++ = cmd32;
+	*cmd++ = addr >> 24;
+    }
+    *cmd++ = addr >> 16;
+    *cmd++ = addr >> 8;
+    *cmd++ = addr;
+
+    return cmd;
+}
+
+static int spiflash_wait_ready(const struct spiflash *flash, int delay)
+{
+    uint8_t cmd = 0x05;		/* Read Status Register 1 */
+    uint8_t sr1 = 0;
+    int rv;
+
+    do {
+	flash->ops->yield(flash->cookie, delay);
+	rv = flash->ops->spi_read(flash->cookie, &cmd, 1,
+				  &sr1, 1, flash->param->tshsl);
+	if (rv)
+	    return rv;
+    } while (sr1 & 0x01);	/* Busy bit set? */
+
+    return 0;
+}
+
+int spiflash_read(const struct spiflash *flash,
+		  uint32_t addr, void *buffer, size_t len)
+{
+    uint8_t cmdbuf[6];
+    uint8_t *cmd;
+
+    /*
+     * 13h = Fast Read
+     * 0Ch = Fast Read with 4-Byte Address
+     */
+    cmd = spiflash_setup_addrcmd(flash, addr, 0x13, 0x0c, cmdbuf);
+    *cmd++ = 0;			/* Dummy bits */
+
+    return flash->ops->spi_read(flash->cookie, cmdbuf, cmd - cmdbuf,
+				buffer, len, flash->param->tshsl1);
+}
+
+static int spiflash_write_enable(const struct spiflash *flash)
+{
+    const uint8_t cmd = 0x06;	/* 06h = Write Enable */
+
+    return flash->ops->spi_write(flash->cookie, &cmd, 1, NULL, 0,
+				 flash->param->tshsl);
+}
+
+static int spiflash_program(const struct spiflash *flash,
+			    uint32_t addr, const void *buffer, size_t len)
+{
+    uint8_t cmdbuf[5];
+    uint8_t *cmd;
+    int rv;
+
+    rv = spiflash_write_enable(flash);
+    if (rv)
+	return rv;
+
+    /*
+     * 02h = Page Program
+     * 12h = Page Program with 4-Byte Address
+     */
+    cmd = spiflash_setup_addrcmd(flash, addr, 0x02, 0x12, cmdbuf);
+    rv = flash->ops->spi_write(flash->cookie, cmdbuf, cmd - cmdbuf,
+			       buffer, len, flash->param->tshsl2);
+    if (rv)
+	return rv;
+
+    return spiflash_wait_ready(flash, flash->param->tpp);
+}
+
+/*
+ * Erase up to (long bits) sectors, using block erase if possible.
+ */
+static int spiflash_erase(const struct spiflash *flash,
+			  uint32_t addr, unsigned long sector_mask)
+{
+    uint8_t cmdbuf[5];
+    uint8_t *cmd;
+    uint8_t cmd24, cmd32;
+    uint32_t nextaddr;
+    int rv;
+    int delay;
+    const uint32_t block_mask = SPIFLASH_BLOCK_SIZE - 1;
+    const unsigned long block_sectors = block_mask >> SPIFLASH_SECTOR_SHIFT;
+
+    while (sector_mask) {
+	if (((sector_mask & block_sectors) == block_sectors) &&
+	    !(addr & block_mask)) {
+		/*
+		 * D8h = 64KB Block Erase
+		 * DCh = 64K Block Erase with 4-Byte Address
+		 */
+		cmd24 = 0xd8; cmd32 = 0xdc;
+		delay = flash->param->tbe2;
+		nextaddr = addr + SPIFLASH_BLOCK_SIZE;
+		sector_mask >>= 16;
+	} else {
+	    if (sector_mask & 1) {
+		/*
+		 * 20h = Sector Erase
+		 * 21h = Sector Erase with 4-Byte Address
+		 */
+		cmd24 = 0x20; cmd32 = 0x21;
+		delay = flash->param->tse;
+		nextaddr = addr + SPIFLASH_SECTOR_SIZE;
+		sector_mask >>= 1;
+	    } else {
+		addr += SPIFLASH_SECTOR_SIZE;
+		sector_mask >>= 1;
+		continue;	/* Skip sector */
+	    }
+	}
+	rv = spiflash_write_enable(flash);
+	if (rv)
+	    return rv;
+
+	cmd = spiflash_setup_addrcmd(flash, addr, cmd24, cmd32, cmdbuf);
+	rv = flash->ops->spi_write(flash->cookie, cmdbuf, cmd - cmdbuf,
+				   NULL, 0, flash->param->tshsl2);
+	if (rv)
+	    return rv;
+
+	rv = spiflash_wait_ready(flash, delay);
+	if (rv)
+	    return rv;
+
+	addr = nextaddr;
+    }
+
+    return 0;
+}
+
+/*
+ * from: current flash contents
+ * to:   desired flash contents
+ *
+ * These are assumed to be aligned full block buffers
+ */
+enum flashmem_status {
+    FMS_DONE,			/* All done, no programming needed */
+    FMS_PROGRAM,		/* Can be programmed */
+    FMS_ERASE,			/* Needs erase before programming */
+    FMS_NOTCHECKED		/* Not checked yet */
+};
+static enum flashmem_status
+spiflash_memcmp(const void *from, const void *to, size_t len)
+{
+    const uint32_t *pf = from;
+    const uint32_t *pt = to;
+    const uint32_t *pfend = (const uint32_t *)((const char *)from + len);
+    uint32_t notok     = 0;
+    uint32_t notprog   = 0;
+
+    while (pf < pfend) {
+	uint32_t f = *pf++;
+	uint32_t t = *pt++;
+
+	notok   |=  f ^ t;	/* Need programming if any data mismatch */
+	notprog |= ~f & t;	/* Need erasing if any 0 -> 1 */
+    }
+
+    return notprog ? FMS_ERASE : notok ? FMS_PROGRAM : FMS_DONE;
+}
+
+/*
+ * Check a block for sectors which need erasing and pages which need
+ * programming; the prog_mask is 256 bits long and so span multiple words.
+ *
+ * The input is spz->optr, and the existing flash content is written
+ * to spz->vptr.
+ *
+ */
+static int spiflash_check_block(spz_stream *spz, uint32_t addr,
+				uint32_t *erase_mask, uint32_t *prog_mask)
+{
+    int rv;
+    const uint8_t *p, *q;
+    unsigned int page;
+
+    rv = spiflash_read(spz->flash, addr, spz->vbuf, SPIFLASH_BLOCK_SIZE);
+    if (rv) {
+	if (!spz->err)
+	    spz->err = rv;
+	return rv;
+    }
+
+    p = spz->optr;
+    q = spz->vbuf;
+
+    for (page = 0; page < SPIFLASH_BLOCK_SIZE/SPIFLASH_PAGE_SIZE; page++) {
+	enum flashmem_status status;
+
+	switch (spiflash_memcmp(p, q, SPIFLASH_PAGE_SIZE)) {
+	case FMS_ERASE:
+	    *erase_mask |= UINT32_C(1) <<
+		(page >> (SPIFLASH_SECTOR_SHIFT-SPIFLASH_PAGE_SHIFT));
+	    break;
+	case FMS_PROGRAM:
+	    prog_mask[page >> 5] |= UINT32_C(1) << (page & 31);
+	    break;
+	default:
+	    /* Nothing to do! */
+	    break;
+	}
+
+	p += SPIFLASH_PAGE_SIZE;
+	q += SPIFLASH_PAGE_SIZE;
+    }
+
+    return 0;
+}
+
+int spiflash_flash_file(const struct spiflash *flash, uint32_t addr)
+{
+    spz_stream _spz;
+    spz_stream * const spz = &_spz; /* For consistency in notation */
+    int rv;
+    bool eof;
+    enum flashmem_status fs;
+
+    rv = spiflash_data_init(spz, flash);
+    eof = rv != Z_OK;
+
+    while (!eof && !spz->err) {
+	unsigned int bytes = spz->zs.next_out - spz->optr;
+	unsigned int padding;
+
+	if (bytes < SPIFLASH_BLOCK_SIZE) {
+	    int rv;
+
+	    rv = spz->read_data(spz);
+	    eof = rv != Z_OK || spz->err;
+	    bytes = spz->zs.next_out - spz->optr;
+	    if (!bytes)
+		break;
+
+	    padding = -bytes & (SPIFLASH_BLOCK_SIZE-1);
+	    if (padding > spz->zs.avail_out) {
+		/* This should never happen */
+		padding = spz->zs.avail_out;
+	    }
+	    if (padding) {
+		memset(spz->zs.next_out, 0xff, padding);
+		spz->zs.avail_out -= padding;
+		bytes += padding;
+	    }
+	}
+
+	uint32_t erase_mask;
+	uint32_t prog_mask[SPIFLASH_BLOCK_SIZE >> (SPIFLASH_PAGE_SHIFT+5)];
+
+	rv = spiflash_check_block(spz, addr, &erase_mask, prog_mask);
+	if (rv)
+	    break;
+
+	if (erase_mask) {
+	    rv = spiflash_erase(spz->flash, addr, erase_mask);
+	    if (rv)
+		break;
+	    rv = spiflash_check_block(spz, addr, &erase_mask, prog_mask);
+	    if (spz->err)
+		break;
+	    if (erase_mask) {
+		spz->err = SPIFLASH_ERR_ERASE_FAILED;
+		break;
+	    }
+	}
+
+	unsigned int page;
+
+	for (page = 0; page < (SPIFLASH_BLOCK_SIZE >> SPIFLASH_PAGE_SHIFT);
+	     page++) {
+	    uint32_t page_offs = page << SPIFLASH_PAGE_SHIFT;
+
+	    if (prog_mask[page >> 5] & (UINT32_C(1) << (page & 31))) {
+		rv = spiflash_program(spz->flash, addr + page_offs,
+				      spz->optr + page_offs,
+				      SPIFLASH_PAGE_SIZE);
+		if (rv) {
+		    spz->err = rv;
+		    break;
+		}
+
+		/* Read back data and verify */
+		rv = spiflash_read(spz->flash, addr+page_offs,
+				   spz->vbuf + page_offs,
+				   SPIFLASH_PAGE_SIZE);
+		if (rv) {
+		    spz->err = rv;
+		    break;
+		}
+
+		if (memcmp(spz->optr + page_offs, spz->vbuf + page_offs,
+			   SPIFLASH_PAGE_SIZE)) {
+			spz->err = SPIFLASH_ERR_PROGRAM_FAILED;
+			break;
+		}
+	    }
+	}
+
+	spz->optr += SPIFLASH_BLOCK_SIZE;
+	addr      += SPIFLASH_BLOCK_SIZE;
+    }
+
+    if (spz->flash->ops->close_data)
+	spz->flash->ops->close_data(spz->flash->cookie);
+
+    spiflash_data_cleanup(spz);
+    return spz->err;
+}
+
+/*
+ * Read unique serial number from flash. Note: returns id in
+ * bigendian ("network") byte order.
+ */
+int spiflash_read_id(const struct spiflash *flash, void *id)
+{
+    static const uint8_t read_unique_id[] = { 0x4b, 0, 0, 0, 0 };
+
+    return flash->ops->spi_read(flash->cookie, read_unique_id,
+				sizeof read_unique_id,
+				id, SPIFLASH_ID_LEN, flash->param->tshsl);
+}
+
+/*
+ * Read vendor and device ID from flash.
+ */
+int spiflash_read_vdid(const struct spiflash *flash, void *vdid)
+{
+    static const uint8_t read_vdid[] = { 0x90, 0, 0, 0 };
+
+    return flash->ops->spi_read(flash->cookie, read_vdid,
+				sizeof read_vdid,
+				vdid, SPIFLASH_VDID_LEN, flash->param->tshsl);
+}

+ 211 - 0
rv32/spiflash.h

@@ -0,0 +1,211 @@
+#ifndef SPIFLASH_H
+#define SPIFLASH_H
+
+#include <inttypes.h>
+#include <stddef.h>
+#include <string.h>
+
+/*
+ * A page is the maximum amount that can be programmed in one operation.
+ * A sector is the minimum amount that can be erased in one operation.
+ * A block is the optimal amount that can be erased in one operation.
+ *
+ * These are defined in hardware!
+ */
+#define SPIFLASH_PAGE_SHIFT	8
+#define SPIFLASH_PAGE_SIZE	(1 << SPIFLASH_PAGE_SHIFT)
+#define SPIFLASH_SECTOR_SHIFT	12
+#define SPIFLASH_SECTOR_SIZE	(1 << SPIFLASH_SECTOR_SHIFT)
+#define SPIFLASH_BLOCK_SHIFT	16
+#define SPIFLASH_BLOCK_SIZE	(1 << SPIFLASH_BLOCK_SHIFT)
+
+/*
+ * Interface to the host. This structure should be passed in to the
+ * initialization routine and will not be modified by the spiflash
+ * routines.
+ *
+ * The spiflash code is reentrant if there are multiple SPI flash
+ * devices. None are timing critical and may be preempted at any
+ * time if applicable. When CS# is active (during spi_read or spi_write),
+ * it MUST NOT be deasserted; if the bus is shared HOLD# can be asserted
+ * (without deasserting CS#) to make the device release the bus without
+ * affecting the state of the device.
+ *
+ * CS# will not be asserted while running in the core code or when
+ * blocking for I/O; the host is obviously allowed to prefetch I/O
+ * within the above constraints if the bus is shared.
+ *
+ * A private cookie pointer is passed to each function; this can be a
+ * pointer back to the spiflash_ops structure, but does not have to
+ * be.
+ */
+struct spiflash_ops {
+    /*
+     * Read input data for flash write. Return the number of bytes
+     * read.  A short read or a 0 byte return value represents end of
+     * file/end of data; a negative value is treated as 0, and will be
+     * returned from the top-level operation as a status code.
+     *
+     * It is not required to detect end of input if and only if the
+     * input is a gzip file, as in that case the gzip data will contain
+     * an end of stream indicator.
+     *
+     * The buffer initially passed to this function will always be
+     * aligned to a malloc() alignment boundary; it will preserve
+     * alignment boundaries if and only if short read returns only byte
+     * counts in multiple of those alignment boundaries.
+     */
+    int (*read_data)(void *cookie, void *buf, unsigned int bufsize);
+
+    /*
+     * Indicates that no more data will be read (end of stream detected
+     * or an error happened.) May be NULL. A nonzero value will be passed
+     * to the top-level routine as an error.
+     */
+    int (*close_data)(void *cookie);
+
+    /*
+     * Perform a SPI write operation. The SPI write operation consists of:
+     * 1. Assert CS# (and deassert HOLD# if applicable)
+     * 2. Transmit the command bytes (discard MISO input)
+     * 3. Transmit the data bytes (discard MISO input)
+     * 4. Deassert CS#
+     * 5. Wait tCHSL (see SPI data table below)
+     *
+     * The number of data bytes may be zero. Note that the command
+     * and data operations are identical and are separated only to
+     * avoid unnecessary data copies.
+     *
+     * If this returns nonzero, no further operations are performed
+     * and the top-level flash routine terminates immediately with
+     * the returned value as a status code.
+     *
+     * It is not required that this routine blocks until tCHSL
+     * is satisfied, however, the host is responsible to not assert
+     * CS# again until tCHSL is satisfied. The SPI clock may run
+     * or not during that time period.
+     *
+     * The SPI flash supports SPI modes 0 and 3.
+     */
+    int (*spi_write)(void *cookie,
+		     const void *cmd, unsigned int cmd_len,
+		     const void *data, unsigned int data_len,
+		     int tshsl);
+
+    /*
+     * Perform a SPI read operation. The SPI read operation consists of:
+     * 1. Assert CS# (and deassert HOLD# if applicable)
+     * 2. Transmit the command bytes (discard MISO input)
+     * 3. Receive the data bytes (MOSI is don't care)
+     * 4. Deassert CS#
+     * 5. Wait tCHSL (see SPI data table below)
+     *
+     * The number of data bytes may be zero. Note that the command
+     * and data operations are identical and are separated only to
+     * avoid unnecessary data copies.
+     *
+     * If this returns nonzero, no further operations are performed
+     * and the top-level flash routine terminates immediately with
+     * the returned value as a status code.
+     *
+     * It is not required that this routine blocks until tCHSL
+     * is satisfied, however, the host is responsible to not assert
+     * CS# again until tCHSL is satisfied. The SPI clock may run
+     * or not during that time period.
+     *
+     * The SPI flash supports SPI modes 0 and 3.
+     */
+    int (*spi_read)(void *cookie,
+		    const void *cmd, unsigned int cmd_len,
+		    void *data, unsigned int data_len,
+		    int tshsl);
+
+    /*
+     * Inform the host that the spiflash code is waiting for an
+     * program or erase operation to complete. This can be used to
+     * yield the host for other operations.
+     *
+     * The value passed in is the corresponding from the SPI data
+     * table below; these are arbitrary cookies/units as far as the
+     * spiflash code is concerned.
+     *
+     * This function may be NULL, in which case the SPI flash is polled
+     * continously.
+     */
+    void (*yield)(void *cookie, int delay);
+};
+
+/*
+ * This table provides some parameters for the SPI flash.
+ */
+enum spiflash_addr_mode {
+    SPIFLASH_ADDR_DYNAMIC,	/* 24-bit for < 16 MB, otherwise 32 bit */
+    SPIFLASH_ADDR_24BIT,	/* 24-bit addressing only */
+    SPIFLASH_ADDR_32BIT		/* 32-bit addressing only */
+};
+
+struct spiflash_param {
+    /*
+     * Addressing mode (see above.) If SPIFLASH_ADDR_DYNAMIC is
+     * specified (default), the chip is assumed to be in 24-bit-default
+     * mode, and 32-bit opcodes will be used as needed.
+     */
+    enum spiflash_addr_mode addr;
+
+    /*
+     * CS# deselect times passed to spi_read() and spi_write().
+     * Arbitrary units or cookies that are only interpreted by
+     * the spi_read and spi_write routines.
+     */
+    int tshsl;			/* All other operations */
+    int tshsl1;			/* Read operations */
+    int tshsl2;			/* Erase, Program, and Write operations */
+
+    /*
+     * Delay values to pass to the yield operation. Arbitrary units
+     * or cookies that are only interpreted by the yield routine.
+     *
+     * Not all of these are used by the current code, but are specified
+     * for future-proofing reasons.
+     */
+    int trst;			/* Reset command to next instruction */
+    int tw;			/* Write Status Register Time */
+    int tpp;			/* Page Program Time */
+    int tse;			/* Sector Erase Time (4K) */
+    int tbe1;			/* Block Erase Time (32K) */
+    int tbe2;			/* Block Erase Time (64K) */
+    int tce;			/* Chip Erase Time */
+};
+
+/* Common structure for the above */
+struct spiflash {
+    const struct spiflash_ops *ops;
+    void *cookie;		/* Pointer passed to spiflash_ops functions */
+    const struct spiflash_param *param;
+};
+
+/*
+ * Additional error codes
+ */
+#define SPIFLASH_ERR_ERASE_FAILED	(-7)
+#define SPIFLASH_ERR_PROGRAM_FAILED	(-8)
+
+/*
+ * Top-level operations. These may return an error value from the ops
+ * functions, any of the negative error values defined in zlib.h,
+ * or one of the above error codes.
+ */
+int spiflash_flash_file(const struct spiflash *flash, uint32_t addr);
+int spiflash_read(const struct spiflash *flash,
+		  uint32_t addr, void *buffer, size_t len);
+
+/*
+ * Read identifying data from SPI flash.
+ */
+#define SPIFLASH_ID_LEN 8
+int spiflash_read_id(const struct spiflash *flash, void *id);
+
+#define SPIFLASH_VDID_LEN 2
+int spiflash_read_vdid(const struct spiflash *flash, void *vdid);
+
+#endif