Patchwork [03/14] ARM: s5pc210: IRQ subsystem support.

login
register
mail settings
Submitter Evgeny Voevodin
Date Dec. 7, 2011, 9:46 a.m.
Message ID <1323251225-1072-4-git-send-email-e.voevodin@samsung.com>
Download mbox | patch
Permalink /patch/129928/
State New
Headers show

Comments

Evgeny Voevodin - Dec. 7, 2011, 9:46 a.m.
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 Makefile.target       |    3 +-
 hw/s5pc210.c          |  163 +++++++++++++++++++-
 hw/s5pc210.h          |   39 +++++
 hw/s5pc210_combiner.c |  381 +++++++++++++++++++++++++++++++++++++++++++++
 hw/s5pc210_gic.c      |  411 +++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 995 insertions(+), 2 deletions(-)
 create mode 100644 hw/s5pc210_combiner.c
 create mode 100644 hw/s5pc210_gic.c

Patch

diff --git a/Makefile.target b/Makefile.target
index 38fc364..ed338a8 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -344,7 +344,8 @@  obj-arm-y = integratorcp.o versatilepb.o arm_pic.o arm_timer.o
 obj-arm-y += arm_boot.o pl011.o pl031.o pl050.o pl080.o pl110.o pl181.o pl190.o
 obj-arm-y += versatile_pci.o
 obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
-obj-arm-y += s5pc210.o s5pc210_cmu.o s5pc210_uart.o
+obj-arm-y += s5pc210.o s5pc210_cmu.o s5pc210_uart.o s5pc210_gic.o \
+             s5pc210_combiner.o
 obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
 obj-arm-y += pl061.o
 obj-arm-y += arm-semi.o
diff --git a/hw/s5pc210.c b/hw/s5pc210.c
index d20ac95..99e9b1b 100644
--- a/hw/s5pc210.c
+++ b/hw/s5pc210.c
@@ -61,6 +61,8 @@ 
 
 #define S5PC210_SFR_BASE_ADDR            0x10000000
 
+#define S5PC210_SMP_PRIVATE_BASE_ADDR    0x10500000
+
 /* SFR Base Address for CMUs */
 #define S5PC210_CMU_BASE_ADDR            0x10030000
 
@@ -76,6 +78,16 @@ 
 #define S5PC210_UART2_FIFO_SIZE     16
 #define S5PC210_UART3_FIFO_SIZE     16
 #define S5PC210_UART4_FIFO_SIZE     64
+/* Interrupt Group of External Interrupt Combiner for UART */
+#define S5PC210_UART_INTG           26
+
+/* External GIC */
+#define S5PC210_EXT_GIC_CPU_BASE_ADDR    0x10480000
+#define S5PC210_EXT_GIC_DIST_BASE_ADDR   0x10490000
+
+/* Combiner */
+#define S5PC210_EXT_COMBINER_BASE_ADDR   0x10440000
+#define S5PC210_INT_COMBINER_BASE_ADDR   0x10448000
 
 #define S5PC210_BASE_BOOT_ADDR           S5PC210_DRAM0_BASE_ADDR
 
@@ -96,6 +108,88 @@  enum s5pc210_mach_id {
     MACH_SMDKC210_ID = 0xB16,
 };
 
