From patchwork Fri Apr 20 02:12:20 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: 153907 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 83FDDB7031 for ; Fri, 20 Apr 2012 12:10:44 +1000 (EST) Received: from localhost ([::1]:52946 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SL3J4-0000df-8y for incoming@patchwork.ozlabs.org; Thu, 19 Apr 2012 22:10:42 -0400 Received: from eggs.gnu.org ([208.118.235.92]:35068) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SL3In-0000PQ-6r for qemu-devel@nongnu.org; Thu, 19 Apr 2012 22:10:27 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1SL3Ik-0001qk-FF for qemu-devel@nongnu.org; Thu, 19 Apr 2012 22:10:24 -0400 Received: from mail-iy0-f173.google.com ([209.85.210.173]:57562) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SL3Ik-0001gY-AQ for qemu-devel@nongnu.org; Thu, 19 Apr 2012 22:10:22 -0400 Received: by mail-iy0-f173.google.com with SMTP id j26so14807715iaf.4 for ; Thu, 19 Apr 2012 19:10:21 -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=3Xw77E4gFuhzq4sof3d3oNT5hq6jCANbqs/d9Py6C+E=; b=BsC/zkApoG2CVIWORaU44T0t7JiZ/Up+HyXEJsTh8WnC94MlpWAAbSRhvui+hY65On R1TepVz7Wq0XAlRHR8Cg3+E0x2bZb5N/80stHEJvdyni+CAodwTFOn2dCHd3vRBdAQGM W8NCqgzn6Df6G/04tMAv1ZYQAJ39+rw+AK0/nTOqjvVFl8cYwMSMLgBvUomi2y+erNlA WbHq4JxjNzYW7WaHcMUMXkRb7ExTEPb63Se/F31WGU+tDSFPaGFL6ANfkZT8enim6Q1Q coLkqetUjbuUT0WM79YAC0xRhSFGEbj3vioKZFbANizc+BnvvgPE3+v1MOJa3aNZ8kua XPOw== Received: by 10.50.156.229 with SMTP id wh5mr19271021igb.28.1334887821454; Thu, 19 Apr 2012 19:10:21 -0700 (PDT) Received: from localhost ([124.148.20.9]) by mx.google.com with ESMTPS id xf6sm2348602igb.13.2012.04.19.19.10.16 (version=TLSv1/SSLv3 cipher=OTHER); Thu, 19 Apr 2012 19:10:20 -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: Fri, 20 Apr 2012 12:12:20 +1000 Message-Id: <9a4b434352391c41728122412037670f4e6a6bfe.1334886618.git.peter.crosthwaite@petalogix.com> X-Mailer: git-send-email 1.7.3.2 In-Reply-To: References: In-Reply-To: References: X-Gm-Message-State: ALoCoQlyzSpwYzzjN5ITP8cl+/wTzSYUo7xFri5W86V3PwN8TkwrNAou4ZG8aPgwObCanOy2RX1U X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 209.85.210.173 Cc: peter.crosthwaite@petalogix.com, john.williams@petalogix.com Subject: [Qemu-devel] [PATCH v3 2/4] m25p80: initial verion 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 SPI flash Signed-off-by: Peter A. G. Crosthwaite --- 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. + * + * 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) 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 . + */ + +#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)