Patchwork [v4,04/11] ARM: exynos4210: IRQ subsystem support.

login
register
mail settings
Submitter Evgeny Voevodin
Date Dec. 19, 2011, 11:53 a.m.
Message ID <1324295617-5798-5-git-send-email-e.voevodin@samsung.com>
Download mbox | patch
Permalink /patch/132216/
State New
Headers show

Comments

Evgeny Voevodin - Dec. 19, 2011, 11:53 a.m.
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 Makefile.target          |    3 +-
 hw/exynos4210.c          |  179 ++++++++++++++++-
 hw/exynos4210.h          |   46 ++++
 hw/exynos4210_combiner.c |  384 ++++++++++++++++++++++++++++++++++
 hw/exynos4210_gic.c      |  509 ++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 1116 insertions(+), 5 deletions(-)
 create mode 100644 hw/exynos4210_combiner.c
 create mode 100644 hw/exynos4210_gic.c
Peter Maydell - Dec. 21, 2011, 1:50 p.m.
On 19 December 2011 11:53, Evgeny Voevodin <e.voevodin@samsung.com> wrote:

> +static uint64_t exynos4210_gic_cpu_read(void *opaque, target_phys_addr_t offset,
> +        unsigned size)
> +{
> +    Exynos4210GicState *s = (Exynos4210GicState *) opaque;
> +    DPRINTF_EXYNOS4210_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);
> +}

arm_gic.c exposes the CPU and distributor interfaces as their own
memory regions now -- you shouldn't need any of this intermediate
layer of functions.

