From patchwork Fri Mar 30 06:37:11 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: 149548 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 328CCB6F9F for ; Fri, 30 Mar 2012 17:38:05 +1100 (EST) Received: from localhost ([::1]:55738 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SDVTF-0000dR-Pg for incoming@patchwork.ozlabs.org; Fri, 30 Mar 2012 02:38:01 -0400 Received: from eggs.gnu.org ([208.118.235.92]:34607) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SDVSt-0000HH-PI for qemu-devel@nongnu.org; Fri, 30 Mar 2012 02:37:45 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1SDVSr-00048O-3B for qemu-devel@nongnu.org; Fri, 30 Mar 2012 02:37:39 -0400 Received: from mail-iy0-f173.google.com ([209.85.210.173]:58765) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SDVSq-0003yv-TB for qemu-devel@nongnu.org; Fri, 30 Mar 2012 02:37:37 -0400 Received: by mail-iy0-f173.google.com with SMTP id j26so695412iaf.4 for ; Thu, 29 Mar 2012 23:37:36 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=mime-version:from:to:cc:subject:date:message-id:x-mailer :in-reply-to:references:in-reply-to:references:x-gm-message-state; bh=Bn25KQsZj2XgmhFs2yB+OHRVvSVw9vtLrB6uY15AEsA=; b=QiRbWDZbzXM8qEzfXx9EX4kU36e6/WHG56fKcbXBBR0IH9NrgOGFpyY0MyDx1bvgzb HnYuy8aPcic3IvzYBidilfRTu0hPGnaOb0BVjbvfxNEFfEIp8uuJbTRAkdP3DxC1EY/l pm59c18tSqJ/5q2FRjafsgCd0I/XVzwvf+kFeLLzVJE7k8xJYP73TQOlK36+eX1T1c7f 0oQ3DbiPgI60ffZAvfeQyVGWyd/GGDvVtQfmwCsHhaDnZMhPEeiQdB646jnUWhL/4WNm UUdXZUCnvl/onWQpvTE9lKL1VSSWSWGSxKok+WeXMUXLlLc49+FQm8dtSQJFJ46jqjzP VtLQ== MIME-Version: 1.0 Received: by 10.50.76.163 with SMTP id l3mr759727igw.10.1333089455953; Thu, 29 Mar 2012 23:37:35 -0700 (PDT) Received: from localhost ([124.148.20.9]) by mx.google.com with ESMTPS id vr4sm7483776igb.1.2012.03.29.23.37.33 (version=TLSv1/SSLv3 cipher=OTHER); Thu, 29 Mar 2012 23:37:35 -0700 (PDT) From: "Peter A. G. Crosthwaite" To: qemu-devel@nongnu.org, paul@codesourcery.com, edgar.iglesias@gmail.com Date: Fri, 30 Mar 2012 16:37:11 +1000 Message-Id: <26d832cdffc1b31cfe1aa6fa9ca50f66d6fb9f71.1333088411.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: ALoCoQlVqPoIqv7kkGeyWYqrrKCl9+8uE4RQHvTq2JZeI1VlWnWIODUzOj0D8WZcqeQMhqpq8PqP 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] [RFC PATCH v1 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 --- Makefile.target | 1 + hw/m25p80.c | 495 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 496 insertions(+), 0 deletions(-) create mode 100644 hw/m25p80.c diff --git a/Makefile.target b/Makefile.target index 8fd3718..fcccf1b 100644 --- a/Makefile.target +++ b/Makefile.target @@ -321,6 +321,7 @@ obj-microblaze-y = petalogix_s3adsp1800_mmu.o obj-microblaze-y += petalogix_ml605_mmu.o obj-microblaze-y += microblaze_boot.o obj-microblaze-y += spi.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..2b67375 --- /dev/null +++ b/hw/m25p80.c @@ -0,0 +1,495 @@ +/* + * 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 "spi.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_SECTOR_ERASE, + STATE_BLOCK_ERASE32, + STATE_READ, + STATE_FAST_READ, + STATE_COLLECTING_DATA, + STATE_READING_DATA, +}; + +struct flash { + SPISlave spi; + SpiSlaveState spi_state; + 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; + int next_state; + + int64_t dirty_page; + + uint64_t waddr; + int write_enable; +}; + +static void flash_sync_page(struct flash *s, int page) +{ + if (s->bdrv) { + int bdrv_sector; + int offset; + + bdrv_sector = (page * s->pagesize) / 512; + offset = bdrv_sector * 512; + bdrv_write(s->bdrv, bdrv_sector, + s->storage + offset, (s->pagesize + 511) / 512); + } +} + +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 int decode_new_cmd(struct flash *s, uint32_t value) +{ + int state = STATE_IDLE; + int val; + + switch (value) { + case PP: + s->needed_bytes = 3; + s->pos = 0; s->len = 0; + state = STATE_COLLECTING_DATA; + s->next_state = STATE_PAGE_PROGRAM; + break; + case READ: + s->needed_bytes = 2; + s->pos = 0; s->len = 0; + state = STATE_COLLECTING_DATA; + s->next_state = STATE_READ; + break; + case FAST_READ: + /* Dummy byte must return data! */ + s->needed_bytes = 3; + s->pos = 0; s->len = 0; + state = STATE_COLLECTING_DATA; + s->next_state = STATE_FAST_READ; + break; + case WRDI: + s->write_enable = 0; + break; + case WREN: + s->write_enable = 1; + break; + + case RDSR: + val = (!!s->write_enable) << 1; + + s->data[0] = val; + s->pos = 0; s->len = 1; s->wrap_read = 0; + state = STATE_READING_DATA; + break; + + case JEDEC_READ: + s->data[0] = 0xef; + s->data[1] = 0x40; + s->data[2] = 0x17; + s->pos = 0; + s->len = 3; + s->wrap_read = 0; + state = STATE_READING_DATA; + break; + + case SECTOR_ERASE: + s->needed_bytes = 2; + s->pos = 0; s->len = 0; + state = STATE_COLLECTING_DATA; + s->next_state = STATE_SECTOR_ERASE; + break; + + case BLOCK_ERASE32: + s->needed_bytes = 2; + s->pos = 0; s->len = 0; + state = STATE_COLLECTING_DATA; + s->next_state = STATE_BLOCK_ERASE32; + 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; + } + + return state; +} + +static void sector_erase(struct flash *s, uint32_t data) +{ + int sector; + + if (s->next_state == STATE_SECTOR_ERASE) { + /* Transition just happened, pick up the address. */ + sector = s->data[0] << 16; + sector |= s->data[1] << 8; + sector |= data; + s->next_state = STATE_IDLE; + } + DB_PRINT("sector_erase sector=%d\n", sector); + flash_sector_erase(s, sector); +} + +static void block_erase32(struct flash *s, uint32_t data) +{ + int addr; + + if (s->next_state == STATE_BLOCK_ERASE32) { + /* Transition just happened, pick up the address. */ + addr = s->data[0] << 16; + addr |= s->data[1] << 8; + addr |= data; + s->next_state = STATE_IDLE; + } + DB_PRINT("block_erase addr=%08x\n", addr); + flash_block_erase32k(s, addr); +} + +static void page_program(struct flash *s, uint32_t data) +{ + if (s->next_state == STATE_PAGE_PROGRAM) { + /* Transition just happened, pick up the address. */ + s->waddr = s->data[0] << 16; + s->waddr |= s->data[1] << 8; + s->waddr |= s->data[2]; + s->next_state = STATE_IDLE; + } + DB_PRINT("page program waddr=%lx data=%x\n", s->waddr, data); + flash_write8(s, s->waddr, data); + s->waddr++; +} + +/* FIXME: these two functions are near identical, merge */ + +static void state_read(struct flash *s, uint32_t data) +{ + int addr; + + if (s->next_state == STATE_READ) { + /* Transition just happened, pick up the address. */ + s->waddr = s->data[0] << 16; + s->waddr |= s->data[1] << 8; + s->waddr |= data; + s->next_state = STATE_IDLE; + s->spi_state = SPI_IDLE; + } + addr = s->waddr; + s->r = s->storage[addr]; + DB_PRINT("READ 0x%lx.0x%x=%x\n", s->waddr, addr, s->r); + addr += 1; + if (addr >= s->size) { + addr = 0; + } + s->waddr = addr; + s->spi_state = SPI_DATA_PENDING; +} + +static void state_fast_read(struct flash *s, uint32_t data) +{ + int addr; + + if (s->next_state == STATE_FAST_READ) { + /* Transition just happened, pick up the address. */ + s->waddr = s->data[0] << 16; + s->waddr |= s->data[1] << 8; + s->waddr |= s->data[2]; + if (s->data[3] != 0) { + hw_error("Fast read dummy byte=%x\n", s->data[3]); + } + s->next_state = STATE_IDLE; + s->spi_state = SPI_IDLE; + } + + addr = s->waddr; + s->r = s->storage[addr]; + DB_PRINT("FAST-READ 0x%lx.0x%x=%x\n", s->waddr, addr, s->r); + addr += 1; + if (addr >= s->size) { + addr = 0; + } + s->waddr = addr; + s->spi_state = SPI_DATA_PENDING; +} + +static void m25p80_cs(SPISlave *ss, uint8_t select) +{ + struct flash *s = FROM_SPI_SLAVE(struct flash, ss); + + if (!select) { + s->len = 0; + s->pos = 0; + s->state = STATE_IDLE; + s->next_state = STATE_IDLE; + flash_sync_dirty(s, -1); + DB_PRINT("deselect\n"); + s->spi_state = SPI_NO_CS; + } else { + s->spi_state = SPI_IDLE; + } +} + +static SpiSlaveState m25p80_send(SPISlave *ss, uint32_t value, int len) +{ + struct flash *s = FROM_SPI_SLAVE(struct flash, ss); + + switch (s->state) { + case STATE_PAGE_PROGRAM: + page_program(s, value); + break; + + case STATE_READ: + state_read(s, value); + break; + + case STATE_FAST_READ: + state_fast_read(s, value); + break; + + case STATE_SECTOR_ERASE: + sector_erase(s, value); + break; + + case STATE_BLOCK_ERASE32: + block_erase32(s, value); + break; + + case STATE_COLLECTING_DATA: + if (len > 0) { + s->data[s->len] = value; + s->len++; + + if (s->len == s->needed_bytes) { + s->state = s->next_state; + } + } + break; + + case STATE_READING_DATA: + s->r = s->data[s->pos]; + s->spi_state = SPI_DATA_PENDING; + s->pos++; + if (s->pos == s->len) { + s->pos = 0; + if (!s->wrap_read) { + s->state = STATE_IDLE; + } + } + break; + + default: + case STATE_IDLE: + if (len > 0) { + s->state = decode_new_cmd(s, value); + } + break; + } + return s->spi_state; +} + +static SpiSlaveState m25p80_recv(SPISlave *ss, uint32_t *data) +{ + struct flash *s = FROM_SPI_SLAVE(struct flash, ss); + + *data = s->r; + s->spi_state = SPI_IDLE; + return SPI_IDLE; +} + +static SpiSlaveState m25p80_get_state(SPISlave *ss) +{ + struct flash *s = FROM_SPI_SLAVE(struct flash, ss); + + return s->spi_state; +} + +static int m25p80_init(SPISlave *ss) +{ + DriveInfo *dinfo; + struct flash *s = FROM_SPI_SLAVE(struct flash, ss); + /* FIXME: This should be handled centrally! */ + 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 = g_malloc0(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) +{ + SPISlaveClass *k = SPI_SLAVE_CLASS(klass); + + k->init = m25p80_init; + k->send = m25p80_send; + k->recv = m25p80_recv; + k->cs = m25p80_cs; + k->get_state = m25p80_get_state; +} + +static TypeInfo m25p80_info = { + .name = "m25p80", + .parent = TYPE_SPI_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)