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

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

Comments

Evgeny Voevodin - Dec. 23, 2011, 11:40 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      |  437 ++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 1044 insertions(+), 5 deletions(-)
 create mode 100644 hw/exynos4210_combiner.c
 create mode 100644 hw/exynos4210_gic.c
Peter Maydell - Jan. 10, 2012, 12:35 p.m.
On 23 December 2011 11:40, Evgeny Voevodin <e.voevodin@samsung.com> wrote:
>
> 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      |  437 ++++++++++++++++++++++++++++++++++++++++++++++
>  5 files changed, 1044 insertions(+), 5 deletions(-)
>  create mode 100644 hw/exynos4210_combiner.c
>  create mode 100644 hw/exynos4210_gic.c
>
> diff --git a/Makefile.target b/Makefile.target
> index 0246947..ccd1f79 100644
> --- a/Makefile.target
> +++ b/Makefile.target
> @@ -336,7 +336,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

Don't use the '\' line continuation, just start a new obj-arm-y += line.

> +    for (n = 0; n < max; n++) {
> +
> +        bit = EXYNOS4210_COMBINER_GET_BIT_NUM(n);
> +
> +        switch (n) {
> +        /* MDNIE_LCD1 INTG1*/

Space before the "*/", please.

> +        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;

The 'break' here is unreachable, right?
Also the indentation is off: the irq[n] line should start 4 chars
in from the 'case', not 8, and the 'continue' or 'break' should
too.

> @@ -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)]);

Having to modify the UART creation to add the IRQ line later suggests
this patch series isn't in quite the right order. My suggestion:

1. hw/sysbus.h: Increase maximum number of device IRQs
2. hw/arm_boot.c: Extend secondary CPU bootloader.
3. ARM: exynos4210: IRQ subsystem support.
4. ARM: Samsung exynos4210-based boards emulation
   [fold hw/exynos4210.c: Boot secondary CPU. into this patch]
5. ARM: exynos4210: UART support
6. ARM: exynos4210: PWM support.
7. hw/lan9118: Add basic 16-bit mode support.
8. hw/exynos4210.c: Add LAN support for SMDKC210.
9. Exynos4210: added display controller implementation


> +++ 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/>.
> + */

I think it would be good to have a paragraph here explaining what
a combiner actually is, for the benefit of people not familiar
with the SoC.

> +static const MemoryRegionOps exynos4210_combiner_ops = {
> +        .read = exynos4210_combiner_read,
> +        .write = exynos4210_combiner_write,
> +        .endianness = DEVICE_NATIVE_ENDIAN,
> +};

Indentation is wrong here.

> +
> +/*
> + * 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);

You don't need to explicitly reset in the qdev init function if you've
registered the reset function as qdev.reset (as you have).

> +    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(),
> +        }
> +};

More dodgy indentation -- can you go through and do a general check,
please?


> +        memory_region_add_subregion(&s->cpu_container,
> +                EXYNOS4210_EXT_GIC_CPU_GET_OFFSET(i), &s->cpu_alias[i]);
> +
> +        /* Map Distributer per SMP Core */

"Distributor".

-- PMM

Patch

diff --git a/Makefile.target b/Makefile.target
index 0246947..ccd1f79 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -336,7 +336,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..9358c5a
--- /dev/null
+++ b/hw/exynos4210_gic.c
@@ -0,0 +1,437 @@ 
+/*
+ * 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    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_EXT_GIC_CPU_REGION_SIZE     0x10000
+#define EXYNOS4210_EXT_GIC_DIST_REGION_SIZE    0x10000
+
+#define EXYNOS4210_EXT_GIC_PER_CPU_OFFSET      0x8000
+#define EXYNOS4210_EXT_GIC_CPU_GET_OFFSET(n) \
+                                        (n * EXYNOS4210_EXT_GIC_PER_CPU_OFFSET)
+#define EXYNOS4210_EXT_GIC_DIST_GET_OFFSET(n) \
+                                        (n * EXYNOS4210_EXT_GIC_PER_CPU_OFFSET)
+
+#define EXYNOS4210_GIC_CPU_REGION_SIZE          0x100
+#define EXYNOS4210_GIC_DIST_REGION_SIZE         0x1000
+
+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;
+    MemoryRegion cpu_alias[NCPU];
+    MemoryRegion dist_alias[NCPU];
+    uint32_t num_cpu;
+} Exynos4210GicState;
+
+static int exynos4210_gic_init(SysBusDevice *dev)
+{
+    Exynos4210GicState *s = FROM_SYSBUSGIC(Exynos4210GicState, dev);
+    uint32_t i;
+    const char cpu_prefix[] = "exynos4210-gic-alias_cpu";
+    const char dist_prefix[] = "exynos4210-gic-alias_dist";
+    char cpu_alias_name[sizeof(cpu_prefix) + 3];
+    char dist_alias_name[sizeof(cpu_prefix) + 3];
+
+    gic_init(&s->gic, s->num_cpu);
+
+    memory_region_init(&s->cpu_container, "exynos4210-cpu-container",
+            EXYNOS4210_EXT_GIC_CPU_REGION_SIZE);
+    memory_region_init(&s->dist_container, "exynos4210-dist-container",
+            EXYNOS4210_EXT_GIC_DIST_REGION_SIZE);
+
+    for (i = 0; i < s->num_cpu; i++) {
+        /* Map CPU interface per SMP Core */
+        sprintf(cpu_alias_name, "%s%x", cpu_prefix, i);
+        memory_region_init_alias(&s->cpu_alias[i],
+                                 cpu_alias_name,
+                                 &s->gic.cpuiomem[0],
+                                 0,
+                                 EXYNOS4210_GIC_CPU_REGION_SIZE);
+        memory_region_add_subregion(&s->cpu_container,
+                EXYNOS4210_EXT_GIC_CPU_GET_OFFSET(i), &s->cpu_alias[i]);
+
+        /* Map Distributer per SMP Core */
+        sprintf(dist_alias_name, "%s%x", dist_prefix, i);
+        memory_region_init_alias(&s->dist_alias[i],
+                                 dist_alias_name,
+                                 &s->gic.iomem,
+                                 0,
+                                 EXYNOS4210_GIC_DIST_REGION_SIZE);
+        memory_region_add_subregion(&s->dist_container,
+                EXYNOS4210_EXT_GIC_DIST_GET_OFFSET(i), &s->dist_alias[i]);
+    }
+
+    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;
+
+    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)