+static void s5pc210_combiner_get_gpioin(S5pc210Irq *irqs,
+        DeviceState *dev,
+        int ext)
+{
+    int n;
+    int bit;
+    int max;
+    qemu_irq *irq;
+
+    max = ext ? S5PC210_MAX_EXT_COMBINER_IN_IRQ :
+        S5PC210_MAX_INT_COMBINER_IN_IRQ;
+    irq = ext ? irqs->ext_combiner_irq : irqs->int_combiner_irq;
+
+    /*
+     * Some IRQs of Int/External Combiner are going to two Combiners groups,
+     * so let split them.
+     */
+    for (n = 0; n < max; n++) {
+
+        bit = S5PC210_COMBINER_GET_BIT_NUM(n);
+
+        switch (n) {
+        /* MDNIE_LCD1 INTG1*/
+        case S5PC210_COMBINER_GET_IRQ_NUM(1, 0) ...
+             S5PC210_COMBINER_GET_IRQ_NUM(1, 3):
+                irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+                        irq[S5PC210_COMBINER_GET_IRQ_NUM(0, bit + 4)]);
+        continue;
+        break;
+
+        /* TMU INTG3*/
+        case S5PC210_COMBINER_GET_IRQ_NUM(3, 4):
+                irq[n] =
+                        qemu_irq_split(qdev_get_gpio_in(dev, n),
+                                irq[S5PC210_COMBINER_GET_IRQ_NUM(2, bit)]);
+        continue;
+        break;
+
+        /* LCD1 INTG12*/
+        case S5PC210_COMBINER_GET_IRQ_NUM(12, 0) ...
+             S5PC210_COMBINER_GET_IRQ_NUM(12, 3):
+                irq[n] =
+                        qemu_irq_split(qdev_get_gpio_in(dev, n),
+                                irq[S5PC210_COMBINER_GET_IRQ_NUM(11, bit + 4)]);
+        continue;
+
+        /* Multi-Core Timer INTG12*/
+        case S5PC210_COMBINER_GET_IRQ_NUM(12, 4) ...
+             S5PC210_COMBINER_GET_IRQ_NUM(12, 8):
+                irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+                        irq[S5PC210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+        continue;
+        break;
+
+        /* Multi-Core Timer INTG35*/
+        case S5PC210_COMBINER_GET_IRQ_NUM(35, 4) ...
+             S5PC210_COMBINER_GET_IRQ_NUM(35, 8):
+                irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+                        irq[S5PC210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+        continue;
+        break;
+
+        /* Multi-Core Timer INTG51*/
+        case S5PC210_COMBINER_GET_IRQ_NUM(51, 4) ...
+             S5PC210_COMBINER_GET_IRQ_NUM(51, 8):
+                irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+                        irq[S5PC210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+        continue;
+        break;
+
+        /* Multi-Core Timer INTG53*/
+        case S5PC210_COMBINER_GET_IRQ_NUM(53, 4) ...
+             S5PC210_COMBINER_GET_IRQ_NUM(53, 8):
+                irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+                        irq[S5PC210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+        continue;
+        break;
+        }
+
+        irq[n] = qdev_get_gpio_in(dev, n);
+    }
+}
 
 static void s5pc210_init(ram_addr_t ram_size,
         const char *boot_device,
@@ -113,8 +207,12 @@  static void s5pc210_init(ram_addr_t ram_size,
     MemoryRegion *irom_alias_mem = g_new(MemoryRegion, 1);
     MemoryRegion *dram0_mem = g_new(MemoryRegion, 1);
     MemoryRegion *dram1_mem = NULL;
+    S5pc210Irq *irqs;
+    qemu_irq *irq_table;
     qemu_irq *irqp;
     qemu_irq cpu_irq[4];
+    DeviceState *dev;
+    SysBusDevice *busdev;
     ram_addr_t mem_size;
     int n;
 
@@ -132,6 +230,12 @@  static void s5pc210_init(ram_addr_t ram_size,
         cpu_model = "cortex-a9";
     }
 
+    irqs = g_malloc0(sizeof(S5pc210Irq));
+    if (!irqs) {
+        fprintf(stderr, "Can't allocate IRQs\n");
+        exit(1);
+    }
+
     for (n = 0; n < smp_cpus; n++) {
         env = cpu_init(cpu_model);
         if (!env) {
@@ -148,6 +252,63 @@  static void s5pc210_init(ram_addr_t ram_size,
         cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ];
     }
 
+    irq_table = s5pc210_init_irq(irqs);
+
+    /*** SMP ***/
+
+    /* Private memory region and Internal GIC */
+    dev = qdev_create(NULL, "a9mpcore_priv");
+    qdev_prop_set_uint32(dev, "num-cpu", smp_cpus);
+    qdev_init_nofail(dev);
+    busdev = sysbus_from_qdev(dev);
+    sysbus_mmio_map(busdev, 0, S5PC210_SMP_PRIVATE_BASE_ADDR);
+    for (n = 0; n < smp_cpus; n++) {
+        sysbus_connect_irq(busdev, n, cpu_irq[n]);
+    }
+    for (n = 0; n < S5PC210_INT_GIC_NIRQ; n++) {
+        irqs->int_gic_irq[n] = qdev_get_gpio_in(dev, n);
+    }
+
+    /* External GIC */
+    dev = qdev_create(NULL, "s5pc210_gic");
+    qdev_prop_set_uint32(dev, "num-cpu", smp_cpus);
+    qdev_init_nofail(dev);
+    busdev = sysbus_from_qdev(dev);
+    /* Map CPU interface */
+    sysbus_mmio_map(busdev, 0, S5PC210_EXT_GIC_CPU_BASE_ADDR);
+    /* Map Distributer interface */
+    sysbus_mmio_map(busdev, 1, S5PC210_EXT_GIC_DIST_BASE_ADDR);
+    for (n = 0; n < smp_cpus; n++) {
+        sysbus_connect_irq(busdev, n, cpu_irq[n]);
+    }
+    for (n = 0; n < S5PC210_EXT_GIC_NIRQ; n++) {
+        irqs->ext_gic_irq[n] = qdev_get_gpio_in(dev, n);
+    }
+
+    /* Internal Interrupt Combiner */
+    dev = qdev_create(NULL, "s5pc210.combiner");
+    qdev_init_nofail(dev);
+    busdev = sysbus_from_qdev(dev);
+    for (n = 0; n < S5PC210_MAX_INT_COMBINER_OUT_IRQ; n++) {
+        sysbus_connect_irq(busdev, n, irqs->int_gic_irq[n]);
+    }
+    s5pc210_combiner_get_gpioin(irqs, dev, 0);
+    sysbus_mmio_map(busdev, 0, S5PC210_INT_COMBINER_BASE_ADDR);
+
+    /* External Interrupt Combiner */
+    dev = qdev_create(NULL, "s5pc210.combiner");
+    qdev_init_nofail(dev);
+    busdev = sysbus_from_qdev(dev);
+    for (n = 0; n < S5PC210_MAX_INT_COMBINER_OUT_IRQ; n++) {
+        sysbus_connect_irq(busdev, n, irqs->ext_gic_irq[n]);
+    }
+    s5pc210_combiner_get_gpioin(irqs, dev, 1);
+    sysbus_mmio_map(busdev, 0, S5PC210_EXT_COMBINER_BASE_ADDR);
+    qdev_prop_set_uint32(dev, "external", 1);
+
+    /* Initialize board IRQs. */
+    s5pc210_init_board_irqs(irqs);
+
     /*** Memory ***/
 
     /* Chip-ID and OMR */
@@ -225,7 +386,7 @@  static void s5pc210_init(ram_addr_t ram_size,
             continue;
         }
 
-        uart_irq = NULL;
+        uart_irq = irq_table[s5pc210_get_irq(S5PC210_UART_INTG, channel)];
 
         s5pc210_uart_create(addr, fifo_size, channel, NULL, uart_irq);
     }
diff --git a/hw/s5pc210.h b/hw/s5pc210.h
index bbf927c..412d004 100644
--- a/hw/s5pc210.h
+++ b/hw/s5pc210.h
@@ -32,6 +32,45 @@ 
 #define S5PC210_MAX_CPUS             2
 
 /*
+ * s5pc210 IRQ subsystem stub definitions.
+ */
+
+#define S5PC210_MAX_INT_COMBINER_OUT_IRQ  64
+#define S5PC210_MAX_EXT_COMBINER_OUT_IRQ  16
+#define S5PC210_MAX_INT_COMBINER_IN_IRQ   (S5PC210_MAX_INT_COMBINER_OUT_IRQ * 8)
+#define S5PC210_MAX_EXT_COMBINER_IN_IRQ   (S5PC210_MAX_EXT_COMBINER_OUT_IRQ * 8)
+
+#define S5PC210_COMBINER_GET_IRQ_NUM(grp, bit)  ((grp)*8 + (bit))
+#define S5PC210_COMBINER_GET_GRP_NUM(irq)       ((irq) / 8)
+#define S5PC210_COMBINER_GET_BIT_NUM(irq) \
+    ((irq) - 8 * S5PC210_COMBINER_GET_GRP_NUM(irq))
+
+/* IRQs number for external and internal GIC */
+#define S5PC210_EXT_GIC_NIRQ                    (160-32)
+#define S5PC210_INT_GIC_NIRQ                    64
+
+typedef struct S5pc210Irq {
+    qemu_irq int_combiner_irq[S5PC210_MAX_INT_COMBINER_IN_IRQ];
+    qemu_irq ext_combiner_irq[S5PC210_MAX_EXT_COMBINER_IN_IRQ];
+    qemu_irq int_gic_irq[S5PC210_INT_GIC_NIRQ];
+    qemu_irq ext_gic_irq[S5PC210_EXT_GIC_NIRQ];
+    qemu_irq board_irqs[S5PC210_MAX_INT_COMBINER_IN_IRQ];
+} S5pc210Irq;
+
+/* Initialize s5pc210 IRQ subsystem stub */
+qemu_irq *s5pc210_init_irq(S5pc210Irq *env);
+
+/* Initialize board IRQs.
+ * These IRQs contain splitted Int/External Combiner and External Gic IRQs */
+void s5pc210_init_board_irqs(S5pc210Irq *s);
+
+/* Get IRQ number from s5pc210 IRQ subsystem stub.
+ * To identify IRQ source use internal combiner group and bit number
+ *  grp - group number
+ *  bit - bit number inside group */
+uint32_t s5pc210_get_irq(uint32_t grp, uint32_t bit);
+
+/*
  * Interface for s5pc210 Clock Management Units (CMUs)
  */
 
diff --git a/hw/s5pc210_combiner.c b/hw/s5pc210_combiner.c
new file mode 100644
index 0000000..04ae024
--- /dev/null
+++ b/hw/s5pc210_combiner.c
@@ -0,0 +1,381 @@ 
+/*
+ * Samsung s5pc210 Interrupt Combiner
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ *
+ * Evgeny Voevodin <e.voevodin@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "sysbus.h"
+
+#include "s5pc210.h"
+
+//#define DEBUG_COMBINER
+
+#ifdef DEBUG_COMBINER
+#define DPRINTF(fmt, ...) \
+        do { fprintf(stdout, "COMBINER: [%s:%d] " fmt, __func__ , __LINE__, \
+                ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+#define    IIC_NGRP        64            /* Internal Interrupt Combiner
+                                            Groups number */
+#define    IIC_NIRQ        (IIC_NGRP*8)  /* Internal Interrupt Combiner
+                                            Interrupts number */
+#define IIC_REGION_SIZE    0x108         /* Size of memory mapped region */
+#define    IIC_REGSET_SIZE 0x41
+
+/*
+ * Combiner registers
+ */
+struct CombinerReg {
+    uint32_t iiesr;            /* Internal Interrupt Enable Set register */
+    uint32_t iiecr;            /* Internal Interrupt Enable Clear register */
+    uint32_t iistr;            /* Internal Interrupt Status register.
+     * Shows status of interrupt pending BEFORE masking
+     */
+    uint32_t iimsr;            /* Internal Interrupt Mask Status register.
+     * Shows status of interrupt pending AFTER masking
+     */
+};
+
+/*
+ * State for each output signal of internal combiner
+ */
+typedef struct CombinerGroupState {
+    uint8_t src_mask;            /* 1 - source enabled, 0 - disabled */
+    uint8_t src_pending;        /* Pending source interrupts before masking */
+} CombinerGroupState;
+
+typedef struct S5pc210CombinerState {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+
+    struct CombinerGroupState group[IIC_NGRP];
+    uint32_t reg_set[IIC_REGSET_SIZE];
+    uint32_t icipsr[2];
+    uint32_t external;          /* 1 means that this combiner is external */
+
+    qemu_irq output_irq[IIC_NGRP];
+} S5pc210CombinerState;
+
+static const VMStateDescription VMState_S5pc210CombinerGroupState = {
+        .name = "s5pc210.combiner.groupstate",
+        .version_id = 1,
+        .minimum_version_id = 1,
+        .minimum_version_id_old = 1,
+        .fields = (VMStateField[]) {
+            VMSTATE_UINT8(src_mask, CombinerGroupState),
+            VMSTATE_UINT8(src_pending, CombinerGroupState),
+            VMSTATE_END_OF_LIST()
+        }
+};
+
+static const VMStateDescription VMState_S5pc210Combiner = {
+        .name = "s5pc210.combiner",
+        .version_id = 1,
+        .minimum_version_id = 1,
+        .minimum_version_id_old = 1,
+        .fields = (VMStateField[]) {
+            VMSTATE_STRUCT_ARRAY(group, S5pc210CombinerState, IIC_NGRP, 0,
+            VMState_S5pc210CombinerGroupState, CombinerGroupState),
+            VMSTATE_UINT32_ARRAY(reg_set, S5pc210CombinerState,
+                    IIC_REGSET_SIZE),
+            VMSTATE_UINT32_ARRAY(icipsr, S5pc210CombinerState, 2),
+            VMSTATE_UINT32(external, S5pc210CombinerState),
+            VMSTATE_END_OF_LIST()
+        }
+};
+
+static uint64_t s5pc210_combiner_read(void *opaque, target_phys_addr_t offset,
+        unsigned size)
+{
+    struct S5pc210CombinerState *s = (struct S5pc210CombinerState *)opaque;
+    uint32_t req_quad_base_n;    /* Base of registers quad. Multiply it by 4 and
+                                   get a start of corresponding group quad */
+    uint32_t grp_quad_base_n;    /* Base of group quad */
+    uint32_t reg_n;              /* Register number inside the quad */
+    uint32_t val;
+
+    if (s->external && (offset > 0x3c && offset != 0x100)) {
+        hw_error("s5pc210.combiner: unallowed read access at offset 0x"
+                TARGET_FMT_plx "\n", offset);
+    }
+
+    req_quad_base_n = offset >> 4;
+    grp_quad_base_n = req_quad_base_n << 2;
+    reg_n = (offset - (req_quad_base_n << 4)) >> 2;
+
+    if (req_quad_base_n >= IIC_NGRP) {
+        /* Read of ICIPSR register */
+        return s->icipsr[reg_n];
+    }
+
+    val = 0;
+
+    switch (reg_n) {
+    /* IISTR */
+    case 2:
+        val |= s->group[grp_quad_base_n].src_pending;
+        val |= s->group[grp_quad_base_n+1].src_pending << 8;
+        val |= s->group[grp_quad_base_n+2].src_pending << 16;
+        val |= s->group[grp_quad_base_n+3].src_pending << 24;
+        break;
+        /* IIMSR */
+    case 3:
+        val |= s->group[grp_quad_base_n].src_mask &
+        s->group[grp_quad_base_n].src_pending;
+        val |= (s->group[grp_quad_base_n+1].src_mask &
+                s->group[grp_quad_base_n+1].src_pending) << 8;
+        val |= (s->group[grp_quad_base_n+2].src_mask &
+                s->group[grp_quad_base_n+2].src_pending) << 16;
+        val |= (s->group[grp_quad_base_n+3].src_mask &
+                s->group[grp_quad_base_n+3].src_pending) << 24;
+        break;
+    default:
+        if (offset >> 2 >= IIC_REGSET_SIZE) {
+            hw_error("s5pc210.combiner: overflow of reg_set by 0x"
+                    TARGET_FMT_plx "offset\n", offset);
+        }
+        val = s->reg_set[offset >> 2];
+        return 0;
+    }
+    return val;
+}
+
+static void s5pc210_combiner_update(void *opaque, uint8_t group_n)
+{
+    struct S5pc210CombinerState *s = (struct S5pc210CombinerState *)opaque;
+
+    /* Send interrupt if needed */
+    if (s->group[group_n].src_mask & s->group[group_n].src_pending) {
+#ifdef DEBUG_COMBINER
+        if (group_n != 26) {
+            /* skip uart */
+            DPRINTF("%s raise IRQ[%d]\n", s->external ? "EXT" : "INT", group_n);
+        }
+#endif
+
+        /* Set Combiner interrupt pending status after masking */
+        if (group_n >= 32) {
+            s->icipsr[1] |= 1 << (group_n-32);
+        } else {
+            s->icipsr[0] |= 1 << group_n;
+        }
+
+        qemu_irq_raise(s->output_irq[group_n]);
+    } else {
+#ifdef DEBUG_COMBINER
+        if (group_n != 26) {
+            /* skip uart */
+            DPRINTF("%s lower IRQ[%d]\n", s->external ? "EXT" : "INT", group_n);
+        }
+#endif
+
+        /* Set Combiner interrupt pending status after masking */
+        if (group_n >= 32) {
+            s->icipsr[1] &= ~(1 << (group_n-32));
+        } else {
+            s->icipsr[0] &= ~(1 << group_n);
+        }
+
+        qemu_irq_lower(s->output_irq[group_n]);
+    }
+}
+
+static void s5pc210_combiner_write(void *opaque, target_phys_addr_t offset,
+        uint64_t val, unsigned size)
+{
+    struct S5pc210CombinerState *s = (struct S5pc210CombinerState *)opaque;
+    uint32_t req_quad_base_n;    /* Base of registers quad. Multiply it by 4 and
+                                   get a start of corresponding group quad */
+    uint32_t grp_quad_base_n;    /* Base of group quad */
+    uint32_t reg_n;              /* Register number inside the quad */
+
+    if (s->external && (offset > 0x3c && offset != 0x100)) {
+        hw_error("s5pc210.combiner: unallowed write access at offset 0x"
+                TARGET_FMT_plx "\n", offset);
+    }
+
+    req_quad_base_n = offset >> 4;
+    grp_quad_base_n = req_quad_base_n << 2;
+    reg_n = (offset - (req_quad_base_n << 4)) >> 2;
+
+    if (req_quad_base_n >= IIC_NGRP) {
+        hw_error("s5pc210.combiner: unallowed write access at offset 0x"
+                TARGET_FMT_plx "\n", offset);
+        return;
+    }
+
+    if (reg_n > 1) {
+        hw_error("s5pc210.combiner: unallowed write access at offset 0x"
+                TARGET_FMT_plx "\n", offset);
+        return;
+    }
+
+    if (offset >> 2 >= IIC_REGSET_SIZE) {
+        hw_error("s5pc210.combiner: overflow of reg_set by 0x"
+                TARGET_FMT_plx "offset\n", offset);
+    }
+    s->reg_set[offset >> 2] = val;
+
+    switch (reg_n) {
+    /* IIESR */
+    case 0:
+        /* FIXME: what if irq is pending, allowed by mask, and we allow it
+         * again. Interrupt will rise again! */
+
+        DPRINTF("%s enable IRQ for groups %d, %d, %d, %d\n",
+                s->external ? "EXT" : "INT", grp_quad_base_n, grp_quad_base_n+1,
+                        grp_quad_base_n+2, grp_quad_base_n+3);
+        /* Enable interrupt sources */
+        s->group[grp_quad_base_n].src_mask |= val&0xFF;
+        s->group[grp_quad_base_n+1].src_mask |= (val&0xFF00)>>8;
+        s->group[grp_quad_base_n+2].src_mask |= (val&0xFF0000)>>16;
+        s->group[grp_quad_base_n+3].src_mask |= (val&0xFF000000)>>24;
+
+        s5pc210_combiner_update(s, grp_quad_base_n);
+        s5pc210_combiner_update(s, grp_quad_base_n+1);
+        s5pc210_combiner_update(s, grp_quad_base_n+2);
+        s5pc210_combiner_update(s, grp_quad_base_n+3);
+        break;
+        /* IIECR */
+    case 1:
+        DPRINTF("%s disable IRQ for groups %d, %d, %d, %d\n",
+                s->external ? "EXT" : "INT", grp_quad_base_n, grp_quad_base_n+1,
+                        grp_quad_base_n+2, grp_quad_base_n+3);
+        /* Disable interrupt sources */
+        s->group[grp_quad_base_n].src_mask &= ~(val&0xFF);
+        s->group[grp_quad_base_n+1].src_mask &= ~((val&0xFF00)>>8);
+        s->group[grp_quad_base_n+2].src_mask &= ~((val&0xFF0000)>>16);
+        s->group[grp_quad_base_n+3].src_mask &= ~((val&0xFF000000)>>24);
+
+        s5pc210_combiner_update(s, grp_quad_base_n);
+        s5pc210_combiner_update(s, grp_quad_base_n+1);
+        s5pc210_combiner_update(s, grp_quad_base_n+2);
+        s5pc210_combiner_update(s, grp_quad_base_n+3);
+        break;
+    default:
+        hw_error("s5pc210.combiner: unallowed write access at offset 0x"
+                TARGET_FMT_plx "\n", offset);
+        break;
+    }
+
+    return;
+}
+
+/* Get combiner group and bit from irq number */
+static uint8_t get_combiner_group_and_bit(int irq, uint8_t *bit)
+{
+    *bit = irq - ((irq >> 3)<<3);
+    return irq >> 3;
+}
+
+/* Process a change in an external IRQ input.  */
+static void s5pc210_combiner_handler(void *opaque, int irq, int level)
+{
+    struct S5pc210CombinerState *s = (struct S5pc210CombinerState *)opaque;
+    uint8_t bit_n, group_n;
+
+    group_n = get_combiner_group_and_bit(irq, &bit_n);
+
+    if (s->external && group_n >= S5PC210_MAX_EXT_COMBINER_OUT_IRQ) {
+        DPRINTF("%s unallowed IRQ group 0x%x\n", s->external ? "EXT" : "INT"
+                , group_n);
+        return;
+    }
+
+    if (level) {
+        s->group[group_n].src_pending |= 1 << bit_n;
+    } else {
+        s->group[group_n].src_pending &= ~(1 << bit_n);
+    }
+
+    s5pc210_combiner_update(s, group_n);
+
+    return;
+}
+
+static void s5pc210_combiner_reset(void *opaque)
+{
+    struct S5pc210CombinerState *s = (struct S5pc210CombinerState *)opaque;
+
+    memset(&s->group, 0, sizeof(s->group));
+    memset(&s->reg_set, 0, sizeof(s->reg_set));
+
+    s->reg_set[0xC0 >> 2] = 0x01010101;
+    s->reg_set[0xC4 >> 2] = 0x01010101;
+    s->reg_set[0xD0 >> 2] = 0x01010101;
+    s->reg_set[0xD4 >> 2] = 0x01010101;
+}
+
+static const MemoryRegionOps s5pc210_combiner_ops = {
+        .read = s5pc210_combiner_read,
+        .write = s5pc210_combiner_write,
+        .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/*
+ * Internal Combiner initialization.
+ */
+static int s5pc210_combiner_init(SysBusDevice *dev)
+{
+    unsigned int i;
+    struct S5pc210CombinerState *s =
+            FROM_SYSBUS(struct S5pc210CombinerState, dev);
+
+    /* Allocate general purpose input signals and connect a handler to each of
+     * them */
+    qdev_init_gpio_in(&s->busdev.qdev, s5pc210_combiner_handler, IIC_NIRQ);
+
+    /* Connect SysBusDev irqs to device specific irqs */
+    for (i = 0; i < IIC_NIRQ; i++) {
+        sysbus_init_irq(dev, &s->output_irq[i]);
+    }
+
+    memory_region_init_io(&s->iomem, &s5pc210_combiner_ops, s,
+            "s5pc210-combiner", IIC_REGION_SIZE);
+    sysbus_init_mmio_region(dev, &s->iomem);
+
+    s5pc210_combiner_reset(s);
+    vmstate_register(NULL, -1, &VMState_S5pc210Combiner, s);
+    return 0;
+}
+
+static SysBusDeviceInfo s5pc210_combiner_info = {
+        .qdev.name  = "s5pc210.combiner",
+        .qdev.size  = sizeof(struct S5pc210CombinerState),
+        .qdev.vmsd = &VMState_S5pc210Combiner,
+        .init = s5pc210_combiner_init,
+        .qdev.props = (Property[]) {
+            DEFINE_PROP_UINT32("external",
+                    struct S5pc210CombinerState,
+                    external,
+                    0),
+                    DEFINE_PROP_END_OF_LIST(),
+        }
+};
+
+static void s5pc210_combiner_register_devices(void)
+{
+    sysbus_register_withprop(&s5pc210_combiner_info);
+}
+
+device_init(s5pc210_combiner_register_devices)
diff --git a/hw/s5pc210_gic.c b/hw/s5pc210_gic.c
new file mode 100644
index 0000000..c6f18d4
--- /dev/null
+++ b/hw/s5pc210_gic.c
@@ -0,0 +1,411 @@ 
+/*
+ * Samsung s5pc210 GIC implementation. Based on hw/arm_gic.c
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ *
+ * Evgeny Voevodin <e.voevodin@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "sysbus.h"
+#include "qemu-common.h"
+#include "irq.h"
+#include "s5pc210.h"
+
+//#define DEBUG_S5PC210_IRQ
+//#define DEBUG_S5PC210_GIC
+
+#ifdef DEBUG_S5PC210_IRQ
+#define DPRINTF_S5PC210_GIC(fmt, ...) \
+        do { fprintf(stdout, "S5PC210_IRQ: [%24s:%5d] " fmt, __func__, \
+                     __LINE__, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF_S5PC210_GIC(fmt, ...) do {} while (0)
+#endif
+
+#ifdef DEBUG_S5PC210_GIC
+#define DPRINTF_S5PC210_GIC(fmt, ...) \
+        do { fprintf(stdout, "EXT_GIC: [%24s:%5d] " fmt, __func__, __LINE__, \
+                     ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF_S5PC210_GIC(fmt, ...) do {} while (0)
+#endif
+
+#define    EXT_GIC_ID_TVENC   127
+#define    EXT_GIC_ID_MFC 126
+#define    EXT_GIC_ID_HDMI_I2C    125
+#define    EXT_GIC_ID_HDMI    124
+#define    EXT_GIC_ID_MIXER   123
+#define    EXT_GIC_ID_PCIe    122
+#define    EXT_GIC_ID_2D  121
+#define    EXT_GIC_ID_JPEG    120
+#define    EXT_GIC_ID_FIMC3   119
+#define    EXT_GIC_ID_FIMC2   118
+#define    EXT_GIC_ID_FIMC1   117
+#define    EXT_GIC_ID_FIMC0   116
+#define    EXT_GIC_ID_ROTATOR 115
+#define    EXT_GIC_ID_ONENAND_AUDI    114
+#define    EXT_GIC_ID_MIPI_DSI_2LANE  113
+#define    EXT_GIC_ID_MIPI_CSI_2LANE  112
+#define    EXT_GIC_ID_MIPI_DSI_4LANE  111
+#define    EXT_GIC_ID_MIPI_CSI_4LANE  110
+#define    EXT_GIC_ID_SDMMC   109
+#define    EXT_GIC_ID_HSMMC3  108
+#define    EXT_GIC_ID_HSMMC2  107
+#define    EXT_GIC_ID_HSMMC1  106
+#define    EXT_GIC_ID_HSMMC0  105
+#define    EXT_GIC_ID_MODEMIF 104
+#define    EXT_GIC_ID_USB_DEVICE  103
+#define    EXT_GIC_ID_USB_HOST    102
+#define    EXT_GIC_ID_MCT_G1  101
+#define    EXT_GIC_ID_SPI2    100
+#define    EXT_GIC_ID_SPI1    99
+#define    EXT_GIC_ID_SPI0    98
+#define    EXT_GIC_ID_I2C7    97
+#define    EXT_GIC_ID_I2C6    96
+#define    EXT_GIC_ID_I2C5    95
+#define    EXT_GIC_ID_I2C4    94
+#define    EXT_GIC_ID_I2C3    93
+#define    EXT_GIC_ID_I2C2    92
+#define    EXT_GIC_ID_I2C1    91
+#define    EXT_GIC_ID_I2C0    90
+#define    EXT_GIC_ID_MCT_G0  89
+#define    EXT_GIC_ID_UART4   88
+#define    EXT_GIC_ID_UART3   87
+#define    EXT_GIC_ID_UART2   86
+#define    EXT_GIC_ID_UART1   85
+#define    EXT_GIC_ID_UART0    84
+#define    EXT_GIC_ID_NFC      83
+#define    EXT_GIC_ID_IEM_IEC 82
+#define    EXT_GIC_ID_IEM_APC 81
+#define    EXT_GIC_ID_MCT_L1  80
+#define    EXT_GIC_ID_GPIO_XA 79
+#define    EXT_GIC_ID_GPIO_XB 78
+#define    EXT_GIC_ID_RTC_TIC 77
+#define    EXT_GIC_ID_RTC_ALARM   76
+#define    EXT_GIC_ID_WDT 75
+#define    EXT_GIC_ID_MCT_L0  74
+#define    EXT_GIC_ID_TIMER4  73
+#define    EXT_GIC_ID_TIMER3  72
+#define    EXT_GIC_ID_TIMER2  71
+#define    EXT_GIC_ID_TIMER1  70
+#define    EXT_GIC_ID_TIMER0  69
+#define    EXT_GIC_ID_PDMA1   68
+#define    EXT_GIC_ID_PDMA0   67
+#define    EXT_GIC_ID_MDMA_LCD0   66
+
+enum ext_int {
+    EXT_GIC_ID_EXTINT0 = 48,
+    EXT_GIC_ID_EXTINT1,
+    EXT_GIC_ID_EXTINT2,
+    EXT_GIC_ID_EXTINT3,
+    EXT_GIC_ID_EXTINT4,
+    EXT_GIC_ID_EXTINT5,
+    EXT_GIC_ID_EXTINT6,
+    EXT_GIC_ID_EXTINT7,
+    EXT_GIC_ID_EXTINT8,
+    EXT_GIC_ID_EXTINT9,
+    EXT_GIC_ID_EXTINT10,
+    EXT_GIC_ID_EXTINT11,
+    EXT_GIC_ID_EXTINT12,
+    EXT_GIC_ID_EXTINT13,
+    EXT_GIC_ID_EXTINT14,
+    EXT_GIC_ID_EXTINT15
+};
+
+/*
+ * External GIC sources which are not from External Interrupt Combiner or
+ * External Interrupts are starting from S5PC210_MAX_EXT_COMBINER_OUT_IRQ,
+ * which is INTG16 in Internal Interrupt Combiner.
+ */
+
+static uint32_t
+combiner_grp_to_gic_id[64-S5PC210_MAX_EXT_COMBINER_OUT_IRQ][8] = {
+        /* int combiner groups 16-19 */
+        {}, {}, {}, {},
+        /* int combiner group 20 */
+        {0, EXT_GIC_ID_MDMA_LCD0},
+        /* int combiner group 21 */
+        {EXT_GIC_ID_PDMA0, EXT_GIC_ID_PDMA1},
+        /* int combiner group 22 */
+        {EXT_GIC_ID_TIMER0, EXT_GIC_ID_TIMER1, EXT_GIC_ID_TIMER2,
+                EXT_GIC_ID_TIMER3, EXT_GIC_ID_TIMER4},
+        /* int combiner group 23 */
+        {EXT_GIC_ID_RTC_ALARM, EXT_GIC_ID_RTC_TIC},
+        /* int combiner group 24 */
+        {EXT_GIC_ID_GPIO_XB, EXT_GIC_ID_GPIO_XA},
+        /* int combiner group 25 */
+        {EXT_GIC_ID_IEM_APC, EXT_GIC_ID_IEM_IEC},
+        /* int combiner group 26 */
+        {EXT_GIC_ID_UART0, EXT_GIC_ID_UART1, EXT_GIC_ID_UART2, EXT_GIC_ID_UART3,
+                EXT_GIC_ID_UART4},
+        /* int combiner group 27 */
+        {EXT_GIC_ID_I2C0, EXT_GIC_ID_I2C1, EXT_GIC_ID_I2C2, EXT_GIC_ID_I2C3,
+                EXT_GIC_ID_I2C4, EXT_GIC_ID_I2C5, EXT_GIC_ID_I2C6,
+                EXT_GIC_ID_I2C7},
+        /* int combiner group 28 */
+        {EXT_GIC_ID_SPI0, EXT_GIC_ID_SPI1, EXT_GIC_ID_SPI2},
+        /* int combiner group 29 */
+        {EXT_GIC_ID_HSMMC0, EXT_GIC_ID_HSMMC1, EXT_GIC_ID_HSMMC2,
+         EXT_GIC_ID_HSMMC3, EXT_GIC_ID_SDMMC},
+        /* int combiner group 30 */
+        {EXT_GIC_ID_MIPI_CSI_4LANE, EXT_GIC_ID_MIPI_CSI_2LANE},
+        /* int combiner group 31 */
+        {EXT_GIC_ID_MIPI_DSI_4LANE, EXT_GIC_ID_MIPI_DSI_2LANE},
+        /* int combiner group 32 */
+        {EXT_GIC_ID_FIMC0, EXT_GIC_ID_FIMC1},
+        /* int combiner group 33 */
+        {EXT_GIC_ID_FIMC2, EXT_GIC_ID_FIMC3},
+        /* int combiner group 34 */
+        {EXT_GIC_ID_ONENAND_AUDI, EXT_GIC_ID_NFC},
+        /* int combiner group 35 */
+        {0, 0, 0, EXT_GIC_ID_MCT_L1, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1},
+        /* int combiner group 36 */
+        {EXT_GIC_ID_MIXER},
+        /* int combiner group 37 */
+        {EXT_GIC_ID_EXTINT4, EXT_GIC_ID_EXTINT5, EXT_GIC_ID_EXTINT6,
+         EXT_GIC_ID_EXTINT7},
+        /* groups 38-50 */
+        {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},
+        /* int combiner group 51 */
+        {EXT_GIC_ID_MCT_L0, 0, 0, 0, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1},
+        /* group 52 */
+        {},
+        /* int combiner group 53 */
+        {EXT_GIC_ID_WDT, 0, 0, 0, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1},
+        /* groups 54-63 */
+        {}, {}, {}, {}, {}, {}, {}, {}, {}, {}
+};
+
+#define GIC_NIRQ 160
+#define NCPU S5PC210_MAX_CPUS
+
+#define S5PC210_GIC_CPU_REGION_SIZE     0x8050
+#define S5PC210_GIC_DIST_REGION_SIZE    0x8F04
+
+static void s5pc210_irq_handler(void *opaque, int irq, int level)
+{
+    S5pc210Irq *s = (S5pc210Irq *)opaque;
+
+    /* Bypass */
+    qemu_set_irq(s->board_irqs[irq], level);
+
+    return;
+}
+
+/*
+ * Initialize s5pc210 IRQ subsystem stub.
+ */
+qemu_irq *s5pc210_init_irq(S5pc210Irq *s)
+{
+    return qemu_allocate_irqs(s5pc210_irq_handler, s,
+            S5PC210_MAX_INT_COMBINER_IN_IRQ);
+}
+
+/*
+ * Initialize board IRQs.
+ * These IRQs contain splitted Int/External Combiner and External Gic IRQs.
+ */
+void s5pc210_init_board_irqs(S5pc210Irq *s)
+{
+    uint32_t grp, bit, irq_id, n;
+
+    for (n = 0; n < S5PC210_MAX_EXT_COMBINER_IN_IRQ; n++) {
+        s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n],
+                s->ext_combiner_irq[n]);
+
+        irq_id = 0;
+        if (n == S5PC210_COMBINER_GET_IRQ_NUM(1, 4) ||
+                n == S5PC210_COMBINER_GET_IRQ_NUM(12, 4)) {
+            /* MCT_G0 is passed to External GIC */
+            irq_id = EXT_GIC_ID_MCT_G0;
+        }
+        if (n == S5PC210_COMBINER_GET_IRQ_NUM(1, 5) ||
+                n == S5PC210_COMBINER_GET_IRQ_NUM(12, 5)) {
+            /* MCT_G1 is passed to External and GIC */
+            irq_id = EXT_GIC_ID_MCT_G1;
+        }
+        if (irq_id) {
+            s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n],
+                    s->ext_gic_irq[irq_id-32]);
+        }
+
+    }
+    for (; n < S5PC210_MAX_INT_COMBINER_IN_IRQ; n++) {
+        /* these IDs are passed to Internal Combiner and External GIC */
+        grp = S5PC210_COMBINER_GET_GRP_NUM(n);
+        bit = S5PC210_COMBINER_GET_BIT_NUM(n);
+        irq_id =
+                combiner_grp_to_gic_id[grp -
+                                       S5PC210_MAX_EXT_COMBINER_OUT_IRQ][bit];
+
+        if (irq_id) {
+            s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n],
+                    s->ext_gic_irq[irq_id-32]);
+        }
+    }
+}
+
+/*
+ * Get IRQ number from s5pc210 IRQ subsystem stub.
+ * To identify IRQ source use internal combiner group and bit number
+ *  grp - group number
+ *  bit - bit number inside group
+ */
+uint32_t s5pc210_get_irq(uint32_t grp, uint32_t bit)
+{
+    return S5PC210_COMBINER_GET_IRQ_NUM(grp, bit);
+}
+
+/********* GIC part *********/
+
+static inline int
+gic_get_current_cpu(void)
+{
+    return cpu_single_env->cpu_index;
+}
+
+#include "arm_gic.c"
+
+typedef struct {
+    gic_state gic;
+    MemoryRegion cpu_container;
+    MemoryRegion dist_container;
+    uint32_t num_cpu;
+} S5pc210GicState;
+
+static uint64_t s5pc210_gic_cpu_read(void *opaque, target_phys_addr_t offset,
+        unsigned size)
+{
+    S5pc210GicState *s = (S5pc210GicState *) opaque;
+    DPRINTF_S5PC210_GIC("CPU%d: read offset 0x%x\n",
+            gic_get_current_cpu(), offset);
+    return gic_cpu_read(&s->gic, gic_get_current_cpu(), offset & ~0x8000);
+}
+
+static void s5pc210_gic_cpu_write(void *opaque, target_phys_addr_t offset,
+        uint64_t value, unsigned size)
+{
+    S5pc210GicState *s = (S5pc210GicState *) opaque;
+    DPRINTF_S5PC210_GIC("CPU%d: write offset 0x%x, value 0x%x\n",
+            gic_get_current_cpu(), offset, value);
+    gic_cpu_write(&s->gic, gic_get_current_cpu(), offset & ~0x8000, value);
+}
+
+static uint32_t s5pc210_gic_dist_readb(void *opaque, target_phys_addr_t offset)
+{
+    S5pc210GicState *s = (S5pc210GicState *) opaque;
+    DPRINTF_S5PC210_GIC("DIST: readb offset 0x%x\n", offset);
+    return gic_dist_readb(&s->gic, offset & ~0x8000);
+}
+
+static uint32_t s5pc210_gic_dist_readw(void *opaque, target_phys_addr_t offset)
+{
+    S5pc210GicState *s = (S5pc210GicState *) opaque;
+    DPRINTF_S5PC210_GIC("DIST: readw offset 0x%x\n", offset);
+    return gic_dist_readw(&s->gic, offset & ~0x8000);
+}
+
+static uint32_t s5pc210_gic_dist_readl(void *opaque, target_phys_addr_t offset)
+{
+    S5pc210GicState *s = (S5pc210GicState *) opaque;
+    DPRINTF_S5PC210_GIC("DIST: readl offset 0x%x\n", offset);
+    return gic_dist_readl(&s->gic, offset & ~0x8000);
+}
+
+static void s5pc210_gic_dist_writeb(void *opaque, target_phys_addr_t offset,
+        uint32_t value)
+{
+    S5pc210GicState *s = (S5pc210GicState *) opaque;
+    DPRINTF_S5PC210_GIC("DIST: writeb offset 0x%x, value 0x%x\n", offset,
+            value);
+    gic_dist_writeb(&s->gic, offset & ~0x8000, value);
+}
+
+static void s5pc210_gic_dist_writew(void *opaque, target_phys_addr_t offset,
+        uint32_t value)
+{
+    S5pc210GicState *s = (S5pc210GicState *) opaque;
+    DPRINTF_S5PC210_GIC("DIST: writew offset 0x%x, value 0x%x\n", offset,
+            value);
+    gic_dist_writew(&s->gic, offset & ~0x8000, value);
+}
+
+static void s5pc210_gic_dist_writel(void *opaque, target_phys_addr_t offset,
+        uint32_t value)
+{
+    S5pc210GicState *s = (S5pc210GicState *) opaque;
+    DPRINTF_S5PC210_GIC("DIST: writel offset 0x%x, value 0x%x\n", offset,
+            value);
+    gic_dist_writel(&s->gic, offset & ~0x8000, value);
+}
+
+static const MemoryRegionOps s5pc210_gic_cpu_ops = {
+        .read = s5pc210_gic_cpu_read,
+        .write = s5pc210_gic_cpu_write,
+        .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const MemoryRegionOps s5pc210_gic_dist_ops = {
+        .old_mmio = {
+                .read = { s5pc210_gic_dist_readb,
+                        s5pc210_gic_dist_readw,
+                        s5pc210_gic_dist_readl, },
+                        .write = { s5pc210_gic_dist_writeb,
+                                s5pc210_gic_dist_writew,
+                                s5pc210_gic_dist_writel, },
+        },
+        .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int s5pc210_gic_init(SysBusDevice *dev)
+{
+    S5pc210GicState *s = FROM_SYSBUSGIC(S5pc210GicState, dev);
+    gic_init(&s->gic, s->num_cpu);
+
+    memory_region_init(&s->cpu_container, "s5pc210-gic-cpu_container",
+            S5PC210_GIC_CPU_REGION_SIZE);
+    memory_region_init(&s->cpu_container, "s5pc210-gic-dist_container",
+            S5PC210_GIC_DIST_REGION_SIZE);
+    memory_region_init_io(&s->cpu_container, &s5pc210_gic_cpu_ops, &s->gic,
+            "s5pc210-gic-cpu", S5PC210_GIC_CPU_REGION_SIZE);
+    memory_region_init_io(&s->dist_container, &s5pc210_gic_dist_ops, &s->gic,
+            "s5pc210-gic-dist", S5PC210_GIC_DIST_REGION_SIZE);
+
+    sysbus_init_mmio_region(dev, &s->cpu_container);
+    sysbus_init_mmio_region(dev, &s->dist_container);
+
+    gic_cpu_write(&s->gic, 1, 0, 1);
+    return 0;
+}
+
+static SysBusDeviceInfo s5pc210_gic_info = {
+        .init = s5pc210_gic_init,
+        .qdev.name  = "s5pc210_gic",
+        .qdev.size  = sizeof(S5pc210GicState),
+        .qdev.props = (Property[]) {
+            DEFINE_PROP_UINT32("num-cpu", S5pc210GicState, num_cpu, 1),
+                    DEFINE_PROP_END_OF_LIST(),
+        }
+};
+
+static void s5pc210_gic_register_devices(void)
+{
+    sysbus_register_withprop(&s5pc210_gic_info);
+}
+
+device_init(s5pc210_gic_register_devices)