From: Kuo-Jung Su <dant...@faraday-tech.com> The FTNANDC021 is an integrated NAND flash controller which re-pack the NAND flash command set with a shorter built-in opcode. It also provides a register base interface for user to easily access the underlying NAND flash chips, and also supports HW ECC.
However the optional hardware ECC function is not implemented. Signed-off-by: Kuo-Jung Su <dant...@faraday-tech.com> --- hw/arm/Makefile.objs | 1 + hw/arm/faraday_a369.c | 14 ++ hw/arm/faraday_a369_soc.c | 9 + hw/arm/ftnandc021.c | 516 +++++++++++++++++++++++++++++++++++++++++++++ hw/arm/ftnandc021.h | 84 ++++++++ 5 files changed, 624 insertions(+) create mode 100644 hw/arm/ftnandc021.c create mode 100644 hw/arm/ftnandc021.h diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs index cc5e343..2622f3f 100644 --- a/hw/arm/Makefile.objs +++ b/hw/arm/Makefile.objs @@ -45,3 +45,4 @@ obj-y += ftwdt010.o obj-y += ftrtc011.o obj-y += ftdmac020.o obj-y += ftapbbrg020.o +obj-y += ftnandc021.o diff --git a/hw/arm/faraday_a369.c b/hw/arm/faraday_a369.c index 3a89a04..c85de25 100644 --- a/hw/arm/faraday_a369.c +++ b/hw/arm/faraday_a369.c @@ -25,6 +25,8 @@ a369_board_init(QEMUMachineInitArgs *args) { DeviceState *ds; FaradaySoCState *s; + DriveInfo *dinfo; + Error *local_errp = NULL; if (!args->cpu_model) { args->cpu_model = "fa626te"; @@ -46,6 +48,18 @@ a369_board_init(QEMUMachineInitArgs *args) s = FARADAY_SOC(ds); + /* Attach the nand flash to ftnandc021 */ + dinfo = drive_get_next(IF_MTD); + ds = nand_init(dinfo ? dinfo->bdrv : NULL, NAND_MFR_SAMSUNG, 0xda); + object_property_set_link(OBJECT(s->nandc[0]), + OBJECT(ds), + "flash", + &local_errp); + if (local_errp) { + fprintf(stderr, "a369: Unable to set flash link for FTNANDC021\n"); + abort(); + } + /* System start-up */ if (args->kernel_filename) { diff --git a/hw/arm/faraday_a369_soc.c b/hw/arm/faraday_a369_soc.c index bb80537..0088915 100644 --- a/hw/arm/faraday_a369_soc.c +++ b/hw/arm/faraday_a369_soc.c @@ -84,6 +84,7 @@ a369soc_device_init(FaradaySoCState *s) DriveInfo *dinfo; DeviceState *ds; qemu_irq *cpu_pic; + qemu_irq ack, req; int i; s->as = get_system_memory(); @@ -232,6 +233,14 @@ a369soc_device_init(FaradaySoCState *s) fprintf(stderr, "a369soc: Unable to set soc link for FTAPBBRG020\n"); abort(); } + + /* ftnandc021 */ + ds = sysbus_create_simple("ftnandc021", 0x90200000, s->pic[30]); + s->nandc[0] = ds; + ack = qdev_get_gpio_in(ds, 0); + req = qdev_get_gpio_in(s->hdma[0], 15); + qdev_connect_gpio_out(s->hdma[0], 15, ack); + qdev_connect_gpio_out(ds, 0, req); } static void a369soc_realize(DeviceState *dev, Error **errp) diff --git a/hw/arm/ftnandc021.c b/hw/arm/ftnandc021.c new file mode 100644 index 0000000..8b4c84c --- /dev/null +++ b/hw/arm/ftnandc021.c @@ -0,0 +1,516 @@ +/* + * QEMU model of the FTNANDC021 NAND Flash Controller + * + * Copyright (C) 2012 Faraday Technology + * Written by Dante Su <dant...@faraday-tech.com> + * + * This file is licensed under GNU GPL v2+. + */ + +#include "hw/sysbus.h" +#include "hw/devices.h" +#include "hw/flash.h" +#include "sysemu/blockdev.h" + +#include "ftnandc021.h" + +#define TYPE_FTNANDC021 "ftnandc021" + +typedef struct Ftnandc021State { + 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; +} Ftnandc021State; + +#define FTNANDC021(obj) \ + OBJECT_CHECK(Ftnandc021State, obj, TYPE_FTNANDC021) + +static void ftnandc021_update_irq(Ftnandc021State *s) +{ + if (s->ier & IER_ENA) { + if ((s->ier & 0x0f) & (s->sr >> 2)) { + qemu_set_irq(s->irq, 1); + } else { + qemu_set_irq(s->irq, 0); + } + } +} + +static void ftnandc021_set_idle(Ftnandc021State *s) +{ + /* CLE=0, ALE=0, CS=1 */ + nand_setpins(s->flash, 0, 0, 1, 1, 0); + + /* Set command compelete */ + s->sr |= SR_CMD; + + /* Update IRQ signal */ + ftnandc021_update_irq(s); +} + +static void ftnandc021_set_cmd(Ftnandc021State *s, uint8_t cmd) +{ + /* 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 void ftnandc021_set_addr(Ftnandc021State *s, int col, int row) +{ + /* 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, extract32(col, 0, 8)); + nand_setio(s->flash, extract32(col, 8, 8)); + } + /* row address */ + if (row >= 0) { + nand_setio(s->flash, extract32(row, 0, 8)); + if (s->alen >= 4) { + nand_setio(s->flash, extract32(row, 8, 8)); + } + if (s->alen >= 5) { + nand_setio(s->flash, extract32(row, 16, 8)); + } + } + } +} + +static void ftnandc021_handle_ack(void *opaque, int line, int level) +{ + Ftnandc021State *s = FTNANDC021(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(Ftnandc021State *s, uint32_t cmd) +{ + int i; + + s->sr &= ~SR_CMD; + s->cmd = cmd; + + switch (cmd) { + case FTNANDC021_CMD_RDID: /* 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 FTNANDC021_CMD_RESET: /* reset */ + ftnandc021_set_cmd(s, 0xff); + break; + case FTNANDC021_CMD_RDST: /* 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 FTNANDC021_CMD_RDPG: /* 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); + s->len = s->pgsz; + break; + case FTNANDC021_CMD_RDOOB: /* read oob */ + ftnandc021_set_cmd(s, 0x00); + ftnandc021_set_addr(s, s->pgsz, s->pi); + ftnandc021_set_cmd(s, 0x30); + 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 FTNANDC021_CMD_WRPG: /* 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); + s->len = s->pgsz; + break; + case FTNANDC021_CMD_ERBLK: /* 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 FTNANDC021_CMD_WROOB: /* 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: + qemu_log_mask(LOG_GUEST_ERROR, + "ftnandc021: unknow command=0x%02x\n", cmd); + break; + } + + /* if cmd is not page read/write, then return to idle mode */ + switch (s->cmd) { + case FTNANDC021_CMD_RDPG: + case FTNANDC021_CMD_WRPG: + if (s->bcr && (s->len > 0)) { + qemu_set_irq(s->req, 1); + } + break; + default: + ftnandc021_set_idle(s); + break; + } +} + +static uint64_t +ftnandc021_mem_read(void *opaque, hwaddr addr, unsigned size) +{ + uint32_t i, ret = 0; + Ftnandc021State *s = FTNANDC021(opaque); + + switch (addr) { + case REG_DR: + if ((s->cmd == FTNANDC021_CMD_RDPG) && (s->len > 0)) { + if (s->bw == 8) { + for (i = 0; i < 4 && s->len > 0; i++, s->len--) { + ret = deposit32(ret, i * 8, 8, nand_getio(s->flash)); + } + } else { + for (i = 0; i < 2 && s->len > 1; i++, s->len -= 2) { + ret = deposit32(ret, i * 16, 16, nand_getio(s->flash)); + } + } + if (s->len <= 0) { + ftnandc021_set_idle(s); + } + } + 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; /* page count = 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; /* AC Timing */ + case REG_ATR2: + return 0x42054209; /* AC Timing */ + case REG_PRR: + return 0x00000001; /* Always ready for I/O */ + case REG_REVR: + return 0x00010100; /* Rev. 1.1.0 */ + case REG_CFGR: + return 0x00081601; /* 8-bit BCH, 16 bit flash, 1 chip */ + default: + qemu_log_mask(LOG_GUEST_ERROR, + "ftnandc021: undefined memory access@%#" HWADDR_PRIx "\n", addr); + break; + } + + return ret; +} + +static void +ftnandc021_mem_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) +{ + uint32_t i; + Ftnandc021State *s = FTNANDC021(opaque); + + switch (addr) { + case REG_DR: + if (s->cmd == FTNANDC021_CMD_WRPG && s->len > 0) { + if (s->bw == 8) { + for (i = 0; i < 4 && s->len > 0; i++, s->len--) { + nand_setio(s->flash, + extract32((uint32_t)val, i * 8, 8)); + } + } else { + for (i = 0; i < 2 && s->len > 1; i++, s->len -= 2) { + nand_setio(s->flash, + extract32((uint32_t)val, i * 16, 16)); + } + } + if (s->len <= 0) { + ftnandc021_set_cmd(s, 0x10); + /* read status */ + ftnandc021_command(s, 0x04); + } + } + break; + case REG_ACR: + if (!(val & ACR_START)) { + break; + } + ftnandc021_command(s, extract32((uint32_t)val, 8, 5)); + 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 & FCR_16BIT) { + 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 (extract32(s->mcr, 8, 2)) { + 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 + extract32(s->mcr, 16, 2))); + /* address length (cycle) */ + s->alen = 3 + extract32(s->mcr, 10, 2); + /* flash size */ + s->size = 1ULL << (24 + extract32(s->mcr, 4, 4)); + break; + case REG_IER: + s->ier = (uint32_t)val & 0x8f; + ftnandc021_update_irq(s); + break; + case REG_ISCR: + s->sr &= ~(((uint32_t)val & 0x0f) << 2); + ftnandc021_update_irq(s); + break; + case REG_BCR: + s->bcr = (uint32_t)val; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "ftnandc021: undefined memory access@%#" HWADDR_PRIx "\n", addr); + break; + } +} + +static const MemoryRegionOps mmio_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 *ds) +{ + Ftnandc021State *s = FTNANDC021(SYS_BUS_DEVICE(ds)); + Error *local_errp = NULL; + + s->flash = DEVICE(object_property_get_link(OBJECT(s), + "flash", + &local_errp)); + if (local_errp) { + fprintf(stderr, "ftnandc021: Unable to get flash link\n"); + abort(); + } + + 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 void ftnandc021_realize(DeviceState *dev, Error **errp) +{ + Ftnandc021State *s = FTNANDC021(dev); + + memory_region_init_io(&s->mmio, + &mmio_ops, + s, + TYPE_FTNANDC021, + 0x1000); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq); + + qdev_init_gpio_in(&s->busdev.qdev, ftnandc021_handle_ack, 1); + qdev_init_gpio_out(&s->busdev.qdev, &s->req, 1); + + object_property_add_link(OBJECT(dev), + "flash", + TYPE_DEVICE, + (Object **) &s->flash, + errp); +} + +static const VMStateDescription vmstate_ftnandc021 = { + .name = TYPE_FTNANDC021, + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(sr, Ftnandc021State), + VMSTATE_UINT32(fcr, Ftnandc021State), + VMSTATE_UINT32(mcr, Ftnandc021State), + VMSTATE_UINT32(ier, Ftnandc021State), + VMSTATE_UINT32(bcr, Ftnandc021State), + VMSTATE_END_OF_LIST() + } +}; + +static void ftnandc021_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->vmsd = &vmstate_ftnandc021; + dc->reset = ftnandc021_reset; + dc->realize = ftnandc021_realize; + dc->no_user = 1; +} + +static const TypeInfo ftnandc021_info = { + .name = TYPE_FTNANDC021, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Ftnandc021State), + .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/arm/ftnandc021.h b/hw/arm/ftnandc021.h new file mode 100644 index 0000000..199de95 --- /dev/null +++ b/hw/arm/ftnandc021.h @@ -0,0 +1,84 @@ +/* + * QEMU model of the FTNANDC021 NAND Flash Controller + * + * Copyright (C) 2012 Faraday Technology + * Written by Dante Su <dant...@faraday-tech.com> + * + * This file is licensed under GNU GPL v2+. + */ + +#ifndef HW_ARM_FTNANDC021_H +#define HW_ARM_FTNANDC021_H + +#include "qemu/bitops.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 */ + + +/* + * Register BITMASK + */ +#define SR_BLANK BIT(7) /* blanking check failed */ +#define SR_ECC BIT(6) /* ecc failed */ +#define SR_STS BIT(4) /* status error */ +#define SR_CRC BIT(3) /* crc error */ +#define SR_CMD BIT(2) /* command finished */ +#define SR_BUSY BIT(1) /* chip busy */ +#define SR_ENA BIT(0) /* chip enabled */ + +#define ACR_CMD(x) (((x) & 0x1f) << 8) /* command code */ +#define ACR_START BIT(7) /* command start */ + +#define FCR_16BIT BIT(4) /* 16 bit data bus */ +#define FCR_WPROT BIT(3) /* write protected */ +#define FCR_NOSC BIT(2) /* bypass status check error */ +#define FCR_MICRON BIT(1) /* Micron 2-plane command */ +#define FCR_NOBC BIT(0) /* skip blanking check error */ + +#define IER_ENA BIT(7) /* interrupt enabled */ +#define IER_ECC BIT(3) /* ecc error timeout */ +#define IER_STS BIT(2) /* status error */ +#define IER_CRC BIT(1) /* crc error */ +#define IER_CMD BIT(0) /* command finished */ + +/* + * FTNANDC021 integrated command set + */ +#define FTNANDC021_CMD_RDID 0x01 /* read id */ +#define FTNANDC021_CMD_RESET 0x02 +#define FTNANDC021_CMD_RDST 0x04 /* read status */ +#define FTNANDC021_CMD_RDPG 0x05 /* read page (data + oob) */ +#define FTNANDC021_CMD_RDOOB 0x06 /* read oob */ +#define FTNANDC021_CMD_WRPG 0x10 /* write page (data + oob) */ +#define FTNANDC021_CMD_ERBLK 0x11 /* erase block */ +#define FTNANDC021_CMD_WROOB 0x13 /* write oob */ + +#endif -- 1.7.9.5