From patchwork Tue Sep 18 02:11:05 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Peter A. G. Crosthwaite" X-Patchwork-Id: 184592 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 946082C0083 for ; Tue, 18 Sep 2012 12:46:18 +1000 (EST) Received: from localhost ([::1]:51591 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1TDnIw-000602-CK for incoming@patchwork.ozlabs.org; Mon, 17 Sep 2012 22:12:50 -0400 Received: from eggs.gnu.org ([208.118.235.92]:45888) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1TDnIi-0005cv-Bk for qemu-devel@nongnu.org; Mon, 17 Sep 2012 22:12:38 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1TDnId-0004VW-Nt for qemu-devel@nongnu.org; Mon, 17 Sep 2012 22:12:36 -0400 Received: from mail-ob0-f173.google.com ([209.85.214.173]:38380) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1TDnId-0004NA-HF for qemu-devel@nongnu.org; Mon, 17 Sep 2012 22:12:31 -0400 Received: by mail-ob0-f173.google.com with SMTP id ta14so9259606obb.4 for ; Mon, 17 Sep 2012 19:12:31 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references :in-reply-to:references:x-gm-message-state; bh=UM6e0K2wcIwGqVCLHNaajs77rc2uND7hoDCfwMqWVpg=; b=JXaE9wwVX86Teg3I2kUXmpXbpNMjXk6RpEnXOl0H+ii3uOm68ojnDGlPcr9uDS9sxW fkY0MCqwcF8BnCfxTFt0PK2XLWCIQOr5QpDfT09U9EEFo5W0w3uMDwqIA0B157aj+x9i +hzcqQatHU6CaRfl9aY/fEfxxLeAj6f6ORWArzcTfne07K1p+oml0Cslfv2AM7444x0S nWHTQaFEfAinYakypFcj0jxl75z7X4imJgOc7WLsPD3Ehueg4o8v+rvCDbUZTcsXo8Jq l1PyVvGejGerezbwa6y+kFBEQDnZp+VfBjK43FCJuKAwsM+ufEoKC5MJ2LS4uJHQf5Pd 9l4A== Received: by 10.182.146.77 with SMTP id ta13mr13477097obb.97.1347934350012; Mon, 17 Sep 2012 19:12:30 -0700 (PDT) Received: from localhost ([124.148.20.9]) by mx.google.com with ESMTPS id bp7sm12788221obc.12.2012.09.17.19.12.26 (version=TLSv1/SSLv3 cipher=OTHER); Mon, 17 Sep 2012 19:12:29 -0700 (PDT) From: "Peter A. G. Crosthwaite" To: qemu-devel@nongnu.org, paul@codesourcery.com, edgar.iglesias@gmail.com, peter.maydell@linaro.org, stefanha@gmail.com Date: Tue, 18 Sep 2012 12:11:05 +1000 Message-Id: <007c857553e5582cb4647aaf703607b95fbb6dc1.1347932427.git.peter.crosthwaite@petalogix.com> X-Mailer: git-send-email 1.7.0.4 In-Reply-To: References: In-Reply-To: References: X-Gm-Message-State: ALoCoQmeQpPSIGA+fpYkugkohAzK9u0XV3xBkf9CeJhJg/a4JtTFlvAkn4uXHgSLTSC9YDgFCbwm X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 209.85.214.173 Cc: "Peter A. G. Crosthwaite" , i.mitsyanko@samsung.com Subject: [Qemu-devel] [PATCH v6 08/13] m25p80: Initial implementation of SPI flash device X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Added device model for m25p80 style SPI flash family. Signed-off-by: Peter A. G. Crosthwaite --- changed from v4: Added write-1 flag (EEPROM mode). n25q128 table entry indentation fix. updated for new SSI interface. various debug messages cleaned up and added. changed from v3: changed licence to v2 or later (PMM review) generalised device model - rather than being fixed to the fl064k, it can handle a wide range of m25p80 devices refactored erase commands (previously they were fl064k specific and used spansions broken terminology) typdef'd strcuts and enums fixed some camel casing added comment to explain why bdrv_sync_complete is a nop (PMM review) removed hardcoded "512" for BDRV_SECTOR_SIZE flash_sync_area: use bdrv_aio_writev instead of bdrv_write flash_chip_erase/flash_block_erase32k/flash_sector_erase: consolidated to one function decode_new_cmd: fixed multi-statement lines (PMM review) CHIP_ERASE->BULK_ERASE init: drive_get -> drive_get_next (PMM review) 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 default-configs/arm-softmmu.mak | 1 + default-configs/microblaze-softmmu.mak | 2 + default-configs/microblazeel-softmmu.mak | 2 + hw/Makefile.objs | 1 + hw/m25p80.c | 572 ++++++++++++++++++++++++++++++ 5 files changed, 578 insertions(+), 0 deletions(-) create mode 100644 hw/m25p80.c diff --git a/default-configs/arm-softmmu.mak b/default-configs/arm-softmmu.mak index f335a72..2f1a5c9 100644 --- a/default-configs/arm-softmmu.mak +++ b/default-configs/arm-softmmu.mak @@ -22,6 +22,7 @@ CONFIG_ADS7846=y CONFIG_MAX111X=y CONFIG_SSI=y CONFIG_SSI_SD=y +CONFIG_SSI_M25P80=y CONFIG_LAN9118=y CONFIG_SMC91C111=y CONFIG_DS1338=y diff --git a/default-configs/microblaze-softmmu.mak b/default-configs/microblaze-softmmu.mak index 64c9485..2f442e5 100644 --- a/default-configs/microblaze-softmmu.mak +++ b/default-configs/microblaze-softmmu.mak @@ -5,3 +5,5 @@ CONFIG_PFLASH_CFI01=y CONFIG_SERIAL=y CONFIG_XILINX=y CONFIG_XILINX_AXI=y +CONFIG_SSI=y +CONFIG_SSI_M25P80=y diff --git a/default-configs/microblazeel-softmmu.mak b/default-configs/microblazeel-softmmu.mak index a962276..af9a3cd 100644 --- a/default-configs/microblazeel-softmmu.mak +++ b/default-configs/microblazeel-softmmu.mak @@ -5,3 +5,5 @@ CONFIG_PFLASH_CFI01=y CONFIG_SERIAL=y CONFIG_XILINX=y CONFIG_XILINX_AXI=y +CONFIG_SSI=y +CONFIG_SSI_M25P80=y diff --git a/hw/Makefile.objs b/hw/Makefile.objs index dcb0bcd..5524b5f 100644 --- a/hw/Makefile.objs +++ b/hw/Makefile.objs @@ -174,6 +174,7 @@ common-obj-y += scsi-disk.o cdrom.o hd-geometry.o block-common.o common-obj-y += scsi-generic.o scsi-bus.o common-obj-y += hid.o common-obj-$(CONFIG_SSI) += ssi.o +common-obj-$(CONFIG_SSI_M25P80) += m25p80.o common-obj-$(CONFIG_SSI_SD) += ssi-sd.o common-obj-$(CONFIG_SD) += sd.o common-obj-y += bt.o bt-l2cap.o bt-sdp.o bt-hci.o bt-hid.o diff --git a/hw/m25p80.c b/hw/m25p80.c new file mode 100644 index 0000000..389adf6 --- /dev/null +++ b/hw/m25p80.c @@ -0,0 +1,572 @@ +/* + * ST M25P80 emulator. Emulate all SPI flash devices based on the m25p80 command + * set. Known devices table current as of Jun/2012 and taked from linux. + * See drivers/mtd/devices/m25p80.c. + * + * Copyright (C) 2011 Edgar E. Iglesias + * Copyright (C) 2012 Peter A. G. Crosthwaite + * 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) a later version 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 . + */ + +#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 + +typedef struct FlashPartInfo { + const char *part_name; + /* jedec code. (jedec >> 16) & 0xff is the 1st byte, >> 8 the 2nd etc */ + uint32_t jedec; + /* extended jedec code */ + uint16_t ext_jedec; + /* there is confusion between manufacturers as to what a sector is. In this + * device model, a "sector" is the size that is erased by the ERASE_SECTOR + * command (opcode 0xd8). + */ + uint32_t sector_size; + uint32_t n_sectors; + uint32_t page_size; + uint8_t flags; + /* erase capabilities */ +#define ER_4K 1 +#define ER_32K 2 + /* set to allow the page program command to write 0s back to 1. Useful for + * modelling EEPROM with SPI flash command set + */ +#define WR_1 0x100 +} FlashPartInfo; + +/* adapted from linux */ + +#define INFO(_part_name, _jedec, _ext_jedec, _sector_size, _n_sectors, _flags)\ + .part_name = (_part_name),\ + .jedec = (_jedec),\ + .ext_jedec = (_ext_jedec),\ + .sector_size = (_sector_size),\ + .n_sectors = (_n_sectors),\ + .page_size = 256,\ + .flags = (_flags),\ + +static const FlashPartInfo known_devices[] = { + /* Atmel -- some are (confusingly) marketed as "DataFlash" */ + { INFO("at25fs010", 0x1f6601, 0, 32 << 10, 4, ER_4K) }, + { INFO("at25fs040", 0x1f6604, 0, 64 << 10, 8, ER_4K) }, + + { INFO("at25df041a", 0x1f4401, 0, 64 << 10, 8, ER_4K) }, + { INFO("at25df321a", 0x1f4701, 0, 64 << 10, 64, ER_4K) }, + { INFO("at25df641", 0x1f4800, 0, 64 << 10, 128, ER_4K) }, + + { INFO("at26f004", 0x1f0400, 0, 64 << 10, 8, ER_4K) }, + { INFO("at26df081a", 0x1f4501, 0, 64 << 10, 16, ER_4K) }, + { INFO("at26df161a", 0x1f4601, 0, 64 << 10, 32, ER_4K) }, + { INFO("at26df321", 0x1f4700, 0, 64 << 10, 64, ER_4K) }, + + /* EON -- en25xxx */ + { INFO("en25f32", 0x1c3116, 0, 64 << 10, 64, ER_4K) }, + { INFO("en25p32", 0x1c2016, 0, 64 << 10, 64, 0) }, + { INFO("en25q32b", 0x1c3016, 0, 64 << 10, 64, 0) }, + { INFO("en25p64", 0x1c2017, 0, 64 << 10, 128, 0) }, + + /* Intel/Numonyx -- xxxs33b */ + { INFO("160s33b", 0x898911, 0, 64 << 10, 32, 0) }, + { INFO("320s33b", 0x898912, 0, 64 << 10, 64, 0) }, + { INFO("640s33b", 0x898913, 0, 64 << 10, 128, 0) }, + + /* Macronix */ + { INFO("mx25l4005a", 0xc22013, 0, 64 << 10, 8, ER_4K) }, + { INFO("mx25l8005", 0xc22014, 0, 64 << 10, 16, 0) }, + { INFO("mx25l1606e", 0xc22015, 0, 64 << 10, 32, ER_4K) }, + { INFO("mx25l3205d", 0xc22016, 0, 64 << 10, 64, 0) }, + { INFO("mx25l6405d", 0xc22017, 0, 64 << 10, 128, 0) }, + { INFO("mx25l12805d", 0xc22018, 0, 64 << 10, 256, 0) }, + { INFO("mx25l12855e", 0xc22618, 0, 64 << 10, 256, 0) }, + { INFO("mx25l25635e", 0xc22019, 0, 64 << 10, 512, 0) }, + { INFO("mx25l25655e", 0xc22619, 0, 64 << 10, 512, 0) }, + + /* Spansion -- single (large) sector size only, at least + * for the chips listed here (without boot sectors). + */ + { INFO("s25sl004a", 0x010212, 0, 64 << 10, 8, 0) }, + { INFO("s25sl008a", 0x010213, 0, 64 << 10, 16, 0) }, + { INFO("s25sl016a", 0x010214, 0, 64 << 10, 32, 0) }, + { INFO("s25sl032a", 0x010215, 0, 64 << 10, 64, 0) }, + { INFO("s25sl032p", 0x010215, 0x4d00, 64 << 10, 64, ER_4K) }, + { INFO("s25sl064a", 0x010216, 0, 64 << 10, 128, 0) }, + { INFO("s25fl256s0", 0x010219, 0x4d00, 256 << 10, 128, 0) }, + { INFO("s25fl256s1", 0x010219, 0x4d01, 64 << 10, 512, 0) }, + { INFO("s25fl512s", 0x010220, 0x4d00, 256 << 10, 256, 0) }, + { INFO("s70fl01gs", 0x010221, 0x4d00, 256 << 10, 256, 0) }, + { INFO("s25sl12800", 0x012018, 0x0300, 256 << 10, 64, 0) }, + { INFO("s25sl12801", 0x012018, 0x0301, 64 << 10, 256, 0) }, + { INFO("s25fl129p0", 0x012018, 0x4d00, 256 << 10, 64, 0) }, + { INFO("s25fl129p1", 0x012018, 0x4d01, 64 << 10, 256, 0) }, + { INFO("s25fl016k", 0xef4015, 0, 64 << 10, 32, ER_4K | ER_32K) }, + { INFO("s25fl064k", 0xef4017, 0, 64 << 10, 128, ER_4K | ER_32K) }, + + /* SST -- large erase sizes are "overlays", "sectors" are 4<< 10 */ + { INFO("sst25vf040b", 0xbf258d, 0, 64 << 10, 8, ER_4K) }, + { INFO("sst25vf080b", 0xbf258e, 0, 64 << 10, 16, ER_4K) }, + { INFO("sst25vf016b", 0xbf2541, 0, 64 << 10, 32, ER_4K) }, + { INFO("sst25vf032b", 0xbf254a, 0, 64 << 10, 64, ER_4K) }, + { INFO("sst25wf512", 0xbf2501, 0, 64 << 10, 1, ER_4K) }, + { INFO("sst25wf010", 0xbf2502, 0, 64 << 10, 2, ER_4K) }, + { INFO("sst25wf020", 0xbf2503, 0, 64 << 10, 4, ER_4K) }, + { INFO("sst25wf040", 0xbf2504, 0, 64 << 10, 8, ER_4K) }, + + /* ST Microelectronics -- newer production may have feature updates */ + { INFO("m25p05", 0x202010, 0, 32 << 10, 2, 0) }, + { INFO("m25p10", 0x202011, 0, 32 << 10, 4, 0) }, + { INFO("m25p20", 0x202012, 0, 64 << 10, 4, 0) }, + { INFO("m25p40", 0x202013, 0, 64 << 10, 8, 0) }, + { INFO("m25p80", 0x202014, 0, 64 << 10, 16, 0) }, + { INFO("m25p16", 0x202015, 0, 64 << 10, 32, 0) }, + { INFO("m25p32", 0x202016, 0, 64 << 10, 64, 0) }, + { INFO("m25p64", 0x202017, 0, 64 << 10, 128, 0) }, + { INFO("m25p128", 0x202018, 0, 256 << 10, 64, 0) }, + + { INFO("m45pe10", 0x204011, 0, 64 << 10, 2, 0) }, + { INFO("m45pe80", 0x204014, 0, 64 << 10, 16, 0) }, + { INFO("m45pe16", 0x204015, 0, 64 << 10, 32, 0) }, + + { INFO("m25pe80", 0x208014, 0, 64 << 10, 16, 0) }, + { INFO("m25pe16", 0x208015, 0, 64 << 10, 32, ER_4K) }, + + { INFO("m25px32", 0x207116, 0, 64 << 10, 64, ER_4K) }, + { INFO("m25px32-s0", 0x207316, 0, 64 << 10, 64, ER_4K) }, + { INFO("m25px32-s1", 0x206316, 0, 64 << 10, 64, ER_4K) }, + { INFO("m25px64", 0x207117, 0, 64 << 10, 128, 0) }, + + /* Winbond -- w25x "blocks" are 64k, "sectors" are 4KiB */ + { INFO("w25x10", 0xef3011, 0, 64 << 10, 2, ER_4K) }, + { INFO("w25x20", 0xef3012, 0, 64 << 10, 4, ER_4K) }, + { INFO("w25x40", 0xef3013, 0, 64 << 10, 8, ER_4K) }, + { INFO("w25x80", 0xef3014, 0, 64 << 10, 16, ER_4K) }, + { INFO("w25x16", 0xef3015, 0, 64 << 10, 32, ER_4K) }, + { INFO("w25x32", 0xef3016, 0, 64 << 10, 64, ER_4K) }, + { INFO("w25q32", 0xef4016, 0, 64 << 10, 64, ER_4K) }, + { INFO("w25x64", 0xef3017, 0, 64 << 10, 128, ER_4K) }, + { INFO("w25q64", 0xef4017, 0, 64 << 10, 128, ER_4K) }, + + /* Numonyx -- n25q128 */ + { INFO("n25q128", 0x20ba18, 0, 64 << 10, 256, 0) }, + + { }, +}; + +typedef enum { + NOP = 0, + PP = 0x2, + READ = 0x3, + WRDI = 0x4, + RDSR = 0x5, + WREN = 0x6, + FAST_READ = 0xb, + ERASE_4K = 0x20, + ERASE_32K = 0x52, + ERASE_SECTOR = 0xd8, + JEDEC_READ = 0x9f, + BULK_ERASE = 0xc7, +} FlashCMD; + +typedef enum { + STATE_IDLE, + STATE_PAGE_PROGRAM, + STATE_READ, + STATE_COLLECTING_DATA, + STATE_READING_DATA, +} CMDState; + +typedef struct Flash { + SSISlave ssidev; + uint32_t r; + + BlockDriverState *bdrv; + CMDState state; + + uint8_t *storage; + uint32_t size; + int page_size; + + uint8_t data[16]; + uint32_t len; + uint32_t pos; + uint8_t needed_bytes; + FlashCMD cmd_in_progress; + + int64_t dirty_page; + + uint64_t waddr; + int write_enable; + + char *part_name; + const FlashPartInfo *pi; + +} Flash; + +static void bdrv_sync_complete(void *opaque, int ret) +{ + /* do nothing. Masters do not directly interact with the backing store, + * only the working copy so no mutexing required. + */ +} + +static void flash_sync_page(Flash *s, int page) +{ + if (s->bdrv) { + int bdrv_sector, nb_sectors; + QEMUIOVector iov; + + bdrv_sector = (page * s->pi->page_size) / BDRV_SECTOR_SIZE; + nb_sectors = DIV_ROUND_UP(s->pi->page_size, BDRV_SECTOR_SIZE); + qemu_iovec_init(&iov, 1); + qemu_iovec_add(&iov, s->storage + bdrv_sector * BDRV_SECTOR_SIZE, + nb_sectors * BDRV_SECTOR_SIZE); + bdrv_aio_writev(s->bdrv, bdrv_sector, &iov, nb_sectors, + bdrv_sync_complete, NULL); + } +} + +static inline void flash_sync_area(Flash *s, int64_t off, int64_t len) +{ + int64_t start, end; + int nb_sectors; + QEMUIOVector iov; + + if (!s->bdrv) { + return; + } + + assert(!(len % BDRV_SECTOR_SIZE)); + start = off / BDRV_SECTOR_SIZE; + end = (off + len) / BDRV_SECTOR_SIZE; + nb_sectors = end - start; + qemu_iovec_init(&iov, 1); + qemu_iovec_add(&iov, s->storage + (start * BDRV_SECTOR_SIZE), + nb_sectors * BDRV_SECTOR_SIZE); + bdrv_aio_writev(s->bdrv, start, &iov, nb_sectors, bdrv_sync_complete, NULL); +} + +static void flash_erase(Flash *s, int offset, FlashCMD cmd) +{ + uint32_t len; + uint8_t capa_to_assert = 0; + + switch (cmd) { + case ERASE_4K: + len = 4 << 10; + capa_to_assert = ER_4K; + break; + case ERASE_32K: + len = 32 << 10; + capa_to_assert = ER_32K; + break; + case ERASE_SECTOR: + len = s->pi->sector_size; + break; + case BULK_ERASE: + len = s->size; + default: + assert(false); + } + + DB_PRINT("offset = %#x, len = %d\n", offset, len); + if ((s->pi->flags & capa_to_assert) != capa_to_assert) { + hw_error("m25p80: %dk erase size not supported by device\n", len); + } + + if (!s->write_enable) { + DB_PRINT("erase with write protect!\n"); + return; + } + memset(s->storage + offset, 0xff, len); + flash_sync_area(s, offset, len); +} + +static inline void flash_sync_dirty(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(Flash *s, uint64_t addr, uint8_t data) +{ + int64_t page = addr / s->pi->page_size; + 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); + } + + if (s->pi->flags & WR_1) { + s->storage[s->waddr] = data; + } else { + s->storage[s->waddr] &= ~data; + } + + flash_sync_dirty(s, page); + s->dirty_page = page; +} + +static void complete_collecting_data(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 ERASE_4K: + case ERASE_32K: + case ERASE_SECTOR: + flash_erase(s, s->waddr, s->cmd_in_progress); + break; + default: + break; + } +} + +static void decode_new_cmd(Flash *s, uint32_t value) +{ + s->cmd_in_progress = value; + DB_PRINT("decoded new command:%x\n", value); + + switch (value) { + + case ERASE_4K: + case ERASE_32K: + case ERASE_SECTOR: + 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->state = STATE_READING_DATA; + break; + + case JEDEC_READ: + DB_PRINT("populated jedec code\n"); + s->data[0] = (s->pi->jedec >> 16) & 0xff; + s->data[1] = (s->pi->jedec >> 8) & 0xff; + s->data[2] = s->pi->jedec & 0xff; + if (s->pi->ext_jedec) { + s->data[3] = (s->pi->ext_jedec >> 8) & 0xff; + s->data[4] = s->pi->ext_jedec & 0xff; + s->len = 5; + } else { + s->len = 3; + } + s->pos = 0; + s->state = STATE_READING_DATA; + break; + + case BULK_ERASE: + if (s->write_enable) { + DB_PRINT("chip erase\n"); + flash_erase(s, 0, BULK_ERASE); + } 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, bool select) +{ + Flash *s = FROM_SSI_SLAVE(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) +{ + Flash *s = FROM_SSI_SLAVE(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; + 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; + Flash *s = FROM_SSI_SLAVE(Flash, ss); + const FlashPartInfo *i; + + if (!s->part_name) { /* default to actual m25p80 if no partname given */ + s->part_name = (char *)"m25p80"; + } + + i = known_devices; + for (i = known_devices;; i++) { + assert(i); + if (!i->part_name) { + fprintf(stderr, "Unknown SPI flash part: \"%s\"\n", s->part_name); + return 1; + } else if (!strcmp(i->part_name, s->part_name)) { + s->pi = i; + break; + } + } + + s->size = s->pi->sector_size * s->pi->n_sectors; + s->dirty_page = -1; + s->storage = qemu_blockalign(s->bdrv, s->size); + + dinfo = drive_get_next(IF_MTD); + + if (dinfo && dinfo->bdrv) { + int rsize; + + DB_PRINT("Binding to IF_MTD drive\n"); + s->bdrv = dinfo->bdrv; + rsize = MIN(bdrv_getlength(s->bdrv), s->size); + /* FIXME: Move to late init */ + if (bdrv_read(s->bdrv, 0, s->storage, DIV_ROUND_UP(s->size, + BDRV_SECTOR_SIZE))) { + fprintf(stderr, "Failed to initialize SPI flash!\n"); + return 1; + } + } else { + memset(s->storage, 0xFF, s->size); + } + + return 0; +} + +static Property m25p80_properties[] = { + DEFINE_PROP_STRING("partname", Flash, part_name), + DEFINE_PROP_END_OF_LIST(), +}; + +static void m25p80_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SSISlaveClass *k = SSI_SLAVE_CLASS(klass); + + k->init = m25p80_init; + k->transfer = m25p80_transfer8; + k->set_cs = m25p80_cs; + k->cs_polarity = SSI_CS_LOW; + dc->props = m25p80_properties; +} + +static TypeInfo m25p80_info = { + .name = "m25p80", + .parent = TYPE_SSI_SLAVE, + .instance_size = sizeof(Flash), + .class_init = m25p80_class_init, +}; + +static void m25p80_register_types(void) +{ + type_register_static(&m25p80_info); +} + +type_init(m25p80_register_types)