(Reviewing the rest of this series is on my todo list but I can't
guarantee I'll get to it until after Christmas now.)

-- PMM
Evgeny Voevodin - Dec. 21, 2011, 3:08 p.m.
On 12/21/2011 05:50 PM, Peter Maydell wrote:
> On 19 December 2011 11:53, Evgeny Voevodin<e.voevodin@samsung.com>  wrote:
>
>> +static uint64_t exynos4210_gic_cpu_read(void *opaque, target_phys_addr_t offset,
>> +        unsigned size)
>> +{
>> +    Exynos4210GicState *s = (Exynos4210GicState *) opaque;
>> +    DPRINTF_EXYNOS4210_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);
>> +}
> arm_gic.c exposes the CPU and distributor interfaces as their own
> memory regions now -- you shouldn't need any of this intermediate
> layer of functions.
>
> (Reviewing the rest of this series is on my todo list but I can't
> guarantee I'll get to it until after Christmas now.)
>
> -- PMM
>
These functions are not actually for splitting CPU and Distributer 
interfaces.
In our board we have two GICs - internal and external. Internal GIC is 
completely
matching arm_gic.c.

Internal GIC CPU[n] and Distributer[n] interfaces are at 0x100 and 
0x1000 offsets from
0x10500000 base.

But external GIC is different.
It's CPU[0] interface is at 0x0 offset from 0x10480000 base
and
       CPU[1] interface is at 0x8000 offset from 0x10480000 base

It's Distributer[0] interface is at 0x0 offset from 0x10490000 base
and
       Distributer[1] interface is at 0x8000 offset from 0x10490000 base

[n] - is corresponding to SMP CPU Core.

So, we need these wrapper functions for External GIC.
In public accessed documentation internal GIC is not covered for some 
reason.
Peter Maydell - Dec. 21, 2011, 8:31 p.m.
On 21 December 2011 15:08, Evgeny Voevodin <e.voevodin@samsung.com> wrote:
> On 12/21/2011 05:50 PM, Peter Maydell wrote:
>> arm_gic.c exposes the CPU and distributor interfaces as their own
>> memory regions now -- you shouldn't need any of this intermediate
>> layer of functions.

> These functions are not actually for splitting CPU and Distributer
> interfaces.
> In our board we have two GICs - internal and external. Internal GIC is
> completely
> matching arm_gic.c.
>
> Internal GIC CPU[n] and Distributer[n] interfaces are at 0x100 and 0x1000
> offsets from
> 0x10500000 base.
>
> But external GIC is different.
> It's CPU[0] interface is at 0x0 offset from 0x10480000 base
> and
>      CPU[1] interface is at 0x8000 offset from 0x10480000 base
>
> It's Distributer[0] interface is at 0x0 offset from 0x10490000 base
> and
>      Distributer[1] interface is at 0x8000 offset from 0x10490000 base
>
> [n] - is corresponding to SMP CPU Core.
>
> So, we need these wrapper functions for External GIC.

I don't understand this reasoning. If there are two GICs then
you should just instantiate two GIC devices and map and/or alias
their memory regions at the right addresses. The reason why
the distributor and CPU interfaces are exposed as multiple
memory regions is exactly so you can put them at different
offsets for different boards/CPUs. If arm_gic doesn't
provide suitably split up memory regions then it should be
fixed to do so.

-- PMM
Evgeny Voevodin - Dec. 22, 2011, 7:03 a.m.
On 12/22/2011 12:31 AM, Peter Maydell wrote:
> On 21 December 2011 15:08, Evgeny Voevodin<e.voevodin@samsung.com>  wrote:
>> On 12/21/2011 05:50 PM, Peter Maydell wrote:
>>> arm_gic.c exposes the CPU and distributor interfaces as their own
>>> memory regions now -- you shouldn't need any of this intermediate
>>> layer of functions.
>> These functions are not actually for splitting CPU and Distributer
>> interfaces.
>> In our board we have two GICs - internal and external. Internal GIC is
>> completely
>> matching arm_gic.c.
>>
>> Internal GIC CPU[n] and Distributer[n] interfaces are at 0x100 and 0x1000
>> offsets from
>> 0x10500000 base.
>>
>> But external GIC is different.
>> It's CPU[0] interface is at 0x0 offset from 0x10480000 base
>> and
>>       CPU[1] interface is at 0x8000 offset from 0x10480000 base
>>
>> It's Distributer[0] interface is at 0x0 offset from 0x10490000 base
>> and
>>       Distributer[1] interface is at 0x8000 offset from 0x10490000 base
>>
>> [n] - is corresponding to SMP CPU Core.
>>
>> So, we need these wrapper functions for External GIC.
> I don't understand this reasoning. If there are two GICs then
> you should just instantiate two GIC devices and map and/or alias
> their memory regions at the right addresses. The reason why
> the distributor and CPU interfaces are exposed as multiple
> memory regions is exactly so you can put them at different
> offsets for different boards/CPUs. If arm_gic doesn't
> provide suitably split up memory regions then it should be
> fixed to do so.
>
> -- PMM
>
One of our GICs (internal) plus private memory region is represented
as "a9mpcore_priv" device. This implementation fits the documentation.

Second GIC (external) is represented as "exynos4210.gic" with splitted
mapping for CPU (0x10480000) and Distributer (0x10490000) (we used
arm_gic.c availability to split CPU and Distributer memories).

The reason for creation of this device with it's own read/write 
functions is:

CPU and Distributer registers which are banked per SMP Core in internal GIC
are not banked in external GIC and their offsets could not be used as is 
with
arm_gic.c.
External GIC registers in comparison to Internal GIC registers are moved
from base by offset n * 0x8000 for each SMP Core, where n is SMP Core index.
The rest functionality of external GIC is identical to internal GIC and 
arm_gic.c.
So, we can use arm_gic.c in external GIC if we will pass correct offsets 
to it's
read/write functions. To obtain arm_gic.c compliant addresses, we introduced
exynos4210_gic/dist_read/write functions. They obtain arm_gic.c compliant
offsets for primary and secondary SMP Cores and simply call arm_gic.c
functions to do all the work.
Peter Maydell - Dec. 22, 2011, 12:30 p.m.
On 22 December 2011 07:03, Evgeny Voevodin <e.voevodin@samsung.com> wrote:
> Second GIC (external) is represented as "exynos4210.gic" with splitted
> mapping for CPU (0x10480000) and Distributer (0x10490000) (we used
> arm_gic.c availability to split CPU and Distributer memories).
>
> The reason for creation of this device with it's own read/write functions
> is:
>
> CPU and Distributer registers which are banked per SMP Core in internal GIC
> are not banked in external GIC and their offsets could not be used as is
> with arm_gic.c.
> External GIC registers in comparison to Internal GIC registers are moved
> from base by offset n * 0x8000 for each SMP Core, where n is SMP Core index.

Right, so just map each of the memory regions arm_gic exposes for
core 0, core 1, ... to these addresses, and don't map the memory
region corresponding to "CPU interface for this core" at all.

-- PMM
Evgeny Voevodin - Dec. 22, 2011, 12:50 p.m.
On 12/22/2011 04:30 PM, Peter Maydell wrote:
> On 22 December 2011 07:03, Evgeny Voevodin<e.voevodin@samsung.com>  wrote:
>> Second GIC (external) is represented as "exynos4210.gic" with splitted
>> mapping for CPU (0x10480000) and Distributer (0x10490000) (we used
>> arm_gic.c availability to split CPU and Distributer memories).
>>
>> The reason for creation of this device with it's own read/write functions
>> is:
>>
>> CPU and Distributer registers which are banked per SMP Core in internal GIC
>> are not banked in external GIC and their offsets could not be used as is
>> with arm_gic.c.
>> External GIC registers in comparison to Internal GIC registers are moved
>> from base by offset n * 0x8000 for each SMP Core, where n is SMP Core index.
> Right, so just map each of the memory regions arm_gic exposes for
> core 0, core 1, ... to these addresses, and don't map the memory
> region corresponding to "CPU interface for this core" at all.
>
> -- PMM
>

Do you mean to use s->gic.cpuiomem[NCPU+1] as in a9mpcore.c 
a9mp_priv_init() done?
What should we use if we need the same for distributor which is 
represented as gic.iomem?
Extend distributor in the same way?
Peter Maydell - Dec. 22, 2011, 3:22 p.m.
On 22 December 2011 12:50, Evgeny Voevodin <e.voevodin@samsung.com> wrote:
> Do you mean to use s->gic.cpuiomem[NCPU+1] as in a9mpcore.c a9mp_priv_init()
> done?

It depends what you want. If you need a memory region
which behaves like "CPU interface for whatever the core
making this read/write is", that is cpuiomem[0]. If you
need a memory region which behaves like "CPU interface
for core 0" regardless of which core accesses it, that
is cpuiomem[1]. For "CPU interface for core 1", cpuiomem[2].

11MPCore uses all of these. A9MP's built in GIC only
needs cpuiomem[0].

If you need any of these at more than one address in the
memory map, you need to create a memory region alias.

> What should we use if we need the same for distributor which is represented
> as gic.iomem?
> Extend distributor in the same way?

Your current wrapper functions for the distributor read/write
routines don't do anything to pass a specific CPU number
into the underlying GIC distributor read/write functions.
So it should be sufficient to just map the distributor's
existing memory region (again, if you need it in more than
one place in the address space then create an alias memory
region for it).

-- PMM

Patch

diff --git a/Makefile.target b/Makefile.target
index ce0e46e..b10cfa2 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -342,7 +342,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 += exynos4_boards.o exynos4210.o exynos4210_uart.o
+obj-arm-y += exynos4_boards.o exynos4210.o exynos4210_uart.o exynos4210_gic.o \
+             exynos4210_combiner.o
 obj-arm-y += arm_mptimer.o
 obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
 obj-arm-y += pl061.o
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index afef4cb..a85eb5d 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -23,6 +23,7 @@ 
 
 #include "boards.h"
 #include "sysemu.h"
+#include "sysbus.h"
 #include "arm-misc.h"
 #include "exynos4210.h"
 
@@ -38,10 +39,101 @@ 
 #define EXYNOS4210_UART1_FIFO_SIZE     64
 #define EXYNOS4210_UART2_FIFO_SIZE     16
 #define EXYNOS4210_UART3_FIFO_SIZE     16
+/* Interrupt Group of External Interrupt Combiner for UART */
+#define EXYNOS4210_UART_INT_GRP        26
+
+/* External GIC */
+#define EXYNOS4210_EXT_GIC_CPU_BASE_ADDR    0x10480000
+#define EXYNOS4210_EXT_GIC_DIST_BASE_ADDR   0x10490000
+
+/* Combiner */
+#define EXYNOS4210_EXT_COMBINER_BASE_ADDR   0x10440000
+#define EXYNOS4210_INT_COMBINER_BASE_ADDR   0x10448000
 
 static uint8_t chipid_and_omr[] = { 0x11, 0x02, 0x21, 0x43,
                                     0x09, 0x00, 0x00, 0x00 };
 
+static void exynos4210_combiner_get_gpioin(Exynos4210Irq *irqs,
+        DeviceState *dev,
+        int ext)
+{
+    int n;
+    int bit;
+    int max;
+    qemu_irq *irq;
+
+    max = ext ? EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ :
+        EXYNOS4210_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 = EXYNOS4210_COMBINER_GET_BIT_NUM(n);
+
+        switch (n) {
+        /* MDNIE_LCD1 INTG1*/
+        case EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 0) ...
+             EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 3):
+                irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+                        irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(0, bit + 4)]);
+        continue;
+        break;
+
+        /* TMU INTG3*/
+        case EXYNOS4210_COMBINER_GET_IRQ_NUM(3, 4):
+                irq[n] =
+                        qemu_irq_split(qdev_get_gpio_in(dev, n),
+                                irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(2, bit)]);
+        continue;
+        break;
+
+        /* LCD1 INTG12*/
+        case EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 0) ...
+             EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 3):
+                irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+                             irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(11, bit + 4)]);
+        continue;
+
+        /* Multi-Core Timer INTG12*/
+        case EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 4) ...
+             EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 8):
+                irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+                        irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+        continue;
+        break;
+
+        /* Multi-Core Timer INTG35*/
+        case EXYNOS4210_COMBINER_GET_IRQ_NUM(35, 4) ...
+             EXYNOS4210_COMBINER_GET_IRQ_NUM(35, 8):
+                irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+                        irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+        continue;
+        break;
+
+        /* Multi-Core Timer INTG51*/
+        case EXYNOS4210_COMBINER_GET_IRQ_NUM(51, 4) ...
+             EXYNOS4210_COMBINER_GET_IRQ_NUM(51, 8):
+                irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+                        irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+        continue;
+        break;
+
+        /* Multi-Core Timer INTG53*/
+        case EXYNOS4210_COMBINER_GET_IRQ_NUM(53, 4) ...
+             EXYNOS4210_COMBINER_GET_IRQ_NUM(53, 8):
+                irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+                        irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+        continue;
+        break;
+        }
+
+        irq[n] = qdev_get_gpio_in(dev, n);
+    }
+}
 
 Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
         unsigned long ram_size)
