Patchwork [v5,13/24] hw/arm: add Faraday FTNANDC021 nand flash controller support

login
register
mail settings
Submitter Kuo-Jung Su
Date Feb. 27, 2013, 7:15 a.m.
Message ID <1361949350-22241-14-git-send-email-dantesu@gmail.com>
Download mbox | patch
Permalink /patch/223527/
State New
Headers show

Comments

Kuo-Jung Su - Feb. 27, 2013, 7:15 a.m.
From: Kuo-Jung Su <dantesu@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 yet
implemented in this patch.

Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
---
 hw/arm/Makefile.objs      |    1 +
 hw/arm/faraday_a369_soc.c |    8 +
 hw/arm/ftnandc021.c       |  513 +++++++++++++++++++++++++++++++++++++++++++++
 hw/arm/ftnandc021.h       |   82 ++++++++
 4 files changed, 604 insertions(+)
 create mode 100644 hw/arm/ftnandc021.c
 create mode 100644 hw/arm/ftnandc021.h

Patch

diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
index 2637806..3f1369a 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_soc.c b/hw/arm/faraday_a369_soc.c
index ada8195..6764e1c 100644
--- a/hw/arm/faraday_a369_soc.c
+++ b/hw/arm/faraday_a369_soc.c
@@ -93,6 +93,7 @@  a369soc_device_init(FaradaySoCState *s)
     DriveInfo *dinfo;
     DeviceState *ds;
     qemu_irq *pic;
+    qemu_irq ack, req;
 
     s->as = get_system_memory();
     s->ram = g_new(MemoryRegion, 1);
@@ -211,6 +212,13 @@  a369soc_device_init(FaradaySoCState *s)
 
     /* ftapbbrg020 */
     s->pdma[0] = sysbus_create_simple("ftapbbrg020", 0x90f00000, pic[14]);
+
+    /* ftnandc021 */
+    ds = sysbus_create_simple("ftnandc021", 0x90200000, pic[30]);
+    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 int a369soc_init(SysBusDevice *busdev)
diff --git a/hw/arm/ftnandc021.c b/hw/arm/ftnandc021.c
new file mode 100644
index 0000000..a5b7b5b
--- /dev/null
+++ b/hw/arm/ftnandc021.c
@@ -0,0 +1,513 @@ 
+/*
+ * QEMU model of the FTNANDC021 NAND Flash Controller
+ *
+ * Copyright (C) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@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 "faraday.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:
+        DPRINTF("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@0x%llx\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@0x%llx\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)
+{
+    SysBusDevice *busdev = SYS_BUS_DEVICE(ds);
+    Ftnandc021State *s = FTNANDC021(FROM_SYSBUS(Ftnandc021State, busdev));
+
+    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)
+{
+    Ftnandc021State *s = FTNANDC021(FROM_SYSBUS(Ftnandc021State, dev));
+    DriveInfo *nand;
+
+    memory_region_init_io(&s->mmio,
+                          &mmio_ops,
+                          s,
+                          TYPE_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);
+
+    /* TODO: create a child bus for these */
+    s->manf_id = NAND_MFR_SAMSUNG;
+    s->chip_id = 0xda;
+
+    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 = 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)
+{
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    k->init     = ftnandc021_init;
+    dc->vmsd    = &vmstate_ftnandc021;
+    dc->reset   = ftnandc021_reset;
+    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..189ddab
--- /dev/null
+++ b/hw/arm/ftnandc021.h
@@ -0,0 +1,82 @@ 
+/*
+ * QEMU model of the FTNANDC021 NAND Flash Controller
+ *
+ * Copyright (C) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@faraday-tech.com>
+ *
+ * This file is licensed under GNU GPL v2+.
+ */
+
+#ifndef HW_ARM_FTNANDC021_H
+#define HW_ARM_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 */
+
+
+/*
+ * 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