Patchwork [4/5] exynos4210: add exynos4210 GPIO implementation

login
register
mail settings
Submitter Mitsyanko Igor
Date March 2, 2012, 11:35 a.m.
Message ID <1330688145-22944-5-git-send-email-i.mitsyanko@samsung.com>
Download mbox | patch
Permalink /patch/144225/
State New
Headers show

Comments

Mitsyanko Igor - March 2, 2012, 11:35 a.m.
Now that we have GPIO emulation for exynos4210 SoC we can use it to
properly hook up IRQ line to lan9215 controller on SMDK board.

Signed-off-by: Igor Mitsyanko <i.mitsyanko@samsung.com>
---
 Makefile.target      |    2 +-
 hw/exynos4210.c      |   46 ++
 hw/exynos4210.h      |   64 +++
 hw/exynos4210_gpio.c | 1117 ++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/exynos4_boards.c  |    2 +-
 5 files changed, 1229 insertions(+), 2 deletions(-)
 create mode 100644 hw/exynos4210_gpio.c

Patch

diff --git a/Makefile.target b/Makefile.target
index 3b309d9..7968120 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -348,7 +348,7 @@  obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
 obj-arm-y += exynos4210_gic.o exynos4210_combiner.o exynos4210.o
 obj-arm-y += exynos4_boards.o exynos4210_uart.o exynos4210_pwm.o
 obj-arm-y += exynos4210_pmu.o exynos4210_mct.o exynos4210_fimd.o
-obj-arm-y += exynos4210_i2c.o
+obj-arm-y += exynos4210_i2c.o exynos4210_gpio.o
 obj-arm-y += arm_l2x0.o
 obj-arm-y += arm_mptimer.o a15mpcore.o
 obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index 464f157..9a32eb8 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -35,6 +35,12 @@ 
 /* MCT */
 #define EXYNOS4210_MCT_BASE_ADDR       0x10050000
 
+/* GPIO */
+#define EXYNOS4210_GPIO1_BASE_ADDR     0x11400000
+#define EXYNOS4210_GPIO2_BASE_ADDR     0x11000000
+#define EXYNOS4210_GPIO2X_BASE_ADDR    0x11000C00
+#define EXYNOS4210_GPIO3_BASE_ADDR     0x03860000
+
 /* I2C */
 #define EXYNOS4210_I2C_SHIFT           0x00010000
 #define EXYNOS4210_I2C_BASE_ADDR       0x13860000
@@ -249,6 +255,46 @@  Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
             s->irq_table[exynos4210_get_irq(35, 3)]);
     sysbus_mmio_map(busdev, 0, EXYNOS4210_MCT_BASE_ADDR);
 