@@ -49,8 +141,12 @@  Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
     qemu_irq cpu_irq[4];
     int n;
     Exynos4210State *s = g_new(Exynos4210State, 1);
+    qemu_irq *irq_table;
     qemu_irq *irqp;
+    qemu_irq gate_irq[EXYNOS4210_IRQ_GATE_NINPUTS];
     unsigned long mem_size;
+    DeviceState *dev;
+    SysBusDevice *busdev;
 
     for (n = 0; n < smp_cpus; n++) {
         s->env[n] = cpu_init("cortex-a9");
@@ -68,6 +164,77 @@  Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
         cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ];
     }
 
+    /*** IRQs ***/
+
+    s->irq_table = exynos4210_init_irq(&s->irqs);
+    irq_table = s->irq_table;
+
+    /* IRQ Gate */
+    dev = qdev_create(NULL, "exynos4210.irq_gate");
+    qdev_init_nofail(dev);
+    /* Get IRQ Gate input in gate_irq */
+    for (n = 0; n < EXYNOS4210_IRQ_GATE_NINPUTS; n++) {
+        gate_irq[n] = qdev_get_gpio_in(dev, n);
+    }
+    busdev = sysbus_from_qdev(dev);
+    /* Connect IRQ Gate output to cpu_irq */
+    for (n = 0; n < smp_cpus; n++) {
+        sysbus_connect_irq(busdev, n, cpu_irq[n]);
+    }
+
+    /* 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, EXYNOS4210_SMP_PRIVATE_BASE_ADDR);
+    for (n = 0; n < smp_cpus; n++) {
+        sysbus_connect_irq(busdev, n, gate_irq[n * 2]);
+    }
+    for (n = 0; n < EXYNOS4210_INT_GIC_NIRQ; n++) {
+        s->irqs.int_gic_irq[n] = qdev_get_gpio_in(dev, n);
+    }
+
+    /* External GIC */
+    dev = qdev_create(NULL, "exynos4210.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, EXYNOS4210_EXT_GIC_CPU_BASE_ADDR);
+    /* Map Distributer interface */
+    sysbus_mmio_map(busdev, 1, EXYNOS4210_EXT_GIC_DIST_BASE_ADDR);
+    for (n = 0; n < smp_cpus; n++) {
+        sysbus_connect_irq(busdev, n, gate_irq[n * 2 + 1]);
+    }
+    for (n = 0; n < EXYNOS4210_EXT_GIC_NIRQ; n++) {
+        s->irqs.ext_gic_irq[n] = qdev_get_gpio_in(dev, n);
+    }
+
+    /* Internal Interrupt Combiner */
+    dev = qdev_create(NULL, "exynos4210.combiner");
+    qdev_init_nofail(dev);
+    busdev = sysbus_from_qdev(dev);
+    for (n = 0; n < EXYNOS4210_MAX_INT_COMBINER_OUT_IRQ; n++) {
+        sysbus_connect_irq(busdev, n, s->irqs.int_gic_irq[n]);
+    }
+    exynos4210_combiner_get_gpioin(&s->irqs, dev, 0);
+    sysbus_mmio_map(busdev, 0, EXYNOS4210_INT_COMBINER_BASE_ADDR);
+
+    /* External Interrupt Combiner */
+    dev = qdev_create(NULL, "exynos4210.combiner");
+    qdev_init_nofail(dev);
+    busdev = sysbus_from_qdev(dev);
+    for (n = 0; n < EXYNOS4210_MAX_INT_COMBINER_OUT_IRQ; n++) {
+        sysbus_connect_irq(busdev, n, s->irqs.ext_gic_irq[n]);
+    }
+    exynos4210_combiner_get_gpioin(&s->irqs, dev, 1);
+    sysbus_mmio_map(busdev, 0, EXYNOS4210_EXT_COMBINER_BASE_ADDR);
+    qdev_prop_set_uint32(dev, "external", 1);
+
+    /* Initialize board IRQs. */
+    exynos4210_init_board_irqs(&s->irqs);
+
     /*** Memory ***/
 
     /* Chip-ID and OMR */
@@ -112,16 +279,20 @@  Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
 
     /*** UARTs ***/
     exynos4210_uart_create(EXYNOS4210_UART0_BASE_ADDR,
-                           EXYNOS4210_UART0_FIFO_SIZE, 0, NULL, NULL);
+                           EXYNOS4210_UART0_FIFO_SIZE, 0, NULL,
+                    irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 0)]);
 
     exynos4210_uart_create(EXYNOS4210_UART1_BASE_ADDR,
-                           EXYNOS4210_UART1_FIFO_SIZE, 1, NULL, NULL);
+                           EXYNOS4210_UART1_FIFO_SIZE, 1, NULL,
+                    irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 1)]);
 
     exynos4210_uart_create(EXYNOS4210_UART2_BASE_ADDR,
-                           EXYNOS4210_UART2_FIFO_SIZE, 2, NULL, NULL);
+                           EXYNOS4210_UART2_FIFO_SIZE, 2, NULL,
+                    irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 2)]);
 
     exynos4210_uart_create(EXYNOS4210_UART3_BASE_ADDR,
-                           EXYNOS4210_UART3_FIFO_SIZE, 3, NULL, NULL);
+                           EXYNOS4210_UART3_FIFO_SIZE, 3, NULL,
+                    irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 3)]);
 
     return s;
 }
