new file mode 100644
@@ -0,0 +1,528 @@
+/*
+ * QEMU model of the FTNANDC021 NAND Flash Controller
+ *
+ * Copyright (C) 2012 Faraday Technology
+ * Copyright (C) 2012 Dante Su <dantesu@faraday-tech.com>
+ *
+ * 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)
new file mode 100644
@@ -0,0 +1,58 @@
+/*
+ * (C) Copyright 2010
+ * Faraday Technology Inc. <www.faraday-tech.com>
+ * Dante Su <dantesu@gmail.com>
+ *
+ * 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 */
Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com> --- 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