+    /* GPIO */
+    s->gpio1 = qdev_create(NULL, "exynos4210.gpio1");
+    qdev_init_nofail(s->gpio1);
+    busdev = sysbus_from_qdev(s->gpio1);
+    sysbus_connect_irq(busdev, 0, s->irq_table[exynos4210_get_irq(24, 1)]);
+    sysbus_mmio_map(busdev, 0, EXYNOS4210_GPIO1_BASE_ADDR);
+
+    s->gpio2 = qdev_create(NULL, "exynos4210.gpio2");
+    qdev_init_nofail(s->gpio2);
+    busdev = sysbus_from_qdev(s->gpio2);
+    sysbus_connect_irq(busdev, 0, s->irq_table[exynos4210_get_irq(24, 0)]);
+    sysbus_mmio_map(busdev, 0, EXYNOS4210_GPIO2X_BASE_ADDR);
+
+    s->gpio2x = qdev_create(NULL, "exynos4210.gpio2x");
+    qdev_init_nofail(s->gpio2x);
+    busdev = sysbus_from_qdev(s->gpio2x);
+    sysbus_connect_irq(busdev, 0, s->irq_table[exynos4210_get_irq(40, 0)]);
+    sysbus_connect_irq(busdev, 1, s->irq_table[exynos4210_get_irq(41, 0)]);
+    sysbus_connect_irq(busdev, 2, s->irq_table[exynos4210_get_irq(42, 0)]);
+    sysbus_connect_irq(busdev, 3, s->irq_table[exynos4210_get_irq(43, 0)]);
+    sysbus_connect_irq(busdev, 4, s->irq_table[exynos4210_get_irq(37, 0)]);
+    sysbus_connect_irq(busdev, 5, s->irq_table[exynos4210_get_irq(37, 1)]);
+    sysbus_connect_irq(busdev, 6, s->irq_table[exynos4210_get_irq(37, 2)]);
+    sysbus_connect_irq(busdev, 7, s->irq_table[exynos4210_get_irq(37, 3)]);
+    sysbus_connect_irq(busdev, 8, s->irq_table[exynos4210_get_irq(38, 0)]);
+    sysbus_connect_irq(busdev, 9, s->irq_table[exynos4210_get_irq(38, 1)]);
+    sysbus_connect_irq(busdev, 10, s->irq_table[exynos4210_get_irq(38, 2)]);
+    sysbus_connect_irq(busdev, 11, s->irq_table[exynos4210_get_irq(38, 3)]);
+    sysbus_connect_irq(busdev, 12, s->irq_table[exynos4210_get_irq(38, 4)]);
+    sysbus_connect_irq(busdev, 13, s->irq_table[exynos4210_get_irq(38, 5)]);
+    sysbus_connect_irq(busdev, 14, s->irq_table[exynos4210_get_irq(38, 6)]);
+    sysbus_connect_irq(busdev, 15, s->irq_table[exynos4210_get_irq(38, 7)]);
+    sysbus_connect_irq(busdev, 16, s->irq_table[exynos4210_get_irq(39, 0)]);
+    sysbus_mmio_map(busdev, 0, EXYNOS4210_GPIO2X_BASE_ADDR);
+
+    s->gpio3 = qdev_create(NULL, "exynos4210.gpio3");
+    qdev_init_nofail(s->gpio3);
+    busdev = sysbus_from_qdev(s->gpio3);
+    sysbus_mmio_map(busdev, 0, EXYNOS4210_GPIO3_BASE_ADDR);
+
     /*** I2C ***/
     for (n = 0; n < EXYNOS4210_I2C_NUMBER; n++) {
         uint32_t addr = EXYNOS4210_I2C_BASE_ADDR + EXYNOS4210_I2C_SHIFT * n;
diff --git a/hw/exynos4210.h b/hw/exynos4210.h
index 07cc579..55b34a6 100644
--- a/hw/exynos4210.h
+++ b/hw/exynos4210.h
@@ -76,6 +76,69 @@ 
 
 #define EXYNOS4210_I2C_NUMBER               9
 
+/*
+ * Exynos4210 general purpose input/output (GPIO)
+ */
+
+/* GPIO part 1 port group numbers */
+#define EXYNOS4210_GPIO1_GPA0               0
+#define EXYNOS4210_GPIO1_GPA1               1
+#define EXYNOS4210_GPIO1_GPB                2
+#define EXYNOS4210_GPIO1_GPC0               3
+#define EXYNOS4210_GPIO1_GPC1               4
+#define EXYNOS4210_GPIO1_GPD0               5
+#define EXYNOS4210_GPIO1_GPD1               6
+#define EXYNOS4210_GPIO1_GPE0               7
+#define EXYNOS4210_GPIO1_GPE1               8
+#define EXYNOS4210_GPIO1_GPE2               9
+#define EXYNOS4210_GPIO1_GPE3               10
+#define EXYNOS4210_GPIO1_GPE4               11
+#define EXYNOS4210_GPIO1_GPF0               12
+#define EXYNOS4210_GPIO1_GPF1               13
+#define EXYNOS4210_GPIO1_GPF2               14
+#define EXYNOS4210_GPIO1_GPF3               15
+#define EXYNOS4210_GPIO1_ETC0               16
+#define EXYNOS4210_GPIO1_ETC1               17
+
+/* GPIO part 2 port group numbers */
+#define EXYNOS4210_GPIO2_GPJ0               0
+#define EXYNOS4210_GPIO2_GPJ1               1
+#define EXYNOS4210_GPIO2_GPK0               2
+#define EXYNOS4210_GPIO2_GPK1               3
+#define EXYNOS4210_GPIO2_GPK2               4
+#define EXYNOS4210_GPIO2_GPK3               5
+#define EXYNOS4210_GPIO2_GPL0               6
+#define EXYNOS4210_GPIO2_GPL1               7
+#define EXYNOS4210_GPIO2_GPL2               8
+#define EXYNOS4210_GPIO2_GPY0               9
+#define EXYNOS4210_GPIO2_GPY1               10
+#define EXYNOS4210_GPIO2_GPY2               11
+#define EXYNOS4210_GPIO2_GPY3               12
+#define EXYNOS4210_GPIO2_GPY4               13
+#define EXYNOS4210_GPIO2_GPY5               14
+#define EXYNOS4210_GPIO2_GPY6               15
+#define EXYNOS4210_GPIO2_ETC6               16
+/* GPIO part 2 X port groups numbers */
+#define EXYNOS4210_GPIO2X_GPX0              0
+#define EXYNOS4210_GPIO2X_GPX1              1
+#define EXYNOS4210_GPIO2X_GPX2              2
+#define EXYNOS4210_GPIO2X_GPX3              3
+
+/* GPIO part 3 port group numbers */
+#define EXYNOS4210_GPIO3_GPZ                0
+
+/* Get GPIO line number from GPIO port group name and group pin number */
+#define EXYNOS4210_GPIO_MAX_PIN_IN_PORT     8
+#define EXYNOS4210_GPIO1_LINE(group, pin) \
+    ((EXYNOS4210_GPIO1_##group * EXYNOS4210_GPIO_MAX_PIN_IN_PORT) + (pin))
+#define EXYNOS4210_GPIO2_LINE(group, pin) \
+    ((EXYNOS4210_GPIO2_##group * EXYNOS4210_GPIO_MAX_PIN_IN_PORT) + (pin))
+#define EXYNOS4210_GPIO2X_LINE(group, pin) \
+    ((EXYNOS4210_GPIO2X_##group * EXYNOS4210_GPIO_MAX_PIN_IN_PORT) + (pin))
+#define EXYNOS4210_GPIO3_LINE(group, pin) \
+    ((EXYNOS4210_GPIO3_##group * EXYNOS4210_GPIO_MAX_PIN_IN_PORT) + (pin))
+
+
 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];
@@ -98,6 +161,7 @@  typedef struct Exynos4210State {
     MemoryRegion boot_secondary;
     MemoryRegion bootreg_mem;
     i2c_bus *i2c_if[EXYNOS4210_I2C_NUMBER];
+    DeviceState *gpio1, *gpio2, *gpio2x, *gpio3;
 } Exynos4210State;
 
 Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
diff --git a/hw/exynos4210_gpio.c b/hw/exynos4210_gpio.c
new file mode 100644
index 0000000..5dd5387
--- /dev/null
+++ b/hw/exynos4210_gpio.c
@@ -0,0 +1,1117 @@ 
+/*
+ *  Exynos4210 General Purpose Input/Output (GPIO) Emulation
+ *
+ *  Copyright (C) 2012 Samsung Electronics Co Ltd.
+ *    Maksim Kozlov, <m.kozlov@samsung.com>
+ *    Igor Mitsyanko, <i.mitsyanko@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 "qemu-common.h"
+#include "sysbus.h"
+#include "qdev.h"
+#include "irq.h"
+
+/* Debug messages configuration */
+#define EXYNOS4210_GPIO_DEBUG             0
+
+#if EXYNOS4210_GPIO_DEBUG == 0
+#define DPRINT_L1(fmt, args...)       do { } while (0)
+#define DPRINT_L2(fmt, args...)       do { } while (0)
+#define DPRINT_ERROR(fmt, args...)    do { } while (0)
+#elif EXYNOS4210_GPIO_DEBUG == 1
+#define DPRINT_L1(fmt, args...)       \
+do {fprintf(stderr, "QEMU GPIO: "fmt, ## args); } while (0)
+#define DPRINT_L2(fmt, args...)       do { } while (0)
+#define DPRINT_ERROR(fmt, args...)    \
+do {fprintf(stderr, "QEMU GPIO ERROR: "fmt, ## args); } while (0)
+#else
+#define DPRINT_L1(fmt, args...)       \
+do {fprintf(stderr, "QEMU GPIO: "fmt, ## args); } while (0)
+#define DPRINT_L2(fmt, args...)       \
+do {fprintf(stderr, "QEMU GPIO: "fmt, ## args); } while (0)
+#define DPRINT_ERROR(fmt, args...)    \
+do {fprintf(stderr, "QEMU GPIO ERROR: "fmt, ## args); } while (0)
+#endif
+
+
+#define TYPE_EXYNOS4210_GPIO       "exynos4210.gpio"
+#define TYPE_EXYNOS4210_GPIO_1_2   "exynos4210.gpio-1_2"
+#define TYPE_EXYNOS4210_GPIO_2X    "exynos4210.gpio2x"
+#define EXYNOS4210_GPIO(obj)       \
+OBJECT_CHECK(Exynos4GpioState, (obj), TYPE_EXYNOS4210_GPIO)
+#define EXYNOS4210_GPIO_1_2(obj)   \
+OBJECT_CHECK(Exynos4Gpio12State, (obj), TYPE_EXYNOS4210_GPIO_1_2)
+#define EXYNOS4210_GPIO2X(obj)     \
+OBJECT_CHECK(Exynos4Gpio2XState, (obj), TYPE_EXYNOS4210_GPIO_2X)
+
+#define GPIO1_REGS_MEM_SIZE  0x0b54
+#define GPIO2_REGS_MEM_SIZE  0x0b38
+#define GPIO2X_REGS_MEM_SIZE 0x0350
+#define GPIO3_REGS_MEM_SIZE  0x0018
+
+/* Port group registers offsets */
+#define GPIOCON              0x0000 /* Port Group Configuration Register */
+#define GPIODAT              0x0004 /* Port Group Data Register */
+#define GPIOPUD              0x0008 /* Port Group Pull-up/down Register */
+#define GPIODRV              0x000C /* Port Group Drive Strength Ctrl Reg */
+#define GPIOCONPDN           0x0010 /* Port Pwr Down Mode Config Reg */
+#define GPIOPUDPDN           0x0014 /* Port Pwr Down Mode Pullup/down Reg */
+
+/* GPIO pin functions */
+#define GPIOCON_IN           0x0    /* input pin */
+#define GPIOCON_OUT          0x1    /* output pin */
+#define GPIOCON_EXTINT       0xf    /* external interrupt pin */
+
+/* External interrupts signaling methods */
+#define GPIO_INTCON_LOW            0x0    /* interrupt on low level */
+#define GPIO_INTCON_HIGH           0x1    /* interrupt on high level */
+#define GPIO_INTCON_FALL           0x2    /* interrupt on falling edge */
+#define GPIO_INTCON_RISE           0x3    /* interrupt on rising edge */
+#define GPIO_INTCON_FALLRISE       0x4    /* interrupt on both edges */
+
+/*
+ * Code assumes that each GPIO port group (GPA0, GPA1, e.t.c.)
+ * has 8 pins. When calculating GPIO line number to pass to function
+ * qdev_get_gpio_in() or qdev_connect_gpio_out(), you must assume the same,
+ * even though it's not true for real hardware.
+ */
+#define GPIO_MAX_PIN_IN_PORT       8
+#define GPIO_PULLUP_STATE          0x3
+#define GPIO_PORTGR_SIZE           0x20
+#define DIV_BY_PORTGR_SIZE(x)      ((x) >> 5)
+#define MOD_PORTGR_SIZE(x)         ((x) & (GPIO_PORTGR_SIZE - 1))
+#define GPIO_NORM_PORT_END         0x0200 /* norm ports mem area end */
+#define GPIO_ETCPORT_END           0x0240 /* etc ports mem area end */
+#define GPIO_EXTINT_CON_START      0x0700 /* extint con mem area start */
+#define GPIO_EXTINT_FLT_START      0x0800 /* extint filter mem area start */
+#define GPIO_EXTINT_MASK_START     0x0900 /* extint mask mem area start */
+#define GPIO_EXTINT_PEND_START     0x0A00 /* extint pend mem area start */
+#define GPIO_EXTINT_SERVICE        0x0B08 /* Current Service Register */
+#define GPIO_EXTINT_SERVICE_PEND   0x0B0C /* Current Service Pending Reg */
+#define GPIO_EXTINT_GRPFIXPRI      0x0B10 /* Ext Int Fixed Priority Ctrl */
+#define GPIO_EXTINT_FIXPRI_START   0x0B14 /* extint fixpri mem area start */
+
+/* GPIO part 1 specific defines */
+#define GPIO1_NORM_PORT_NUM        16
+#define GPIO1_ETC_PORT_NUM         2
+#define GPIO1_NUM_OF_PORTS         (GPIO1_NORM_PORT_NUM + GPIO1_ETC_PORT_NUM)
+#define GPIO1_PORTINT_NUM          GPIO1_NORM_PORT_NUM
+#define GPIO1_ETCPORT_START        0x0200
+#define GPIO1_EXTINT_CON_END       0x073C
+#define GPIO1_EXTINT_FLT_END       0x087C
+#define GPIO1_EXTINT_MASK_END      0x093C
+#define GPIO1_EXTINT_PEND_END      0x0A3C
+#define GPIO1_EXTINT_FIXPRI_END    0x0B50
+
+/* GPIO part 2 specific defines */
+#define GPIO2_NORM_PORT_NUM        16
+#define GPIO2_ETC_PORT_NUM         1
+#define GPIO2_NUM_OF_PORTS         (GPIO2_NORM_PORT_NUM + GPIO2_ETC_PORT_NUM)
+#define GPIO2_PORTINT_NUM          9
+#define GPIO2_ETCPORT_START        0x0220
+#define GPIO2_EXTINT_CON_END       0x0724
+#define GPIO2_EXTINT_FLT_END       0x0848
+#define GPIO2_EXTINT_MASK_END      0x0924
+#define GPIO2_EXTINT_PEND_END      0x0A24
+#define GPIO2_EXTINT_FIXPRI_END    0x0B38
+
+/* GPIO part2 XPORT specific defines
+ * In Exynos documentation X ports are a part of GPIO part2, but we separate
+ * them to simplify implementation */
+#define GPIO2_X_PORT_NUM           4
+#define GPIO2_X_PORTINT_NUM        GPIO2_X_PORT_NUM
+#define GPIO2_X_PORT_IRQ_NUM       17
+#define GPIO2_XPORT_END            0x0080
+#define GPIO2_WKPINT_CON_START     0x0200
+#define GPIO2_WKPINT_CON_END       0x0210
+#define GPIO2_WKPINT_FLT_START     0x0280
+#define GPIO2_WKPINT_FLT_END       0x02A0
+#define GPIO2_WKPINT_MASK_START    0x0300
+#define GPIO2_WKPINT_MASK_END      0x0310
+#define GPIO2_WKPINT_PEND_START    0x0340
+#define GPIO2_WKPINT_PEND_END      0x0350
+
+/* GPIO part 3 specific defines */
+#define GPIO3_NUM_OF_PORTS         1
+#define GPIO3_NORM_PORT_END        0x0020
+
+typedef enum {
+    GPIO_PART2X = 0,
+    GPIO_PART1,
+    GPIO_PART2,
+    GPIO_PART3,
+} Exynos4210GpioPart;
+
+typedef struct Exynos4PortGroup {
+    uint32_t con;                /* configuration register */
+    uint32_t dat;                /* data register */
+    uint32_t pud;                /* pull-up/down register */
+    uint32_t drv;                /* drive strength control register */
+    uint32_t conpdn;             /* configuration register in power down mode */
+    uint32_t pudpdn;             /* pull-up/down register in power down mode */
+
+    const char *name;            /* port specific name */
+    const uint32_t def_con;      /* default value for configuration register */
+    const uint32_t def_pud;      /* default value for pull-up/down register */
+    const uint32_t def_drv;      /* default value for drive strength control */
+} Exynos4PortGroup;
+
+static const VMStateDescription exynos4_gpio_portgroup_vmstate = {
+    .name = "exynos4210.gpio-port",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields      = (VMStateField[]) {
+        VMSTATE_UINT32(con, Exynos4PortGroup),
+        VMSTATE_UINT32(dat, Exynos4PortGroup),
+        VMSTATE_UINT32(pud, Exynos4PortGroup),
+        VMSTATE_UINT32(drv, Exynos4PortGroup),
+        VMSTATE_UINT32(conpdn, Exynos4PortGroup),
+        VMSTATE_UINT32(pudpdn, Exynos4PortGroup),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+typedef struct Exynos4PortIntState {
+    uint32_t con;                /* configuration register */
+    uint32_t fltcon[2];          /* filter configuraton registers 1,2 */
+    uint32_t mask;               /* mask register */
+    uint32_t pend;               /* interrupt pending register */
+    uint32_t fixpri;             /* fixed priority control register */
+
+    const uint32_t def_mask;     /* default value for mask register */
+    const uint8_t int_line_num;  /* external interrupt line number */
+} Exynos4PortIntState;
+
+static const VMStateDescription exynos4_gpio_portint_vmstate = {
+    .name = "exynos4210.gpio-int",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields      = (VMStateField[]) {
+        VMSTATE_UINT32(con, Exynos4PortIntState),
+        VMSTATE_UINT32(con, Exynos4PortIntState),
+        VMSTATE_UINT32_ARRAY(fltcon, Exynos4PortIntState, 2),
+        VMSTATE_UINT32(mask, Exynos4PortIntState),
+        VMSTATE_UINT32(pend, Exynos4PortIntState),
+        VMSTATE_UINT32(fixpri, Exynos4PortIntState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static Exynos4PortGroup gpio1_ports[GPIO1_NUM_OF_PORTS] = {
+    { .name = "A0", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+    { .name = "A1", .def_con = 0, .def_pud = 0x0555, .def_drv = 0 },
+    { .name = "B",  .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+    { .name = "C0", .def_con = 0, .def_pud = 0x0155, .def_drv = 0 },
+    { .name = "C1", .def_con = 0, .def_pud = 0x0155, .def_drv = 0 },
+    { .name = "D0", .def_con = 0, .def_pud = 0x0055, .def_drv = 0 },
+    { .name = "D1", .def_con = 0, .def_pud = 0x0055, .def_drv = 0 },
+    { .name = "E0", .def_con = 0, .def_pud = 0x0155, .def_drv = 0 },
+    { .name = "E1", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+    { .name = "E2", .def_con = 0, .def_pud = 0x0555, .def_drv = 0 },
+    { .name = "E3", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+    { .name = "E4", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+    { .name = "F0", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+    { .name = "F1", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+    { .name = "F2", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+    { .name = "F3", .def_con = 0, .def_pud = 0x0555, .def_drv = 0 },
+    { .name = "ETC0", .def_con = 0, .def_pud = 0x0400, .def_drv = 0 },
+    { .name = "ETC1", .def_con = 0, .def_pud = 0x005, .def_drv = 0 },
+};
+
+/* All pins of all port groups of GPIO part1 share single GPIO IRQ line */
+static Exynos4PortIntState gpio1_ports_interrupts[GPIO1_PORTINT_NUM] = {
+    { .int_line_num = 1, .def_mask = 0x000000FF },
+    { .int_line_num = 2, .def_mask = 0x0000003F },
+    { .int_line_num = 3, .def_mask = 0x000000FF },
+    { .int_line_num = 4, .def_mask = 0x0000001F },
+    { .int_line_num = 5, .def_mask = 0x0000001F },
+    { .int_line_num = 6, .def_mask = 0x0000000F },
+    { .int_line_num = 7, .def_mask = 0x0000000F },
+    { .int_line_num = 8, .def_mask = 0x0000001F },
+    { .int_line_num = 9, .def_mask = 0x000000FF },
+    { .int_line_num = 10, .def_mask = 0x0000003F },
+    { .int_line_num = 11, .def_mask = 0x000000FF },
+    { .int_line_num = 12, .def_mask = 0x000000FF },
+    { .int_line_num = 13, .def_mask = 0x000000FF },
+    { .int_line_num = 14, .def_mask = 0x000000FF },
+    { .int_line_num = 15, .def_mask = 0x000000FF },
+    { .int_line_num = 16, .def_mask = 0x0000003F },
+};
+
+static Exynos4PortGroup gpio2_ports[GPIO2_NUM_OF_PORTS] = {
+    { .name = "J0", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+    { .name = "J1", .def_con = 0, .def_pud = 0x0155, .def_drv = 0 },
+    { .name = "K0", .def_con = 0, .def_pud = 0x1555, .def_drv = 0x002AAA },
+    { .name = "K1", .def_con = 0, .def_pud = 0x1555, .def_drv = 0 },
+    { .name = "K2", .def_con = 0, .def_pud = 0x1555, .def_drv = 0 },
+    { .name = "K3", .def_con = 0, .def_pud = 0x1555, .def_drv = 0 },
+    { .name = "L0", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+    { .name = "L1", .def_con = 0, .def_pud = 0x0015, .def_drv = 0 },
+    { .name = "L2", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+    { .name = "Y0", .def_con = 0x00225522, .def_pud = 0, .def_drv = 0x000AAA },
+    { .name = "Y1", .def_con = 0x00002222, .def_pud = 0, .def_drv = 0x0000AA },
+    { .name = "Y2", .def_con = 0x00255555, .def_pud = 0, .def_drv = 0x000AAA },
+    { .name = "Y3", .def_con = 0x22222222, .def_pud = 0, .def_drv = 0x00AAAA },
+    { .name = "Y4", .def_con = 0x22222222, .def_pud = 0, .def_drv = 0x00AAAA },
+    { .name = "Y5", .def_con = 0x22222222, .def_pud = 0, .def_drv = 0x00AAAA },
+    { .name = "Y6", .def_con = 0x22222222, .def_pud = 0, .def_drv = 0x00AAAA },
+    { .name = "ETC6", .def_con = 0, .def_pud = 0xC0C0, .def_drv = 0 },
+};
+
+/* All pins of all port groups of GPIO part2 share single interrupt line */
+static Exynos4PortIntState gpio2_ports_interrupts[GPIO2_PORTINT_NUM] = {
+    { .int_line_num = 21, .def_mask = 0x000000FF },
+    { .int_line_num = 22, .def_mask = 0x0000001F },
+    { .int_line_num = 23, .def_mask = 0x0000007F },
+    { .int_line_num = 24, .def_mask = 0x0000007F },
+    { .int_line_num = 25, .def_mask = 0x0000007F },
+    { .int_line_num = 26, .def_mask = 0x0000007F },
+    { .int_line_num = 27, .def_mask = 0x000000FF },
+    { .int_line_num = 28, .def_mask = 0x00000007 },
+    { .int_line_num = 29, .def_mask = 0x000000FF },
+};
+
+static Exynos4PortGroup gpio2x_ports[GPIO2_X_PORT_NUM] = {
+    { .name = "X0", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+    { .name = "X1", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+    { .name = "X2", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+    { .name = "X3", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+};
+
+/* Ports X0 and X1 have separate external irq lines for every pin.
+ * All pins of ports X2 and X3 share single external irq line */
+static Exynos4PortIntState gpio2x_ports_interrupts[GPIO2_X_PORTINT_NUM] = {
+    { .int_line_num = 0, .def_mask = 0x000000FF },
+    { .int_line_num = 1, .def_mask = 0x000000FF },
+    { .int_line_num = 2, .def_mask = 0x000000FF },
+    { .int_line_num = 3, .def_mask = 0x000000FF },
+};
+
+static Exynos4PortGroup gpio3_ports = {
+    .name = "Z", .def_con = 0, .def_pud = 0x1555, .def_drv = 0
+};
+
+typedef struct Exynos4GpioState {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    Exynos4210GpioPart part;
+    qemu_irq *out_cb;             /* Callbacks on writing to GPIO line */
+    Exynos4PortGroup *ports;
+    Exynos4PortIntState *port_ints;
+    uint16_t num_of_ports;
+    uint16_t num_of_portints;
+} Exynos4GpioState;
+
+static const VMStateDescription exynos4_gpio_vmstate = {
+    .name = "exynos4210.gpio",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields      = (VMStateField[]) {
+        VMSTATE_STRUCT_VARRAY_POINTER_UINT16(ports, Exynos4GpioState,
+            num_of_ports, exynos4_gpio_portgroup_vmstate, Exynos4PortGroup),
+        VMSTATE_STRUCT_VARRAY_POINTER_UINT16(port_ints, Exynos4GpioState,
+            num_of_portints, exynos4_gpio_portint_vmstate, Exynos4PortIntState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+typedef struct Exynos4Gpio12State {
+    Exynos4GpioState gpio_common;
+    qemu_irq irq_gpio;        /* GPIO interrupt request */
+    /* Group index and pin # of highest priority currently pended irq line */
+    uint32_t extint_serv;
+    uint32_t extint_serv_pend;
+    /* Index of highest priority interrupt group */
+    uint32_t extint_grpfixpri;
+} Exynos4Gpio12State;
+
+typedef struct Exynos4Gpio2XState {
+    Exynos4GpioState gpio_common;
+    qemu_irq ext_irq[GPIO2_X_PORT_IRQ_NUM];
+} Exynos4Gpio2XState;
+
+static inline void exynos4_gpio_reset_portgr(Exynos4PortGroup *group)
+{
+    group->con = group->def_con;
+    group->dat = 0;
+    group->pud = group->def_pud;
+    group->drv = group->def_drv;
+    group->conpdn = 0;
+    group->pudpdn = 0;
+}
+
+static inline void exynos4_gpio_reset_portint(Exynos4PortIntState *pint)
+{
+    pint->con = 0;
+    pint->mask = pint->def_mask;
+    pint->fltcon[0] = 0;
+    pint->fltcon[1] = 0;
+    pint->pend = 0;
+    pint->fixpri = 0;
+}
+
+static void exynos4_gpio_reset(DeviceState *dev)
+{
+    Exynos4GpioState *g = EXYNOS4210_GPIO(dev);
+    unsigned i;
+
+    DPRINT_L2("GPIO PART%u RESET\n", g->part);
+
+    switch (g->part) {
+    case GPIO_PART1: case GPIO_PART2:
+        qemu_irq_lower(EXYNOS4210_GPIO_1_2(g)->irq_gpio);
+        EXYNOS4210_GPIO_1_2(g)->extint_serv = 0;
+        EXYNOS4210_GPIO_1_2(g)->extint_grpfixpri = 0;
+        EXYNOS4210_GPIO_1_2(g)->extint_serv_pend = 0;
+        break;
+    case GPIO_PART2X:
+        for (i = 0; i < GPIO2_X_PORT_IRQ_NUM; i++) {
+            qemu_irq_lower(EXYNOS4210_GPIO2X(g)->ext_irq[i]);
+        }
+        break;
+    default:
+        break;
+    }
+
+    for (i = 0; i < g->num_of_portints; i++) {
+        exynos4_gpio_reset_portint(&g->port_ints[i]);
+    }
+
+    for (i = 0; i < g->num_of_ports; i++) {
+        exynos4_gpio_reset_portgr(&g->ports[i]);
+    }
+}
+
+static uint32_t
+exynos4_gpio_portgr_read(Exynos4PortGroup *group, target_phys_addr_t off)
+{
+    DPRINT_L2("Port group GP%s read off 0x%x\n", group->name, off);
+
+    switch (off) {
+    case GPIOCON:
+        return group->con;
+    case GPIODAT:
+        return group->dat & 0xff;
+    case GPIOPUD:
+        return group->pud;
+    case GPIODRV:
+        return group->drv;
+    case GPIOCONPDN:
+        return group->conpdn;
+    case GPIOPUDPDN:
+        return group->pudpdn;
+    default:
+        DPRINT_ERROR("Port group GP%s bad read off 0x%x\n", group->name, off);
+        return 0xBAADBAAD;
+    }
+}
+
+static uint32_t
+exynos4_etc_portgroup_read(Exynos4PortGroup *group, target_phys_addr_t off)
+{
+    DPRINT_L1("Port group %s read off 0x%x\n", group->name, off);
+
+    switch (off) {
+    case GPIOPUD:
+        return group->pud;
+    case GPIODRV:
+        return group->drv;
+    default:
+        DPRINT_ERROR("Port group %s bad read off 0x%x\n", group->name, off);
+        return 0xBAADBAAD;
+    }
+}
+
+static void exynos4_etc_portgroup_write(Exynos4PortGroup *group,
+        target_phys_addr_t off, uint32_t value)
+{
+    DPRINT_L1("Port group %s write: off 0x%x = %u(0x%x)\n",
+            group->name, off, value, value);
+
+    switch (off) {
+    case GPIOPUD:
+        group->pud = value;
+        break;
+    case GPIODRV:
+        group->drv = value;
+        break;
+    default:
+        DPRINT_ERROR("Port group %s bad write: off 0x%x = %u(0x%x)\n",
+            group->name, off, value, value);
+        break;
+    }
+}
+
+/* Returns index of currently pended external interrupt line with highest
+ * priority 1..MAX_INDEX for GPIO parts 1 and 2 */
+static unsigned int
+gpio_group_get_highest_prio(Exynos4GpioState *g)
+{
+    Exynos4Gpio12State *g12 = EXYNOS4210_GPIO_1_2(g);
+    unsigned i;
+    const unsigned num_of_prio = g->num_of_portints;
+    uint8_t highest_prio = g12->extint_grpfixpri;
+
+    if (highest_prio == 0) {
+        /* Zero extint_grpfixpri is equal to extint_grpfixpri == 1 */
+        highest_prio = 1;
+    }
+
+    /* Corresponding group index is less then EXTINT index by one */
+    highest_prio--;
+    for (i = 0; i < num_of_prio; i++) {
+        if (g->port_ints[highest_prio].pend &
+                ~g->port_ints[highest_prio].mask) {
+            return highest_prio + 1;
+        }
+        if (++highest_prio >= num_of_prio) {
+            highest_prio = 0;
+        }
+    }
+
+    return 0;
+}
+
+/* Returns line number of highest pended external irq within portgroup */
+static unsigned int gpio_get_highest_intnum(Exynos4GpioState *g, unsigned group)
+{
+    uint8_t highest_prio = g->port_ints[group].fixpri;
+    uint8_t pend = g->port_ints[group].pend;
+    unsigned i;
+
+    for (i = 0; i < GPIO_MAX_PIN_IN_PORT; i++) {
+        if (pend & (1 << highest_prio)) {
+            return highest_prio;
+        }
+        if (++highest_prio >= GPIO_MAX_PIN_IN_PORT) {
+            highest_prio = 0;
+        }
+    }
+
+    return 0;
+}
+
+/* Clear GPIO IRQ if none of gpio interrupt lines are pended */
+static void exynos4_gpioirq_update(Exynos4GpioState *g)
+{
+    Exynos4Gpio12State *g12 = EXYNOS4210_GPIO_1_2(g);
+    unsigned pend_prio = gpio_group_get_highest_prio(g);
+
+    if (pend_prio == 0) {
+        DPRINT_L2("GPIO part %u interrupt cleared\n", g->part);
+        g12->extint_serv = g12->extint_serv_pend = 0;
+        qemu_irq_lower(g12->irq_gpio);
+    } else if (pend_prio != ((g12->extint_serv >> 3) & 0x1f)) {
+        g12->extint_serv = (pend_prio << 3) |
+                gpio_get_highest_intnum(g, pend_prio - 1);
+        g12->extint_serv_pend = g->port_ints[pend_prio - 1].pend;
+    }
+}
+
+static void exynos4_gpio_portgr_write(Exynos4GpioState *g, int idx,
+        unsigned int off, uint32_t value)
+{
+    Exynos4PortGroup *group = &g->ports[idx];
+    unsigned pin;
+    uint32_t diff, old_con, new_dat;
+
+    DPRINT_L1("Port group GP%s write: off 0x%x = %u(0x%x)\n",
+            group->name, off, value, value);
+
+    switch (off) {
+    case GPIOCON:
+        old_con = group->con;
+        group->con = value;
+        for (pin = 0; pin < GPIO_MAX_PIN_IN_PORT; pin++) {
+            if (((value >> pin * 4) & 0xf) != ((old_con >> pin * 4) & 0xf)) {
+                qemu_irq_raise(g->out_cb[idx * GPIO_MAX_PIN_IN_PORT + pin]);
+            }
+        }
+        break;
+    case GPIODAT:
+        new_dat = group->dat;
+        value &= (1 << GPIO_MAX_PIN_IN_PORT) - 1;
+        for (pin = 0; pin < GPIO_MAX_PIN_IN_PORT; pin++) {
+            if (((group->con >> pin * 4) & 0xf) == GPIOCON_OUT) {
+                new_dat = (new_dat & ~(1 << pin)) | (value & (1 << pin));
+            }
+        }
+        diff = group->dat ^ new_dat;
+        group->dat = new_dat & ((1 << GPIO_MAX_PIN_IN_PORT) - 1);
+        while ((pin = ffs(diff))) {
+            pin--;
+            DPRINT_L2("Port group GP%s pin #%u write callback %s raised\n",
+                      group->name, pin, (g->out_cb[idx *
+                      GPIO_MAX_PIN_IN_PORT + pin] ? "" : "wasn't"));
+            qemu_set_irq(g->out_cb[idx * GPIO_MAX_PIN_IN_PORT + pin],
+                    (group->dat & (1 << pin)));
+            diff &= ~(1 << pin);
+        }
+        break;
+    case GPIOPUD:
+        for (pin = 0; pin < GPIO_MAX_PIN_IN_PORT; pin++) {
+            if (((value >> 2 * pin) & 0x3) == GPIO_PULLUP_STATE &&
+                    ((group->pud >> 2 * pin) & 0x3) != GPIO_PULLUP_STATE) {
+                group->dat |= 1 << pin;
+            }
+        }
+        group->pud = value;
+        break;
+    case GPIODRV:
+        group->drv = value;
+        break;
+    case GPIOCONPDN:
+        group->conpdn = value;
+        break;
+    case GPIOPUDPDN:
+        group->pudpdn = value;
+        break;
+    default:
+        DPRINT_ERROR("Port group GP%s bad write: offset 0x%x = %u(0x%x)\n",
+            group->name, off, value, value);
+        break;
+    }
+}
+
+static void exynos4_gpio_set_cb(void *opaque, int line, int level)
+{
+    Exynos4GpioState *g = (Exynos4GpioState *)opaque;
+    const unsigned group_num = line >> 3;
+    const unsigned pin = line & (GPIO_MAX_PIN_IN_PORT - 1);
+    bool irq_is_triggered = false;
+    const uint32_t dat_prev = g->ports[group_num].dat & (1 << pin);
+    const unsigned pin_func = (g->ports[group_num].con >> pin * 4) & 0xf;
+
+    /* Check that corresponding pin is in input state */
+    if (pin_func != GPIOCON_EXTINT && pin_func != GPIOCON_IN) {
+        return;
+    }
+
+    DPRINT_L1("Input pin GPIO%s_PIN%u %s by external device\n",
+            g->ports[group_num].name, pin, (level ? "set" : "cleared"));
+    /* Set new value on corresponding gpio pin */
+    (level) ? (g->ports[group_num].dat |= (1 << pin)) :
+            (g->ports[group_num].dat &= ~(1 << pin));
+
+    /* Check that external interrupt function is active for this pin */
+    if (pin_func != GPIOCON_EXTINT || group_num >= g->num_of_portints) {
+        return;
+    }
+
+    /* Do nothing if corresponding interrupt line is masked or already pended */
+    if ((g->port_ints[group_num].mask & (1 << pin)) ||
+            (g->port_ints[group_num].pend & (1 << pin))) {
+        return;
+    }
+
+    /* Get interrupt line signaling method */
+    switch ((g->port_ints[group_num].con >> (pin * 4)) & 7) {
+    case GPIO_INTCON_LOW:
+        irq_is_triggered = !level;
+        break;
+    case GPIO_INTCON_HIGH:
+        irq_is_triggered = !!level;
+        break;
+    case GPIO_INTCON_FALL:
+        irq_is_triggered = dat_prev && !(g->ports[group_num].dat & (1 << pin));
+        break;
+    case GPIO_INTCON_RISE:
+        irq_is_triggered = !dat_prev && (g->ports[group_num].dat & (1 << pin));
+        break;
+    case GPIO_INTCON_FALLRISE:
+        irq_is_triggered =
+            (dat_prev && !(g->ports[group_num].dat & (1 << pin))) ||
+            (!dat_prev && (g->ports[group_num].dat & (1 << pin)));
+        break;
+    default:
+        DPRINT_ERROR("GPIO PART%u: unknown triggering method of EXT_IRQ_%u\n",
+                g->part, g->port_ints[group_num].int_line_num);
+        break;
+    }
+
+    if (irq_is_triggered) {
+        g->port_ints[group_num].pend |= 1 << pin;
+        if (g->part == GPIO_PART2X) {
+            unsigned irq = group_num * GPIO_MAX_PIN_IN_PORT + pin;
+            DPRINT_L1("IRQ_EINT%u raised\n", irq);
+            if (irq >= GPIO2_X_PORT_IRQ_NUM) {
+                irq = GPIO2_X_PORT_IRQ_NUM - 1;
+            }
+            qemu_irq_raise(EXYNOS4210_GPIO2X(g)->ext_irq[irq]);
+        } else {
+            Exynos4Gpio12State *g12 = EXYNOS4210_GPIO_1_2(g);
+            DPRINT_L1("GPIO_INT%u[PIN%u] raised and GPIO_P%u_IRQ raised\n",
+                    g->port_ints[group_num].int_line_num, pin, g->part);
+
+            if ((group_num + 1) == gpio_group_get_highest_prio(g)) {
+                g12->extint_serv = ((group_num + 1) << 3) |
+                        gpio_get_highest_intnum(g, group_num);
+                g12->extint_serv_pend = g->port_ints[group_num].pend;
+            }
+            qemu_irq_raise(g12->irq_gpio);
+        }
+    }
+}
+
+static uint64_t exynos4_gpio_read(void *opaque, target_phys_addr_t off,
+                                  unsigned size)
+{
+    Exynos4GpioState *g = (Exynos4GpioState *)opaque;
+    unsigned port_end, extint_con_start, extint_con_end;
+    unsigned extint_flt_start, extint_flt_end;
+    unsigned extint_mask_start, extint_mask_end;
+    unsigned extint_pend_start, extint_pend_end;
+    unsigned etcp_start_addr, etcp_start_idx, extint_pri_end;
+    unsigned idx;
+
+    DPRINT_L2("GPIO%u read off 0x%x\n", g->part, (uint32_t)off);
+
+    switch (g->part) {
+    case GPIO_PART2X:
+        port_end = GPIO2_XPORT_END;
+        extint_con_start = GPIO2_WKPINT_CON_START;
+        extint_con_end = GPIO2_WKPINT_CON_END;
+        extint_flt_start = GPIO2_WKPINT_FLT_START;
+        extint_flt_end = GPIO2_WKPINT_FLT_END;
+        extint_mask_start = GPIO2_WKPINT_MASK_START;
+        extint_mask_end = GPIO2_WKPINT_MASK_END;
+        extint_pend_start = GPIO2_WKPINT_PEND_START;
+        extint_pend_end = GPIO2_WKPINT_PEND_END;
+        etcp_start_addr = etcp_start_idx = extint_pri_end = 0;
+        break;
+    case GPIO_PART1: default:
+        port_end = GPIO_NORM_PORT_END;
+        extint_con_start = GPIO_EXTINT_CON_START;
+        extint_con_end = GPIO1_EXTINT_CON_END;
+        extint_flt_start = GPIO_EXTINT_FLT_START;
+        extint_flt_end = GPIO1_EXTINT_FLT_END;
+        extint_mask_start = GPIO_EXTINT_MASK_START;
+        extint_mask_end = GPIO1_EXTINT_MASK_END;
+        extint_pend_start = GPIO_EXTINT_PEND_START;
+        extint_pend_end = GPIO1_EXTINT_PEND_END;
+        etcp_start_addr = GPIO1_ETCPORT_START;
+        etcp_start_idx = GPIO1_NORM_PORT_NUM;
+        extint_pri_end = GPIO1_EXTINT_FIXPRI_END;
+        break;
+    case GPIO_PART2:
+        port_end = GPIO_NORM_PORT_END;
+        extint_con_start = GPIO_EXTINT_CON_START;
+        extint_con_end = GPIO2_EXTINT_CON_END;
+        extint_flt_start = GPIO_EXTINT_FLT_START;
+        extint_flt_end = GPIO2_EXTINT_FLT_END;
+        extint_mask_start = GPIO_EXTINT_MASK_START;
+        extint_mask_end = GPIO2_EXTINT_MASK_END;
+        extint_pend_start = GPIO_EXTINT_PEND_START;
+        extint_pend_end = GPIO2_EXTINT_PEND_END;
+        etcp_start_addr = GPIO2_ETCPORT_START;
+        etcp_start_idx = GPIO2_NORM_PORT_NUM;
+        extint_pri_end = GPIO2_EXTINT_FIXPRI_END;
+        break;
+    case GPIO_PART3:
+        if (off < GPIO3_NORM_PORT_END) {
+            idx = DIV_BY_PORTGR_SIZE(off);
+            return exynos4_gpio_portgr_read(&g->ports[idx],
+                    MOD_PORTGR_SIZE(off));
+        }
+        DPRINT_ERROR("GPIO part 3 bad read off 0x%x\n", off);
+        return 0xBAADBAAD;
+    }
+
+    if (off < port_end) {
+        idx = DIV_BY_PORTGR_SIZE(off);
+        return exynos4_gpio_portgr_read(&g->ports[idx], MOD_PORTGR_SIZE(off));
+    } else if (off >= extint_mask_start && off < extint_mask_end) {
+        idx = (off - extint_mask_start) >> 2;
+        DPRINT_L1("GPIO%u EXTINT%u_MASK register read\n", g->part,
+                g->port_ints[idx].int_line_num);
+        return g->port_ints[idx].mask;
+    } else if (off >= extint_pend_start && off < extint_pend_end) {
+        idx = (off - extint_pend_start) >> 2;
+        DPRINT_L1("GPIO%u EXTINT%u_PEND register read\n", g->part,
+                g->port_ints[idx].int_line_num);
+        return g->port_ints[idx].pend;
+    } else if (off >= extint_con_start && off < extint_con_end) {
+        idx = (off - extint_con_start) >> 2;
+        DPRINT_L1("GPIO%u EXTINT%u_CON register read\n", g->part,
+                g->port_ints[idx].int_line_num);
+        return g->port_ints[idx].con;
+    } else if (off >= extint_flt_start && off < extint_flt_end) {
+        unsigned i = ((off - extint_flt_start) >> 2) & 1;
+        idx = (off - extint_flt_start) >> 3;
+        DPRINT_L1("GPIO%u EXTINT%u_FLTCON%u register read\n", g->part,
+                g->port_ints[idx].int_line_num, i);
+        return g->port_ints[idx].fltcon[i];
+    } else if (g->part == GPIO_PART2X) {
+        /* GPIO group X has no more registers */
+        DPRINT_ERROR("GPIO group X bad read off 0x%x\n", (uint32_t)off);
+        return 0xBAADBAAD;
+    } else if (off == GPIO_EXTINT_SERVICE) {
+        DPRINT_L1("GPIO%u EXT_INT_SERVICE register read\n", g->part);
+        return EXYNOS4210_GPIO_1_2(g)->extint_serv;
+    } else if (off == GPIO_EXTINT_SERVICE_PEND) {
+        DPRINT_L1("GPIO%u EXT_INT_SERVICE_PEND register read\n", g->part);
+        return EXYNOS4210_GPIO_1_2(g)->extint_serv_pend;
+    } else if (off == GPIO_EXTINT_GRPFIXPRI) {
+        DPRINT_L1("GPIO%u EXT_INT_GRPFIXPRI register read\n", g->part);
+        return EXYNOS4210_GPIO_1_2(g)->extint_grpfixpri;
+    } else if (off >= GPIO_EXTINT_FIXPRI_START && off < extint_pri_end) {
+        idx = (off - GPIO_EXTINT_FIXPRI_START) >> 2;
+        DPRINT_L1("GPIO%u EXTINT%u_FIXPRI register read\n", g->part,
+                g->port_ints[idx].int_line_num);
+        return g->port_ints[idx].fixpri;
+    } else if (off >= etcp_start_addr && off < GPIO_ETCPORT_END) {
+        idx = DIV_BY_PORTGR_SIZE(off - etcp_start_addr) +
+                etcp_start_idx;
+        return exynos4_etc_portgroup_read(&g->ports[idx],
+                MOD_PORTGR_SIZE(off - etcp_start_addr));
+    }
+
+    DPRINT_ERROR("GPIO_P%u bad read off 0x%x\n", g->part, (uint32_t)off);
+    return 0xBAADBAAD;
+}
+
+static void exynos4_gpio_write(void *opaque, target_phys_addr_t off,
+                               uint64_t value, unsigned size)
+{
+    Exynos4GpioState *g = (Exynos4GpioState *)opaque;
+    unsigned int port_end, extint_con_start, extint_con_end;
+    unsigned int extint_flt_start, extint_flt_end;
+    unsigned int extint_mask_start, extint_mask_end;
+    unsigned int extint_pend_start, extint_pend_end;
+    unsigned int etcp_start_addr, etcp_start_idx, extint_pri_end;
+    unsigned idx;
+
+    DPRINT_L2("GPIO%u write off 0x%x = %u(0x%x)\n", g->part,
+            (uint32_t)off, (uint32_t)value, (uint32_t)value);
+
+    switch (g->part) {
+    case GPIO_PART2X:
+        port_end = GPIO2_XPORT_END;
+        extint_con_start = GPIO2_WKPINT_CON_START;
+        extint_con_end = GPIO2_WKPINT_CON_END;
+        extint_flt_start = GPIO2_WKPINT_FLT_START;
+        extint_flt_end = GPIO2_WKPINT_FLT_END;
+        extint_mask_start = GPIO2_WKPINT_MASK_START;
+        extint_mask_end = GPIO2_WKPINT_MASK_END;
+        extint_pend_start = GPIO2_WKPINT_PEND_START;
+        extint_pend_end = GPIO2_WKPINT_PEND_END;
+        etcp_start_addr = etcp_start_idx = extint_pri_end = 0;
+        break;
+    case GPIO_PART1: default:
+        port_end = GPIO_NORM_PORT_END;
+        extint_con_start = GPIO_EXTINT_CON_START;
+        extint_con_end = GPIO1_EXTINT_CON_END;
+        extint_flt_start = GPIO_EXTINT_FLT_START;
+        extint_flt_end = GPIO1_EXTINT_FLT_END;
+        extint_mask_start = GPIO_EXTINT_MASK_START;
+        extint_mask_end = GPIO1_EXTINT_MASK_END;
+        extint_pend_start = GPIO_EXTINT_PEND_START;
+        extint_pend_end = GPIO1_EXTINT_PEND_END;
+        etcp_start_addr = GPIO1_ETCPORT_START;
+        etcp_start_idx = GPIO1_NORM_PORT_NUM;
+        extint_pri_end = GPIO1_EXTINT_FIXPRI_END;
+        break;
+    case GPIO_PART2:
+        port_end = GPIO_NORM_PORT_END;
+        extint_con_start = GPIO_EXTINT_CON_START;
+        extint_con_end = GPIO2_EXTINT_CON_END;
+        extint_flt_start = GPIO_EXTINT_FLT_START;
+        extint_flt_end = GPIO2_EXTINT_FLT_END;
+        extint_mask_start = GPIO_EXTINT_MASK_START;
+        extint_mask_end = GPIO2_EXTINT_MASK_END;
+        extint_pend_start = GPIO_EXTINT_PEND_START;
+        extint_pend_end = GPIO2_EXTINT_PEND_END;
+        etcp_start_addr = GPIO2_ETCPORT_START;
+        etcp_start_idx = GPIO2_NORM_PORT_NUM;
+        extint_pri_end = GPIO2_EXTINT_FIXPRI_END;
+        break;
+    case GPIO_PART3:
+        if (off < GPIO3_NORM_PORT_END) {
+            idx = DIV_BY_PORTGR_SIZE(off);
+            exynos4_gpio_portgr_write(g, idx, MOD_PORTGR_SIZE(off), value);
+        } else {
+            DPRINT_ERROR("GPIO3 bad write off 0x%x = %u(0x%x)\n",
+                    (uint32_t)off, (uint32_t)value, (uint32_t)value);
+        }
+        return;
+    }
+
+    if (off < port_end) {
+        idx = DIV_BY_PORTGR_SIZE(off);
+        exynos4_gpio_portgr_write(g, idx, MOD_PORTGR_SIZE(off), value);
+    } else if (off >= extint_mask_start && off < extint_mask_end) {
+        idx = (off - extint_mask_start) >> 2;
+        DPRINT_L1("GPIO%u EXTINT%u_MASK register write = %u(0x%x)\n", g->part,
+            g->port_ints[idx].int_line_num, (uint32_t)value, (uint32_t)value);
+        g->port_ints[idx].mask = value;
+    } else if (off >= extint_pend_start && off < extint_pend_end) {
+        idx = (off - extint_pend_start) >> 2;
+        DPRINT_L1("GPIO%u EXTINT%u_PEND register write = %u(0x%x)\n", g->part,
+            g->port_ints[idx].int_line_num, (uint32_t)value, (uint32_t)value);
+        if (g->part == GPIO_PART2X) {
+            unsigned i, irq_n;
+            Exynos4Gpio2XState *g2 = EXYNOS4210_GPIO2X(g);
+            for (i = 0; i < GPIO_MAX_PIN_IN_PORT; i++) {
+                if ((g->port_ints[idx].pend & (1 << i)) && (value & (1 << i))) {
+                    g->port_ints[idx].pend &= ~(1 << i);
+                    irq_n = idx * GPIO_MAX_PIN_IN_PORT + i;
+                    if (irq_n >= GPIO2_X_PORT_IRQ_NUM) {
+                        irq_n = GPIO2_X_PORT_IRQ_NUM - 1;
+                    }
+                    qemu_irq_lower(g2->ext_irq[irq_n]);
+                }
+            }
+        } else {
+            g->port_ints[idx].pend &= ~value;
+            exynos4_gpioirq_update(g);
+        }
+    } else if (off >= extint_con_start && off < extint_con_end) {
+        idx = (off - extint_con_start) >> 2;
+        DPRINT_L1("GPIO%u EXTINT%u_CON register write = %u(0x%x)\n", g->part,
+            g->port_ints[idx].int_line_num, (uint32_t)value, (uint32_t)value);
+        g->port_ints[idx].con = value;
+    } else if (off >= extint_flt_start && off < extint_flt_end) {
+        unsigned i = ((off - extint_flt_start) >> 2) & 1;
+        idx = (off - extint_flt_start) >> 3;
+        DPRINT_L1("GPIO%u EXTINT%u_FLTCON%u reg write = %u(0x%x)\n", g->part,
+          g->port_ints[idx].int_line_num, i, (uint32_t)value, (uint32_t)value);
+        g->port_ints[idx].fltcon[i] = value;
+    } else if (g->part == GPIO_PART2X) {
+        DPRINT_ERROR("GPIO2 group X bad write off 0x%x = %u(0x%x)\n",
+                (uint32_t)off, (uint32_t)value, (uint32_t)value);
+        return;
+    } else if (off == GPIO_EXTINT_SERVICE) {
+        DPRINT_L1("GPIO%u EXT_INT_SERVICE register write = %u(0x%x)\n",
+                g->part, (uint32_t)value, (uint32_t)value);
+        EXYNOS4210_GPIO_1_2(g)->extint_serv = value;
+    } else if (off == GPIO_EXTINT_SERVICE_PEND) {
+        DPRINT_L1("GPIO%u EXT_INT_SERVICE_PEND register write = %u(0x%x)\n",
+                g->part, (uint32_t)value, (uint32_t)value);
+        EXYNOS4210_GPIO_1_2(g)->extint_serv_pend = value;
+    } else if (off == GPIO_EXTINT_GRPFIXPRI) {
+        DPRINT_L1("GPIO%u EXT_INT_GRPFIXPRI register write = %u(0x%x)\n",
+                g->part, (uint32_t)value, (uint32_t)value);
+        EXYNOS4210_GPIO_1_2(g)->extint_grpfixpri = value;
+    } else if (off >= GPIO_EXTINT_FIXPRI_START && off < extint_pri_end) {
+        idx = (off - GPIO_EXTINT_FIXPRI_START) >> 2;
+        DPRINT_L1("GPIO%u EXTINT%u_FIXPRI register write = %u(0x%x)\n", g->part,
+            g->port_ints[idx].int_line_num, (uint32_t)value, (uint32_t)value);
+        g->port_ints[idx].fixpri = value;
+    } else if (off >= etcp_start_addr && off < GPIO_ETCPORT_END) {
+        idx = etcp_start_idx + DIV_BY_PORTGR_SIZE(off - etcp_start_addr);
+        exynos4_etc_portgroup_write(&g->ports[idx],
+            MOD_PORTGR_SIZE(off - etcp_start_addr), value);
+    } else {
+        DPRINT_ERROR("GPIO%u bad write off 0x%x = %u(0x%x)\n", g->part,
+            (uint32_t)off, (uint32_t)value, (uint32_t)value);
+    }
+}
+
+static const MemoryRegionOps exynos4_gpio_mmio_ops = {
+    .read = exynos4_gpio_read,
+    .write = exynos4_gpio_write,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false
+    },
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription exynos4_gpio_1_2_vmstate = {
+    .name = "exynos4210.gpio-1_2",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields      = (VMStateField[]) {
+        VMSTATE_STRUCT(gpio_common, Exynos4Gpio12State, 1,
+                exynos4_gpio_vmstate, Exynos4GpioState),
+        VMSTATE_UINT32(extint_serv, Exynos4Gpio12State),
+        VMSTATE_UINT32(extint_serv_pend, Exynos4Gpio12State),
+        VMSTATE_UINT32(extint_grpfixpri, Exynos4Gpio12State),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void exynos4_gpio1_init(Object *obj)
+{
+    Exynos4GpioState *g = EXYNOS4210_GPIO(obj);
+
+    g->part = GPIO_PART1;
+    g->ports = gpio1_ports;
+    g->port_ints = gpio1_ports_interrupts;
+    g->num_of_ports = sizeof(gpio1_ports) / sizeof(Exynos4PortGroup);
+    g->num_of_portints =
+            sizeof(gpio1_ports_interrupts) / sizeof(Exynos4PortIntState);
+}
+
+static void exynos4_gpio2_init(Object *obj)
+{
+    Exynos4GpioState *g = EXYNOS4210_GPIO(obj);
+
+    g->part = GPIO_PART2;
+    g->ports = gpio2_ports;
+    g->port_ints = gpio2_ports_interrupts;
+    g->num_of_ports = sizeof(gpio2_ports) / sizeof(Exynos4PortGroup);
+    g->num_of_portints =
+            sizeof(gpio2_ports_interrupts) / sizeof(Exynos4PortIntState);
+}
+
+static void exynos4_gpio2x_init(Object *obj)
+{
+    Exynos4GpioState *g = EXYNOS4210_GPIO(obj);
+
+    g->part = GPIO_PART2X;
+    g->ports = gpio2x_ports;
+    g->port_ints = gpio2x_ports_interrupts;
+    g->num_of_ports = sizeof(gpio2x_ports) / sizeof(Exynos4PortGroup);
+    g->num_of_portints =
+            sizeof(gpio2x_ports_interrupts) / sizeof(Exynos4PortIntState);
+}
+
+static void exynos4_gpio3_init(Object *obj)
+{
+    Exynos4GpioState *g = EXYNOS4210_GPIO(obj);
+
+    g->part = GPIO_PART3;
+    g->ports = &gpio3_ports;
+    g->num_of_ports = sizeof(gpio3_ports) / sizeof(Exynos4PortGroup);
+    g->num_of_portints = 0;
+}
+
+static int exynos4_gpio_realize(SysBusDevice *busdev)
+{
+    Exynos4GpioState *g = EXYNOS4210_GPIO(busdev);
+    unsigned int mem_size, i;
+    const char *iomem_name;
+
+    switch (g->part) {
+    case GPIO_PART1:
+        iomem_name = "exynos4210.gpio1";
+        mem_size = GPIO1_REGS_MEM_SIZE;
+        sysbus_init_irq(busdev, &EXYNOS4210_GPIO_1_2(busdev)->irq_gpio);
+        break;
+    case GPIO_PART2:
+        iomem_name = "exynos4210.gpio2";
+        mem_size = GPIO2_REGS_MEM_SIZE;
+        sysbus_init_irq(busdev, &EXYNOS4210_GPIO_1_2(busdev)->irq_gpio);
+        break;
+    case GPIO_PART2X:
+        iomem_name = "exynos4210.gpio2x";
+        mem_size = GPIO2X_REGS_MEM_SIZE;
+        for (i = 0; i < GPIO2_X_PORT_IRQ_NUM; i++) {
+            Exynos4Gpio2XState *g2x = EXYNOS4210_GPIO2X(busdev);
+            sysbus_init_irq(busdev, &g2x->ext_irq[i]);
+        }
+        break;
+    case GPIO_PART3:
+        iomem_name = "exynos4210.gpio3";
+        mem_size = GPIO3_REGS_MEM_SIZE;
+        break;
+    default:
+        hw_error("QEMU GPIO INIT ERROR: unknown GPIO part\n");
+    }
+
+    g->out_cb = g_new0(qemu_irq, g->num_of_ports * GPIO_MAX_PIN_IN_PORT);
+    qdev_init_gpio_in(DEVICE(busdev), exynos4_gpio_set_cb,
+            g->num_of_ports * GPIO_MAX_PIN_IN_PORT);
+    qdev_init_gpio_out(DEVICE(busdev), g->out_cb,
+            g->num_of_ports * GPIO_MAX_PIN_IN_PORT);
+    memory_region_init_io(&g->iomem, &exynos4_gpio_mmio_ops, g,
+            iomem_name, mem_size);
+    sysbus_init_mmio(busdev, &g->iomem);
+    return 0;
+}
+
+static void exynos4_gpio_finalize(Object *obj)
+{
+    Exynos4GpioState *g = EXYNOS4210_GPIO(obj);
+
+    if (g->out_cb) {
+        g_free(g->out_cb);
+        g->out_cb = NULL;
+    }
+}
+
+static void exynos4_gpio_class_init(ObjectClass *class, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(class);
+    SysBusDeviceClass *sbd = SYS_BUS_DEVICE_CLASS(class);
+    dc->reset = exynos4_gpio_reset;
+    dc->vmsd = &exynos4_gpio_vmstate;
+    sbd->init = exynos4_gpio_realize;
+}
+
+static void exynos4_gpio_1_2_class_init(ObjectClass *class, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(class);
+
+    dc->vmsd = &exynos4_gpio_1_2_vmstate;
+}
+
+static TypeInfo exynos4_gpio_type_info = {
+    .name          = TYPE_EXYNOS4210_GPIO,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(Exynos4GpioState),
+    .class_init    = exynos4_gpio_class_init,
+    .instance_finalize = exynos4_gpio_finalize,
+    .abstract      = true
+};
+
+static TypeInfo exynos4_gpio_1_2_type_info = {
+    .name          = TYPE_EXYNOS4210_GPIO_1_2,
+    .parent        = TYPE_EXYNOS4210_GPIO,
+    .instance_size = sizeof(Exynos4Gpio12State),
+    .class_init    = exynos4_gpio_1_2_class_init,
+    .abstract      = true
+};
+
+static TypeInfo exynos4_gpio1_type_info = {
+    .name          = "exynos4210.gpio1",
+    .parent        = TYPE_EXYNOS4210_GPIO_1_2,
+    .instance_init = exynos4_gpio1_init,
+};
+
+static TypeInfo exynos4_gpio2_type_info = {
+    .name          = "exynos4210.gpio2",
+    .parent        = TYPE_EXYNOS4210_GPIO_1_2,
+    .instance_init = exynos4_gpio2_init,
+};
+
+static TypeInfo exynos4_gpio2x_type_info = {
+    .name          = TYPE_EXYNOS4210_GPIO_2X,
+    .parent        = TYPE_EXYNOS4210_GPIO,
+    .instance_size = sizeof(Exynos4Gpio2XState),
+    .instance_init = exynos4_gpio2x_init,
+};
+
+static TypeInfo exynos4_gpio3_type_info = {
+    .name          = "exynos4210.gpio3",
+    .parent        = TYPE_EXYNOS4210_GPIO,
+    .instance_init = exynos4_gpio3_init,
+};
+
+static void exynos4_gpio_register_types(void)
+{
+    type_register_static(&exynos4_gpio_type_info);
+    type_register_static(&exynos4_gpio_1_2_type_info);
+    type_register_static(&exynos4_gpio1_type_info);
+    type_register_static(&exynos4_gpio2_type_info);
+    type_register_static(&exynos4_gpio2x_type_info);
+    type_register_static(&exynos4_gpio3_type_info);
+}
+
+type_init(exynos4_gpio_register_types)
diff --git a/hw/exynos4_boards.c b/hw/exynos4_boards.c
index 553a02b..b438361 100644
--- a/hw/exynos4_boards.c
+++ b/hw/exynos4_boards.c
@@ -149,7 +149,7 @@  static void smdkc210_init(ram_addr_t ram_size,
             kernel_cmdline, initrd_filename, EXYNOS4_BOARD_SMDKC210);
 
     lan9215_init(SMDK_LAN9118_BASE_ADDR,
-            qemu_irq_invert(s->irq_table[exynos4210_get_irq(37, 1)]));
+            qdev_get_gpio_in(s->gpio2x, EXYNOS4210_GPIO2X_LINE(GPX0, 5)));
     arm_load_kernel(first_cpu, &exynos4_board_binfo);
 }