diff --git a/hw/ftspi020.c b/hw/ftspi020.c
new file mode 100644
index 0000000..eb68bc6
--- /dev/null
+++ b/hw/ftspi020.c
@@ -0,0 +1,338 @@
+/*
+ * Faraday FTSPI020 Flash Controller
+ *
+ * Copyright (c) 2012 Faraday Technology
+ *
+ * Written by Dante Su <dantesu@faraday-tech.com>
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw.h"
+#include "sysemu/sysemu.h"
+#include "sysbus.h"
+#include "ssi.h"
+
+#include "ftspi020.h"
+
+#ifndef min
+#define min(a, b)                ((a) < (b) ? (a) : (b))
+#endif
+
+#ifndef max
+#define max(a, b)                ((a) > (b) ? (a) : (b))
+#endif
+
+typedef struct {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    qemu_irq irq;
+    
+    /* DMA hardware handshake */
+    qemu_irq req;
+    
+    SSIBus *spi;
+    uint8_t num_cs;
+    qemu_irq *cs_lines;
+
+    int wip;    /* SPI Flash Status: Write In Progress BIT shift */
+    uint32_t datacnt;
+    
+    /* HW register caches */
+    uint32_t cmd[4];
+    uint32_t ctrl;
+    uint32_t timing;
+    uint32_t icr;
+    uint32_t isr;
+    uint32_t rdsr;
+} ftspi020_state;
+
+static inline void ftspi020_update_irq(ftspi020_state *s)
+{
+    if (s->isr)
+        qemu_set_irq(s->irq, 1);
+    else
+        qemu_set_irq(s->irq, 0);
+}
+
+static void ftspi020_handle_ack(void *opaque, int line, int level)
+{
+    ftspi020_state *s = (ftspi020_state *)opaque;
+
+    if (!(s->icr & 0x01))
+        return;
+
+    if (level) {
+        qemu_set_irq(s->req, 0);
+    } else if (s->datacnt > 0) {
+        qemu_set_irq(s->req, 1);
+    }
+}
+
+static int
+ftspi020_do_command(ftspi020_state *s)
+{
+    int cs   = (s->cmd[3] >> 8)  & 0x03;
+    int cmd  = (s->cmd[3] >> 24) & 0xff;
+    int ilen = (s->cmd[1] >> 24) & 0x03;
+    int alen = (s->cmd[1] >> 0)  & 0x07;
+    int dcyc = (s->cmd[1] >> 16) & 0xff;
+    
+    /* make sure the spi flash is de-activated */
+    qemu_set_irq(s->cs_lines[cs], 1);
+    
+    /* activate the spi flash */
+    qemu_set_irq(s->cs_lines[cs], 0);
+
+    /* if it's a SPI flash READ_STATUS command */
+    if ((s->cmd[3] & 0x06) == 0x04) {
+        do {
+            ssi_transfer(s->spi, cmd);
+            s->rdsr = ssi_transfer(s->spi, 0x00);
+            if (s->cmd[3] & 0x08)
+                break;
+        } while(s->rdsr & (1 << s->wip));
+    } else {
+    /* otherwise */
+        int i;
+        
+        ilen = min(ilen, 2);
+        alen = min(alen, 4);
+        
+        /* command cycles */
+        for (i = 0; i < ilen; ++i)
+            ssi_transfer(s->spi, cmd);
+        /* address cycles */
+        for (i = alen - 1; i >= 0; --i)
+            ssi_transfer(s->spi, (s->cmd[0] >> (8 * i)) & 0xff);
+        /* dummy cycles */
+        for (i = 0; i < (dcyc >> 3); ++i)
+            ssi_transfer(s->spi, 0x00);
+    }
+
+    if (s->datacnt <= 0)
+        qemu_set_irq(s->cs_lines[cs], 1);
+    else if (s->icr & 0x01)
+        qemu_set_irq(s->req, 1);
+
+    if (s->cmd[3] & 0x01)
+        s->isr |= 0x01;
+    ftspi020_update_irq(s);
+
+    return 0;
+}
+
+static void 
+ftspi020_chip_reset(ftspi020_state *s)
+{
+    int i;
+    
+    s->datacnt = 0;
+    for (i = 0; i < 4; ++i)
+        s->cmd[i] = 0;    
+    s->wip = 0;
+    s->ctrl = 0;
+    s->timing = 0;
+    s->icr = 0;
+    s->isr = 0;
+    s->rdsr = 0;
+
+    qemu_set_irq(s->irq, 0);
+}
+
+static uint64_t 
+ftspi020_mem_read(void *opaque, hwaddr addr, unsigned size)
+{
+    ftspi020_state *s = (ftspi020_state *) opaque;
+    uint64_t ret = 0;
+
+    switch (addr) {
+    case REG_DATA:
+        if (!(s->cmd[3] & 0x02)) {
+            if (s->datacnt > 0) {
+                ret |= (uint32_t)(ssi_transfer(s->spi, 0x00) & 0xff) << 0;
+                ret |= (uint32_t)(ssi_transfer(s->spi, 0x00) & 0xff) << 8;
+                ret |= (uint32_t)(ssi_transfer(s->spi, 0x00) & 0xff) << 16;
+                ret |= (uint32_t)(ssi_transfer(s->spi, 0x00) & 0xff) << 24;
+                if (s->datacnt < 4)
+                    s->datacnt = 0;
+                else
+                    s->datacnt -= 4;
+            }
+            if (s->datacnt == 0) {
+                uint8_t cs = (s->cmd[3] >> 8)  & 0x03;
+                qemu_set_irq(s->cs_lines[cs], 1);
+                if (s->cmd[3] & 0x01)
+                    s->isr |= 0x01;
+                ftspi020_update_irq(s);
+            }
+        }    
+        break;
+    case REG_RDST:
+        return s->rdsr;
+    case REG_CMD0:
+        return s->cmd[0];
+    case REG_CMD1:
+        return s->cmd[1];
+    case REG_CMD2:
+        return s->cmd[2];
+    case REG_CMD3:
+        return s->cmd[3];
+    case REG_STR:
+        /* In QEMU, the data fifo is always ready for read/write */
+        return 0x00000003;
+    case REG_ISR:
+        return s->isr;
+    case REG_ICR:
+        return s->icr;
+    case REG_CTRL:
+        return s->ctrl;
+    case REG_ACT:
+        return s->timing;
+    case REG_REV:
+        return 0x00010001;
+    case REG_FEA:
+        return 0x02022020;
+    default:
+        break;
+    }
+
+    return ret;
+}
+
+static void 
+ftspi020_mem_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
+{
+    ftspi020_state *s = (ftspi020_state *) opaque;
+
+    switch (addr) {
+    case REG_DATA:
+        if (s->cmd[3] & 0x02) {
+            if (s->datacnt > 0) {
+                ssi_transfer(s->spi, (uint8_t)((val >> 0) & 0xff));
+                ssi_transfer(s->spi, (uint8_t)((val >> 8) & 0xff));
+                ssi_transfer(s->spi, (uint8_t)((val >> 16) & 0xff));
+                ssi_transfer(s->spi, (uint8_t)((val >> 24) & 0xff));
+                if (s->datacnt < 4)
+                    s->datacnt = 0;
+                else
+                    s->datacnt -= 4;
+            }
+            if (s->datacnt == 0) {
+                uint8_t cs = (s->cmd[3] >> 8)  & 0x03;
+                qemu_set_irq(s->cs_lines[cs], 1);
+                if (s->cmd[3] & 0x01)
+                    s->isr |= 0x01;
+                ftspi020_update_irq(s);
+            }
+        }
+        break;
+    case REG_CMD0:
+        s->cmd[0] = (uint32_t)val;
+        break;
+    case REG_CMD1:
+        s->cmd[1] = (uint32_t)val;
+        break;
+    case REG_CMD2:
+        s->datacnt = s->cmd[2] = (uint32_t)val;
+        break;
+    case REG_CMD3:
+        s->cmd[3] = (uint32_t)val;
+        ftspi020_do_command(s);
+        break;
+    case REG_ISR:
+        s->isr &= ~((uint32_t)val);
+        ftspi020_update_irq(s);
+        break;
+    case REG_ICR:
+        s->icr = (uint32_t)val;
+        break;
+    case REG_CTRL:
+        if (val & 0x100)
+            ftspi020_chip_reset(s);
+        s->ctrl = (uint32_t)val & 0x70013;
+        s->wip = ((uint32_t)val >> 16) & 0x07;
+        break;
+    case REG_ACT:
+        s->timing = (uint32_t)val;
+        break;
+    default:
+        break;
+    }
+}
+
+static const MemoryRegionOps ftspi020_mem_ops = {
+    .read  = ftspi020_mem_read,
+    .write = ftspi020_mem_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void ftspi020_reset(DeviceState *d)
+{
+    ftspi020_state *s = DO_UPCAST(ftspi020_state, busdev.qdev, d);
+    ftspi020_chip_reset(s);
+}
+
+static int ftspi020_init(SysBusDevice *dev)
+{
+    ftspi020_state *s = FROM_SYSBUS(ftspi020_state, dev);
+    int i;
+
+    memory_region_init_io(&s->iomem, &ftspi020_mem_ops, s, "ftspi020", 0x1000);
+    sysbus_init_mmio(dev, &s->iomem);
+    sysbus_init_irq(dev, &s->irq);
+    
+    s->spi = ssi_create_bus(&dev->qdev, "spi");
+    s->num_cs = 4;
+    s->cs_lines = g_new(qemu_irq, s->num_cs);
+    ssi_auto_connect_slaves(DEVICE(s), s->cs_lines, s->spi);
+    for (i = 0; i < s->num_cs; ++i) {
+        sysbus_init_irq(dev, &s->cs_lines[i]);
+    }
+    
+    qdev_init_gpio_in (&s->busdev.qdev, ftspi020_handle_ack, 1);
+    qdev_init_gpio_out(&s->busdev.qdev, &s->req, 1);
+
+    return 0;
+}
+
+static const VMStateDescription vmstate_ftspi020_regs = {
+    .name = "ftspi020",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(cmd, ftspi020_state, 4),
+        VMSTATE_UINT32(ctrl, ftspi020_state),
+        VMSTATE_UINT32(timing, ftspi020_state),
+        VMSTATE_UINT32(icr, ftspi020_state),
+        VMSTATE_UINT32(isr, ftspi020_state),
+        VMSTATE_UINT32(rdsr, ftspi020_state),
+        VMSTATE_END_OF_LIST(),
+    }
+};
+
+static void ftspi020_dev_class_init(ObjectClass *klass, void *data)
+{
+    SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+    DeviceClass *k = DEVICE_CLASS(klass);
+
+    sdc->init  = ftspi020_init;
+    k->vmsd    = &vmstate_ftspi020_regs;
+    k->reset   = ftspi020_reset;
+    k->no_user = 1;
+}
+
+static TypeInfo ftspi020_dev_info = {
+    .name          = "ftspi020",
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(ftspi020_state),
+    .class_init    = ftspi020_dev_class_init,
+};
+
+static void ftspi020_register_types(void)
+{
+    type_register_static(&ftspi020_dev_info);
+}
+
+type_init(ftspi020_register_types)
diff --git a/hw/ftspi020.h b/hw/ftspi020.h
new file mode 100644
index 0000000..2c737cc
--- /dev/null
+++ b/hw/ftspi020.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2011, Dante Su (dantesu@faraday-tech.com)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *    This product includes software developed by the <organization>.
+ * 4. Neither the name of the <organization> nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _FTSPI020_H
+#define _FTSPI020_H
+
+/******************************************************************************
+ * FTSPI020 registers
+ *****************************************************************************/
+#define    REG_CMD0            0x00    /* Flash address */
+#define    REG_CMD1            0x04
+#define    REG_CMD2            0x08
+#define    REG_CMD3            0x0c
+#define    REG_CTRL            0x10    /* Control Register */
+#define    REG_ACT                0x14    /* AC Timing Register */
+#define    REG_STR                0x18    /* Status Register */
+#define    REG_ICR                0x20    /* Interrupt Control Register */
+#define    REG_ISR                0x24    /* Interrupt Status Register */
+#define    REG_RDST            0x28    /* Read Status Register */
+#define REG_REV                0x50    /* Revision Register */
+#define REG_FEA                0x54    /* Feature Register */
+#define REG_DATA            0x100    /* Data Register */
+
+/******************************************************************************
+ * Common SPI flash opcodes (Not FTSPI020 specific)
+ *****************************************************************************/
+#define    OPCODE_WREN            0x06    /* Write enable */
+#define    OPCODE_WRSR            0x01    /* Write status register 1 byte */
+#define    OPCODE_RDSR            0x05    /* Read status register */
+#define    OPCODE_NORM_READ    0x03    /* Read data bytes (low frequency) */
+#define    OPCODE_NORM_READ4    0x13    /* Read data bytes (low frequency, 4 bytes address) */
+#define    OPCODE_FAST_READ    0x0b    /* Read data bytes (high frequency) */
+#define    OPCODE_FAST_READ4    0x0c    /* Read data bytes (high frequency, 4 bytes address) */
+#define    OPCODE_PP            0x02    /* Page program (up to 256 bytes) */
+#define    OPCODE_PP4            0x12    /* Page program (up to 256 bytes, 4 bytes address) */
+#define    OPCODE_SE            0xd8    /* Sector erase (usually 64KiB) */
+#define    OPCODE_SE4            0xdc    /* Sector erase (usually 64KiB, 4 bytes address) */
+#define    OPCODE_RDID            0x9f    /* Read JEDEC ID */
+
+/* Status Register bits. */
+#define    SR_WIP           1        /* Write in progress */
+#define    SR_WEL           2        /* Write enable latch */
+
+#endif