diff --git a/hw/exynos4210.h b/hw/exynos4210.h
index 4dc119a..b55d159 100644
--- a/hw/exynos4210.h
+++ b/hw/exynos4210.h
@@ -45,8 +45,41 @@ 
 
 #define EXYNOS4210_BASE_BOOT_ADDR           EXYNOS4210_DRAM0_BASE_ADDR
 
+#define EXYNOS4210_SMP_PRIVATE_BASE_ADDR    0x10500000
+
+/*
+ * exynos4210 IRQ subsystem stub definitions.
+ */
+#define EXYNOS4210_IRQ_GATE_NINPUTS 8
+
+#define EXYNOS4210_MAX_INT_COMBINER_OUT_IRQ  64
+#define EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ  16
+#define EXYNOS4210_MAX_INT_COMBINER_IN_IRQ   \
+    (EXYNOS4210_MAX_INT_COMBINER_OUT_IRQ * 8)
+#define EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ   \
+    (EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ * 8)
+
+#define EXYNOS4210_COMBINER_GET_IRQ_NUM(grp, bit)  ((grp)*8 + (bit))
+#define EXYNOS4210_COMBINER_GET_GRP_NUM(irq)       ((irq) / 8)
+#define EXYNOS4210_COMBINER_GET_BIT_NUM(irq) \
+    ((irq) - 8 * EXYNOS4210_COMBINER_GET_GRP_NUM(irq))
+
+/* IRQs number for external and internal GIC */
+#define EXYNOS4210_EXT_GIC_NIRQ     (160-32)
+#define EXYNOS4210_INT_GIC_NIRQ     64
+
+typedef struct Exynos4210Irq {
+    qemu_irq int_combiner_irq[EXYNOS4210_MAX_INT_COMBINER_IN_IRQ];
+    qemu_irq ext_combiner_irq[EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ];
+    qemu_irq int_gic_irq[EXYNOS4210_INT_GIC_NIRQ];
+    qemu_irq ext_gic_irq[EXYNOS4210_EXT_GIC_NIRQ];
+    qemu_irq board_irqs[EXYNOS4210_MAX_INT_COMBINER_IN_IRQ];
+} Exynos4210Irq;
+
 typedef struct Exynos4210State {
     CPUState * env[EXYNOS4210_MAX_CPUS];
+    Exynos4210Irq irqs;
+    qemu_irq *irq_table;
 
     MemoryRegion chipid_mem;
     MemoryRegion iram_mem;
@@ -60,6 +93,19 @@  typedef struct Exynos4210State {
 Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
         unsigned long ram_size);
 
+/* Initialize exynos4210 IRQ subsystem stub */
+qemu_irq *exynos4210_init_irq(Exynos4210Irq *env);
+
+/* Initialize board IRQs.
+ * These IRQs contain splitted Int/External Combiner and External Gic IRQs */
+void exynos4210_init_board_irqs(Exynos4210Irq *s);
+
+/* Get IRQ number from exynos4210 IRQ subsystem stub.
+ * To identify IRQ source use internal combiner group and bit number
+ *  grp - group number
+ *  bit - bit number inside group */
+uint32_t exynos4210_get_irq(uint32_t grp, uint32_t bit);
+
 /*
  * exynos4210 UART
  */
diff --git a/hw/exynos4210_combiner.c b/hw/exynos4210_combiner.c
new file mode 100644
index 0000000..4820f40
--- /dev/null
+++ b/hw/exynos4210_combiner.c
@@ -0,0 +1,384 @@ 
+/*
+ * Samsung exynos4210 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "sysbus.h"
+
+#include "exynos4210.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 Exynos4210CombinerState {
+    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];
+} Exynos4210CombinerState;
+
+static const VMStateDescription VMState_Exynos4210CombinerGroupState = {
+        .name = "exynos4210.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_Exynos4210Combiner = {
+        .name = "exynos4210.combiner",
+        .version_id = 1,
+        .minimum_version_id = 1,
+        .minimum_version_id_old = 1,
+        .fields = (VMStateField[]) {
+            VMSTATE_STRUCT_ARRAY(group, Exynos4210CombinerState, IIC_NGRP, 0,
+            VMState_Exynos4210CombinerGroupState, CombinerGroupState),
+            VMSTATE_UINT32_ARRAY(reg_set, Exynos4210CombinerState,
+                    IIC_REGSET_SIZE),
+            VMSTATE_UINT32_ARRAY(icipsr, Exynos4210CombinerState, 2),
+            VMSTATE_UINT32(external, Exynos4210CombinerState),
+            VMSTATE_END_OF_LIST()
+        }
+};
+
+static uint64_t
+exynos4210_combiner_read(void *opaque, target_phys_addr_t offset, unsigned size)
+{
+    struct Exynos4210CombinerState *s =
+            (struct Exynos4210CombinerState *)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("exynos4210.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("exynos4210.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 exynos4210_combiner_update(void *opaque, uint8_t group_n)
+{
+    struct Exynos4210CombinerState *s =
+            (struct Exynos4210CombinerState *)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 exynos4210_combiner_write(void *opaque, target_phys_addr_t offset,
+        uint64_t val, unsigned size)
+{
+    struct Exynos4210CombinerState *s =
+            (struct Exynos4210CombinerState *)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("exynos4210.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("exynos4210.combiner: unallowed write access at offset 0x"
+                TARGET_FMT_plx "\n", offset);
+        return;
+    }
+
+    if (reg_n > 1) {
+        hw_error("exynos4210.combiner: unallowed write access at offset 0x"
+                TARGET_FMT_plx "\n", offset);
+        return;
+    }
+
+    if (offset >> 2 >= IIC_REGSET_SIZE) {
+        hw_error("exynos4210.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;
+
+        exynos4210_combiner_update(s, grp_quad_base_n);
+        exynos4210_combiner_update(s, grp_quad_base_n+1);
+        exynos4210_combiner_update(s, grp_quad_base_n+2);
+        exynos4210_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);
+
+        exynos4210_combiner_update(s, grp_quad_base_n);
+        exynos4210_combiner_update(s, grp_quad_base_n+1);
+        exynos4210_combiner_update(s, grp_quad_base_n+2);
+        exynos4210_combiner_update(s, grp_quad_base_n+3);
+        break;
+    default:
+        hw_error("exynos4210.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 exynos4210_combiner_handler(void *opaque, int irq, int level)
+{
+    struct Exynos4210CombinerState *s =
+            (struct Exynos4210CombinerState *)opaque;
+    uint8_t bit_n, group_n;
+
+    group_n = get_combiner_group_and_bit(irq, &bit_n);
+
+    if (s->external && group_n >= EXYNOS4210_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);
+    }
+
+    exynos4210_combiner_update(s, group_n);
+
+    return;
+}
+
+static void exynos4210_combiner_reset(DeviceState *d)
+{
+    struct Exynos4210CombinerState *s = (struct Exynos4210CombinerState *)d;
+
+    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 exynos4210_combiner_ops = {
+        .read = exynos4210_combiner_read,
+        .write = exynos4210_combiner_write,
+        .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/*
+ * Internal Combiner initialization.
+ */
+static int exynos4210_combiner_init(SysBusDevice *dev)
+{
+    unsigned int i;
+    struct Exynos4210CombinerState *s =
+            FROM_SYSBUS(struct Exynos4210CombinerState, dev);
+
+    /* Allocate general purpose input signals and connect a handler to each of
+     * them */
+    qdev_init_gpio_in(&s->busdev.qdev, exynos4210_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, &exynos4210_combiner_ops, s,
+            "exynos4210-combiner", IIC_REGION_SIZE);
+    sysbus_init_mmio(dev, &s->iomem);
+
+    exynos4210_combiner_reset((DeviceState *)s);
+    return 0;
+}
+
+static SysBusDeviceInfo exynos4210_combiner_info = {
+        .qdev.name  = "exynos4210.combiner",
+        .qdev.size  = sizeof(struct Exynos4210CombinerState),
+        .qdev.reset = exynos4210_combiner_reset,
+        .qdev.vmsd = &VMState_Exynos4210Combiner,
+        .init = exynos4210_combiner_init,
+        .qdev.props = (Property[]) {
+            DEFINE_PROP_UINT32("external",
+                    struct Exynos4210CombinerState,
+                    external,
+                    0),
+                    DEFINE_PROP_END_OF_LIST(),
+        }
+};
+
+static void exynos4210_combiner_register_devices(void)
+{
+    sysbus_register_withprop(&exynos4210_combiner_info);
+}
+
+device_init(exynos4210_combiner_register_devices)
diff --git a/hw/exynos4210_gic.c b/hw/exynos4210_gic.c
new file mode 100644
index 0000000..f4ba6b9
--- /dev/null
+++ b/hw/exynos4210_gic.c
@@ -0,0 +1,509 @@ 
+/*
+ * Samsung exynos4210 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "sysbus.h"
+#include "qemu-common.h"
+#include "irq.h"
+#include "exynos4210.h"
+
+//#define DEBUG_EXYNOS4210_IRQ
+//#define DEBUG_EXYNOS4210_GIC
+
+#ifdef DEBUG_EXYNOS4210_IRQ
+#define DPRINTF_EXYNOS4210_IRQ(fmt, ...) \
+        do { fprintf(stdout, "IRQ_GATE: " fmt, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF_EXYNOS4210_IRQ(fmt, ...) do {} while (0)
+#endif
+
+#ifdef DEBUG_EXYNOS4210_GIC
+#define DPRINTF_EXYNOS4210_GIC(fmt, ...) \
+        do { fprintf(stdout, "EXT_GIC: " fmt, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF_EXYNOS4210_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 EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ,
+ * which is INTG16 in Internal Interrupt Combiner.
+ */
+
+static uint32_t
+combiner_grp_to_gic_id[64-EXYNOS4210_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                EXYNOS4210_MAX_CPUS
+
+#define EXYNOS4210_GIC_CPU_REGION_SIZE     0x8050
+#define EXYNOS4210_GIC_DIST_REGION_SIZE    0x8F04
+
+static void exynos4210_irq_handler(void *opaque, int irq, int level)
+{
+    Exynos4210Irq *s = (Exynos4210Irq *)opaque;
+
+    /* Bypass */
+    qemu_set_irq(s->board_irqs[irq], level);
+
+    return;
+}
+
+/*
+ * Initialize exynos4210 IRQ subsystem stub.
+ */
+qemu_irq *exynos4210_init_irq(Exynos4210Irq *s)
+{
+    return qemu_allocate_irqs(exynos4210_irq_handler, s,
+            EXYNOS4210_MAX_INT_COMBINER_IN_IRQ);
+}
+
+/*
+ * Initialize board IRQs.
+ * These IRQs contain splitted Int/External Combiner and External Gic IRQs.
+ */
+void exynos4210_init_board_irqs(Exynos4210Irq *s)
+{
+    uint32_t grp, bit, irq_id, n;
+
+    for (n = 0; n < EXYNOS4210_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 == EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 4) ||
+                n == EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 4)) {
+            /* MCT_G0 is passed to External GIC */
+            irq_id = EXT_GIC_ID_MCT_G0;
+        }
+        if (n == EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 5) ||
+                n == EXYNOS4210_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 < EXYNOS4210_MAX_INT_COMBINER_IN_IRQ; n++) {
+        /* these IDs are passed to Internal Combiner and External GIC */
+        grp = EXYNOS4210_COMBINER_GET_GRP_NUM(n);
+        bit = EXYNOS4210_COMBINER_GET_BIT_NUM(n);
+        irq_id =
+                combiner_grp_to_gic_id[grp -
+                                      EXYNOS4210_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 exynos4210 IRQ subsystem stub.
+ * To identify IRQ source use internal combiner group and bit number
+ *  grp - group number
+ *  bit - bit number inside group
+ */
+uint32_t exynos4210_get_irq(uint32_t grp, uint32_t bit)
+{
+    return EXYNOS4210_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;
+} Exynos4210GicState;
+
+static uint64_t exynos4210_gic_cpu_read(void *opaque, target_phys_addr_t offset,
+        unsigned size)
+{
+    Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+    DPRINTF_EXYNOS4210_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 exynos4210_gic_cpu_write(void *opaque, target_phys_addr_t offset,
+        uint64_t value, unsigned size)
+{
+    Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+    DPRINTF_EXYNOS4210_GIC("CPU%d: write offset 0x%x, value 0x%llx\n",
+            gic_get_current_cpu(), offset, value);
+    gic_cpu_write(&s->gic, gic_get_current_cpu(), offset & ~0x8000, value);
+}
+
+static uint32_t
+exynos4210_gic_dist_readb(void *opaque, target_phys_addr_t offset)
+{
+    Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+    DPRINTF_EXYNOS4210_GIC("DIST: readb offset 0x%x\n", offset);
+    return gic_dist_readb(&s->gic, offset & ~0x8000);
+}
+
+static uint32_t
+exynos4210_gic_dist_readw(void *opaque, target_phys_addr_t offset)
+{
+    Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+    DPRINTF_EXYNOS4210_GIC("DIST: readw offset 0x%x\n", offset);
+    return gic_dist_readw(&s->gic, offset & ~0x8000);
+}
+
+static uint32_t
+exynos4210_gic_dist_readl(void *opaque, target_phys_addr_t offset)
+{
+    Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+    DPRINTF_EXYNOS4210_GIC("DIST: readl offset 0x%x\n", offset);
+    return gic_dist_readl(&s->gic, offset & ~0x8000);
+}
+
+static void
+exynos4210_gic_dist_writeb(void *opaque, target_phys_addr_t offset,
+        uint32_t value)
+{
+    Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+    DPRINTF_EXYNOS4210_GIC("DIST: writeb offset 0x%x, value 0x%x\n", offset,
+            value);
+    gic_dist_writeb(&s->gic, offset & ~0x8000, value);
+}
+
+static void exynos4210_gic_dist_writew(void *opaque, target_phys_addr_t offset,
+        uint32_t value)
+{
+    Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+    DPRINTF_EXYNOS4210_GIC("DIST: writew offset 0x%x, value 0x%x\n", offset,
+            value);
+    gic_dist_writew(&s->gic, offset & ~0x8000, value);
+}
+
+static void exynos4210_gic_dist_writel(void *opaque, target_phys_addr_t offset,
+        uint32_t value)
+{
+    Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+    DPRINTF_EXYNOS4210_GIC("DIST: writel offset 0x%x, value 0x%x\n", offset,
+            value);
+    gic_dist_writel(&s->gic, offset & ~0x8000, value);
+}
+
+static const MemoryRegionOps exynos4210_gic_cpu_ops = {
+        .read = exynos4210_gic_cpu_read,
+        .write = exynos4210_gic_cpu_write,
+        .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const MemoryRegionOps exynos4210_gic_dist_ops = {
+        .old_mmio = {
+                .read = { exynos4210_gic_dist_readb,
+                        exynos4210_gic_dist_readw,
+                        exynos4210_gic_dist_readl, },
+                        .write = { exynos4210_gic_dist_writeb,
+                                exynos4210_gic_dist_writew,
+                                exynos4210_gic_dist_writel, },
+        },
+        .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int exynos4210_gic_init(SysBusDevice *dev)
+{
+    Exynos4210GicState *s = FROM_SYSBUSGIC(Exynos4210GicState, dev);
+    gic_init(&s->gic, s->num_cpu);
+
+    memory_region_init(&s->cpu_container, "exynos4210-gic-cpu_container",
+            EXYNOS4210_GIC_CPU_REGION_SIZE);
+    memory_region_init(&s->dist_container, "exynos4210-gic-dist_container",
+            EXYNOS4210_GIC_DIST_REGION_SIZE);
+    memory_region_init_io(&s->cpu_container, &exynos4210_gic_cpu_ops, &s->gic,
+            "exynos4210-gic-cpu", EXYNOS4210_GIC_CPU_REGION_SIZE);
+    memory_region_init_io(&s->dist_container, &exynos4210_gic_dist_ops, &s->gic,
+            "exynos4210-gic-dist", EXYNOS4210_GIC_DIST_REGION_SIZE);
+
+    sysbus_init_mmio(dev, &s->cpu_container);
+    sysbus_init_mmio(dev, &s->dist_container);
+
+    gic_cpu_write(&s->gic, 1, 0, 1);
+    return 0;
+}
+
+static SysBusDeviceInfo exynos4210_gic_info = {
+        .init = exynos4210_gic_init,
+        .qdev.name  = "exynos4210.gic",
+        .qdev.size  = sizeof(Exynos4210GicState),
+        .qdev.props = (Property[]) {
+            DEFINE_PROP_UINT32("num-cpu", Exynos4210GicState, num_cpu, 1),
+                    DEFINE_PROP_END_OF_LIST(),
+        }
+};
+
+static void exynos4210_gic_register_devices(void)
+{
+    sysbus_register_withprop(&exynos4210_gic_info);
+}
+
+device_init(exynos4210_gic_register_devices)
+
+/*
+ * IRQGate struct.
+ * IRQ Gate represents OR gate between GICs to pass IRQ to PIC.
+ */
+typedef struct {
+    SysBusDevice busdev;
+
+    qemu_irq pic_irq[NCPU]; /* output IRQs to PICs */
+    uint32_t gpio_level[EXYNOS4210_IRQ_GATE_NINPUTS]; /* Input levels */
+} Exynos4210IRQGateState;
+
+static const VMStateDescription VMState_Exynos4210IRQGate = {
+        .name = "exynos4210.irq_gate",
+        .version_id = 1,
+        .minimum_version_id = 1,
+        .minimum_version_id_old = 1,
+        .fields = (VMStateField[]) {
+            VMSTATE_UINT32_ARRAY(gpio_level, Exynos4210IRQGateState,
+                    EXYNOS4210_IRQ_GATE_NINPUTS),
+            VMSTATE_END_OF_LIST()
+        }
+};
+
+/* Process a change in an external IRQ input.  */
+static void exynos4210_irq_gate_handler(void *opaque, int irq, int level)
+{
+    Exynos4210IRQGateState *s =
+            (Exynos4210IRQGateState *)opaque;
+    uint32_t odd, even;
+
+    if (irq & 1) {
+        odd = irq;
+        even = irq & ~1;
+    } else {
+        even = irq;
+        odd = irq | 1;
+    }
+
+    assert(irq < EXYNOS4210_IRQ_GATE_NINPUTS);
+    s->gpio_level[irq] = level;
+
+    DPRINTF_EXYNOS4210_IRQ("odd level=0x%x, even level=0x%x\n",
+            s->gpio_level[odd], s->gpio_level[even]);
+
+    if (s->gpio_level[odd] >= 1 || s->gpio_level[even] >= 1) {
+        qemu_irq_raise(s->pic_irq[even >> 1]);
+    } else {
+        qemu_irq_lower(s->pic_irq[even >> 1]);
+    }
+
+    return;
+}
+
+static void exynos4210_irq_gate_reset(DeviceState *d)
+{
+    Exynos4210IRQGateState *s = (Exynos4210IRQGateState *)d;
+
+    memset(&s->gpio_level, 0, sizeof(s->gpio_level));
+}
+
+/*
+ * IRQ Gate initialization.
+ */
+static int exynos4210_irq_gate_init(SysBusDevice *dev)
+{
+    unsigned int i;
+    Exynos4210IRQGateState *s =
+            FROM_SYSBUS(Exynos4210IRQGateState, dev);
+
+    /* Allocate general purpose input signals and connect a handler to each of
+     * them */
+    qdev_init_gpio_in(&s->busdev.qdev, exynos4210_irq_gate_handler,
+            EXYNOS4210_IRQ_GATE_NINPUTS);
+
+    /* Connect SysBusDev irqs to device specific irqs */
+    for (i = 0; i < NCPU; i++) {
+        sysbus_init_irq(dev, &s->pic_irq[i]);
+    }
+
+    return 0;
+}
+
+static SysBusDeviceInfo exynos4210_irq_gate_info = {
+        .qdev.name  = "exynos4210.irq_gate",
+        .qdev.size  = sizeof(Exynos4210IRQGateState),
+        .qdev.reset = exynos4210_irq_gate_reset,
+        .qdev.vmsd = &VMState_Exynos4210IRQGate,
+        .init = exynos4210_irq_gate_init,
+};
+
+static void exynos4210_irq_gate_register_devices(void)
+{
+    sysbus_register_withprop(&exynos4210_irq_gate_info);
+}
+
+device_init(exynos4210_irq_gate_register_devices)