Patchwork [v3,2/4] m25p80: initial verion

login
register
mail settings
Submitter Peter A. G. Crosthwaite
Date April 20, 2012, 2:12 a.m.
Message ID <9a4b434352391c41728122412037670f4e6a6bfe.1334886618.git.peter.crosthwaite@petalogix.com>
Download mbox | patch
Permalink /patch/153907/
State New
Headers show

Comments

Peter A. G. Crosthwaite - April 20, 2012, 2:12 a.m.
Added device model for m25p80 SPI flash

Signed-off-by: Peter A. G. Crosthwaite <peter.crosthwaite@petalogix.com>
---
changed from v2:
updated for SSI slave interface
used async io (suggested - Stefan Hajnoczi)
changed from v1:
converted spi api to modified txrx style
factored out lots of common code and inlined overly short single call functions.
undated for txrx style spi interface

 Makefile.target |    1 +
 hw/m25p80.c     |  378 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 379 insertions(+), 0 deletions(-)
 create mode 100644 hw/m25p80.c
Stefan Hajnoczi - April 20, 2012, 11:12 a.m.
On Fri, Apr 20, 2012 at 3:12 AM, Peter A. G. Crosthwaite
<peter.crosthwaite@petalogix.com> wrote:
> +static inline void flash_sync_area(struct flash *s, int64_t off, int64_t len)
> +{
> +    int64_t start, end;
> +
> +    if (!s->bdrv) {
> +        return;
> +    }
> +
> +    start = off / 512;
> +    end = (off + len) / 512;
> +    bdrv_write(s->bdrv, start, s->storage + (start * 512), end - start);

Is it possible to use bdrv_aio_write() like in flash_sync_page()?

Stefan
Peter Maydell - April 24, 2012, 5:05 p.m.
On 20 April 2012 03:12, Peter A. G. Crosthwaite
<peter.crosthwaite@petalogix.com> wrote:
> Subject: [PATCH v3 2/4] m25p80: initial verion

> Added device model for m25p80 SPI flash

This commit message could be improved; I'd suggest a summary line of
"m25p80: Initial implementation of SPI flash device"


>
> Signed-off-by: Peter A. G. Crosthwaite <peter.crosthwaite@petalogix.com>
> ---
> changed from v2:
> updated for SSI slave interface
> used async io (suggested - Stefan Hajnoczi)
> changed from v1:
> converted spi api to modified txrx style
> factored out lots of common code and inlined overly short single call functions.
> undated for txrx style spi interface
>
>  Makefile.target |    1 +
>  hw/m25p80.c     |  378 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 379 insertions(+), 0 deletions(-)
>  create mode 100644 hw/m25p80.c
>
> diff --git a/Makefile.target b/Makefile.target
> index 84951a0..3f7c38e 100644
> --- a/Makefile.target
> +++ b/Makefile.target
> @@ -326,6 +326,7 @@ obj-mips-$(CONFIG_FULONG) += bonito.o vt82c686.o mips_fulong2e.o
>  obj-microblaze-y = petalogix_s3adsp1800_mmu.o
>  obj-microblaze-y += petalogix_ml605_mmu.o
>  obj-microblaze-y += microblaze_boot.o
> +obj-microblaze-y += m25p80.o
>
>  obj-microblaze-y += microblaze_pic_cpu.o
>  obj-microblaze-y += xilinx_intc.o
> diff --git a/hw/m25p80.c b/hw/m25p80.c
> new file mode 100644
> index 0000000..e6c1f3b
> --- /dev/null
> +++ b/hw/m25p80.c
> @@ -0,0 +1,378 @@
> +/*
> + * ST M25P80 emulator.

"ST M25P80 SPI Flash device." -- don't force readers to go and google
for the part number to find out what it is :-)

> + *
> + * Copyright (C) 2011 Edgar E. Iglesias <edgar.iglesias@gmail.com>
> + * Copyright (C) 2012 Peter A. G. Crosthwaite <peter.crosthwaite@petalogix.com>
> + * Copyright (C) 2012 PetaLogix
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 or
> + * (at your option) version 3 of the License.

Do we really want "GPL 2 or 3", rather than "2 or later" ?

> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "hw.h"
> +#include "blockdev.h"
> +#include "ssi.h"
> +#include "devices.h"
> +
> +#ifdef M25P80_ERR_DEBUG
> +#define DB_PRINT(...) do { \
> +    fprintf(stderr,  ": %s: ", __func__); \
> +    fprintf(stderr, ## __VA_ARGS__); \
> +    } while (0);
> +#else
> +    #define DB_PRINT(...)
> +#endif
> +
> +enum FlashCMD {
> +    NOP = 0,
> +    PP = 0x2,
> +    READ = 0x3,
> +    WRDI = 0x4,
> +    RDSR = 0x5,
> +    WREN = 0x6,
> +    FAST_READ = 0xb,
> +    SECTOR_ERASE = 0x20,
> +    BLOCK_ERASE32 = 0x52,
> +    JEDEC_READ = 0x9f,
> +    CHIP_ERASE = 0xc7,
> +};
> +
> +enum CMDState {
> +    STATE_IDLE,
> +    STATE_PAGE_PROGRAM,
> +    STATE_READ,
> +    STATE_COLLECTING_DATA,
> +    STATE_READING_DATA,
> +};
> +
> +struct flash {
> +    SSISlave ssidev;
> +    uint32_t r;
> +
> +    BlockDriverState *bdrv;
> +    enum CMDState state;
> +
> +    uint8_t *storage;
> +    uint64_t size;
> +    int pagesize;
> +    int sectorsize;
> +    int blocksize;
> +
> +    uint8_t data[16];
> +    int len;
> +    int pos;
> +    int wrap_read;
> +    int needed_bytes;
> +    enum FlashCMD cmd_in_progress;
> +
> +    int64_t dirty_page;
> +
> +    uint64_t waddr;
> +    int write_enable;
> +};

Missing save/load support (which will cause you to want to turn a lot
of those 'int's into either 'bool' or known-width types).

> +
> +static void bdrv_sync_complete(void *opaque, int ret)
> +{
> +
> +}

Is there really nothing to do here? If so, perhaps a comment explaining
why...

> +
> +static void flash_sync_page(struct flash *s, int page)
> +{
> +    if (s->bdrv) {
> +        int bdrv_sector, nb_sectors;
> +        QEMUIOVector iov;
> +
> +        bdrv_sector = (page * s->pagesize) / 512;
> +        nb_sectors = (s->pagesize + 511) / 512;

There's a DIV_ROUND_UP macro, if you like:
  nb_sectors = DIV_ROUND_UP(s->pagesize, BDRV_SECTOR_SIZE);
(though it isn't really used much in qemu currently.)

> +        qemu_iovec_init(&iov, 1);
> +        qemu_iovec_add(&iov, s->storage + bdrv_sector * 512,
> +                                                    nb_sectors * 512);

Lots of hardcoded 512 here and elsewhere, you probably mean
BDRV_SECTOR_SIZE.

> +        bdrv_aio_writev(s->bdrv, bdrv_sector, &iov, nb_sectors,
> +                                                bdrv_sync_complete, NULL);
> +    }
> +}
> +
> +static inline void flash_sync_area(struct flash *s, int64_t off, int64_t len)
> +{
> +    int64_t start, end;
> +
> +    if (!s->bdrv) {
> +        return;
> +    }
> +
> +    start = off / 512;
> +    end = (off + len) / 512;

This rounds down so you probably want at least a comment to the effect
that off and len must be on BDRV_SECTOR_SIZE boundaries. (Or if the device
semantics allow it, maybe you could round 'end' upwards and avoid the
constraint?)

> +    bdrv_write(s->bdrv, start, s->storage + (start * 512), end - start);
> +}
> +
> +static void flash_sector_erase(struct flash *s, int sector)
> +{
> +    if (!s->write_enable) {
> +        DB_PRINT("write with write protect!\n");
> +    }
> +    memset(s->storage + sector, 0xff, s->sectorsize);
> +    flash_sync_area(s, sector, s->sectorsize);
> +}
> +
> +static void flash_block_erase32k(struct flash *s, int addr)
> +{
> +    if (!s->write_enable) {
> +        DB_PRINT("write with write protect!\n");
> +    }
> +    memset(s->storage + addr, 0xff, 32 * 1024);
> +    flash_sync_area(s, addr, 32 * 1024);
> +}
> +
> +static void flash_chip_erase(struct flash *s)
> +{
> +    if (!s->write_enable) {
> +        DB_PRINT("write with write protect!\n");
> +    }
> +    memset(s->storage, 0xff, s->size);
> +    flash_sync_area(s, 0, s->size);
> +}
> +
> +static inline void flash_sync_dirty(struct flash *s, int64_t newpage)
> +{
> +    if (s->dirty_page >= 0 && s->dirty_page != newpage) {
> +        flash_sync_page(s, s->dirty_page);
> +        s->dirty_page = newpage;
> +    }
> +}
> +
> +static inline
> +void flash_write8(struct flash *s, uint64_t addr, uint8_t data)
> +{
> +    int64_t page = addr / s->pagesize;
> +    uint8_t prev = s->storage[s->waddr];
> +
> +    if (!s->write_enable) {
> +        DB_PRINT("write with write protect!\n");
> +    }
> +
> +    if ((prev ^ data) & data) {
> +        DB_PRINT("programming zero to one! addr=%lx  %x -> %x\n",
> +                  addr, prev, data);
> +    }
> +    s->storage[s->waddr] ^= ~data & s->storage[s->waddr];
> +
> +    flash_sync_dirty(s, page);
> +    s->dirty_page = page;
> +}
> +
> +static void complete_collecting_data(struct flash *s)
> +{
> +    s->waddr = s->data[0] << 16;
> +    s->waddr |= s->data[1] << 8;
> +    s->waddr |= s->data[2];
> +
> +    switch (s->cmd_in_progress) {
> +    case PP:
> +        s->state = STATE_PAGE_PROGRAM;
> +        break;
> +    case READ:
> +    case FAST_READ:
> +        s->state = STATE_READ;
> +        break;
> +    case SECTOR_ERASE:
> +        DB_PRINT("sector_erase sector=%x\n", (unsigned)s->waddr);
> +        flash_sector_erase(s, s->waddr);
> +        break;
> +    case BLOCK_ERASE32:
> +        DB_PRINT("block_erase addr=%x\n", (unsigned)s->waddr);
> +        flash_block_erase32k(s, s->waddr);
> +        break;
> +    default:
> +        break;
> +    }
> +}
> +
> +static void decode_new_cmd(struct flash *s, uint32_t value)
> +{
> +    s->cmd_in_progress = value;
> +    DB_PRINT("decoded new command:%d\n", value);
> +
> +    switch (value) {
> +
> +    case SECTOR_ERASE:
> +    case BLOCK_ERASE32:
> +    case READ:
> +    case PP:
> +        s->needed_bytes = 3;
> +        s->pos = 0; s->len = 0;

One statement per line, please.

> +        s->state = STATE_COLLECTING_DATA;
> +        break;
> +    case FAST_READ:
> +        s->needed_bytes = 4;
> +        s->pos = 0; s->len = 0;
> +        s->state = STATE_COLLECTING_DATA;
> +        break;
> +

Be consistent about whether you want a newline after a break or not.

> +    case WRDI:
> +        s->write_enable = 0;
> +        break;
> +    case WREN:
> +        s->write_enable = 1;
> +        break;
> +
> +    case RDSR:
> +        s->data[0] = (!!s->write_enable) << 1;
> +        s->pos = 0; s->len = 1; s->wrap_read = 0;
> +        s->state = STATE_READING_DATA;
> +        break;
> +
> +    case JEDEC_READ:
> +        DB_PRINT("populated jedec code\n");
> +        s->data[0] = 0xef;
> +        s->data[1] = 0x40;
> +        s->data[2] = 0x17;
> +        s->pos = 0;
> +        s->len = 3;
> +        s->wrap_read = 0;
> +        s->state = STATE_READING_DATA;
> +        break;
> +
> +    case CHIP_ERASE:
> +        if (s->write_enable) {
> +            DB_PRINT("chip erase\n");
> +            flash_chip_erase(s);
> +        } else {
> +            DB_PRINT("chip erase with write protect!\n");
> +        }
> +        break;
> +    case NOP:
> +        break;
> +    default:
> +        DB_PRINT("Unknown cmd %x\n", value);
> +        break;
> +    }
> +}
> +
> +static int m25p80_cs(SSISlave *ss, int select)
> +{
> +    struct flash *s = FROM_SSI_SLAVE(struct flash, ss);
> +
> +    if (!select) {
> +        s->len = 0;
> +        s->pos = 0;
> +        s->state = STATE_IDLE;
> +        flash_sync_dirty(s, -1);
> +        DB_PRINT("deselect\n");
> +    }
> +
> +    return 0;
> +}
> +
> +static uint32_t m25p80_transfer8(SSISlave *ss, uint32_t tx)
> +{
> +    struct flash *s = FROM_SSI_SLAVE(struct flash, ss);
> +    uint32_t r = 0;
> +
> +    switch (s->state) {
> +
> +    case STATE_PAGE_PROGRAM:
> +        DB_PRINT("page program waddr=%lx data=%x\n", s->waddr, (uint8_t)tx);
> +        flash_write8(s, s->waddr, (uint8_t)tx);
> +        s->waddr++;
> +        break;
> +
> +    case STATE_READ:
> +        r = s->storage[s->waddr];
> +        DB_PRINT("READ 0x%lx=%x\n", s->waddr, r);
> +        s->waddr = (s->waddr + 1) % s->size;
> +        break;
> +
> +    case STATE_COLLECTING_DATA:
> +        s->data[s->len] = (uint8_t)tx;
> +        s->len++;
> +
> +        if (s->len == s->needed_bytes) {
> +            complete_collecting_data(s);
> +        }
> +        break;
> +
> +    case STATE_READING_DATA:
> +        r = s->data[s->pos];
> +        s->pos++;
> +        if (s->pos == s->len) {
> +            s->pos = 0;
> +            if (!s->wrap_read) {
> +                s->state = STATE_IDLE;
> +            }
> +        }
> +        break;
> +
> +    default:
> +    case STATE_IDLE:
> +        decode_new_cmd(s, (uint8_t)tx);
> +        break;
> +    }
> +
> +    return r;
> +}
> +
> +static int m25p80_init(SSISlave *ss)
> +{
> +    DriveInfo *dinfo;
> +    struct flash *s = FROM_SSI_SLAVE(struct flash, ss);
> +    static int mtdblock_idx;
> +    dinfo = drive_get(IF_MTD, 0, mtdblock_idx++);

Looks like you wanted drive_get_next(IF_MTD) ?

> +
> +    DB_PRINT("inited m25p80 device model - dinfo = %p\n", dinfo);
> +    /* TODO: parameterize */

Good idea :-)

