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 fb6bd55..22b36a8 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..5447639
--- /dev/null
+++ b/hw/arm/ftnandc021.c
@@ -0,0 +1,509 @@
+/*
+ * 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:
+        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:
+        break;
+    }
+}
+
+static const MemoryRegionOps ftnandc021_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,
+                          &ftnandc021_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
