diff --git a/hw/ftintc020.c b/hw/ftintc020.c
new file mode 100644
index 0000000..2f78390
--- /dev/null
+++ b/hw/ftintc020.c
@@ -0,0 +1,342 @@
+/*
+ * Faraday FTINTC020 Programmable Interrupt 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 "sysbus.h"
+
+typedef struct {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    ARMCPU *cpu;
+    qemu_irq irqs[64];
+    
+    uint32_t irq_pin[2];    /* IRQ pin state */
+    uint32_t fiq_pin[2];    /* IRQ pin state */
+
+    /* HW register caches */
+    uint32_t irq_src[2];    /* IRQ source register */
+    uint32_t irq_ena[2];    /* IRQ enable register */
+    uint32_t irq_mod[2];    /* IRQ mode register */
+    uint32_t irq_lvl[2];    /* IRQ level register */
+    uint32_t fiq_src[2];    /* FIQ source register */
+    uint32_t fiq_ena[2];    /* FIQ enable register */
+    uint32_t fiq_mod[2];    /* FIQ mode register */
+    uint32_t fiq_lvl[2];    /* FIQ level register */
+} ftintc020_state;
+
+qemu_irq *ftintc020_init(hwaddr base, ARMCPU *cpu);
+
+static void ftintc020_update(void *opaque)
+{
+    uint32_t mask[2];
+    ftintc020_state *s = (ftintc020_state *) opaque;
+
+    /* FIQ */
+    mask[0] = s->fiq_src[0] & s->fiq_ena[0];
+    mask[1] = s->fiq_src[1] & s->fiq_ena[1];
+
+    if (mask[0] || mask[1]) {
+        cpu_interrupt(&s->cpu->env, CPU_INTERRUPT_FIQ);
+    } else {
+        cpu_reset_interrupt(&s->cpu->env, CPU_INTERRUPT_FIQ);
+    }
+
+    /* IRQ */
+    mask[0] = s->irq_src[0] & s->irq_ena[0];
+    mask[1] = s->irq_src[1] & s->irq_ena[1];
+
+    if (mask[0] || mask[1]) {
+        cpu_interrupt(&s->cpu->env, CPU_INTERRUPT_HARD);
+    } else {
+        cpu_reset_interrupt(&s->cpu->env, CPU_INTERRUPT_HARD);
+    }
+}
+
+/* Note: Here level means state of the signal on a pin */
+static void ftintc020_set_irq(void *opaque, int irq, int level)
+{
+    ftintc020_state *s = (ftintc020_state *) opaque;
+    uint32_t i = irq / 32;
+    uint32_t mask = 1 << (irq % 32);
+
+    if (s->irq_mod[i] & mask) {
+        /* Edge Triggered */
+        if (s->irq_lvl[i] & mask) {
+            /* Falling Active */
+            if ((s->irq_pin[i] & mask) && !level) {
+                s->irq_src[i] |=  mask;
+                s->fiq_src[i] |=  mask;
+            }
+        } else {
+            /* Rising Active */
+            if (!(s->irq_pin[i] & mask) && level) {
+                s->irq_src[i] |=  mask;
+                s->fiq_src[i] |=  mask;
+            }
+        }
+    } else {
+        /* Level Triggered */
+        if (s->irq_lvl[i] & mask) {
+            /* Low Active */
+            if (level) {
+                s->irq_src[i] &= ~mask;
+                s->fiq_src[i] &= ~mask;
+            } else {
+                s->irq_src[i] |=  mask;
+                s->fiq_src[i] |=  mask;
+            }
+        } else {
+            /* High Active */
+            if (!level) {
+                s->irq_src[i] &= ~mask;
+                s->fiq_src[i] &= ~mask;
+            } else {
+                s->irq_src[i] |=  mask;
+                s->fiq_src[i] |=  mask;
+            }
+        }
+    }
+    
+    /* update IRQ/FIQ pin states */
+    if (level) {
+        s->irq_pin[i] |= mask;
+        s->fiq_pin[i] |= mask;
+    } else {
+        s->irq_pin[i] &= ~mask;
+        s->fiq_pin[i] &= ~mask;
+    }
+
+    ftintc020_update(s);
+}
+
+static uint64_t ftintc020_mem_read(void *opaque, hwaddr offset, unsigned size)
+{
+    ftintc020_state *s = (ftintc020_state *) opaque;
+
+    switch (offset) {
+    /*
+     * IRQ
+     */
+    case 0x0000:    /* IRQ source register */
+        return s->irq_src[0];
+    case 0x0004:    /* IRQ enable register */
+        return s->irq_ena[0];
+    case 0x000C:    /* IRQ trigger-mode register */
+        return s->irq_mod[0];
+    case 0x0010:    /* IRQ trigger-level register */
+        return s->irq_lvl[0];
+    case 0x0014:    /* IRQ status register */
+        return s->irq_src[0] & s->irq_ena[0];
+    case 0x0060:    /* IRQ source register */
+        return s->irq_src[1];
+    case 0x0064:    /* IRQ enable register */
+        return s->irq_ena[1];
+    case 0x006C:    /* IRQ trigger-mode register */
+        return s->irq_mod[1];
+    case 0x0070:    /* IRQ trigger-level register */
+        return s->irq_lvl[1];
+    case 0x0074:    /* IRQ status register */
+        return s->irq_src[1] & s->irq_ena[1];
+
+    /*
+     * FIQ
+     */
+    case 0x0020:    /* FIQ source register */
+        return s->fiq_src[0];
+    case 0x0024:    /* FIQ enable register */
+        return s->fiq_ena[0];
+    case 0x002C:    /* FIQ trigger-mode register */
+        return s->fiq_mod[0];
+    case 0x0030:    /* FIQ trigger-level register */
+        return s->fiq_lvl[0];
+    case 0x0034:    /* FIQ status register */
+        return s->fiq_src[0] & s->fiq_ena[0];
+    case 0x0080:    /* FIQ source register */
+        return s->fiq_src[1];
+    case 0x0084:    /* FIQ enable register */
+        return s->fiq_ena[1];
+    case 0x008C:    /* FIQ trigger-mode register */
+        return s->fiq_mod[1];
+    case 0x0090:    /* FIQ trigger-level register */
+        return s->fiq_lvl[1];
+    case 0x0094:    /* FIQ status register */
+        return s->fiq_src[1] & s->fiq_ena[1];
+
+    default:
+        return 0;
+    }
+}
+
+static void ftintc020_mem_write(void *opaque, hwaddr offset, uint64_t value, unsigned size)
+{
+    ftintc020_state *s = (ftintc020_state *) opaque;
+    switch (offset) {
+    /*
+     * IRQ
+     */
+    case 0x0004:    /* IRQ enable register */
+        s->irq_ena[0] = (uint32_t)value;
+        break;
+    case 0x0008:    /* IRQ interrupt clear register */
+        value = ~(value & s->irq_mod[0]);
+        s->irq_src[0] &= (uint32_t)value;
+        break;
+    case 0x000C:
+        s->irq_mod[0] = (uint32_t)value;
+        break;
+    case 0x0010:
+        s->irq_lvl[0] = (uint32_t)value;
+        break;
+    case 0x0064:    /* IRQ enable register */
+        s->irq_ena[1] = (uint32_t)value;
+        break;
+    case 0x0068:    /* IRQ interrupt clear register */
+        value = ~(value & s->irq_mod[1]);
+        s->irq_src[1] &= (uint32_t)value;
+        break;
+    case 0x006C:
+        s->irq_mod[1] = (uint32_t)value;
+        break;
+    case 0x0070:
+        s->irq_lvl[1] = (uint32_t)value;
+        break;
+
+    /*
+     * FIQ
+     */
+    case 0x0024:    /* FIQ enable register */
+        s->fiq_ena[0] = (uint32_t)value;
+        break;
+    case 0x0028:    /* FIQ interrupt clear register */
+        value = ~(value & s->fiq_mod[0]);
+        s->fiq_src[0] &= (uint32_t)value;
+        break;
+    case 0x002C:
+        s->fiq_mod[0] = (uint32_t)value;
+        break;
+    case 0x0030:
+        s->fiq_lvl[0] = (uint32_t)value;
+        break;
+    case 0x0084:    /* FIQ enable register */
+        s->fiq_ena[1] = (uint32_t)value;
+        break;
+    case 0x0088:    /* FIQ interrupt clear register */
+        value = ~(value & s->fiq_mod[1]);
+        s->fiq_src[1] &= (uint32_t)value;
+        break;
+    case 0x008C:
+        s->fiq_mod[1] = (uint32_t)value;
+        break;
+    case 0x0090:
+        s->fiq_lvl[1] = (uint32_t)value;
+        break;
+
+    default:
+        return;
+    }
+
+    ftintc020_update(s);
+}
+
+static const MemoryRegionOps ftintc020_mem_ops = {
+    .read = ftintc020_mem_read,
+    .write = ftintc020_mem_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void ftintc020_reset(DeviceState *d)
+{
+    ftintc020_state *s = DO_UPCAST(ftintc020_state, busdev.qdev, d);
+    
+    s->irq_pin[0] = 0;
+    s->irq_pin[1] = 0;
+    s->fiq_pin[0] = 0;
+    s->fiq_pin[1] = 0;
+    
+    s->irq_src[0] = 0;
+    s->irq_src[1] = 0;
+    s->irq_ena[0] = 0;
+    s->irq_ena[1] = 0;
+    s->fiq_src[0] = 0;
+    s->fiq_src[1] = 0;
+    s->fiq_ena[0] = 0;
+    s->fiq_ena[1] = 0;
+
+    ftintc020_update(s);
+}
+
+qemu_irq *ftintc020_init(hwaddr base, ARMCPU *cpu)
+{
+    int i;
+    DeviceState *ds = qdev_create(NULL, "ftintc020");
+    ftintc020_state *s = FROM_SYSBUS(ftintc020_state, sysbus_from_qdev(ds));
+
+    s->cpu = cpu;
+    ftintc020_reset(ds);
+    qdev_init_nofail(ds);
+    qdev_init_gpio_in(ds, ftintc020_set_irq, 64);
+    for (i = 0; i < 64; ++i)
+        s->irqs[i] = qdev_get_gpio_in(ds, i);
+
+    /* Enable IC memory-mapped registers access.  */
+    memory_region_init_io(&s->iomem, &ftintc020_mem_ops, s, "ftintc020", 0x1000);
+    sysbus_init_mmio(sysbus_from_qdev(ds), &s->iomem);
+    sysbus_mmio_map(sysbus_from_qdev(ds), 0, base);
+
+    return s->irqs;
+}
+
+static VMStateDescription vmstate_ftintc020_regs = {
+    .name = "ftintc020",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(irq_src, ftintc020_state, 2),
+        VMSTATE_UINT32_ARRAY(irq_ena, ftintc020_state, 2),
+        VMSTATE_UINT32_ARRAY(irq_mod, ftintc020_state, 2),
+        VMSTATE_UINT32_ARRAY(irq_lvl, ftintc020_state, 2),
+        VMSTATE_UINT32_ARRAY(fiq_src, ftintc020_state, 2),
+        VMSTATE_UINT32_ARRAY(fiq_ena, ftintc020_state, 2),
+        VMSTATE_UINT32_ARRAY(fiq_mod, ftintc020_state, 2),
+        VMSTATE_UINT32_ARRAY(fiq_lvl, ftintc020_state, 2),
+        VMSTATE_END_OF_LIST(),
+    },
+};
+
+static int ftintc020_initfn(SysBusDevice *dev)
+{
+    return 0;
+}
+
+static void ftintc020_class_init(ObjectClass *klass, void *data)
+{
+    SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+    DeviceClass *k = DEVICE_CLASS(klass);
+
+    sdc->init  = ftintc020_initfn;
+    k->vmsd    = &vmstate_ftintc020_regs;
+    k->reset   = ftintc020_reset;
+    k->no_user = 1;
+}
+
+static TypeInfo ftintc020_info = {
+    .name          = "ftintc020",
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(ftintc020_state),
+    .class_init    = ftintc020_class_init,
+};
+
+static void ftintc020_register_types(void)
+{
+    type_register_static(&ftintc020_info);
+}
+
+type_init(ftintc020_register_types)