> +    s->size = 8 * 1024 * 1024;
> +    s->pagesize = 256;
> +    s->sectorsize = 4 * 1024;
> +    s->dirty_page = -1;
> +    s->storage = qemu_blockalign(s->bdrv, s->size);
> +
> +    if (dinfo && dinfo->bdrv) {
> +        int rsize;
> +
> +        s->bdrv = dinfo->bdrv;
> +        rsize = MIN(bdrv_getlength(s->bdrv), s->size);
> +        if (bdrv_read(s->bdrv, 0, s->storage, (s->size + 511) / 512)) {
> +            fprintf(stderr, "Failed to initialize SPI flash!\n");
> +            return 1;
> +        }
> +    } else {
> +        s->write_enable = 1;
> +        flash_chip_erase(s);
> +        s->write_enable = 0;
> +    }
> +
> +    return 0;
> +}
> +
> +static void m25p80_class_init(ObjectClass *klass, void *data)
> +{
> +    SSISlaveClass *k = SSI_SLAVE_CLASS(klass);
> +
> +    k->init = m25p80_init;
> +    k->transfer = m25p80_transfer8;
> +    k->set_cs = m25p80_cs;
> +}
> +
> +static TypeInfo m25p80_info = {
> +    .name           = "m25p80",
> +    .parent         = TYPE_SSI_SLAVE,
> +    .instance_size  = sizeof(struct flash),
> +    .class_init     = m25p80_class_init,
> +};
> +
> +static void m25p80_register_types(void)
> +{
> +    type_register_static(&m25p80_info);
> +}
> +
> +type_init(m25p80_register_types)
> --

