diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
index 104e0e7..536d6cf 100644
--- a/hw/arm/Makefile.objs
+++ b/hw/arm/Makefile.objs
@@ -37,3 +37,4 @@ obj-y += omap1.o omap2.o
 
 obj-y += faraday_a369.o faraday_a369_soc.o faraday_a369_scu.o \
             faraday_a369_kpd.o
+obj-y += ftintc020.o
diff --git a/hw/arm/faraday_a369_soc.c b/hw/arm/faraday_a369_soc.c
index 4e19c7e..eab05dc 100644
--- a/hw/arm/faraday_a369_soc.c
+++ b/hw/arm/faraday_a369_soc.c
@@ -72,6 +72,8 @@ a369soc_device_init(FaradaySoCState *s)
 {
     DriveInfo *dinfo;
     DeviceState *ds;
+    qemu_irq *cpu_pic;
+    int i;
 
     s->as = get_system_memory();
     s->ram = g_new(MemoryRegion, 1);
@@ -111,6 +113,15 @@ a369soc_device_init(FaradaySoCState *s)
         abort();
     }
 
+    /* Interrupt Controller */
+    cpu_pic = arm_pic_init_cpu(s->cpu);
+    ds = sysbus_create_varargs("ftintc020", 0x90100000,
+                               cpu_pic[ARM_PIC_CPU_IRQ],
+                               cpu_pic[ARM_PIC_CPU_FIQ], NULL);
+    for (i = 0; i < ARRAY_SIZE(s->pic); ++i) {
+        s->pic[i] = qdev_get_gpio_in(ds, i);
+    }
+
     /* Serial (FTUART010 which is 16550A compatible) */
     if (serial_hds[0]) {
         serial_mm_init(s->as,
diff --git a/hw/arm/ftintc020.c b/hw/arm/ftintc020.c
new file mode 100644
index 0000000..1a476d3
--- /dev/null
+++ b/hw/arm/ftintc020.c
@@ -0,0 +1,302 @@
+/*
+ * Faraday FTINTC020 Programmable Interrupt Controller.
+ *
+ * Copyright (c) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@faraday-tech.com>
+ *
+ * This code is licensed under GNU GPL v2+.
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+
+#include "ftintc020.h"
+
+#define TYPE_FTINTC020  "ftintc020"
+
+#define CFG_REGSIZE     (0x100 / 4)
+
+typedef struct Ftintc020State {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+
+    qemu_irq irq;
+    qemu_irq fiq;
+
+    uint32_t irq_ps[2]; /* IRQ pin state */
+    uint32_t fiq_ps[2]; /* FIQ pin state */
+
+    /* HW register caches */
+    uint32_t regs[CFG_REGSIZE];
+} Ftintc020State;
+
+#define FTINTC020(obj) \
+    OBJECT_CHECK(Ftintc020State, obj, TYPE_FTINTC020)
+
+#define PIC_REG32(s, off) \
+    ((s)->regs[(off) / 4])
+
+#define IRQ_REG32(s, n, off) \
+    ((s)->regs[(REG_IRQ(n) + ((off) & REG_MASK)) / 4])
+
+#define FIQ_REG32(s, n, off) \
+    ((s)->regs[(REG_FIQ(n) + ((off) & REG_MASK)) / 4])
+
+static void
+ftintc020_update_irq(Ftintc020State *s)
+{
+    uint32_t mask[2];
+
+    /* FIQ */
+    mask[0] = PIC_REG32(s, REG_FIQ32SRC) & PIC_REG32(s, REG_FIQ32ENA);
+    mask[1] = PIC_REG32(s, REG_FIQ64SRC) & PIC_REG32(s, REG_FIQ64ENA);
+    qemu_set_irq(s->fiq, !!(mask[0] || mask[1]));
+
+    /* IRQ */
+    mask[0] = PIC_REG32(s, REG_IRQ32SRC) & PIC_REG32(s, REG_IRQ32ENA);
+    mask[1] = PIC_REG32(s, REG_IRQ64SRC) & PIC_REG32(s, REG_IRQ64ENA);
+    qemu_set_irq(s->irq, !!(mask[0] || mask[1]));
+}
+
+/* Note: Here level means state of the signal on a pin */
+static void
+ftintc020_set_irq(void *opaque, int irq, int level)
+{
+    Ftintc020State *s = FTINTC020(opaque);
+    uint32_t i = irq / 32;
+    uint32_t mask = BIT(irq & 0x1f);
+
+    switch (irq) {
+    case 0  ... 63:
+        /* IRQ */
+        if (IRQ_REG32(s, irq, REG_MDR) & mask) {
+            /* Edge Triggered */
+            if (IRQ_REG32(s, irq, REG_LVR) & mask) {
+                /* Falling Active */
+                if ((s->irq_ps[i] & mask) && !level) {
+                    IRQ_REG32(s, irq, REG_SRC) |= mask;
+                }
+            } else {
+                /* Rising Active */
+                if (!(s->irq_ps[i] & mask) && level) {
+                    IRQ_REG32(s, irq, REG_SRC) |= mask;
+                }
+            }
+        } else {
+            /* Level Triggered */
+            if (IRQ_REG32(s, irq, REG_LVR) & mask) {
+                /* Low Active */
+                if (level) {
+                    IRQ_REG32(s, irq, REG_SRC) &= ~mask;
+                } else {
+                    IRQ_REG32(s, irq, REG_SRC) |= mask;
+                }
+            } else {
+                /* High Active */
+                if (level) {
+                    IRQ_REG32(s, irq, REG_SRC) |= mask;
+                } else {
+                    IRQ_REG32(s, irq, REG_SRC) &= ~mask;
+                }
+            }
+        }
+
+        /* FIQ */
+        if (FIQ_REG32(s, irq, REG_MDR) & mask) {
+            /* Edge Triggered */
+            if (FIQ_REG32(s, irq, REG_LVR) & mask) {
+                /* Falling Active */
+                if ((s->fiq_ps[i] & mask) && !level) {
+                    FIQ_REG32(s, irq, REG_SRC) |= mask;
+                }
+            } else {
+                /* Rising Active */
+                if (!(s->fiq_ps[i] & mask) && level) {
+                    FIQ_REG32(s, irq, REG_SRC) |= mask;
+                }
+            }
+        } else {
+            /* Level Triggered */
+            if (FIQ_REG32(s, irq, REG_LVR) & mask) {
+                /* Low Active */
+                if (level) {
+                    FIQ_REG32(s, irq, REG_SRC) &= ~mask;
+                } else {
+                    FIQ_REG32(s, irq, REG_SRC) |= mask;
+                }
+            } else {
+                /* High Active */
+                if (level) {
+                    FIQ_REG32(s, irq, REG_SRC) |= mask;
+                } else {
+                    FIQ_REG32(s, irq, REG_SRC) &= ~mask;
+                }
+            }
+        }
+        /* update IRQ/FIQ pin states */
+        if (level) {
+            s->irq_ps[i] |= mask;
+            s->fiq_ps[i] |= mask;
+        } else {
+            s->irq_ps[i] &= ~mask;
+            s->fiq_ps[i] &= ~mask;
+        }
+        break;
+
+    default:
+        fprintf(stderr, "ftintc020: undefined irq=%d\n", irq);
+        abort();
+    }
+
+    ftintc020_update_irq(s);
+}
+
+static uint64_t
+ftintc020_mem_read(void *opaque, hwaddr addr, unsigned size)
+{
+    Ftintc020State *s = FTINTC020(opaque);
+    uint32_t ret = 0;
+
+    if (addr > REG_FIQ64SR) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+            "ftintc020: undefined memory access@%#" HWADDR_PRIx "\n", addr);
+        return ret;
+    }
+
+    switch (addr) {
+    case REG_IRQ32SR:
+        ret = PIC_REG32(s, REG_IRQ32SRC) & PIC_REG32(s, REG_IRQ32ENA);
+        break;
+    case REG_IRQ64SR:
+        ret = PIC_REG32(s, REG_IRQ64SRC) & PIC_REG32(s, REG_IRQ64ENA);
+        break;
+    case REG_FIQ32SR:
+        ret = PIC_REG32(s, REG_FIQ32SRC) & PIC_REG32(s, REG_FIQ32ENA);
+        break;
+    case REG_FIQ64SR:
+        ret = PIC_REG32(s, REG_FIQ64SRC) & PIC_REG32(s, REG_FIQ64ENA);
+        break;
+    default:
+        ret = s->regs[addr / 4];
+        break;
+    }
+
+    return ret;
+}
+
+static void
+ftintc020_mem_write(void *opaque, hwaddr addr, uint64_t value, unsigned size)
+{
+    Ftintc020State *s = FTINTC020(opaque);
+
+    if (addr > REG_FIQ64SR) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+            "ftintc020: undefined memory access@%#" HWADDR_PRIx "\n", addr);
+        return;
+    }
+
+    switch (addr) {
+    case REG_IRQ32SCR:
+        /* clear edge triggered interrupts only */
+        value = ~(value & PIC_REG32(s, REG_IRQ32MDR));
+        PIC_REG32(s, REG_IRQ32SRC) &= (uint32_t)value;
+        break;
+    case REG_IRQ64SCR:
+        /* clear edge triggered interrupts only */
+        value = ~(value & PIC_REG32(s, REG_IRQ64MDR));
+        PIC_REG32(s, REG_IRQ64SRC) &= (uint32_t)value;
+        break;
+    case REG_FIQ32SCR:
+        /* clear edge triggered interrupts only */
+        value = ~(value & PIC_REG32(s, REG_FIQ32MDR));
+        PIC_REG32(s, REG_FIQ32SRC) &= (uint32_t)value;
+        break;
+    case REG_FIQ64SCR:
+        /* clear edge triggered interrupts only */
+        value = ~(value & PIC_REG32(s, REG_FIQ64MDR));
+        PIC_REG32(s, REG_FIQ64SRC) &= (uint32_t)value;
+        break;
+    default:
+        s->regs[addr / 4] = (uint32_t)value;
+        break;
+    }
+
+    ftintc020_update_irq(s);
+}
+
+static const MemoryRegionOps mmio_ops = {
+    .read  = ftintc020_mem_read,
+    .write = ftintc020_mem_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    }
+};
+
+static void ftintc020_reset(DeviceState *ds)
+{
+    Ftintc020State *s = FTINTC020(SYS_BUS_DEVICE(ds));
+
+    s->irq_ps[0] = 0;
+    s->irq_ps[1] = 0;
+    s->fiq_ps[0] = 0;
+    s->fiq_ps[1] = 0;
+    memset(s->regs, 0, sizeof(s->regs));
+
+    ftintc020_update_irq(s);
+}
+
+static VMStateDescription vmstate_ftintc020 = {
+    .name = TYPE_FTINTC020,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(regs, Ftintc020State, CFG_REGSIZE),
+        VMSTATE_UINT32_ARRAY(irq_ps, Ftintc020State, 2),
+        VMSTATE_UINT32_ARRAY(fiq_ps, Ftintc020State, 2),
+        VMSTATE_END_OF_LIST(),
+    },
+};
+
+static void ftintc020_realize(DeviceState *dev, Error **errp)
+{
+    Ftintc020State *s = FTINTC020(dev);
+
+    /* Enable IC memory-mapped registers access.  */
+    memory_region_init_io(&s->iomem,
+                          &mmio_ops,
+                          s,
+                          TYPE_FTINTC020,
+                          0x1000);
+    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
+
+    qdev_init_gpio_in(&s->busdev.qdev, ftintc020_set_irq, 64);
+    sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
+    sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->fiq);
+}
+
+static void ftintc020_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->vmsd    = &vmstate_ftintc020;
+    dc->reset   = ftintc020_reset;
+    dc->realize = ftintc020_realize;
+    dc->no_user = 1;
+}
+
+static const TypeInfo ftintc020_info = {
+    .name          = TYPE_FTINTC020,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(Ftintc020State),
+    .class_init    = ftintc020_class_init,
+};
+
+static void ftintc020_register_types(void)
+{
+    type_register_static(&ftintc020_info);
+}
+
+type_init(ftintc020_register_types)
diff --git a/hw/arm/ftintc020.h b/hw/arm/ftintc020.h
new file mode 100644
index 0000000..20d300c
--- /dev/null
+++ b/hw/arm/ftintc020.h
@@ -0,0 +1,57 @@
+/*
+ * Faraday FTINTC020 Programmable Interrupt Controller.
+ *
+ * Copyright (c) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@faraday-tech.com>
+ *
+ * This code is licensed under GNU GPL v2+.
+ */
+
+#ifndef HW_ARM_FTINTC020_H
+#define HW_ARM_FTINTC020_H
+
+#include "qemu/bitops.h"
+
+/* IRQ/FIO: 0 ~ 31 */
+#define REG_IRQ32SRC    0x00    /* IRQ source register */
+#define REG_IRQ32ENA    0x04    /* IRQ enable register */
+#define REG_IRQ32SCR    0x08    /* IRQ status clear register */
+#define REG_IRQ32MDR    0x0C    /* IRQ trigger mode register */
+#define REG_IRQ32LVR    0x10    /* IRQ trigger level register */
+#define REG_IRQ32SR     0x14    /* IRQ status register */
+
+#define REG_FIQ32SRC    0x20    /* FIQ source register */
+#define REG_FIQ32ENA    0x24    /* FIQ enable register */
+#define REG_FIQ32SCR    0x28    /* FIQ status clear register */
+#define REG_FIQ32MDR    0x2C    /* FIQ trigger mode register */
+#define REG_FIQ32LVR    0x30    /* FIQ trigger level register */
+#define REG_FIQ32SR     0x34    /* FIQ status register */
+
+/* Extended IRQ/FIO: 32 ~ 63 */
+#define REG_IRQ64SRC    0x60    /* Extended IRQ source register */
+#define REG_IRQ64ENA    0x64    /* Extended IRQ enable register */
+#define REG_IRQ64SCR    0x68    /* Extended IRQ status clear register */
+#define REG_IRQ64MDR    0x6C    /* Extended IRQ trigger mode register */
+#define REG_IRQ64LVR    0x70    /* Extended IRQ trigger level register */
+#define REG_IRQ64SR     0x74    /* Extended IRQ status register */
+
+#define REG_FIQ64SRC    0x80    /* Extended FIQ source register */
+#define REG_FIQ64ENA    0x84    /* Extended FIQ enable register */
+#define REG_FIQ64SCR    0x88    /* Extended FIQ status clear register */
+#define REG_FIQ64MDR    0x8C    /* Extended FIQ trigger mode register */
+#define REG_FIQ64LVR    0x90    /* Extended FIQ trigger level register */
+#define REG_FIQ64SR     0x94    /* Extended FIQ status register */
+
+/* Common register offset to IRQ/FIQ */
+#define REG_SRC         0x00    /* Source register */
+#define REG_ENA         0x04    /* Enable register */
+#define REG_SCR         0x08    /* Status clear register */
+#define REG_MDR         0x0C    /* Trigger mode register */
+#define REG_LVR         0x10    /* Trigger level register */
+#define REG_SR          0x14    /* Status register */
+#define REG_MASK        0x1f
+
+#define REG_IRQ(n)      (((n) < 32) ? REG_IRQ32SRC : REG_IRQ64SRC)
+#define REG_FIQ(n)      (((n) < 32) ? REG_FIQ32SRC : REG_FIQ64SRC)
+
+#endif
