From patchwork Fri Jan 18 06:31:07 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [09/18] hw: add QEMU model for Faraday NAND flash controller Date: Thu, 17 Jan 2013 20:31:07 -0000 From: Dante X-Patchwork-Id: 213494 Message-Id: <1358490667-18485-1-git-send-email-dantesu@faraday-tech.com> To: Cc: peter.maydell@linaro.org, paul@codesourcery.com, dantesu@faraday-tech.com Signed-off-by: Kuo-Jung Su --- hw/ftnandc021.c | 528 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/ftnandc021.h | 58 ++++++ 2 files changed, 586 insertions(+) create mode 100644 hw/ftnandc021.c create mode 100644 hw/ftnandc021.h diff --git a/hw/ftnandc021.c b/hw/ftnandc021.c new file mode 100644 index 0000000..0eb4e83 --- /dev/null +++ b/hw/ftnandc021.c @@ -0,0 +1,528 @@ +/* + * QEMU model of the FTNANDC021 NAND Flash Controller + * + * Copyright (C) 2012 Faraday Technology + * Copyright (C) 2012 Dante Su + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "sysbus.h" +#include "devices.h" +#include "sysemu/blockdev.h" +#include "flash.h" + +#include "ftnandc021.h" + +typedef struct { + SysBusDevice busdev; + MemoryRegion mmio; + + qemu_irq irq; + DeviceState *flash; + + /* DMA hardware handshake */ + qemu_irq req; + + uint8_t manf_id, chip_id; + + int cmd; + int len; /* buffer length for page read/write */ + int pi; /* page index */ + int bw; /* bus width (8-bits, 16-bits) */ + + uint64_t size; /* flash size (maximum access range) */ + uint32_t pgsz; /* page size (Bytes) */ + uint32_t bksz; /* block size (Bytes) */ + uint32_t alen; /* address length (cycle) */ + + uint32_t id[2]; + uint8_t oob[8];/* 5 bytes for 512/2048 page; 7 bytes for 4096 page */ + + /* HW register caches */ + uint32_t sr; + uint32_t fcr; + uint32_t mcr; + uint32_t ier; + uint32_t bcr; +} ftnandc021_state; + +static inline void +ftnandc021_irq_update(void *opaque) +{ + ftnandc021_state *s = opaque; + + if (s->ier & (1 << 7)) { + if ((s->ier & 0x0f) & (s->sr >> 2)) + qemu_set_irq(s->irq, 1); + else + qemu_set_irq(s->irq, 0); + } +} + +static inline void +ftnandc021_set_idle(void *opaque) +{ + ftnandc021_state *s = opaque; + + /* CLE=0, ALE=0, CS=1 */ + nand_setpins(s->flash, 0, 0, 1, 1, 0); + + /* Set command compelete */ + s->sr |= (1 << 2); + + /* Update IRQ signal */ + ftnandc021_irq_update(s); +} + +static inline void +ftnandc021_set_cmd(void *opaque, uint8_t cmd) +{ + ftnandc021_state *s = opaque; + + /* CLE=1, ALE=0, CS=0 */ + nand_setpins(s->flash, 1, 0, 0, 1, 0); + + /* Write out command code */ + nand_setio(s->flash, cmd); +} + +static inline void +ftnandc021_set_addr(void *opaque, int col, int row) +{ + ftnandc021_state *s = opaque; + + /* CLE=0, ALE=1, CS=0 */ + nand_setpins(s->flash, 0, 1, 0, 1, 0); + + if (col < 0 && row < 0) { + /* special case for READ_ID (0x90) */ + nand_setio(s->flash, 0); + } else { + /* column address */ + if (col >= 0) { + nand_setio(s->flash, (col & 0x00ff) >> 0); + nand_setio(s->flash, (col & 0xff00) >> 8); + } + /* row address */ + if (row >= 0) { + nand_setio(s->flash, (row & 0x0000ff) >> 0); + if (s->alen >= 4) + nand_setio(s->flash, (row & 0x00ff00) >> 8); + if (s->alen >= 5) + nand_setio(s->flash, (row & 0xff0000) >> 16); + } + } +} + +static void +ftnandc021_handle_ack(void *opaque, int line, int level) +{ + ftnandc021_state *s = (ftnandc021_state *)opaque; + + if (!s->bcr) + return; + + if (level) { + qemu_set_irq(s->req, 0); + } else if (s->len > 0) { + qemu_set_irq(s->req, 1); + } +} + +static void +ftnandc021_command(void *opaque, uint32_t cmd) +{ + int i; + ftnandc021_state *s = opaque; + + s->sr &= ~(1 << 2); + s->cmd = cmd; + + switch(cmd) { + case 0x01: /* read id */ + ftnandc021_set_cmd(s, 0x90); + ftnandc021_set_addr(s, -1, -1); + nand_setpins(s->flash, 0, 0, 0, 1, 0); + if (s->bw == 8) { + s->id[0] = (nand_getio(s->flash) << 0) + | (nand_getio(s->flash) << 8) + | (nand_getio(s->flash) << 16) + | (nand_getio(s->flash) << 24); + s->id[1] = (nand_getio(s->flash) << 0); + } else { + s->id[0] = (nand_getio(s->flash) << 0) + | (nand_getio(s->flash) << 16); + s->id[1] = (nand_getio(s->flash) << 0); + } + break; + case 0x02: /* reset */ + ftnandc021_set_cmd(s, 0xff); + break; + case 0x04: /* read status */ + ftnandc021_set_cmd(s, 0x70); + nand_setpins(s->flash, 0, 0, 0, 1, 0); + s->id[1] = (nand_getio(s->flash) << 0); + break; + case 0x05: /* read page */ + ftnandc021_set_cmd(s, 0x00); + ftnandc021_set_addr(s, 0, s->pi); + ftnandc021_set_cmd(s, 0x30); + nand_setpins(s->flash, 0, 0, 0, 1, 0); +#if 0 + s->len = s->pgsz + 16 * (s->pgsz / 512); +#else + s->len = s->pgsz; +#endif + break; + case 0x06: /* read oob */ +#if 0 + ftnandc021_set_cmd(s, 0x50); + ftnandc021_set_addr(s, 0, s->pi); +#else + ftnandc021_set_cmd(s, 0x00); + ftnandc021_set_addr(s, s->pgsz, s->pi); + ftnandc021_set_cmd(s, 0x30); +#endif + nand_setpins(s->flash, 0, 0, 0, 1, 0); + for (i = 0; i < 16 * (s->pgsz / 512); ) { + if (s->bw == 8) { + if (i < 7) + s->oob[i] = (uint8_t)nand_getio(s->flash); + else + (void)nand_getio(s->flash); + i += 1; + } else { + if (i < 7) + *(uint16_t *)(s->oob + i) = (uint16_t)nand_getio(s->flash); + else + (void)nand_getio(s->flash); + i += 2; + } + } + break; + case 0x10: /* write page + read status */ + ftnandc021_set_cmd(s, 0x80); + ftnandc021_set_addr(s, 0, s->pi); + /* data phase */ + nand_setpins(s->flash, 0, 0, 0, 1, 0); +#if 0 + s->len = s->pgsz + 16 * (s->pgsz / 512); +#else + s->len = s->pgsz; +#endif + break; + case 0x11: /* erase block + read status */ + ftnandc021_set_cmd(s, 0x60); + ftnandc021_set_addr(s, -1, s->pi); + ftnandc021_set_cmd(s, 0xd0); + /* read status */ + ftnandc021_command(s, 0x04); + break; + case 0x13: /* write oob + read status */ + ftnandc021_set_cmd(s, 0x80); + ftnandc021_set_addr(s, s->pgsz, s->pi); + /* data phase */ + nand_setpins(s->flash, 0, 0, 0, 1, 0); + for (i = 0; i < 16 * (s->pgsz / 512); ) { + if (s->bw == 8) { + if (i <= 7) + nand_setio(s->flash, s->oob[i]); + else + nand_setio(s->flash, 0xffffffff); + i += 1; + } else { + if (i <= 7) + nand_setio(s->flash, s->oob[i] | (s->oob[i + 1] << 8)); + else + nand_setio(s->flash, 0xffffffff); + i += 2; + } + } + ftnandc021_set_cmd(s, 0x10); + /* read status */ + ftnandc021_command(s, 0x04); + break; + default: + printf("[qemu] ftnandc021: unknow command=0x%02x\n", cmd); + break; + } + + /* if cmd is not page read/write, then return to idle mode */ + if (s->cmd != 0x05 && s->cmd != 0x10) { + ftnandc021_set_idle(s); + } else if (s->bcr && (s->len > 0)) { + qemu_set_irq(s->req, 1); + } +} + +static uint64_t +ftnandc021_mem_read(void *opaque, hwaddr addr, unsigned int size) +{ + ftnandc021_state *s = opaque; + + switch (addr) { + case REG_DR: + if (s->cmd == 0x05 && s->len > 0) { + uint32_t val = 0; + if (s->bw == 8) { + val |= (nand_getio(s->flash) & 0xff) << 0; + val |= (nand_getio(s->flash) & 0xff) << 8; + val |= (nand_getio(s->flash) & 0xff) << 16; + val |= (nand_getio(s->flash) & 0xff) << 24; + } else { + val |= (nand_getio(s->flash) & 0xffff) << 0; + val |= (nand_getio(s->flash) & 0xffff) << 16; + } + s->len -= 4; + if (s->len <= 0) { + ftnandc021_set_idle(s); + } + return val; + } + break; + case REG_SR: + return s->sr; + case REG_ACR: + return s->cmd << 8; + case REG_RDBR: + return s->oob[0]; + case REG_RDLSN: + return s->oob[1] | (s->oob[2] << 8); + case REG_RDCRC: + if (s->pgsz > 2048) + return s->oob[3] | (s->oob[4] << 8) | (s->oob[5] << 16) | (s->oob[6] << 24); + else + return s->oob[3] | (s->oob[4] << 8); + case REG_FCR: + return s->fcr; + case REG_PIR: + return s->pi; + case REG_PCR: + return 1; + case REG_MCR: + return s->mcr; + case REG_IDRL: + return s->id[0]; + case REG_IDRH: + return s->id[1]; + case REG_IER: + return s->ier; + case REG_BCR: + return s->bcr; + case REG_ATR1: + return 0x02240264; + case REG_ATR2: + return 0x42054209; + case REG_PRR: + return 0x00000001; + case REG_REVR: + return 0x00010100; + case REG_CFGR: + return 0x00081602; + default: + break; + } + + return 0; +} + +static void +ftnandc021_mem_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size) +{ + ftnandc021_state *s = opaque; + + switch (addr) { + case REG_DR: + if (s->cmd == 0x10 && s->len > 0) { + if (s->bw == 8) { + nand_setio(s->flash, ((uint32_t)val >> 0) & 0xff); + nand_setio(s->flash, ((uint32_t)val >> 8) & 0xff); + nand_setio(s->flash, ((uint32_t)val >> 16) & 0xff); + nand_setio(s->flash, ((uint32_t)val >> 24) & 0xff); + } else { + nand_setio(s->flash, ((uint32_t)val >> 0) & 0xffff); + nand_setio(s->flash, ((uint32_t)val >> 16) & 0xffff); + } + s->len -= 4; + if (s->len <= 0) { + ftnandc021_set_cmd(s, 0x10); + /* read status */ + ftnandc021_command(s, 0x04); + } + } + break; + case REG_ACR: + if (!((uint32_t)val & (1 << 7))) + break; + ftnandc021_command(s, ((uint32_t)val >> 8) & 0x1f); + break; + case REG_WRBR: + s->oob[0] = (uint32_t)val & 0xff; + break; + case REG_WRLSN: + s->oob[1] = ((uint32_t)val >> 0) & 0xff; + s->oob[2] = ((uint32_t)val >> 8) & 0xff; + break; + case REG_WRCRC: + s->oob[3] = ((uint32_t)val >> 0) & 0xff; + s->oob[4] = ((uint32_t)val >> 8) & 0xff; + if (s->pgsz > 2048) { + s->oob[5] = ((uint32_t)val >> 16) & 0xff; + s->oob[6] = ((uint32_t)val >> 24) & 0xff; + } + break; + case REG_FCR: + s->fcr = (uint32_t)val; + if (s->fcr & (1 << 4)) + s->bw = 16; + else + s->bw = 8; + break; + case REG_PIR: + s->pi = (uint32_t)val & 0x03ffffff; + break; + case REG_MCR: + s->mcr = (uint32_t)val; + /* page size */ + switch((s->mcr >> 8) & 0x03) { + case 0: + s->pgsz = 512; + break; + case 2: + s->pgsz = 4096; + break; + default: + s->pgsz = 2048; + break; + } + /* block size */ + s->bksz = s->pgsz * (1 << (4 + ((s->mcr >> 16) & 0x03))); + /* address length (cycle) */ + s->alen = 3 + ((s->mcr >> 10) & 0x03); + /* flash size */ + s->size = 1ULL << (24 + ((s->mcr >> 4) & 0x0f)); + break; + case REG_IER: + s->ier = (uint32_t)val & 0x8f; + ftnandc021_irq_update(s); + break; + case REG_ISCR: + s->sr &= ~(((uint32_t)val & 0x0f) << 2); + ftnandc021_irq_update(s); + break; + case REG_BCR: + s->bcr = (uint32_t)val; + break; + default: + break; + } +} + +static const MemoryRegionOps bus_ops = { + .read = ftnandc021_mem_read, + .write = ftnandc021_mem_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4 + } +}; + +static void ftnandc021_reset(DeviceState *d) +{ + ftnandc021_state *s = DO_UPCAST(ftnandc021_state, busdev.qdev, d); + + s->sr = 0; + s->fcr = 0; + s->mcr = 0; + s->ier = 0; + s->bcr = 0; + s->id[0]= 0; + s->id[1]= 0; + + /* We can assume our GPIO outputs have been wired up now */ + qemu_set_irq(s->req, 0); +} + +static int ftnandc021_init(SysBusDevice *dev) +{ + ftnandc021_state *s = FROM_SYSBUS(typeof(*s), dev); + DriveInfo *nand; + + memory_region_init_io(&s->mmio, &bus_ops, s, "ftnandc021", 0x1000); + sysbus_init_mmio(dev, &s->mmio); + sysbus_init_irq(dev, &s->irq); + + qdev_init_gpio_in (&s->busdev.qdev, ftnandc021_handle_ack, 1); + qdev_init_gpio_out(&s->busdev.qdev, &s->req, 1); + + nand = drive_get_next(IF_MTD); + s->flash = nand_init(nand ? nand->bdrv : NULL, s->manf_id, s->chip_id); + + return 0; +} + +static const VMStateDescription vmstate_ftnandc021 = { + .name = "ftnandc021", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(sr, ftnandc021_state), + VMSTATE_UINT32(fcr, ftnandc021_state), + VMSTATE_UINT32(mcr, ftnandc021_state), + VMSTATE_UINT32(ier, ftnandc021_state), + VMSTATE_UINT32(bcr, ftnandc021_state), + VMSTATE_END_OF_LIST() + } +}; + +static Property ftnandc021_properties[] = { + /* Samsung 1 Gigabit (256MB) */ + DEFINE_PROP_UINT8("manufacturer_id", ftnandc021_state, manf_id, NAND_MFR_SAMSUNG), + DEFINE_PROP_UINT8("chip_id", ftnandc021_state, chip_id, 0xda), + DEFINE_PROP_END_OF_LIST(), +}; + +static void ftnandc021_class_init(ObjectClass *klass, void *data) +{ + SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); + DeviceClass *k = DEVICE_CLASS(klass); + + sdc->init = ftnandc021_init; + k->vmsd = &vmstate_ftnandc021; + k->props = ftnandc021_properties; + k->reset = ftnandc021_reset; + k->no_user = 1; +} + +static TypeInfo ftnandc021_info = { + .name = "ftnandc021", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(ftnandc021_state), + .class_init = ftnandc021_class_init, +}; + +static void ftnandc021_register_types(void) +{ + type_register_static(&ftnandc021_info); +} + +type_init(ftnandc021_register_types) diff --git a/hw/ftnandc021.h b/hw/ftnandc021.h new file mode 100644 index 0000000..bfe218a --- /dev/null +++ b/hw/ftnandc021.h @@ -0,0 +1,58 @@ +/* + * (C) Copyright 2010 + * Faraday Technology Inc. + * Dante Su + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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 of + * the License, or (at your option) any later version. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef FTNANDC021_H +#define FTNANDC021_H + +/* NANDC control registers */ +#define REG_SR 0x100 /* Status Register */ +#define REG_ACR 0x104 /* Access Control Register */ +#define REG_FCR 0x108 /* Flow Control Register */ +#define REG_PIR 0x10C /* Page Index Register */ +#define REG_MCR 0x110 /* Memory Configuration Register */ +#define REG_ATR1 0x114 /* AC Timing Register 1 */ +#define REG_ATR2 0x118 /* AC Timing Register 2 */ +#define REG_IDRL 0x120 /* ID Register LSB */ +#define REG_IDRH 0x124 /* ID Register MSB */ +#define REG_IER 0x128 /* Interrupt Enable Register */ +#define REG_ISCR 0x12C /* Interrupt Status Clear Register */ +#define REG_WRBR 0x140 /* Write Bad Block Register */ +#define REG_WRLSN 0x144 /* Write LSN Register */ +#define REG_WRCRC 0x148 /* Write LSN CRC Register */ +#define REG_RDBR 0x150 /* Read Bad Block Register */ +#define REG_RDLSN 0x154 /* Read LSN Register */ +#define REG_RDCRC 0x158 /* Read LSN CRC Register */ + +/* BMC control registers */ +#define REG_PRR 0x208 /* BMC PIO Ready Register */ +#define REG_BCR 0x20C /* BMC Burst Control Register */ + +/** MISC register **/ +#define REG_DR 0x300 /* Data Register */ +#define REG_PCR 0x308 /* Page Count Register */ +#define REG_RSTR 0x30C /* MLC Reset Register */ +#define REG_REVR 0x3F8 /* Revision Register */ +#define REG_CFGR 0x3FC /* Configuration Register */ + +#endif /* EOF */