-- PMM

Patch

diff --git a/Makefile.target b/Makefile.target
index 84951a0..3f7c38e 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -326,6 +326,7 @@  obj-mips-$(CONFIG_FULONG) += bonito.o vt82c686.o mips_fulong2e.o
 obj-microblaze-y = petalogix_s3adsp1800_mmu.o
 obj-microblaze-y += petalogix_ml605_mmu.o
 obj-microblaze-y += microblaze_boot.o
+obj-microblaze-y += m25p80.o
 
 obj-microblaze-y += microblaze_pic_cpu.o
 obj-microblaze-y += xilinx_intc.o
diff --git a/hw/m25p80.c b/hw/m25p80.c
new file mode 100644
index 0000000..e6c1f3b
--- /dev/null
+++ b/hw/m25p80.c
@@ -0,0 +1,378 @@ 
+/*
+ * ST M25P80 emulator.
+ *
+ * Copyright (C) 2011 Edgar E. Iglesias <edgar.iglesias@gmail.com>
+ * Copyright (C) 2012 Peter A. G. Crosthwaite <peter.crosthwaite@petalogix.com>
+ * Copyright (C) 2012 PetaLogix
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw.h"
+#include "blockdev.h"
+#include "ssi.h"
+#include "devices.h"
+
+#ifdef M25P80_ERR_DEBUG
+#define DB_PRINT(...) do { \
+    fprintf(stderr,  ": %s: ", __func__); \
+    fprintf(stderr, ## __VA_ARGS__); \
+    } while (0);
+#else
+    #define DB_PRINT(...)
+#endif
+
+enum FlashCMD {
+    NOP = 0,
+    PP = 0x2,
+    READ = 0x3,
+    WRDI = 0x4,
+    RDSR = 0x5,
+    WREN = 0x6,
+    FAST_READ = 0xb,
+    SECTOR_ERASE = 0x20,
+    BLOCK_ERASE32 = 0x52,
+    JEDEC_READ = 0x9f,
+    CHIP_ERASE = 0xc7,
+};
+
+enum CMDState {
+    STATE_IDLE,
+    STATE_PAGE_PROGRAM,
+    STATE_READ,
+    STATE_COLLECTING_DATA,
+    STATE_READING_DATA,
+};
+
+struct flash {
+    SSISlave ssidev;
+    uint32_t r;
+
+    BlockDriverState *bdrv;
+    enum CMDState state;
+
+    uint8_t *storage;
+    uint64_t size;
+    int pagesize;
+    int sectorsize;
+    int blocksize;
+
+    uint8_t data[16];
+    int len;
+    int pos;
+    int wrap_read;
+    int needed_bytes;
+    enum FlashCMD cmd_in_progress;
+
+    int64_t dirty_page;
+
+    uint64_t waddr;
+    int write_enable;
+};
+
+static void bdrv_sync_complete(void *opaque, int ret)
+{
+
+}
+
+static void flash_sync_page(struct flash *s, int page)
+{
+    if (s->bdrv) {
+        int bdrv_sector, nb_sectors;
+        QEMUIOVector iov;
+
+        bdrv_sector = (page * s->pagesize) / 512;
+        nb_sectors = (s->pagesize + 511) / 512;
+        qemu_iovec_init(&iov, 1);
+        qemu_iovec_add(&iov, s->storage + bdrv_sector * 512,
+                                                    nb_sectors * 512);
+        bdrv_aio_writev(s->bdrv, bdrv_sector, &iov, nb_sectors,
+                                                bdrv_sync_complete, NULL);
+    }
+}
+
+static inline void flash_sync_area(struct flash *s, int64_t off, int64_t len)
+{
+    int64_t start, end;
+
+    if (!s->bdrv) {
+        return;
+    }
+
+    start = off / 512;
+    end = (off + len) / 512;
+    bdrv_write(s->bdrv, start, s->storage + (start * 512), end - start);
+}
+
+static void flash_sector_erase(struct flash *s, int sector)
+{
+    if (!s->write_enable) {
+        DB_PRINT("write with write protect!\n");
+    }
+    memset(s->storage + sector, 0xff, s->sectorsize);
+    flash_sync_area(s, sector, s->sectorsize);
+}
+
+static void flash_block_erase32k(struct flash *s, int addr)
+{
+    if (!s->write_enable) {
+        DB_PRINT("write with write protect!\n");
+    }
+    memset(s->storage + addr, 0xff, 32 * 1024);
+    flash_sync_area(s, addr, 32 * 1024);
+}
+
+static void flash_chip_erase(struct flash *s)
+{
+    if (!s->write_enable) {
+        DB_PRINT("write with write protect!\n");
+    }
+    memset(s->storage, 0xff, s->size);
+    flash_sync_area(s, 0, s->size);
+}
+
+static inline void flash_sync_dirty(struct flash *s, int64_t newpage)
+{
+    if (s->dirty_page >= 0 && s->dirty_page != newpage) {
+        flash_sync_page(s, s->dirty_page);
+        s->dirty_page = newpage;
+    }
+}
+
+static inline
+void flash_write8(struct flash *s, uint64_t addr, uint8_t data)
+{
+    int64_t page = addr / s->pagesize;
+    uint8_t prev = s->storage[s->waddr];
+
+    if (!s->write_enable) {
+        DB_PRINT("write with write protect!\n");
+    }
+
+    if ((prev ^ data) & data) {
+        DB_PRINT("programming zero to one! addr=%lx  %x -> %x\n",
+                  addr, prev, data);
+    }
+    s->storage[s->waddr] ^= ~data & s->storage[s->waddr];
+
+    flash_sync_dirty(s, page);
+    s->dirty_page = page;
+}
+
+static void complete_collecting_data(struct flash *s)
+{
+    s->waddr = s->data[0] << 16;
+    s->waddr |= s->data[1] << 8;
+    s->waddr |= s->data[2];
+
+    switch (s->cmd_in_progress) {
+    case PP:
+        s->state = STATE_PAGE_PROGRAM;
+        break;
+    case READ:
+    case FAST_READ:
+        s->state = STATE_READ;
+        break;
+    case SECTOR_ERASE:
+        DB_PRINT("sector_erase sector=%x\n", (unsigned)s->waddr);
+        flash_sector_erase(s, s->waddr);
+        break;
+    case BLOCK_ERASE32:
+        DB_PRINT("block_erase addr=%x\n", (unsigned)s->waddr);
+        flash_block_erase32k(s, s->waddr);
+        break;
+    default:
+        break;
+    }
+}
+
+static void decode_new_cmd(struct flash *s, uint32_t value)
+{
+    s->cmd_in_progress = value;
+    DB_PRINT("decoded new command:%d\n", value);
+
+    switch (value) {
+
+    case SECTOR_ERASE:
+    case BLOCK_ERASE32:
+    case READ:
+    case PP:
+        s->needed_bytes = 3;
+        s->pos = 0; s->len = 0;
+        s->state = STATE_COLLECTING_DATA;
+        break;
+    case FAST_READ:
+        s->needed_bytes = 4;
+        s->pos = 0; s->len = 0;
+        s->state = STATE_COLLECTING_DATA;
+        break;
+
+    case WRDI:
+        s->write_enable = 0;
+        break;
+    case WREN:
+        s->write_enable = 1;
+        break;
+
+    case RDSR:
+        s->data[0] = (!!s->write_enable) << 1;
+        s->pos = 0; s->len = 1; s->wrap_read = 0;
+        s->state = STATE_READING_DATA;
+        break;
+
+    case JEDEC_READ:
+        DB_PRINT("populated jedec code\n");
+        s->data[0] = 0xef;
+        s->data[1] = 0x40;
+        s->data[2] = 0x17;
+        s->pos = 0;
+        s->len = 3;
+        s->wrap_read = 0;
+        s->state = STATE_READING_DATA;
+        break;
+
+    case CHIP_ERASE:
+        if (s->write_enable) {
+            DB_PRINT("chip erase\n");
+            flash_chip_erase(s);
+        } else {
+            DB_PRINT("chip erase with write protect!\n");
+        }
+        break;
+    case NOP:
+        break;
+    default:
+        DB_PRINT("Unknown cmd %x\n", value);
+        break;
+    }
+}
+
+static int m25p80_cs(SSISlave *ss, int select)
+{
+    struct flash *s = FROM_SSI_SLAVE(struct flash, ss);
+
+    if (!select) {
+        s->len = 0;
+        s->pos = 0;
+        s->state = STATE_IDLE;
+        flash_sync_dirty(s, -1);
+        DB_PRINT("deselect\n");
+    }
+
+    return 0;
+}
+
+static uint32_t m25p80_transfer8(SSISlave *ss, uint32_t tx)
+{
+    struct flash *s = FROM_SSI_SLAVE(struct flash, ss);
+    uint32_t r = 0;
+
+    switch (s->state) {
+
+    case STATE_PAGE_PROGRAM:
+        DB_PRINT("page program waddr=%lx data=%x\n", s->waddr, (uint8_t)tx);
+        flash_write8(s, s->waddr, (uint8_t)tx);
+        s->waddr++;
+        break;
+
+    case STATE_READ:
+        r = s->storage[s->waddr];
+        DB_PRINT("READ 0x%lx=%x\n", s->waddr, r);
+        s->waddr = (s->waddr + 1) % s->size;
+        break;
+
+    case STATE_COLLECTING_DATA:
+        s->data[s->len] = (uint8_t)tx;
+        s->len++;
+
+        if (s->len == s->needed_bytes) {
+            complete_collecting_data(s);
+        }
+        break;
+
+    case STATE_READING_DATA:
+        r = s->data[s->pos];
+        s->pos++;
+        if (s->pos == s->len) {
+            s->pos = 0;
+            if (!s->wrap_read) {
+                s->state = STATE_IDLE;
+            }
+        }
+        break;
+
+    default:
+    case STATE_IDLE:
+        decode_new_cmd(s, (uint8_t)tx);
+        break;
+    }
+
+    return r;
+}
+
+static int m25p80_init(SSISlave *ss)
+{
+    DriveInfo *dinfo;
+    struct flash *s = FROM_SSI_SLAVE(struct flash, ss);
+    static int mtdblock_idx;
+    dinfo = drive_get(IF_MTD, 0, mtdblock_idx++);
+
+    DB_PRINT("inited m25p80 device model - dinfo = %p\n", dinfo);
+    /* TODO: parameterize */
+    s->size = 8 * 1024 * 1024;
+    s->pagesize = 256;
+    s->sectorsize = 4 * 1024;
+    s->dirty_page = -1;
+    s->storage = qemu_blockalign(s->bdrv, s->size);
+
+    if (dinfo && dinfo->bdrv) {
+        int rsize;
+
+        s->bdrv = dinfo->bdrv;
+        rsize = MIN(bdrv_getlength(s->bdrv), s->size);
+        if (bdrv_read(s->bdrv, 0, s->storage, (s->size + 511) / 512)) {
+            fprintf(stderr, "Failed to initialize SPI flash!\n");
+            return 1;
+        }
+    } else {
+        s->write_enable = 1;
+        flash_chip_erase(s);
+        s->write_enable = 0;
+    }
+
+    return 0;
+}
+
+static void m25p80_class_init(ObjectClass *klass, void *data)
+{
+    SSISlaveClass *k = SSI_SLAVE_CLASS(klass);
+
+    k->init = m25p80_init;
+    k->transfer = m25p80_transfer8;
+    k->set_cs = m25p80_cs;
+}
+
+static TypeInfo m25p80_info = {
+    .name           = "m25p80",
+    .parent         = TYPE_SSI_SLAVE,
+    .instance_size  = sizeof(struct flash),
+    .class_init     = m25p80_class_init,
+};
+
+static void m25p80_register_types(void)
+{
+    type_register_static(&m25p80_info);
+}
+
+type_init(m25p80_register_types)