All of lore.kernel.org
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support.
@ 2011-12-09 13:34 Evgeny Voevodin
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 01/15] ARM: Samsung exynos4210-based boards emulation Evgeny Voevodin
                   ` (14 more replies)
  0 siblings, 15 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin

Second version of patches: add support for Samsung Exynos4210-based boards NURI
and SMDKC210.
Tested on Linux kernel v3.x series. Usage of "-smp 2" option is reuired for now.

Evgeny Voevodin (9):
  hw/sysbus.h: Increase maximum number of device IRQs.
  ARM: exynos4210: IRQ subsystem support.
  ARM: exynos4210: PWM support.
  hw/arm_boot.c: Add new secondary CPU bootloader.
  hw/arm_gic.c: lower IRQ only on changing of enable bit.
  ARM: exynos4210: MCT support.
  hw/exynos4210.c: Boot secondary CPU.
  hw/lan9118: Add basic 16-bit mode support.
  hw/exynos4210.c: Add LAN support for SMDKC210.

Maksim Kozlov (3):
  ARM: Samsung exynos4210-based boards emulation
  ARM: exynos4210: CMU support
  ARM: exynos4210: UART support

Mitsyanko Igor (3):
  hw/sd.c, hw/sd.h: add receive ready query routine to SD/MMC API
  ARM: exynos4210: added SD/MMC host controller
  ARM: exynos4210: added display controller implementation

 Makefile.target          |    3 +
 hw/arm-misc.h            |    1 +
 hw/arm_boot.c            |   22 +-
 hw/arm_gic.c             |   20 +-
 hw/devices.h             |    2 +-
 hw/exynos4210.c          |  547 +++++++++++++++
 hw/exynos4210.h          |  106 +++
 hw/exynos4210_cmu.c      | 1146 ++++++++++++++++++++++++++++++
 hw/exynos4210_combiner.c |  385 ++++++++++
 hw/exynos4210_fimd.c     | 1737 ++++++++++++++++++++++++++++++++++++++++++++++
 hw/exynos4210_gic.c      |  415 +++++++++++
 hw/exynos4210_mct.c      | 1486 +++++++++++++++++++++++++++++++++++++++
 hw/exynos4210_pwm.c      |  433 ++++++++++++
 hw/exynos4210_sdhc.c     | 1666 ++++++++++++++++++++++++++++++++++++++++++++
 hw/exynos4210_uart.c     |  674 ++++++++++++++++++
 hw/lan9118.c             |  115 +++-
 hw/sd.c                  |    5 +
 hw/sd.h                  |    1 +
 hw/sysbus.h              |    2 +-
 19 files changed, 8749 insertions(+), 17 deletions(-)
 create mode 100644 hw/exynos4210.c
 create mode 100644 hw/exynos4210.h
 create mode 100644 hw/exynos4210_cmu.c
 create mode 100644 hw/exynos4210_combiner.c
 create mode 100644 hw/exynos4210_fimd.c
 create mode 100644 hw/exynos4210_gic.c
 create mode 100644 hw/exynos4210_mct.c
 create mode 100644 hw/exynos4210_pwm.c
 create mode 100644 hw/exynos4210_sdhc.c
 create mode 100644 hw/exynos4210_uart.c

-- 
1.7.4.1

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [Qemu-devel] [PATCH 01/15] ARM: Samsung exynos4210-based boards emulation
  2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 02/15] ARM: exynos4210: CMU support Evgeny Voevodin
                   ` (13 subsequent siblings)
  14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin

From: Maksim Kozlov <m.kozlov@samsung.com>

Add initial code for support of NURI and SMDKC210 boards

Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 Makefile.target |    1 +
 hw/exynos4210.c |  224 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/exynos4210.h |   34 ++++++++
 3 files changed, 259 insertions(+), 0 deletions(-)
 create mode 100644 hw/exynos4210.c
 create mode 100644 hw/exynos4210.h

diff --git a/Makefile.target b/Makefile.target
index a111521..624a142 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -344,6 +344,7 @@ 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 += exynos4210.o
 obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
 obj-arm-y += pl061.o
 obj-arm-y += arm-semi.o
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
new file mode 100644
index 0000000..1550016
--- /dev/null
+++ b/hw/exynos4210.c
@@ -0,0 +1,224 @@
+/*
+ *  Samsung exynos4210-based boards emulation
+ *
+ *  Copyright (c) 2011 Samsung Electronics Co., Ltd. All rights reserved.
+ *    Maksim Kozlov <m.kozlov@samsung.com>
+ *    Evgeny Voevodin <e.voevodin@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, write to the Free Software Foundation, Inc., 51
+ *  Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "boards.h"
+#include "sysemu.h"
+#include "sysbus.h"
+#include "arm-misc.h"
+#include "exec-memory.h"
+#include "exynos4210.h"
+
+#undef DEBUG
+
+//#define DEBUG
+
+#ifdef DEBUG
+    #undef PRINT_DEBUG
+    #define  PRINT_DEBUG(fmt, args...) \
+        do { \
+            fprintf(stderr, "  [%s:%d]   "fmt, __func__, __LINE__, ##args); \
+        } while (0)
+#else
+    #define  PRINT_DEBUG(fmt, args...) \
+        do {} while (0)
+#endif
+
+#define EXYNOS4210_DRAM0_BASE_ADDR          0x40000000
+#define EXYNOS4210_DRAM1_BASE_ADDR          0xa0000000
+#define EXYNOS4210_DRAM_MAX_SIZE            0x60000000  /* 1.5 GB */
+
+#define EXYNOS4210_IROM_BASE_ADDR           0x00000000
+#define EXYNOS4210_IROM_SIZE                0x00010000  /* 64 KB */
+#define EXYNOS4210_IROM_MIRROR_BASE_ADDR    0x02000000
+#define EXYNOS4210_IROM_MIRROR_SIZE         0x00010000  /* 64 KB */
+
+#define EXYNOS4210_IRAM_BASE_ADDR           0x02020000
+#define EXYNOS4210_IRAM_SIZE                0x00020000  /* 128 KB */
+
+#define EXYNOS4210_SFR_BASE_ADDR            0x10000000
+
+#define EXYNOS4210_BASE_BOOT_ADDR           EXYNOS4210_DRAM0_BASE_ADDR
+
+static struct arm_boot_info exynos4210_binfo = {
+        .loader_start     = EXYNOS4210_BASE_BOOT_ADDR,
+};
+
+static uint8_t chipid_and_omr[] = { 0x11, 0x02, 0x21, 0x43,
+                                    0x09, 0x00, 0x00, 0x00 };
+
+enum exynos4210_board_type {
+    BOARD_EXYNOS4210_NURI,
+    BOARD_EXYNOS4210_SMDKC210,
+};
+
+enum exynos4210_mach_id {
+    MACH_NURI_ID     = 0xD33,
+    MACH_SMDKC210_ID = 0xB16,
+};
+
+
+static void exynos4210_init(ram_addr_t ram_size,
+        const char *boot_device,
+        const char *kernel_filename,
+        const char *kernel_cmdline,
+        const char *initrd_filename,
+        const char *cpu_model,
+        enum exynos4210_board_type board_type)
+{
+    CPUState *env;
+    MemoryRegion *system_mem = get_system_memory();
+    MemoryRegion *chipid_mem = g_new(MemoryRegion, 1);
+    MemoryRegion *iram_mem = g_new(MemoryRegion, 1);
+    MemoryRegion *irom_mem = g_new(MemoryRegion, 1);
+    MemoryRegion *irom_alias_mem = g_new(MemoryRegion, 1);
+    MemoryRegion *dram0_mem = g_new(MemoryRegion, 1);
+    MemoryRegion *dram1_mem = NULL;
+    qemu_irq *irqp;
+    qemu_irq cpu_irq[4];
+    ram_addr_t mem_size;
+    int n;
+
+    switch (board_type) {
+    case BOARD_EXYNOS4210_NURI:
+        exynos4210_binfo.board_id      = MACH_NURI_ID;
+    break;
+    case BOARD_EXYNOS4210_SMDKC210:
+        exynos4210_binfo.board_id = MACH_SMDKC210_ID;
+    break;
+    default:
+    break;
+    }
+    if (!cpu_model) {
+        cpu_model = "cortex-a9";
+    }
+
+    for (n = 0; n < smp_cpus; n++) {
+        env = cpu_init(cpu_model);
+        if (!env) {
+            fprintf(stderr, "Unable to find CPU %d definition\n", n);
+            exit(1);
+        }
+        /* Create PIC controller for each processor instance */
+        irqp = arm_pic_init_cpu(env);
+
+        /*
+         * Get GICs gpio_in cpu_irq to connect a combiner to them later.
+         * Use only IRQ for a while.
+         */
+        cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ];
+    }
+
+    /*** Memory ***/
+
+    /* Chip-ID and OMR */
+    memory_region_init_ram_ptr(chipid_mem, NULL, "exynos4210.chipid",
+            sizeof(chipid_and_omr), chipid_and_omr);
+    memory_region_set_readonly(chipid_mem, true);
+    memory_region_add_subregion(system_mem, EXYNOS4210_SFR_BASE_ADDR,
+                                chipid_mem);
+
+    /* Internal ROM */
+    memory_region_init_ram(irom_mem, NULL, "exynos4210.irom",
+                           EXYNOS4210_IROM_SIZE);
+    memory_region_set_readonly(irom_mem, true);
+    memory_region_add_subregion(system_mem, EXYNOS4210_IROM_BASE_ADDR,
+                                irom_mem);
+    /* mirror of 0x0 – 0x10000 */
+    memory_region_init_alias(irom_alias_mem, "exynos4210.irom_alias",
+            irom_mem, 0, EXYNOS4210_IROM_SIZE);
+    memory_region_set_readonly(irom_alias_mem, true);
+    memory_region_add_subregion(system_mem, EXYNOS4210_IROM_MIRROR_BASE_ADDR,
+            irom_alias_mem);
+
+    /* Internal RAM */
+    memory_region_init_ram(iram_mem, NULL, "exynos4210.iram",
+                           EXYNOS4210_IRAM_SIZE);
+    memory_region_set_readonly(iram_mem, false);
+    memory_region_add_subregion(system_mem, EXYNOS4210_IRAM_BASE_ADDR,
+                                iram_mem);
+
+    /* DRAM */
+    mem_size = ram_size;
+    if (mem_size > EXYNOS4210_DRAM_MAX_SIZE) {
+        dram1_mem = g_new(MemoryRegion, 1);
+        memory_region_init_ram(dram1_mem, NULL, "exynos4210.dram1",
+                mem_size - EXYNOS4210_DRAM_MAX_SIZE);
+        memory_region_add_subregion(system_mem, EXYNOS4210_DRAM1_BASE_ADDR,
+                dram1_mem);
+        mem_size = EXYNOS4210_DRAM_MAX_SIZE;
+    }
+    memory_region_init_ram(dram0_mem, NULL, "exynos4210.dram0", mem_size);
+    memory_region_add_subregion(system_mem, EXYNOS4210_DRAM0_BASE_ADDR,
+            dram0_mem);
+
+    /*** Load kernel ***/
+
+    exynos4210_binfo.ram_size = ram_size;
+    exynos4210_binfo.nb_cpus = smp_cpus;
+    exynos4210_binfo.kernel_filename = kernel_filename;
+    exynos4210_binfo.initrd_filename = initrd_filename;
+    exynos4210_binfo.kernel_cmdline = kernel_cmdline;
+
+    arm_load_kernel(first_cpu, &exynos4210_binfo);
+}
+
+static void exynos4210_nuri_init(ram_addr_t ram_size,
+        const char *boot_device,
+        const char *kernel_filename, const char *kernel_cmdline,
+        const char *initrd_filename, const char *cpu_model)
+{
+    exynos4210_init(ram_size, boot_device, kernel_filename, kernel_cmdline,
+            initrd_filename, cpu_model, BOARD_EXYNOS4210_NURI);
+}
+
+static void exynos4210_smdkc210_init(ram_addr_t ram_size,
+        const char *boot_device,
+        const char *kernel_filename, const char *kernel_cmdline,
+        const char *initrd_filename, const char *cpu_model)
+{
+    exynos4210_init(ram_size, boot_device, kernel_filename, kernel_cmdline,
+            initrd_filename, cpu_model, BOARD_EXYNOS4210_SMDKC210);
+}
+
+static QEMUMachine exynos4210_nuri_machine = {
+        .name = "exynos4210-nuri",
+        .desc = "Samsung Exynos4210 NURI board",
+        .init = exynos4210_nuri_init,
+        .max_cpus = EXYNOS4210_MAX_CPUS,
+};
+
+static QEMUMachine exynos4210_smdkc210_machine = {
+        .name = "exynos4210-smdkc210",
+        .desc = "Samsung Exynos4210 SMDKC210 board",
+        .init = exynos4210_smdkc210_init,
+        .max_cpus = EXYNOS4210_MAX_CPUS,
+};
+
+static void exynos4210_machine_init(void)
+{
+    qemu_register_machine(&exynos4210_nuri_machine);
+    qemu_register_machine(&exynos4210_smdkc210_machine);
+}
+
+machine_init(exynos4210_machine_init);
diff --git a/hw/exynos4210.h b/hw/exynos4210.h
new file mode 100644
index 0000000..7137630
--- /dev/null
+++ b/hw/exynos4210.h
@@ -0,0 +1,34 @@
+/*
+ *  Samsung exynos4210-based boards emulation
+ *
+ *  Copyright (c) 2011 Samsung Electronics Co., Ltd. All rights reserved.
+ *    Maksim Kozlov <m.kozlov@samsung.com>
+ *    Evgeny Voevodin <e.voevodin@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, write to the Free Software Foundation, Inc., 51
+ *  Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+
+#ifndef EXYNOS4210_H_
+#define EXYNOS4210_H_
+
+#include "qemu-common.h"
+
+#define EXYNOS4210_MAX_CPUS                2
+
+#endif /* EXYNOS4210_H_ */
-- 
1.7.4.1

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Qemu-devel] [PATCH 02/15] ARM: exynos4210: CMU support
  2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 01/15] ARM: Samsung exynos4210-based boards emulation Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 03/15] ARM: exynos4210: UART support Evgeny Voevodin
                   ` (12 subsequent siblings)
  14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin

From: Maksim Kozlov <m.kozlov@samsung.com>

Add exynos4210 Clock Management Units emulation

Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 Makefile.target     |    2 +-
 hw/exynos4210.c     |    7 +
 hw/exynos4210.h     |   22 +
 hw/exynos4210_cmu.c | 1146 +++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 1176 insertions(+), 1 deletions(-)
 create mode 100644 hw/exynos4210_cmu.c

diff --git a/Makefile.target b/Makefile.target
index 624a142..ce4f1f8 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -344,7 +344,7 @@ 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 += exynos4210.o
+obj-arm-y += exynos4210.o exynos4210_cmu.o
 obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
 obj-arm-y += pl061.o
 obj-arm-y += arm-semi.o
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index 1550016..1a6e353 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -60,6 +60,10 @@
 
 #define EXYNOS4210_BASE_BOOT_ADDR           EXYNOS4210_DRAM0_BASE_ADDR
 
+/* SFR Base Address for CMUs */
+#define EXYNOS4210_CMU_BASE_ADDR            0x10030000
+
+
 static struct arm_boot_info exynos4210_binfo = {
         .loader_start     = EXYNOS4210_BASE_BOOT_ADDR,
 };
@@ -172,6 +176,9 @@ static void exynos4210_init(ram_addr_t ram_size,
     memory_region_add_subregion(system_mem, EXYNOS4210_DRAM0_BASE_ADDR,
             dram0_mem);
 
+    /* CMU */
+    sysbus_create_simple("exynos4210.cmu", EXYNOS4210_CMU_BASE_ADDR, NULL);
+
     /*** Load kernel ***/
 
     exynos4210_binfo.ram_size = ram_size;
diff --git a/hw/exynos4210.h b/hw/exynos4210.h
index 7137630..683a4a6 100644
--- a/hw/exynos4210.h
+++ b/hw/exynos4210.h
@@ -31,4 +31,26 @@
 
 #define EXYNOS4210_MAX_CPUS                2
 
+/*
+ * Interface for exynos4210 Clock Management Units (CMUs)
+ */
+
+typedef enum {
+    XXTI,
+    XUSBXTI,
+    APLL,
+    MPLL,
+    SCLK_APLL,
+    SCLK_MPLL,
+    ACLK_100,
+    SCLK_UART0,
+    SCLK_UART1,
+    SCLK_UART2,
+    SCLK_UART3,
+    SCLK_UART4,
+    CLOCKS_NUMBER
+} Exynos4210CmuClock;
+
+uint64_t exynos4210_cmu_get_rate(Exynos4210CmuClock clock);
+
 #endif /* EXYNOS4210_H_ */
diff --git a/hw/exynos4210_cmu.c b/hw/exynos4210_cmu.c
new file mode 100644
index 0000000..fe4100c
--- /dev/null
+++ b/hw/exynos4210_cmu.c
@@ -0,0 +1,1146 @@
+/*
+ *  exynos4210 Clock Management Units (CMUs) Emulation
+ *
+ *  Copyright (C) 2011 Samsung Electronics Co Ltd.
+ *    Maksim Kozlov, <m.kozlov@samsung.com>
+ *
+ *  Created on: 07.2011
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License as published by the
+ *  Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ *  for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc., 51
+ *  Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "sysbus.h"
+
+#include "exynos4210.h"
+
+
+
+#undef DEBUG_CMU
+
+//#define DEBUG_CMU
+//#define DEBUG_CMU_EXTEND
+
+
+#define  PRINT_DEBUG(fmt, args...)  \
+        do {} while (0)
+#define  PRINT_DEBUG_SIMPLE(fmt, args...)  \
+        do {} while (0)
+#define  PRINT_DEBUG_EXTEND(fmt, args...) \
+        do {} while (0)
+#define  PRINT_ERROR(fmt, args...) \
+        do { \
+            fprintf(stderr, "  [%s:%d]   "fmt, __func__, __LINE__, ##args); \
+        } while (0)
+
+
+#ifdef DEBUG_CMU
+
+    #undef PRINT_DEBUG
+    #define  PRINT_DEBUG(fmt, args...)  \
+        do { \
+            fprintf(stderr, "  [%s:%d]   "fmt, __func__, __LINE__, ##args); \
+        } while (0)
+
+    #undef PRINT_DEBUG_SIMPLE
+    #define  PRINT_DEBUG_SIMPLE(fmt, args...)  \
+        do { \
+            fprintf(stderr, fmt, ## args); \
+        } while (0)
+
+#ifdef DEBUG_CMU_EXTEND
+
+    #undef PRINT_DEBUG_EXTEND
+    #define  PRINT_DEBUG_EXTEND(fmt, args...) \
+        do { \
+            fprintf(stderr, "  [%s:%d]   "fmt, __func__, __LINE__, ##args); \
+        } while (0)
+
+#endif /* EXTEND */
+#endif
+
+
+/*
+ *  Offsets for CMUs registers
+ */
+
+/* CMU_LEFTBUS registers */
+#define     CLK_SRC_LEFTBUS              0x04200
+#define     CLK_MUX_STAT_LEFTBUS         0x04400
+#define     CLK_DIV_LEFTBUS              0x04500
+#define     CLK_DIV_STAT_LEFTBUS         0x04600
+#define     CLK_GATE_IP_LEFTBUS          0x04800
+#define     CLKOUT_CMU_LEFTBUS           0x04A00
+#define     CLKOUT_CMU_LEFTBUS_DIV_STAT  0x04A04
+/* CMU_RIGHTBUS registers */
+#define     CLK_SRC_RIGHTBUS             0x08200
+#define     CLK_MUX_STAT_RIGHTBUS        0x08400
+#define     CLK_DIV_RIGHTBUS             0x08500
+#define     CLK_DIV_STAT_RIGHTBUS        0x08600
+#define     CLK_GATE_IP_RIGHTBUS         0x08800
+#define     CLKOUT_CMU_RIGHTBUS          0x08A00
+#define     CLKOUT_CMU_RIGHTBUS_DIV_STAT 0x08A04
+/* CMU_TOP registers */
+#define     EPLL_LOCK                    0x0C010
+#define     VPLL_LOCK                    0x0C020
+#define     EPLL_CON0                    0x0C110
+#define     EPLL_CON1                    0x0C114
+#define     VPLL_CON0                    0x0C120
+#define     VPLL_CON1                    0x0C124
+#define     CLK_SRC_TOP0                 0x0C210
+#define     CLK_SRC_TOP1                 0x0C214
+#define     CLK_SRC_CAM                  0x0C220
+#define     CLK_SRC_TV                   0x0C224
+#define     CLK_SRC_MFC                  0x0C228
+#define     CLK_SRC_G3D                  0x0C22C
+#define     CLK_SRC_IMAGE                0x0C230
+#define     CLK_SRC_LCD0                 0x0C234
+#define     CLK_SRC_LCD1                 0x0C238
+#define     CLK_SRC_MAUDIO               0x0C23C
+#define     CLK_SRC_FSYS                 0x0C240
+#define     CLK_SRC_PERIL0               0x0C250
+#define     CLK_SRC_PERIL1               0x0C254
+#define     CLK_SRC_MASK_TOP             0x0C310
+#define     CLK_SRC_MASK_CAM             0x0C320
+#define     CLK_SRC_MASK_TV              0x0C324
+#define     CLK_SRC_MASK_LCD0            0x0C334
+#define     CLK_SRC_MASK_LCD1            0x0C338
+#define     CLK_SRC_MASK_MAUDIO          0x0C33C
+#define     CLK_SRC_MASK_FSYS            0x0C340
+#define     CLK_SRC_MASK_PERIL0          0x0C350
+#define     CLK_SRC_MASK_PERIL1          0x0C354
+#define     CLK_MUX_STAT_TOP             0x0C410
+#define     CLK_MUX_STAT_MFC             0x0C428
+#define     CLK_MUX_STAT_G3D             0x0C42C
+#define     CLK_MUX_STAT_IMAGE           0x0C430
+#define     CLK_DIV_TOP                  0x0C510
+#define     CLK_DIV_CAM                  0x0C520
+#define     CLK_DIV_TV                   0x0C524
+#define     CLK_DIV_MFC                  0x0C528
+#define     CLK_DIV_G3D                  0x0C52C
+#define     CLK_DIV_IMAGE                0x0C530
+#define     CLK_DIV_LCD0                 0x0C534
+#define     CLK_DIV_LCD1                 0x0C538
+#define     CLK_DIV_MAUDIO               0x0C53C
+#define     CLK_DIV_FSYS0                0x0C540
+#define     CLK_DIV_FSYS1                0x0C544
+#define     CLK_DIV_FSYS2                0x0C548
+#define     CLK_DIV_FSYS3                0x0C54C
+#define     CLK_DIV_PERIL0               0x0C550
+#define     CLK_DIV_PERIL1               0x0C554
+#define     CLK_DIV_PERIL2               0x0C558
+#define     CLK_DIV_PERIL3               0x0C55C
+#define     CLK_DIV_PERIL4               0x0C560
+#define     CLK_DIV_PERIL5               0x0C564
+#define     CLKDIV2_RATIO                0x0C580
+#define     CLK_DIV_STAT_TOP             0x0C610
+#define     CLK_DIV_STAT_CAM             0x0C620
+#define     CLK_DIV_STAT_TV              0x0C624
+#define     CLK_DIV_STAT_MFC             0x0C628
+#define     CLK_DIV_STAT_G3D             0x0C62C
+#define     CLK_DIV_STAT_IMAGE           0x0C630
+#define     CLK_DIV_STAT_LCD0            0x0C634
+#define     CLK_DIV_STAT_LCD1            0x0C638
+#define     CLK_DIV_STAT_MAUDIO          0x0C63C
+#define     CLK_DIV_STAT_FSYS0           0x0C640
+#define     CLK_DIV_STAT_FSYS1           0x0C644
+#define     CLK_DIV_STAT_FSYS2           0x0C648
+#define     CLK_DIV_STAT_FSYS3           0x0C64C
+#define     CLK_DIV_STAT_PERIL0          0x0C650
+#define     CLK_DIV_STAT_PERIL1          0x0C654
+#define     CLK_DIV_STAT_PERIL2          0x0C658
+#define     CLK_DIV_STAT_PERIL3          0x0C65C
+#define     CLK_DIV_STAT_PERIL4          0x0C660
+#define     CLK_DIV_STAT_PERIL5          0x0C664
+#define     CLKDIV2_STAT                 0x0C680
+#define     CLK_GATE_SCLK_CAM            0x0C820
+#define     CLK_GATE_IP_CAM              0x0C920
+#define     CLK_GATE_IP_TV               0x0C924
+#define     CLK_GATE_IP_MFC              0x0C928
+#define     CLK_GATE_IP_G3D              0x0C92C
+#define     CLK_GATE_IP_IMAGE            0x0C930
+#define     CLK_GATE_IP_LCD0             0x0C934
+#define     CLK_GATE_IP_LCD1             0x0C938
+#define     CLK_GATE_IP_FSYS             0x0C940
+#define     CLK_GATE_IP_GPS              0x0C94C
+#define     CLK_GATE_IP_PERIL            0x0C950
+#define     CLK_GATE_IP_PERIR            0x0C960
+#define     CLK_GATE_BLOCK               0x0C970
+#define     CLKOUT_CMU_TOP               0x0CA00
+#define     CLKOUT_CMU_TOP_DIV_STAT      0x0CA04
+/* CMU_DMC registers */
+#define     CLK_SRC_DMC                  0x10200
+#define     CLK_SRC_MASK_DMC             0x10300
+#define     CLK_MUX_STAT_DMC             0x10400
+#define     CLK_DIV_DMC0                 0x10500
+#define     CLK_DIV_DMC1                 0x10504
+#define     CLK_DIV_STAT_DMC0            0x10600
+#define     CLK_DIV_STAT_DMC1            0x10604
+#define     CLK_GATE_IP_DMC              0x10900
+#define     CLKOUT_CMU_DMC               0x10A00
+#define     CLKOUT_CMU_DMC_DIV_STAT      0x10A04
+#define     DCGIDX_MAP0                  0x11000
+#define     DCGIDX_MAP1                  0x11004
+#define     DCGIDX_MAP2                  0x11008
+#define     DCGPERF_MAP0                 0x11020
+#define     DCGPERF_MAP1                 0x11024
+#define     DVCIDX_MAP                   0x11040
+#define     FREQ_CPU                     0x11060
+#define     FREQ_DPM                     0x11064
+#define     DVSEMCLK_EN                  0x11080
+#define     MAXPERF                      0x11084
+#define     APLL_LOCK                    0x14000
+#define     MPLL_LOCK                    0x14008
+#define     APLL_CON0                    0x14100
+#define     APLL_CON1                    0x14104
+#define     MPLL_CON0                    0x14108
+#define     MPLL_CON1                    0x1410C
+/* CMU_CPU registers */
+#define     CLK_SRC_CPU                  0x14200
+#define     CLK_MUX_STAT_CPU             0x14400
+#define     CLK_DIV_CPU0                 0x14500
+#define     CLK_DIV_CPU1                 0x14504
+#define     CLK_DIV_STAT_CPU0            0x14600
+#define     CLK_DIV_STAT_CPU1            0x14604
+#define     CLK_GATE_SCLK_CPU            0x14800
+#define     CLK_GATE_IP_CPU              0x14900
+#define     CLKOUT_CMU_CPU               0x14A00
+#define     CLKOUT_CMU_CPU_DIV_STAT      0x14A04
+#define     ARMCLK_STOPCTRL              0x15000
+#define     ATCLK_STOPCTRL               0x15004
+#define     PARITYFAIL_STATUS            0x15010
+#define     PARITYFAIL_CLEAR             0x15014
+#define     PWR_CTRL                     0x15020
+#define     APLL_CON0_L8                 0x15100
+#define     APLL_CON0_L7                 0x15104
+#define     APLL_CON0_L6                 0x15108
+#define     APLL_CON0_L5                 0x1510C
+#define     APLL_CON0_L4                 0x15110
+#define     APLL_CON0_L3                 0x15114
+#define     APLL_CON0_L2                 0x15118
+#define     APLL_CON0_L1                 0x1511C
+#define     IEM_CONTROL                  0x15120
+#define     APLL_CON1_L8                 0x15200
+#define     APLL_CON1_L7                 0x15204
+#define     APLL_CON1_L6                 0x15208
+#define     APLL_CON1_L5                 0x1520C
+#define     APLL_CON1_L4                 0x15210
+#define     APLL_CON1_L3                 0x15214
+#define     APLL_CON1_L2                 0x15218
+#define     APLL_CON1_L1                 0x1521C
+#define     CLKDIV_IEM_L8                0x15300
+#define     CLKDIV_IEM_L7                0x15304
+#define     CLKDIV_IEM_L6                0x15308
+#define     CLKDIV_IEM_L5                0x1530C
+#define     CLKDIV_IEM_L4                0x15310
+#define     CLKDIV_IEM_L3                0x15314
+#define     CLKDIV_IEM_L2                0x15318
+#define     CLKDIV_IEM_L1                0x1531C
+
+
+typedef struct Exynos4210CmuReg {
+        const char *name; /* for debugging */
+        uint32_t    offset;
+        uint32_t    reset_value;
+} Exynos4210CmuReg;
+
+
+static Exynos4210CmuReg exynos4210_cmu_regs[] = {
+    /* CMU_LEFTBUS registers */
+    {"CLK_SRC_LEFTBUS",              CLK_SRC_LEFTBUS,              0x00000000},
+    {"CLK_MUX_STAT_LEFTBUS",         CLK_MUX_STAT_LEFTBUS,         0x00000001},
+    {"CLK_DIV_LEFTBUS",              CLK_DIV_LEFTBUS,              0x00000000},
+    {"CLK_DIV_STAT_LEFTBUS",         CLK_DIV_STAT_LEFTBUS,         0x00000000},
+    {"CLK_GATE_IP_LEFTBUS",          CLK_GATE_IP_LEFTBUS,          0xFFFFFFFF},
+    {"CLKOUT_CMU_LEFTBUS",           CLKOUT_CMU_LEFTBUS,           0x00010000},
+    {"CLKOUT_CMU_LEFTBUS_DIV_STAT",  CLKOUT_CMU_LEFTBUS_DIV_STAT,  0x00000000},
+    /* CMU_RIGHTBUS registers */
+    {"CLK_SRC_RIGHTBUS",             CLK_SRC_RIGHTBUS,             0x00000000},
+    {"CLK_MUX_STAT_RIGHTBUS",        CLK_MUX_STAT_RIGHTBUS,        0x00000001},
+    {"CLK_DIV_RIGHTBUS",             CLK_DIV_RIGHTBUS,             0x00000000},
+    {"CLK_DIV_STAT_RIGHTBUS",        CLK_DIV_STAT_RIGHTBUS,        0x00000000},
+    {"CLK_GATE_IP_RIGHTBUS",         CLK_GATE_IP_RIGHTBUS,         0xFFFFFFFF},
+    {"CLKOUT_CMU_RIGHTBUS",          CLKOUT_CMU_RIGHTBUS,          0x00010000},
+    {"CLKOUT_CMU_RIGHTBUS_DIV_STAT", CLKOUT_CMU_RIGHTBUS_DIV_STAT, 0x00000000},
+    /* CMU_TOP registers */
+    {"EPLL_LOCK",               EPLL_LOCK,               0x00000FFF},
+    {"VPLL_LOCK",               VPLL_LOCK,               0x00000FFF},
+    {"EPLL_CON0",               EPLL_CON0,               0x00300301},
+    {"EPLL_CON1",               EPLL_CON1,               0x00000000},
+    {"VPLL_CON0",               VPLL_CON0,               0x00240201},
+    {"VPLL_CON1",               VPLL_CON1,               0x66010464},
+    {"CLK_SRC_TOP0",            CLK_SRC_TOP0,            0x00000000},
+    {"CLK_SRC_TOP1",            CLK_SRC_TOP1,            0x00000000},
+    {"CLK_SRC_CAM",             CLK_SRC_CAM,             0x11111111},
+    {"CLK_SRC_TV",              CLK_SRC_TV,              0x00000000},
+    {"CLK_SRC_MFC",             CLK_SRC_MFC,             0x00000000},
+    {"CLK_SRC_G3D",             CLK_SRC_G3D,             0x00000000},
+    {"CLK_SRC_IMAGE",           CLK_SRC_IMAGE,           0x00000000},
+    {"CLK_SRC_LCD0",            CLK_SRC_LCD0,            0x00001111},
+    {"CLK_SRC_LCD1",            CLK_SRC_LCD1,            0x00001111},
+    {"CLK_SRC_MAUDIO",          CLK_SRC_MAUDIO,          0x00000005},
+    {"CLK_SRC_FSYS",            CLK_SRC_FSYS,            0x00011111},
+    {"CLK_SRC_PERIL0",          CLK_SRC_PERIL0,          0x00011111},
+    {"CLK_SRC_PERIL1",          CLK_SRC_PERIL1,          0x01110055},
+    {"CLK_SRC_MASK_TOP",        CLK_SRC_MASK_TOP,        0x00000001},
+    {"CLK_SRC_MASK_CAM",        CLK_SRC_MASK_CAM,        0x11111111},
+    {"CLK_SRC_MASK_TV",         CLK_SRC_MASK_TV,         0x00000111},
+    {"CLK_SRC_MASK_LCD0",       CLK_SRC_MASK_LCD0,       0x00001111},
+    {"CLK_SRC_MASK_LCD1",       CLK_SRC_MASK_LCD1,       0x00001111},
+    {"CLK_SRC_MASK_MAUDIO",     CLK_SRC_MASK_MAUDIO,     0x00000001},
+    {"CLK_SRC_MASK_FSYS",       CLK_SRC_MASK_FSYS,       0x01011111},
+    {"CLK_SRC_MASK_PERIL0",     CLK_SRC_MASK_PERIL0,     0x00011111},
+    {"CLK_SRC_MASK_PERIL1",     CLK_SRC_MASK_PERIL1,     0x01110111},
+    {"CLK_MUX_STAT_TOP",        CLK_MUX_STAT_TOP,        0x11111111},
+    {"CLK_MUX_STAT_MFC",        CLK_MUX_STAT_MFC,        0x00000111},
+    {"CLK_MUX_STAT_G3D",        CLK_MUX_STAT_G3D,        0x00000111},
+    {"CLK_MUX_STAT_IMAGE",      CLK_MUX_STAT_IMAGE,      0x00000111},
+    {"CLK_DIV_TOP",             CLK_DIV_TOP,             0x00000000},
+    {"CLK_DIV_CAM",             CLK_DIV_CAM,             0x00000000},
+    {"CLK_DIV_TV",              CLK_DIV_TV,              0x00000000},
+    {"CLK_DIV_MFC",             CLK_DIV_MFC,             0x00000000},
+    {"CLK_DIV_G3D",             CLK_DIV_G3D,             0x00000000},
+    {"CLK_DIV_IMAGE",           CLK_DIV_IMAGE,           0x00000000},
+    {"CLK_DIV_LCD0",            CLK_DIV_LCD0,            0x00700000},
+    {"CLK_DIV_LCD1",            CLK_DIV_LCD1,            0x00700000},
+    {"CLK_DIV_MAUDIO",          CLK_DIV_MAUDIO,          0x00000000},
+    {"CLK_DIV_FSYS0",           CLK_DIV_FSYS0,           0x00B00000},
+    {"CLK_DIV_FSYS1",           CLK_DIV_FSYS1,           0x00000000},
+    {"CLK_DIV_FSYS2",           CLK_DIV_FSYS2,           0x00000000},
+    {"CLK_DIV_FSYS3",           CLK_DIV_FSYS3,           0x00000000},
+    {"CLK_DIV_PERIL0",          CLK_DIV_PERIL0,          0x00000000},
+    {"CLK_DIV_PERIL1",          CLK_DIV_PERIL1,          0x00000000},
+    {"CLK_DIV_PERIL2",          CLK_DIV_PERIL2,          0x00000000},
+    {"CLK_DIV_PERIL3",          CLK_DIV_PERIL3,          0x00000000},
+    {"CLK_DIV_PERIL4",          CLK_DIV_PERIL4,          0x00000000},
+    {"CLK_DIV_PERIL5",          CLK_DIV_PERIL5,          0x00000000},
+    {"CLKDIV2_RATIO",           CLKDIV2_RATIO,           0x11111111},
+    {"CLK_DIV_STAT_TOP",        CLK_DIV_STAT_TOP,        0x00000000},
+    {"CLK_DIV_STAT_CAM",        CLK_DIV_STAT_CAM,        0x00000000},
+    {"CLK_DIV_STAT_TV",         CLK_DIV_STAT_TV,         0x00000000},
+    {"CLK_DIV_STAT_MFC",        CLK_DIV_STAT_MFC,        0x00000000},
+    {"CLK_DIV_STAT_G3D",        CLK_DIV_STAT_G3D,        0x00000000},
+    {"CLK_DIV_STAT_IMAGE",      CLK_DIV_STAT_IMAGE,      0x00000000},
+    {"CLK_DIV_STAT_LCD0",       CLK_DIV_STAT_LCD0,       0x00000000},
+    {"CLK_DIV_STAT_LCD1",       CLK_DIV_STAT_LCD1,       0x00000000},
+    {"CLK_DIV_STAT_MAUDIO",     CLK_DIV_STAT_MAUDIO,     0x00000000},
+    {"CLK_DIV_STAT_FSYS0",      CLK_DIV_STAT_FSYS0,      0x00000000},
+    {"CLK_DIV_STAT_FSYS1",      CLK_DIV_STAT_FSYS1,      0x00000000},
+    {"CLK_DIV_STAT_FSYS2",      CLK_DIV_STAT_FSYS2,      0x00000000},
+    {"CLK_DIV_STAT_FSYS3",      CLK_DIV_STAT_FSYS3,      0x00000000},
+    {"CLK_DIV_STAT_PERIL0",     CLK_DIV_STAT_PERIL0,     0x00000000},
+    {"CLK_DIV_STAT_PERIL1",     CLK_DIV_STAT_PERIL1,     0x00000000},
+    {"CLK_DIV_STAT_PERIL2",     CLK_DIV_STAT_PERIL2,     0x00000000},
+    {"CLK_DIV_STAT_PERIL3",     CLK_DIV_STAT_PERIL3,     0x00000000},
+    {"CLK_DIV_STAT_PERIL4",     CLK_DIV_STAT_PERIL4,     0x00000000},
+    {"CLK_DIV_STAT_PERIL5",     CLK_DIV_STAT_PERIL5,     0x00000000},
+    {"CLKDIV2_STAT",            CLKDIV2_STAT,            0x00000000},
+    {"CLK_GATE_SCLK_CAM",       CLK_GATE_SCLK_CAM,       0xFFFFFFFF},
+    {"CLK_GATE_IP_CAM",         CLK_GATE_IP_CAM,         0xFFFFFFFF},
+    {"CLK_GATE_IP_TV",          CLK_GATE_IP_TV,          0xFFFFFFFF},
+    {"CLK_GATE_IP_MFC",         CLK_GATE_IP_MFC,         0xFFFFFFFF},
+    {"CLK_GATE_IP_G3D",         CLK_GATE_IP_G3D,         0xFFFFFFFF},
+    {"CLK_GATE_IP_IMAGE",       CLK_GATE_IP_IMAGE,       0xFFFFFFFF},
+    {"CLK_GATE_IP_LCD0",        CLK_GATE_IP_LCD0,        0xFFFFFFFF},
+    {"CLK_GATE_IP_LCD1",        CLK_GATE_IP_LCD1,        0xFFFFFFFF},
+    {"CLK_GATE_IP_FSYS",        CLK_GATE_IP_FSYS,        0xFFFFFFFF},
+    {"CLK_GATE_IP_GPS",         CLK_GATE_IP_GPS,         0xFFFFFFFF},
+    {"CLK_GATE_IP_PERIL",       CLK_GATE_IP_PERIL,       0xFFFFFFFF},
+    {"CLK_GATE_IP_PERIR",       CLK_GATE_IP_PERIR,       0xFFFFFFFF},
+    {"CLK_GATE_BLOCK",          CLK_GATE_BLOCK,          0xFFFFFFFF},
+    {"CLKOUT_CMU_TOP",          CLKOUT_CMU_TOP,          0x00010000},
+    {"CLKOUT_CMU_TOP_DIV_STAT", CLKOUT_CMU_TOP_DIV_STAT, 0x00000000},
+    /* CMU_DMC registers */
+    {"CLK_SRC_DMC",             CLK_SRC_DMC,             0x00010000},
+    {"CLK_SRC_MASK_DMC",        CLK_SRC_MASK_DMC,        0x00010000},
+    {"CLK_MUX_STAT_DMC",        CLK_MUX_STAT_DMC,        0x11100110},
+    {"CLK_DIV_DMC0",            CLK_DIV_DMC0,            0x00000000},
+    {"CLK_DIV_DMC1",            CLK_DIV_DMC1,            0x00000000},
+    {"CLK_DIV_STAT_DMC0",       CLK_DIV_STAT_DMC0,       0x00000000},
+    {"CLK_DIV_STAT_DMC1",       CLK_DIV_STAT_DMC1,       0x00000000},
+    {"CLK_GATE_IP_DMC",         CLK_GATE_IP_DMC,         0xFFFFFFFF},
+    {"CLKOUT_CMU_DMC",          CLKOUT_CMU_DMC,          0x00010000},
+    {"CLKOUT_CMU_DMC_DIV_STAT", CLKOUT_CMU_DMC_DIV_STAT, 0x00000000},
+    {"DCGIDX_MAP0",             DCGIDX_MAP0,             0xFFFFFFFF},
+    {"DCGIDX_MAP1",             DCGIDX_MAP1,             0xFFFFFFFF},
+    {"DCGIDX_MAP2",             DCGIDX_MAP2,             0xFFFFFFFF},
+    {"DCGPERF_MAP0",            DCGPERF_MAP0,            0xFFFFFFFF},
+    {"DCGPERF_MAP1",            DCGPERF_MAP1,            0xFFFFFFFF},
+    {"DVCIDX_MAP",              DVCIDX_MAP,              0xFFFFFFFF},
+    {"FREQ_CPU",                FREQ_CPU,                0x00000000},
+    {"FREQ_DPM",                FREQ_DPM,                0x00000000},
+    {"DVSEMCLK_EN",             DVSEMCLK_EN,             0x00000000},
+    {"MAXPERF",                 MAXPERF,                 0x00000000},
+    {"APLL_LOCK",               APLL_LOCK,               0x00000FFF},
+    {"MPLL_LOCK",               MPLL_LOCK,               0x00000FFF},
+    {"APLL_CON0",               APLL_CON0,               0x00C80601},
+    {"APLL_CON1",               APLL_CON1,               0x0000001C},
+    {"MPLL_CON0",               MPLL_CON0,               0x00C80601},
+    {"MPLL_CON1",               MPLL_CON1,               0x0000001C},
+    /* CMU_CPU registers */
+    {"CLK_SRC_CPU",             CLK_SRC_CPU,             0x00000000},
+    {"CLK_MUX_STAT_CPU",        CLK_MUX_STAT_CPU,        0x00110101},
+    {"CLK_DIV_CPU0",            CLK_DIV_CPU0,            0x00000000},
+    {"CLK_DIV_CPU1",            CLK_DIV_CPU1,            0x00000000},
+    {"CLK_DIV_STAT_CPU0",       CLK_DIV_STAT_CPU0,       0x00000000},
+    {"CLK_DIV_STAT_CPU1",       CLK_DIV_STAT_CPU1,       0x00000000},
+    {"CLK_GATE_SCLK_CPU",       CLK_GATE_SCLK_CPU,       0xFFFFFFFF},
+    {"CLK_GATE_IP_CPU",         CLK_GATE_IP_CPU,         0xFFFFFFFF},
+    {"CLKOUT_CMU_CPU",          CLKOUT_CMU_CPU,          0x00010000},
+    {"CLKOUT_CMU_CPU_DIV_STAT", CLKOUT_CMU_CPU_DIV_STAT, 0x00000000},
+    {"ARMCLK_STOPCTRL",         ARMCLK_STOPCTRL,         0x00000044},
+    {"ATCLK_STOPCTRL",          ATCLK_STOPCTRL,          0x00000044},
+    {"PARITYFAIL_STATUS",       PARITYFAIL_STATUS,       0x00000000},
+    {"PARITYFAIL_CLEAR",        PARITYFAIL_CLEAR,        0x00000000},
+    {"PWR_CTRL",                PWR_CTRL,                0x00000033},
+    {"APLL_CON0_L8",            APLL_CON0_L8,            0x00C80601},
+    {"APLL_CON0_L7",            APLL_CON0_L7,            0x00C80601},
+    {"APLL_CON0_L6",            APLL_CON0_L6,            0x00C80601},
+    {"APLL_CON0_L5",            APLL_CON0_L5,            0x00C80601},
+    {"APLL_CON0_L4",            APLL_CON0_L4,            0x00C80601},
+    {"APLL_CON0_L3",            APLL_CON0_L3,            0x00C80601},
+    {"APLL_CON0_L2",            APLL_CON0_L2,            0x00C80601},
+    {"APLL_CON0_L1",            APLL_CON0_L1,            0x00C80601},
+    {"IEM_CONTROL",             IEM_CONTROL,             0x00000000},
+    {"APLL_CON1_L8",            APLL_CON1_L8,            0x00000000},
+    {"APLL_CON1_L7",            APLL_CON1_L7,            0x00000000},
+    {"APLL_CON1_L6",            APLL_CON1_L6,            0x00000000},
+    {"APLL_CON1_L5",            APLL_CON1_L5,            0x00000000},
+    {"APLL_CON1_L4",            APLL_CON1_L4,            0x00000000},
+    {"APLL_CON1_L3",            APLL_CON1_L3,            0x00000000},
+    {"APLL_CON1_L2",            APLL_CON1_L2,            0x00000000},
+    {"APLL_CON1_L1",            APLL_CON1_L1,            0x00000000},
+    {"CLKDIV_IEM_L8",           CLKDIV_IEM_L8,           0x00000000},
+    {"CLKDIV_IEM_L7",           CLKDIV_IEM_L7,           0x00000000},
+    {"CLKDIV_IEM_L6",           CLKDIV_IEM_L6,           0x00000000},
+    {"CLKDIV_IEM_L5",           CLKDIV_IEM_L5,           0x00000000},
+    {"CLKDIV_IEM_L4",           CLKDIV_IEM_L4,           0x00000000},
+    {"CLKDIV_IEM_L3",           CLKDIV_IEM_L3,           0x00000000},
+    {"CLKDIV_IEM_L2",           CLKDIV_IEM_L2,           0x00000000},
+    {"CLKDIV_IEM_L1",           CLKDIV_IEM_L1,           0x00000000},
+};
+
+
+/*
+ * There are five CMUs:
+ *
+ *  CMU_LEFTBUS
+ *  CMU_RIGHTBUS
+ *  CMU_TOP
+ *  CMU_DMC
+ *  CMU_CPU
+ *
+ *  each of them uses 16KB address space for SFRs
+ *
+ *  + 0x4000 because SFR region for CMUs starts at 0x10030000,
+ *  but the first CMU (CMU_LEFTBUS) starts with this offset
+ *
+ */
+#define EXYNOS4210_CMU_REGS_MEM_SIZE   (0x4000 * 5 + 0x4000)
+
+/*
+ * for indexing register in the uint32_t array
+ *
+ * 'reg' - register offset (see offsets definitions above)
+ *
+ */
+#define I_(reg) (reg / sizeof(uint32_t))
+
+#define XOM_0 1 /* Select XXTI (0) or XUSBXTI (1) base clock source */
+
+/*
+ *  Offsets in CLK_SRC_CPU register
+ *  for control MUXMPLL and MUXAPLL
+ *
+ *  0 = FINPLL, 1 = MOUTM(A)PLLFOUT
+ */
+#define MUX_APLL_SEL_SHIFT 0
+#define MUX_MPLL_SEL_SHIFT 8
+#define MUX_CORE_SEL_SHIFT 16
+#define MUX_HPM_SEL_SHIFT  20
+
+#define MUX_APLL_SEL  (1 << MUX_APLL_SEL_SHIFT)
+#define MUX_MPLL_SEL  (1 << MUX_MPLL_SEL_SHIFT)
+#define MUX_CORE_SEL  (1 << MUX_CORE_SEL_SHIFT)
+#define MUX_HPM_SEL   (1 << MUX_HPM_SEL_SHIFT)
+
+/* Offsets for fields in CLK_MUX_STAT_CPU register */
+#define APLL_SEL_SHIFT         0
+#define APLL_SEL_MASK          0x00000007
+#define MPLL_SEL_SHIFT         8
+#define MPLL_SEL_MASK          0x00000700
+#define CORE_SEL_SHIFT         16
+#define CORE_SEL_MASK          0x00070000
+#define HPM_SEL_SHIFT          20
+#define HPM_SEL_MASK           0x00700000
+
+
+/* Offsets for fields in <pll>_CON0 register */
+#define PLL_ENABLE_SHIFT 31
+#define PLL_ENABLE_MASK  0x80000000 /* [31] bit */
+#define PLL_LOCKED_MASK  0x20000000 /* [29] bit */
+#define PLL_MDIV_SHIFT   16
+#define PLL_MDIV_MASK    0x03FF0000 /* [25:16] bits */
+#define PLL_PDIV_SHIFT   8
+#define PLL_PDIV_MASK    0x00003F00 /* [13:8] bits */
+#define PLL_SDIV_SHIFT   0
+#define PLL_SDIV_MASK    0x00000007 /* [2:0] bits */
+
+
+
+/*
+ *  Offset in CLK_DIV_CPU0 register
+ *  for DIVAPLL clock divider ratio
+ */
+#define APLL_RATIO_SHIFT 24
+#define APLL_RATIO_MASK  0x07000000 /* [26:24] bits */
+
+/*
+ *  Offset in CLK_DIV_TOP register
+ *  for DIVACLK_100 clock divider ratio
+ */
+#define ACLK_100_RATIO_SHIFT 4
+#define ACLK_100_RATIO_MASK  0x000000f0 /* [7:4] bits */
+
+/* Offset in CLK_SRC_TOP0 register */
+#define MUX_ACLK_100_SEL_SHIFT 16
+
+/*
+ * Offsets in CLK_SRC_PERIL0 register
+ * for clock sources of UARTs
+ */
+#define UART0_SEL_SHIFT    0
+#define UART1_SEL_SHIFT    4
+#define UART2_SEL_SHIFT    8
+#define UART3_SEL_SHIFT    12
+#define UART4_SEL_SHIFT    16
+/*
+ * Offsets in CLK_DIV_PERIL0 register
+ * for clock divider of UARTs
+ */
+#define UART0_DIV_SHIFT    0
+#define UART1_DIV_SHIFT    4
+#define UART2_DIV_SHIFT    8
+#define UART3_DIV_SHIFT    12
+#define UART4_DIV_SHIFT    16
+
+typedef struct {
+        SysBusDevice busdev;
+        MemoryRegion iomem;
+
+        uint32_t reg[EXYNOS4210_CMU_REGS_MEM_SIZE];
+
+} Exynos4210CmuState;
+
+#define SOURCES_NUMBER    9
+#define RECIPIENTS_NUMBER 9
+
+typedef struct Exynos4210CmuClockState {
+
+        const char  *name;
+        uint64_t     rate;
+
+        /* Current source clock */
+        struct Exynos4210CmuClockState *source;
+        /*
+         * Available sources. Their order must correspond to CLK_SRC_ register
+         */
+        struct Exynos4210CmuClockState *sources[SOURCES_NUMBER];
+        /* Who uses this clock? */
+        struct Exynos4210CmuClockState *recipients[RECIPIENTS_NUMBER];
+
+        uint32_t src_reg; /* Offset of CLK_SRC_<*> register */
+        uint32_t div_reg; /* Offset of CLK_DIV_<*> register */
+
+        /*
+         *  Shift for MUX_<clk>_SEL value which is stored
+         *  in appropriate CLK_MUX_STAT_<cmu> register
+         */
+        uint8_t mux_shift;
+
+        /*
+         *  Shift for <clk>_RATIO value which is stored
+         *  in appropriate CLK_DIV_<cmu> register
+         */
+        uint8_t div_shift;
+
+} Exynos4210CmuClockState;
+
+
+/* Clocks from Clock Pads */
+
+/* It should be used only for testing purposes. XOM_0 is 0 */
+static Exynos4210CmuClockState xxti = {
+        .name       = "XXTI",
+        .rate       = 24000000,
+};
+
+/* Main source. XOM_0 is 1 */
+static Exynos4210CmuClockState xusbxti = {
+        .name       = "XUSBXTI",
+        .rate       = 24000000,
+};
+
+/* PLLs */
+
+static Exynos4210CmuClockState mpll = {
+        .name       = "MPLL",
+        .source     = (XOM_0 ? &xusbxti : &xxti),
+};
+
+static Exynos4210CmuClockState apll = {
+        .name       = "APLL",
+        .source     = (XOM_0 ? &xusbxti : &xxti),
+};
+
+
+/**/
+static Exynos4210CmuClockState sclk_mpll = {
+        .name       = "SCLK_MPLL",
+        .sources    = {XOM_0 ? &xusbxti : &xxti, &mpll},
+        .src_reg    = CLK_SRC_CPU,
+        .mux_shift  = MUX_MPLL_SEL_SHIFT,
+};
+
+static Exynos4210CmuClockState sclk_apll = {
+        .name       = "SCLK_APLL",
+        .sources    = {XOM_0 ? &xusbxti : &xxti, &apll},
+        .src_reg    = CLK_SRC_CPU,
+        .div_reg    = CLK_DIV_CPU0,
+        .mux_shift  = MUX_APLL_SEL_SHIFT,
+        .div_shift  = APLL_RATIO_SHIFT,
+};
+
+static Exynos4210CmuClockState aclk_100 = {
+        .name      = "ACLK_100",
+        .sources   = {&sclk_mpll, &sclk_apll},
+        .src_reg   = CLK_SRC_TOP0,
+        .div_reg   = CLK_DIV_TOP,
+        .mux_shift = MUX_ACLK_100_SEL_SHIFT,
+        .div_shift = ACLK_100_RATIO_SHIFT,
+};
+
+
+/* TODO: add other needed structures for UARTs sources */
+static Exynos4210CmuClockState sclk_uart0 = {
+        .name      = "SCLK_UART0",
+        .sources   = {&xxti, &xusbxti, NULL, NULL, NULL, NULL, &sclk_mpll},
+        .src_reg   = CLK_SRC_PERIL0,
+        .div_reg   = CLK_DIV_PERIL0,
+        .mux_shift = UART0_SEL_SHIFT,
+        .div_shift = UART0_DIV_SHIFT,
+};
+
+static Exynos4210CmuClockState sclk_uart1 = {
+        .name      = "SCLK_UART1",
+        .sources   = {&xxti, &xusbxti, NULL, NULL, NULL, NULL, &sclk_mpll},
+        .src_reg   = CLK_SRC_PERIL0,
+        .div_reg   = CLK_DIV_PERIL0,
+        .mux_shift = UART1_SEL_SHIFT,
+        .div_shift = UART1_DIV_SHIFT,
+};
+
+static Exynos4210CmuClockState sclk_uart2 = {
+        .name      = "SCLK_UART2",
+        .sources   = {&xxti, &xusbxti, NULL, NULL, NULL, NULL, &sclk_mpll},
+        .src_reg   = CLK_SRC_PERIL0,
+        .div_reg   = CLK_DIV_PERIL0,
+        .mux_shift = UART2_SEL_SHIFT,
+        .div_shift = UART2_DIV_SHIFT,
+};
+
+static Exynos4210CmuClockState sclk_uart3 = {
+        .name      = "SCLK_UART3",
+        .sources   = {&xxti, &xusbxti, NULL, NULL, NULL, NULL, &sclk_mpll},
+        .src_reg   = CLK_SRC_PERIL0,
+        .div_reg   = CLK_DIV_PERIL0,
+        .mux_shift = UART3_SEL_SHIFT,
+        .div_shift = UART3_DIV_SHIFT,
+};
+
+static Exynos4210CmuClockState sclk_uart4 = {
+        .name      = "SCLK_UART4",
+        .sources   = {&xxti, &xusbxti, NULL, NULL, NULL, NULL, &sclk_mpll},
+        .src_reg   = CLK_SRC_PERIL0,
+        .div_reg   = CLK_DIV_PERIL0,
+        .mux_shift = UART4_SEL_SHIFT,
+        .div_shift = UART4_DIV_SHIFT,
+};
+
+/*
+ *  This array must correspond to Exynos4210CmuClock enumerator
+ *  which is defined in exynos4210.h file
+ *
+ */
+static Exynos4210CmuClockState *exynos4210_clock[] = {
+        &xxti,
+        &xusbxti,
+        &apll,
+        &mpll,
+        &sclk_apll,
+        &sclk_mpll,
+        &aclk_100,
+        &sclk_uart0,
+        &sclk_uart1,
+        &sclk_uart2,
+        &sclk_uart3,
+        &sclk_uart4,
+        NULL
+};
+
+
+uint64_t exynos4210_cmu_get_rate(Exynos4210CmuClock clock)
+{
+    return exynos4210_clock[clock]->rate;
+}
+
+#ifdef DEBUG_CMU
+/* The only meaning of life - debugging. This functions should be only used
+ * inside PRINT_DEBUG_... macroses
+ */
+static const char *exynos4210_cmu_regname(target_phys_addr_t  offset)
+{
+
+    int regs_number = sizeof(exynos4210_cmu_regs)/sizeof(Exynos4210CmuReg);
+    int i;
+
+    for (i = 0; i < regs_number; i++) {
+        if (offset == exynos4210_cmu_regs[i].offset) {
+            return exynos4210_cmu_regs[i].name;
+        }
+    }
+
+    return NULL;
+}
+#endif
+
+static void exynos4210_cmu_set_pll(void *opaque, target_phys_addr_t offset)
+{
+    Exynos4210CmuState *s = (Exynos4210CmuState *)opaque;
+    uint32_t pdiv, mdiv, sdiv, enable;
+
+    /*
+     * FOUT = MDIV * FIN / (PDIV * 2^(SDIV-1))
+     */
+
+    enable = (s->reg[I_(offset)] & PLL_ENABLE_MASK) >> PLL_ENABLE_SHIFT;
+    mdiv   = (s->reg[I_(offset)] & PLL_MDIV_MASK)   >> PLL_MDIV_SHIFT;
+    pdiv   = (s->reg[I_(offset)] & PLL_PDIV_MASK)   >> PLL_PDIV_SHIFT;
+    sdiv   = (s->reg[I_(offset)] & PLL_SDIV_MASK)   >> PLL_SDIV_SHIFT;
+
+    switch (offset) {
+
+    case MPLL_CON0:
+        if (mpll.source) {
+            if (enable) {
+                mpll.rate = mdiv * mpll.source->rate / (pdiv * (1 << (sdiv-1)));
+            } else {
+                mpll.rate = 0;
+            }
+        } else {
+            hw_error("exynos4210_cmu_set_pll: Source undefined for %s\n",
+                     mpll.name);
+        }
+        PRINT_DEBUG("mpll.rate: %llu\n", (unsigned long long int)mpll.rate);
+        break;
+
+    case APLL_CON0:
+        if (apll.source) {
+            if (enable) {
+                apll.rate = mdiv * apll.source->rate / (pdiv * (1 << (sdiv-1)));
+            } else {
+                apll.rate = 0;
+            }
+        } else {
+            hw_error("exynos4210_cmu_set_pll: Source undefined for %s\n",
+                     apll.name);
+        }
+        PRINT_DEBUG("apll.rate: %llu\n", (unsigned long long int)apll.rate);
+        break;
+
+    default:
+        hw_error("exynos4210_cmu_set_pll: Bad offset 0x%x\n", (int)offset);
+    }
+
+    s->reg[I_(offset)] |= PLL_LOCKED_MASK;
+}
+
+
+static void
+exynos4210_cmu_set_rate(void *opaque, Exynos4210CmuClockState *clock)
+{
+    Exynos4210CmuState *s = (Exynos4210CmuState *)opaque;
+    int i;
+
+    /* Rates of PLLs are calculated differently than ordinary clocks rates */
+    if (clock == &mpll) {
+        exynos4210_cmu_set_pll(s, MPLL_CON0);
+    } else if (clock == &apll) {
+        exynos4210_cmu_set_pll(s, APLL_CON0);
+    } else if ((clock != &xxti) && (clock != &xusbxti)) {
+        /*
+         *  Not root clock. We don't need calculating rate
+         *  of root clock because it is hard coded.
+         */
+        uint32_t src_index = I_(clock->src_reg);
+        uint32_t div_index = I_(clock->div_reg);
+        clock->source = clock->sources[(s->reg[src_index] >>
+                clock->mux_shift) & 0xf];
+        clock->rate = muldiv64(clock->source->rate, 1,
+                               (((s->reg[div_index] >> clock->div_shift) & 0xf)
+                                       + 1));
+
+        PRINT_DEBUG_EXTEND("SRC: <0x%05x> %s, SHIFT: %d\n",
+                           clock->src_reg,
+                           exynos4210_cmu_regname(clock->src_reg),
+                           clock->mux_shift);
+
+        PRINT_DEBUG("%s [%s:%llu]: %llu\n",
+                    clock->name,
+                    clock->source->name,
+                    (uint64_t)clock->source->rate,
+                    (uint64_t)clock->rate);
+    }
+
+    /* Visit all recipients for given clock */
+    i = 0;
+    do {
+
+        Exynos4210CmuClockState *recipient_clock = clock->recipients[i];
+
+        if (recipient_clock == NULL) {
+            PRINT_DEBUG_EXTEND("%s have %d recipients\n", clock->name, i);
+            break;
+        }
+
+        uint32_t src_index = recipient_clock->src_reg / sizeof(uint32_t);
+        int source_index = s->reg[src_index] >>
+                recipient_clock->mux_shift & 0xf;
+        recipient_clock->source = recipient_clock->sources[source_index];
+
+        if (recipient_clock->source != clock) {
+            break;
+        }
+
+        exynos4210_cmu_set_rate(s, recipient_clock);
+
+        i++;
+    } while (i < RECIPIENTS_NUMBER);
+}
+
+
+static uint64_t exynos4210_cmu_read(void *opaque, target_phys_addr_t offset,
+                                  unsigned size)
+{
+    Exynos4210CmuState *s = (Exynos4210CmuState *)opaque;
+
+    if (offset > (EXYNOS4210_CMU_REGS_MEM_SIZE - sizeof(uint32_t))) {
+        hw_error("exynos4210_cmu_read: Bad offset 0x%x\n", (int)offset);
+    }
+
+    PRINT_DEBUG_EXTEND("<0x%05x> %s -> %08x\n", offset,
+                       exynos4210_cmu_regname(offset), s->reg[I_(offset)]);
+
+    return s->reg[I_(offset)];
+}
+
+
+static void exynos4210_cmu_write(void *opaque, target_phys_addr_t offset,
+                               uint64_t val, unsigned size)
+{
+    Exynos4210CmuState *s = (Exynos4210CmuState *)opaque;
+    uint32_t pre_val;
+
+    if (offset > (EXYNOS4210_CMU_REGS_MEM_SIZE - sizeof(uint32_t))) {
+        hw_error("exynos4210_cmu_write: Bad offset 0x%x\n", (int)offset);
+    }
+
+    pre_val = s->reg[I_(offset)];
+    s->reg[I_(offset)] = val;
+
+    PRINT_DEBUG_EXTEND("<0x%05x> %s <- %08x\n", offset,
+                       exynos4210_cmu_regname(offset), s->reg[I_(offset)]);
+
+    switch (offset) {
+
+    case APLL_CON0:
+        val = (val & ~PLL_LOCKED_MASK) | (pre_val & PLL_LOCKED_MASK);
+        s->reg[I_(offset)] = val;
+        exynos4210_cmu_set_rate(s, &apll);
+        break;
+    case MPLL_CON0:
+        val = (val & ~PLL_LOCKED_MASK) | (pre_val & PLL_LOCKED_MASK);
+        s->reg[I_(offset)] = val;
+        exynos4210_cmu_set_rate(s, &mpll);
+        break;
+    case CLK_SRC_CPU:
+    {
+        if (val & MUX_APLL_SEL) {
+            s->reg[I_(CLK_MUX_STAT_CPU)] =
+                    (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(APLL_SEL_MASK)) |
+                    (2 << APLL_SEL_SHIFT);
+
+            if ((pre_val & MUX_APLL_SEL) !=
+                    (s->reg[I_(offset)] & MUX_APLL_SEL)) {
+                exynos4210_cmu_set_rate(s, &apll);
+            }
+
+        } else {
+            s->reg[I_(CLK_MUX_STAT_CPU)] =
+                    (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(APLL_SEL_MASK)) |
+                    (1 << APLL_SEL_SHIFT);
+
+            if ((pre_val & MUX_APLL_SEL) !=
+                    (s->reg[I_(offset)] & MUX_APLL_SEL)) {
+                exynos4210_cmu_set_rate(s, XOM_0 ? &xusbxti : &xxti);
+            }
+        }
+
+
+        if (val & MUX_MPLL_SEL) {
+            s->reg[I_(CLK_MUX_STAT_CPU)] =
+                    (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(MPLL_SEL_MASK)) |
+                    (2 << MPLL_SEL_SHIFT);
+
+            if ((pre_val & MUX_MPLL_SEL) !=
+                    (s->reg[I_(offset)] & MUX_MPLL_SEL)) {
+                exynos4210_cmu_set_rate(s, &mpll);
+            }
+
+        } else {
+            s->reg[I_(CLK_MUX_STAT_CPU)] =
+                    (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(MPLL_SEL_MASK)) |
+                    (1 << MPLL_SEL_SHIFT);
+
+            if ((pre_val & MUX_MPLL_SEL) !=
+                    (s->reg[I_(offset)] & MUX_MPLL_SEL)) {
+                exynos4210_cmu_set_rate(s, XOM_0 ? &xusbxti : &xxti);
+            }
+        }
+
+        if (val & MUX_CORE_SEL) {
+            s->reg[I_(CLK_MUX_STAT_CPU)] =
+                    (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(CORE_SEL_MASK)) |
+                    (2 << CORE_SEL_SHIFT);
+
+            if ((pre_val & MUX_CORE_SEL) !=
+                    (s->reg[I_(offset)] & MUX_CORE_SEL)) {
+                exynos4210_cmu_set_rate(s, &sclk_mpll);
+            }
+
+        } else {
+            s->reg[I_(CLK_MUX_STAT_CPU)] =
+                    (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(CORE_SEL_MASK)) |
+                    (1 << CORE_SEL_SHIFT);
+
+            if ((pre_val & MUX_CORE_SEL) !=
+                    (s->reg[I_(offset)] & MUX_CORE_SEL)) {
+                exynos4210_cmu_set_rate(s, XOM_0 ? &xusbxti : &xxti);
+            }
+        }
+
+        if (val & MUX_HPM_SEL) {
+            exynos4210_cmu_set_rate(s, &sclk_mpll);
+            s->reg[I_(CLK_MUX_STAT_CPU)] =
+                    (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(HPM_SEL_MASK)) |
+                    (2 << HPM_SEL_SHIFT);
+
+            if ((pre_val & MUX_HPM_SEL) != (s->reg[I_(offset)] & MUX_HPM_SEL)) {
+                exynos4210_cmu_set_rate(s, &sclk_mpll);
+            }
+
+        } else {
+            s->reg[I_(CLK_MUX_STAT_CPU)] =
+                    (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(HPM_SEL_MASK)) |
+                    (1 << HPM_SEL_SHIFT);
+
+            if ((pre_val & MUX_HPM_SEL) != (s->reg[I_(offset)] & MUX_HPM_SEL)) {
+                exynos4210_cmu_set_rate(s, XOM_0 ? &xusbxti : &xxti);
+            }
+        }
+    }
+    break;
+    case CLK_DIV_CPU0:
+        exynos4210_cmu_set_rate(s, &sclk_apll);
+        exynos4210_cmu_set_rate(s, &sclk_mpll);
+        break;
+    case CLK_SRC_TOP0:
+    case CLK_DIV_TOP:
+        exynos4210_cmu_set_rate(s, &aclk_100);
+        break;
+    default:
+        break;
+    }
+}
+
+
+static const MemoryRegionOps exynos4210_cmu_ops = {
+        .read = exynos4210_cmu_read,
+        .write = exynos4210_cmu_write,
+        .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+
+static void exynos4210_cmu_reset(void *opaque)
+{
+    Exynos4210CmuState *s = (Exynos4210CmuState *)opaque;
+    int i = 0, j = 0, n = 0;
+    int regs_number = sizeof(exynos4210_cmu_regs)/sizeof(Exynos4210CmuReg);
+    uint32_t index = 0;
+
+    /* Set default values for registers */
+    for (i = 0; i < regs_number; i++) {
+        index = (exynos4210_cmu_regs[i].offset) / sizeof(uint32_t);
+        s->reg[index] = exynos4210_cmu_regs[i].reset_value;
+    }
+
+    /* clear recipients array from previous reset */
+    for (i = 0; i < CLOCKS_NUMBER; i++) {
+        bzero(exynos4210_clock[i]->recipients,
+              RECIPIENTS_NUMBER * sizeof(Exynos4210CmuClockState *));
+    }
+
+    /*
+     * Here we fill '.recipients' fields in all clocks. Also we fill empty
+     * 'sources[]' arrays by values of 'source' fields (it is necessary
+     * for set rate, for example). If 'sources[]' array and 'source' field
+     * is empty simultaneously we get hw_error.
+     *
+     */
+    for (i = 0; i < CLOCKS_NUMBER; i++) {
+
+        /* visit all clocks in the exynos4210_clock */
+
+        PRINT_DEBUG("[SOURCES] %s: ", exynos4210_clock[i]->name);
+
+        j = 0;
+        do { /* visit all sources for current clock (exynos4210_clock[i]) */
+
+            if ((exynos4210_clock[i]->sources[j] == NULL)) {
+
+                if (j == 0) { /* check if we have empty '.sources[]' array */
+                    if (exynos4210_clock[i]->source != NULL) {
+                        exynos4210_clock[i]->sources[j] =
+                                exynos4210_clock[i]->source;
+                    } else {
+                        /*
+                         * We haven't any defined sources for this clock. Error
+                         * during definition of appropriate clock structure
+                         *
+                         */
+                        if ((exynos4210_clock[i] != &xusbxti) &&
+                            (exynos4210_clock[i] != &xxti)) {
+
+                            hw_error("exynos4210_cmu_reset:"
+                                    "There aren't any sources for %s clock!\n",
+                                     exynos4210_clock[i]->name);
+                        } else {
+                            /*
+                             * we don't need any sources for this clock
+                             * because it's a root clock
+                             */
+                            break;
+                        }
+                    }
+                } else {
+                    break; /* leave because there are no more sources */
+                }
+
+            }
+
+            PRINT_DEBUG_SIMPLE(" %s", exynos4210_clock[i]->sources[j]->name);
+
+            /*
+             *  find first empty place in 'recipients[]' array of
+             *  current 'sources' element and put current clock there
+             */
+            n = 0;
+            do {
+                if ((exynos4210_clock[i]->sources[j]->recipients[n]) == NULL) {
+                    exynos4210_clock[i]->sources[j]->recipients[n] =
+                            exynos4210_clock[i];
+                    break;
+                }
+                n++;
+            } while (n < RECIPIENTS_NUMBER);
+
+            j++;
+
+        } while (j < SOURCES_NUMBER);
+
+        PRINT_DEBUG_SIMPLE("\n");
+
+    } /* CLOCKS_NUMBER */
+
+#ifdef DEBUG_CMU
+    for (i = 0; i < CLOCKS_NUMBER; i++) {
+        PRINT_DEBUG("[RECIPIENTS] %s: ", exynos4210_clock[i]->name);
+        for (j = 0;
+             (j < RECIPIENTS_NUMBER) &&
+             ((exynos4210_clock[i]->recipients[j]) != NULL);
+             j++) {
+            PRINT_DEBUG_SIMPLE("%s ", exynos4210_clock[i]->recipients[j]->name);
+        }
+        PRINT_DEBUG_SIMPLE("\n");
+    }
+#endif
+
+    exynos4210_cmu_set_rate(s, XOM_0 ? &xusbxti : &xxti);
+}
+
+static const VMStateDescription vmstate_exynos4210_cmu = {
+        .name = "exynos4210.cmu",
+        .version_id = 1,
+        .minimum_version_id = 1,
+        .minimum_version_id_old = 1,
+        .fields = (VMStateField[]) {
+        /*
+         * TODO: Maybe we should save Exynos4210CmuClockState structs as well
+         */
+            VMSTATE_UINT32_ARRAY(reg, Exynos4210CmuState,
+                                 EXYNOS4210_CMU_REGS_MEM_SIZE),
+            VMSTATE_END_OF_LIST()
+        }
+};
+
+static int exynos4210_cmu_init(SysBusDevice *dev)
+{
+    Exynos4210CmuState *s = FROM_SYSBUS(Exynos4210CmuState, dev);
+
+    /* memory mapping */
+    memory_region_init_io(&s->iomem, &exynos4210_cmu_ops, s, "exynos4210.cmu",
+                          EXYNOS4210_CMU_REGS_MEM_SIZE);
+    sysbus_init_mmio(dev, &s->iomem);
+
+    qemu_register_reset(exynos4210_cmu_reset, s);
+
+    vmstate_register(&dev->qdev, -1, &vmstate_exynos4210_cmu, s);
+
+    exynos4210_cmu_reset(s);
+
+    return 0;
+}
+
+
+static void exynos4210_cmu_register(void)
+{
+    sysbus_register_dev("exynos4210.cmu",
+                        sizeof(Exynos4210CmuState),
+                        exynos4210_cmu_init);
+}
+
+
+device_init(exynos4210_cmu_register)
-- 
1.7.4.1

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Qemu-devel] [PATCH 03/15] ARM: exynos4210: UART support
  2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 01/15] ARM: Samsung exynos4210-based boards emulation Evgeny Voevodin
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 02/15] ARM: exynos4210: CMU support Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 04/15] hw/sysbus.h: Increase maximum number of device IRQs Evgeny Voevodin
                   ` (11 subsequent siblings)
  14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin

From: Maksim Kozlov <m.kozlov@samsung.com>

Add basic support of exynos4210 UART

Conflicts:

	Makefile.target

Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 Makefile.target      |    2 +-
 hw/exynos4210.c      |   51 ++++
 hw/exynos4210.h      |    9 +
 hw/exynos4210_uart.c |  674 ++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 735 insertions(+), 1 deletions(-)
 create mode 100644 hw/exynos4210_uart.c

diff --git a/Makefile.target b/Makefile.target
index ce4f1f8..4c706b1 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -344,7 +344,7 @@ 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 += exynos4210.o exynos4210_cmu.o
+obj-arm-y += exynos4210.o exynos4210_cmu.o exynos4210_uart.o
 obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
 obj-arm-y += pl061.o
 obj-arm-y += arm-semi.o
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index 1a6e353..d5a1fe0 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -63,6 +63,18 @@
 /* SFR Base Address for CMUs */
 #define EXYNOS4210_CMU_BASE_ADDR            0x10030000
 
+/* UART's definitions */
+#define EXYNOS4210_UART_BASE_ADDR           0x13800000
+#define EXYNOS4210_UART_SHIFT               0x00010000
+
+#define EXYNOS4210_UARTS_NUMBER             4
+
+#define EXYNOS4210_UART_CHANNEL(addr)       ((addr >> 16) & 0x7)
+#define EXYNOS4210_UART0_FIFO_SIZE          256
+#define EXYNOS4210_UART1_FIFO_SIZE          64
+#define EXYNOS4210_UART2_FIFO_SIZE          16
+#define EXYNOS4210_UART3_FIFO_SIZE          16
+
 
 static struct arm_boot_info exynos4210_binfo = {
         .loader_start     = EXYNOS4210_BASE_BOOT_ADDR,
@@ -179,6 +191,45 @@ static void exynos4210_init(ram_addr_t ram_size,
     /* CMU */
     sysbus_create_simple("exynos4210.cmu", EXYNOS4210_CMU_BASE_ADDR, NULL);
 
+    /*** UARTs ***/
+    for (n = 0; n < EXYNOS4210_UARTS_NUMBER; n++) {
+
+        uint32_t addr = EXYNOS4210_UART_BASE_ADDR + EXYNOS4210_UART_SHIFT * n;
+        int channel = EXYNOS4210_UART_CHANNEL(addr);
+        qemu_irq uart_irq;
+        int fifo_size = 0;
+
+        switch (channel) {
+        case 0:
+            fifo_size = EXYNOS4210_UART0_FIFO_SIZE;
+            break;
+        case 1:
+            fifo_size = EXYNOS4210_UART1_FIFO_SIZE;
+            break;
+        case 2:
+            fifo_size = EXYNOS4210_UART2_FIFO_SIZE;
+            break;
+        case 3:
+            fifo_size = EXYNOS4210_UART3_FIFO_SIZE;
+            break;
+        default:
+            fifo_size = 0;
+            PRINT_DEBUG("Wrong channel number: %d\n", channel);
+            break;
+        }
+
+        if (fifo_size == 0) {
+            PRINT_DEBUG("Can't create UART%d with fifo size %d\n",
+                        channel, fifo_size);
+            continue;
+        }
+
+        uart_irq = NULL;
+
+        exynos4210_uart_create(addr, fifo_size, channel, NULL, uart_irq);
+    }
+
+
     /*** Load kernel ***/
 
     exynos4210_binfo.ram_size = ram_size;
diff --git a/hw/exynos4210.h b/hw/exynos4210.h
index 683a4a6..3df7322 100644
--- a/hw/exynos4210.h
+++ b/hw/exynos4210.h
@@ -53,4 +53,13 @@ typedef enum {
 
 uint64_t exynos4210_cmu_get_rate(Exynos4210CmuClock clock);
 
+/*
+ * exynos4210 UART
+ */
+DeviceState *exynos4210_uart_create(target_phys_addr_t addr,
+                                    int fifo_size,
+                                    int channel,
+                                    CharDriverState *chr,
+                                    qemu_irq irq);
+
 #endif /* EXYNOS4210_H_ */
diff --git a/hw/exynos4210_uart.c b/hw/exynos4210_uart.c
new file mode 100644
index 0000000..22c24b7
--- /dev/null
+++ b/hw/exynos4210_uart.c
@@ -0,0 +1,674 @@
+/*
+ *  exynos4210 UART Emulation
+ *
+ *  Copyright (C) 2011 Samsung Electronics Co Ltd.
+ *    Maksim Kozlov, <m.kozlov@samsung.com>
+ *
+ *  Created on: 07.2011
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License as published by the
+ *  Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ *  for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc., 51
+ *  Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "sysbus.h"
+#include "sysemu.h"
+#include "qemu-char.h"
+
+#include "exynos4210.h"
+
+#undef DEBUG_UART
+#undef DEBUG_UART_EXTEND
+#undef DEBUG_IRQ
+#undef DEBUG_Rx_DATA
+#undef DEBUG_Tx_DATA
+
+
+//#define DEBUG_UART
+//#define DEBUG_UART_EXTEND
+//#define DEBUG_IRQ
+//#define DEBUG_Rx_DATA
+//#define DEBUG_Tx_DATA
+
+
+#define  PRINT_DEBUG(fmt, args...)  \
+        do {} while (0)
+#define  PRINT_DEBUG_EXTEND(fmt, args...) \
+        do {} while (0)
+#define  PRINT_ERROR(fmt, args...) \
+        do { \
+            fprintf(stderr, "  [%s:%d]   "fmt, __func__, __LINE__, ##args); \
+        } while (0)
+
+#ifdef DEBUG_UART
+
+#undef PRINT_DEBUG
+#define  PRINT_DEBUG(fmt, args...)  \
+        do { \
+            fprintf(stderr, "  [%s:%d]   "fmt, __func__, __LINE__, ##args); \
+        } while (0)
+
+#ifdef DEBUG_UART_EXTEND
+
+#undef PRINT_DEBUG_EXTEND
+#define  PRINT_DEBUG_EXTEND(fmt, args...) \
+        do { \
+            fprintf(stderr, "  [%s:%d]   "fmt, __func__, __LINE__, ##args); \
+        } while (0)
+
+#endif /* EXTEND */
+#endif
+
+
+/*
+ *  Offsets for UART registers relative to SFR base address
+ *  for UARTn
+ *
+ */
+#define ULCON      0x0000 /* Line Control             */
+#define UCON       0x0004 /* Control                  */
+#define UFCON      0x0008 /* FIFO Control             */
+#define UMCON      0x000C /* Modem Control            */
+#define UTRSTAT    0x0010 /* Tx/Rx Status             */
+#define UERSTAT    0x0014 /* Rx Error Status          */
+#define UFSTAT     0x0018 /* FIFO Status              */
+#define UMSTAT     0x001C /* Modem Status             */
+#define UTXH       0x0020 /* Transmit Buffer          */
+#define URXH       0x0024 /* Receive Buffer           */
+#define UBRDIV     0x0028 /* Baud Rate Divisor        */
+#define UFRACVAL   0x002C /* Divisor Fractional Value */
+#define UINTP      0x0030 /* Interrupt Pending        */
+#define UINTSP     0x0034 /* Interrupt Source Pending */
+#define UINTM      0x0038 /* Interrupt Mask           */
+
+/*
+ * for indexing register in the uint32_t array
+ *
+ * 'reg' - register offset (see offsets definitions above)
+ *
+ */
+#define I_(reg) (reg / sizeof(uint32_t))
+
+typedef struct Exynos4210UartReg {
+    const char         *name; /* the only reason is the debug output */
+    target_phys_addr_t  offset;
+    uint32_t            reset_value;
+} Exynos4210UartReg;
+
+static Exynos4210UartReg exynos4210_uart_regs[] = {
+        {"ULCON"   , ULCON   , 0x00000000},
+        {"UCON"    , UCON    , 0x00003000},
+        {"UFCON"   , UFCON   , 0x00000000},
+        {"UMCON"   , UMCON   , 0x00000000},
+        {"UTRSTAT" , UTRSTAT , 0x00000006}, /* RO */
+        {"UERSTAT" , UERSTAT , 0x00000000}, /* RO */
+        {"UFSTAT"  , UFSTAT  , 0x00000000}, /* RO */
+        {"UMSTAT"  , UMSTAT  , 0x00000000}, /* RO */
+        {"UTXH"    , UTXH    , 0x5c5c5c5c}, /* WO, undefined reset value*/
+        {"URXH"    , URXH    , 0x00000000}, /* RO */
+        {"UBRDIV"  , UBRDIV  , 0x00000000},
+        {"UFRACVAL", UFRACVAL, 0x00000000},
+        {"UINTP"   , UINTP   , 0x00000000},
+        {"UINTSP"  , UINTSP  , 0x00000000},
+        {"UINTM"   , UINTM   , 0x00000000},
+};
+
+#define EXYNOS4210_UART_REGS_MEM_SIZE    0x3c
+
+/* UART FIFO Control */
+#define UFCON_FIFO_ENABLE                    0x1
+#define UFCON_Rx_FIFO_RESET                  0x2
+#define UFCON_Tx_FIFO_RESET                  0x4
+#define UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT    8
+#define UFCON_Tx_FIFO_TRIGGER_LEVEL \
+        (7 << UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT)
+#define UFCON_Rx_FIFO_TRIGGER_LEVEL_SHIFT    4
+#define UFCON_Rx_FIFO_TRIGGER_LEVEL \
+        (7 << UFCON_Rx_FIFO_TRIGGER_LEVEL_SHIFT)
+
+/* Uart FIFO Status */
+#define UFSTAT_Rx_FIFO_COUNT        0xff
+#define UFSTAT_Rx_FIFO_FULL         0x100
+#define UFSTAT_Rx_FIFO_ERROR        0x200
+#define UFSTAT_Tx_FIFO_COUNT_SHIFT  16
+#define UFSTAT_Tx_FIFO_COUNT        (0xff << UFSTAT_Tx_FIFO_COUNT_SHIFT)
+#define UFSTAT_Tx_FIFO_FULL_SHIFT   24
+#define UFSTAT_Tx_FIFO_FULL         (1 << UFSTAT_Tx_FIFO_FULL_SHIFT)
+
+/* UART Interrupt Source Pending */
+#define UINTSP_RXD      0x1 /* Receive interrupt  */
+#define UINTSP_ERROR    0x2 /* Error interrupt    */
+#define UINTSP_TXD      0x4 /* Transmit interrupt */
+#define UINTSP_MODEM    0x8 /* Modem interrupt    */
+
+/* UART Line Control */
+#define ULCON_IR_MODE_SHIFT   6
+#define ULCON_PARITY_SHIFT    3
+#define ULCON_STOP_BIT_SHIFT  1
+
+
+
+/* Specifies Tx/Rx Status */
+#define UTRSTAT_TRANSMITTER_EMPTY       0x4
+#define UTRSTAT_Tx_BUFFER_EMPTY         0x2
+#define UTRSTAT_Rx_BUFFER_DATA_READY    0x1
+
+typedef struct {
+    uint8_t    *data;
+    uint32_t    sp, rp; /* store and retrieve pointers */
+    uint32_t    size;
+} Exynos4210UartFIFO;
+
+typedef struct {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+
+    uint32_t          reg[EXYNOS4210_UART_REGS_MEM_SIZE];
+    Exynos4210UartFIFO   rx;
+    Exynos4210UartFIFO   tx;
+
+    CharDriverState  *chr;
+    qemu_irq          irq;
+
+    uint32_t channel;
+
+} Exynos4210UartState;
+
+
+#ifdef DEBUG_UART
+/* The only meaning of life - debugging. This functions should be only used
+ * inside PRINT_DEBUG_... macroses
+ */
+static const char *exynos4210_uart_regname(target_phys_addr_t  offset)
+{
+
+    int regs_number = sizeof(exynos4210_uart_regs)/sizeof(Exynos4210UartReg);
+    int i;
+
+    for (i = 0; i < regs_number; i++) {
+        if (offset == exynos4210_uart_regs[i].offset) {
+            return exynos4210_uart_regs[i].name;
+        }
+    }
+
+    return NULL;
+}
+#endif
+
+
+static void fifo_store(Exynos4210UartFIFO *q, uint8_t ch)
+{
+    q->data[q->sp] = ch;
+    q->sp = (q->sp + 1) % q->size;
+}
+
+static uint8_t fifo_retrieve(Exynos4210UartFIFO *q)
+{
+    uint8_t ret = q->data[q->rp];
+    q->rp = (q->rp + 1) % q->size;
+    return  ret;
+}
+
+static int fifo_elements_number(Exynos4210UartFIFO *q)
+{
+    if (q->sp < q->rp) {
+        return q->size - q->rp + q->sp;
+    }
+
+    return q->sp - q->rp;
+}
+
+static int fifo_empty_elements_number(Exynos4210UartFIFO *q)
+{
+    return q->size - fifo_elements_number(q);
+}
+
+static void fifo_reset(Exynos4210UartFIFO *q)
+{
+    if (q->data != NULL) {
+        g_free(q->data);
+        q->data = NULL;
+    }
+
+    q->data = (uint8_t *)g_malloc0(q->size);
+
+    q->sp = 0;
+    q->rp = 0;
+}
+
+static uint32_t exynos4210_uart_Tx_FIFO_trigger_level(Exynos4210UartState *s)
+{
+    uint32_t level = 0;
+    uint32_t reg;
+
+    reg = (s->reg[I_(UFCON)] && UFCON_Tx_FIFO_TRIGGER_LEVEL) >>
+            UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT;
+
+    switch (s->channel) {
+    case 0:
+        level = reg * 32;
+        break;
+    case 1:
+    case 4:
+        level = reg * 8;
+        break;
+    case 2:
+    case 3:
+        level = reg * 2;
+        break;
+    default:
+        level = 0;
+        PRINT_ERROR("Wrong UART chennel number: %d\n", s->channel);
+    }
+
+    return level;
+}
+
+static void exynos4210_uart_update_irq(Exynos4210UartState *s)
+{
+    /*
+     * The Tx interrupt is always requested if the number of data in the
+     * transmit FIFO is smaller than the trigger level.
+     */
+    if (s->reg[I_(UFCON)] && UFCON_FIFO_ENABLE) {
+
+        uint32_t count = (s->reg[I_(UFSTAT)] && UFSTAT_Tx_FIFO_COUNT) >>
+                UFSTAT_Tx_FIFO_COUNT_SHIFT;
+
+        if (count <= exynos4210_uart_Tx_FIFO_trigger_level(s)) {
+            s->reg[I_(UINTSP)] |= UINTSP_TXD;
+        }
+    }
+
+    s->reg[I_(UINTP)] = s->reg[I_(UINTSP)] & ~s->reg[I_(UINTM)];
+
+    if (s->reg[I_(UINTP)]) {
+        qemu_irq_raise(s->irq);
+
+#ifdef DEBUG_IRQ
+        fprintf(stderr, "UART%d: IRQ have been raised: %08x\n",
+                s->channel, s->reg[I_(UINTP)]);
+#endif
+
+    } else {
+        qemu_irq_lower(s->irq);
+    }
+}
+
+static void exynos4210_uart_update_parameters(Exynos4210UartState *s)
+{
+    int speed, parity, data_bits, stop_bits, frame_size;
+    QEMUSerialSetParams ssp;
+    uint64_t uclk_rate;
+
+    if (s->reg[I_(UBRDIV)] == 0) {
+        return;
+    }
+
+    frame_size = 1; /* start bit */
+    if (s->reg[I_(ULCON)] & 0x20) {
+        frame_size++; /* parity bit */
+        if (s->reg[I_(ULCON)] & 0x28) {
+            parity = 'E';
+        } else {
+            parity = 'O';
+        }
+    } else {
+        parity = 'N';
+    }
+
+    if (s->reg[I_(ULCON)] & 0x4) {
+        stop_bits = 2;
+    } else {
+        stop_bits = 1;
+    }
+
+    data_bits = (s->reg[I_(ULCON)] & 0x3) + 5;
+
+    frame_size += data_bits + stop_bits;
+
+    switch (s->channel) {
+    case 0:
+        uclk_rate = exynos4210_cmu_get_rate(SCLK_UART0); break;
+    case 1:
+        uclk_rate = exynos4210_cmu_get_rate(SCLK_UART1); break;
+    case 2:
+        uclk_rate = exynos4210_cmu_get_rate(SCLK_UART2); break;
+    case 3:
+        uclk_rate = exynos4210_cmu_get_rate(SCLK_UART3); break;
+    case 4:
+        uclk_rate = exynos4210_cmu_get_rate(SCLK_UART4); break;
+    default:
+        hw_error("%s: Incorrect UART channel: %d\n",
+                 __func__, s->channel);
+    }
+
+    speed = uclk_rate / ((16 * (s->reg[I_(UBRDIV)]) & 0xffff) +
+            (s->reg[I_(UFRACVAL)] & 0x7) + 16);
+
+    ssp.speed     = speed;
+    ssp.parity    = parity;
+    ssp.data_bits = data_bits;
+    ssp.stop_bits = stop_bits;
+
+    qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp);
+
+    PRINT_DEBUG("UART%d: speed: %d, parity: %c, data: %d, stop: %d\n",
+                s->channel, speed, parity, data_bits, stop_bits);
+}
+
+static void exynos4210_uart_write(void *opaque, target_phys_addr_t offset,
+                               uint64_t val, unsigned size)
+{
+    Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+    uint8_t ch;
+
+    if (offset > (EXYNOS4210_UART_REGS_MEM_SIZE - sizeof(uint32_t))) {
+        hw_error("exynos4210_cmu_write: Bad offset 0x%x\n", (int)offset);
+    }
+
+    PRINT_DEBUG_EXTEND("UART%d: <0x%04x> %s <- 0x%08llx\n",
+                   s->channel, offset, exynos4210_uart_regname(offset), val);
+
+    switch (offset) {
+    case ULCON:
+    case UBRDIV:
+    case UFRACVAL:
+        s->reg[I_(offset)] = val;
+        exynos4210_uart_update_parameters(s);
+        break;
+    case UFCON:
+        s->reg[I_(UFCON)] = val;
+        if (val & UFCON_Rx_FIFO_RESET) {
+            fifo_reset(&s->rx);
+            s->reg[I_(UFCON)] &= ~UFCON_Rx_FIFO_RESET;
+            PRINT_DEBUG("UART%d: Rx FIFO Reset\n", s->channel);
+        }
+        if (val & UFCON_Tx_FIFO_RESET) {
+            fifo_reset(&s->tx);
+            s->reg[I_(UFCON)] &= ~UFCON_Tx_FIFO_RESET;
+            PRINT_DEBUG("UART%d: Tx FIFO Reset\n", s->channel);
+        }
+        break;
+
+    case UTXH:
+        if (s->chr) {
+            s->reg[I_(UTRSTAT)] &= ~(UTRSTAT_TRANSMITTER_EMPTY |
+                    UTRSTAT_Tx_BUFFER_EMPTY);
+            ch = (uint8_t)val;
+            qemu_chr_fe_write(s->chr, &ch, 1);
+#ifdef DEBUG_Tx_DATA
+            fprintf(stderr, "%c", ch);
+#endif
+            s->reg[I_(UTRSTAT)] |= UTRSTAT_TRANSMITTER_EMPTY |
+                    UTRSTAT_Tx_BUFFER_EMPTY;
+            s->reg[I_(UINTSP)]  |= UINTSP_TXD;
+            exynos4210_uart_update_irq(s);
+        }
+        break;
+
+    case UINTP:
+        s->reg[I_(UINTP)]  &= ~val;
+        /* XXX: It's the assumption that it works in this way */
+        s->reg[I_(UINTSP)]  &= ~val;
+        PRINT_DEBUG("UART%d: UINTP [%04x] have been cleared: %08x\n",
+                    s->channel, offset, s->reg[I_(UINTP)]);
+        exynos4210_uart_update_irq(s);
+        break;
+    case UTRSTAT:
+    case UERSTAT:
+    case UFSTAT:
+    case UMSTAT:
+    case URXH:
+        PRINT_DEBUG("UART%d: Trying to write into RO register: %s [%04x]\n",
+                    s->channel, exynos4210_uart_regname(offset), offset);
+        break;
+    case UINTSP:
+        /* XXX: It's the assumption that it works in this way */
+        s->reg[I_(UINTSP)]  &= ~val;
+        break;
+    case UINTM:
+        s->reg[I_(UINTM)] = val;
+        exynos4210_uart_update_irq(s);
+        break;
+    case UCON:
+    case UMCON:
+    default:
+        s->reg[I_(offset)] = val;
+        break;
+    }
+}
+static uint64_t exynos4210_uart_read(void *opaque, target_phys_addr_t offset,
+                                  unsigned size)
+{
+    Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+    uint32_t res;
+
+    if (offset > (EXYNOS4210_UART_REGS_MEM_SIZE - sizeof(uint32_t))) {
+        hw_error("exynos4210_cmu_read: Bad offset 0x%x\n", (int)offset);
+    }
+
+    switch (offset) {
+    case UERSTAT: /* Read Only */
+        res = s->reg[I_(UERSTAT)];
+        s->reg[I_(UERSTAT)] = 0;
+        return res;
+    case UFSTAT: /* Read Only */
+        s->reg[I_(UFSTAT)] = fifo_elements_number(&s->rx) & 0xff;
+        if (fifo_empty_elements_number(&s->rx) == 0) {
+            s->reg[I_(UFSTAT)] |= UFSTAT_Rx_FIFO_FULL;
+            s->reg[I_(UFSTAT)] &= ~0xff;
+        }
+        return s->reg[I_(UFSTAT)];
+    case URXH:
+        if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) {
+            if (fifo_elements_number(&s->rx)) {
+                res = fifo_retrieve(&s->rx);
+#ifdef DEBUG_Rx_DATA
+                fprintf(stderr, "%c", res);
+#endif
+                if (!fifo_elements_number(&s->rx)) {
+                    s->reg[I_(UTRSTAT)] &= ~UTRSTAT_Rx_BUFFER_DATA_READY;
+                } else {
+                    s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
+                }
+            } else {
+                s->reg[I_(UINTSP)] |= UINTSP_ERROR;
+                exynos4210_uart_update_irq(s);
+                res = 0;
+            }
+        } else {
+            s->reg[I_(UTRSTAT)] &= ~UTRSTAT_Rx_BUFFER_DATA_READY;
+            res = s->reg[I_(URXH)];
+        }
+        return res;
+    case UTXH:
+        PRINT_DEBUG("UART%d: Trying to read from WO register: %s [%04x]\n",
+                    s->channel, exynos4210_uart_regname(offset), offset);
+        break;
+    default:
+        return s->reg[I_(offset)];
+        break;
+    }
+
+    return 0;
+}
+
+
+static const MemoryRegionOps exynos4210_uart_ops = {
+        .read = exynos4210_uart_read,
+        .write = exynos4210_uart_write,
+        .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int exynos4210_uart_can_receive(void *opaque)
+{
+    Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+
+    return fifo_empty_elements_number(&s->rx);
+}
+
+
+static void exynos4210_uart_receive(void *opaque, const uint8_t *buf, int size)
+{
+    Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+    int i;
+
+    if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) {
+        if (fifo_empty_elements_number(&s->rx) < size) {
+            for (i = 0; i < fifo_empty_elements_number(&s->rx); i++) {
+                fifo_store(&s->rx, buf[i]);
+            }
+            s->reg[I_(UINTSP)] |= UINTSP_ERROR;
+            s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
+        } else {
+            for (i = 0; i < size; i++) {
+                fifo_store(&s->rx, buf[i]);
+            }
+            s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
+        }
+        /* XXX: after achieve trigger level*/
+        s->reg[I_(UINTSP)] |= UINTSP_RXD;
+    } else {
+        s->reg[I_(URXH)] = buf[0];
+        s->reg[I_(UINTSP)] |= UINTSP_RXD;
+        s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
+    }
+
+    exynos4210_uart_update_irq(s);
+}
+
+
+static void exynos4210_uart_event(void *opaque, int event)
+{
+    /*
+     * TODO: Implement this.
+     *
+     */
+}
+
+
+static void exynos4210_uart_reset(DeviceState *dev)
+{
+    Exynos4210UartState *s =
+            container_of(dev, Exynos4210UartState, busdev.qdev);
+    int regs_number = sizeof(exynos4210_uart_regs)/sizeof(Exynos4210UartReg);
+    int i;
+
+    for (i = 0; i < regs_number; i++) {
+        s->reg[I_(exynos4210_uart_regs[i].offset)] =
+                exynos4210_uart_regs[i].reset_value;
+    }
+
+    fifo_reset(&s->rx);
+    fifo_reset(&s->tx);
+
+    PRINT_DEBUG("UART%d: Rx FIFO size: %d\n", s->channel, s->rx.size);
+}
+
+
+static const VMStateDescription vmstate_exynos4210_uart = {
+        .name = "exynos4210.uart",
+        .version_id = 1,
+        .minimum_version_id = 1,
+        .minimum_version_id_old = 1,
+        .fields = (VMStateField[]) {
+            /*
+             * TODO: We should save fifo too
+             */
+            VMSTATE_UINT32_ARRAY(reg, Exynos4210UartState,
+                                 EXYNOS4210_UART_REGS_MEM_SIZE),
+            VMSTATE_END_OF_LIST()
+        }
+};
+
+DeviceState *exynos4210_uart_create(target_phys_addr_t addr,
+                                 int fifo_size,
+                                 int channel,
+                                 CharDriverState *chr,
+                                 qemu_irq irq)
+{
+    DeviceState  *dev;
+    SysBusDevice *bus;
+
+    dev = qdev_create(NULL, "exynos4210.uart");
+
+    if (!chr) {
+        if (channel >= MAX_SERIAL_PORTS) {
+            hw_error("Only %d serial ports are supported by QEMU.\n",
+                     MAX_SERIAL_PORTS);
+        }
+        chr = serial_hds[channel];
+        if (!chr) {
+            chr = qemu_chr_new("exynos4210.uart", "null", NULL);
+            if (!(chr)) {
+                hw_error("Can't assign serial port to UART%d.\n", channel);
+            }
+        }
+    }
+
+    qdev_prop_set_chr(dev, "chardev", chr);
+    qdev_prop_set_uint32(dev, "channel", channel);
+    qdev_prop_set_uint32(dev, "rx-size", fifo_size);
+    qdev_prop_set_uint32(dev, "tx-size", fifo_size);
+
+    bus = sysbus_from_qdev(dev);
+    qdev_init_nofail(dev);
+    if (addr != (target_phys_addr_t)-1) {
+        sysbus_mmio_map(bus, 0, addr);
+    }
+    sysbus_connect_irq(bus, 0, irq);
+
+    return dev;
+}
+
+static int exynos4210_uart_init(SysBusDevice *dev)
+{
+    Exynos4210UartState *s = FROM_SYSBUS(Exynos4210UartState, dev);
+
+    /* memory mapping */
+    memory_region_init_io(&s->iomem, &exynos4210_uart_ops, s, "exynos4210.uart",
+                          EXYNOS4210_UART_REGS_MEM_SIZE);
+    sysbus_init_mmio(dev, &s->iomem);
+
+    sysbus_init_irq(dev, &s->irq);
+
+    qemu_chr_add_handlers(s->chr, exynos4210_uart_can_receive,
+                          exynos4210_uart_receive, exynos4210_uart_event, s);
+
+    return 0;
+}
+
+static SysBusDeviceInfo exynos4210_uart_info = {
+        .init       = exynos4210_uart_init,
+        .qdev.name  = "exynos4210.uart",
+        .qdev.size  = sizeof(Exynos4210UartState),
+        .qdev.vmsd  = &vmstate_exynos4210_uart,
+        .qdev.reset = exynos4210_uart_reset,
+        .qdev.props = (Property[]) {
+            DEFINE_PROP_CHR("chardev", Exynos4210UartState, chr),
+            DEFINE_PROP_UINT32("channel", Exynos4210UartState, channel, 0),
+            DEFINE_PROP_UINT32("rx-size", Exynos4210UartState, rx.size, 16),
+            DEFINE_PROP_UINT32("tx-size", Exynos4210UartState, tx.size, 16),
+            DEFINE_PROP_END_OF_LIST(),
+        }
+};
+
+static void exynos4210_uart_register(void)
+{
+    sysbus_register_withprop(&exynos4210_uart_info);
+}
+
+device_init(exynos4210_uart_register)
-- 
1.7.4.1

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Qemu-devel] [PATCH 04/15] hw/sysbus.h: Increase maximum number of device IRQs.
  2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
                   ` (2 preceding siblings ...)
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 03/15] ARM: exynos4210: UART support Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 05/15] ARM: exynos4210: IRQ subsystem support Evgeny Voevodin
                   ` (10 subsequent siblings)
  14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin

Samsung exynos4210 Interrupt Combiner needs 512 IRQ sources.

Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 hw/sysbus.h |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/hw/sysbus.h b/hw/sysbus.h
index 9bac582..4ef0c3c 100644
--- a/hw/sysbus.h
+++ b/hw/sysbus.h
@@ -8,7 +8,7 @@
 
 #define QDEV_MAX_MMIO 32
 #define QDEV_MAX_PIO 32
-#define QDEV_MAX_IRQ 256
+#define QDEV_MAX_IRQ 512
 
 typedef struct SysBusDevice SysBusDevice;
 typedef void (*mmio_mapfunc)(SysBusDevice *dev, target_phys_addr_t addr);
-- 
1.7.4.1

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Qemu-devel] [PATCH 05/15] ARM: exynos4210: IRQ subsystem support.
  2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
                   ` (3 preceding siblings ...)
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 04/15] hw/sysbus.h: Increase maximum number of device IRQs Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 06/15] ARM: exynos4210: PWM support Evgeny Voevodin
                   ` (9 subsequent siblings)
  14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin


Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 Makefile.target          |    3 +-
 hw/exynos4210.c          |  161 ++++++++++++++++++-
 hw/exynos4210.h          |   41 +++++
 hw/exynos4210_combiner.c |  385 ++++++++++++++++++++++++++++++++++++++++++
 hw/exynos4210_gic.c      |  415 ++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 1003 insertions(+), 2 deletions(-)
 create mode 100644 hw/exynos4210_combiner.c
 create mode 100644 hw/exynos4210_gic.c

diff --git a/Makefile.target b/Makefile.target
index 4c706b1..779c9d4 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -344,7 +344,8 @@ obj-arm-y = integratorcp.o versatilepb.o arm_pic.o arm_timer.o
 obj-arm-y += arm_boot.o pl011.o pl031.o pl050.o pl080.o pl110.o pl181.o pl190.o
 obj-arm-y += versatile_pci.o
 obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
-obj-arm-y += exynos4210.o exynos4210_cmu.o exynos4210_uart.o
+obj-arm-y += exynos4210.o exynos4210_cmu.o exynos4210_uart.o exynos4210_gic.o \
+             exynos4210_combiner.o
 obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
 obj-arm-y += pl061.o
 obj-arm-y += arm-semi.o
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index d5a1fe0..a936dcf 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -58,6 +58,8 @@
 
 #define EXYNOS4210_SFR_BASE_ADDR            0x10000000
 
+#define EXYNOS4210_SMP_PRIVATE_BASE_ADDR    0x10500000
+
 #define EXYNOS4210_BASE_BOOT_ADDR           EXYNOS4210_DRAM0_BASE_ADDR
 
 /* SFR Base Address for CMUs */
@@ -74,7 +76,16 @@
 #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_INTG           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 struct arm_boot_info exynos4210_binfo = {
         .loader_start     = EXYNOS4210_BASE_BOOT_ADDR,
@@ -93,6 +104,87 @@ enum exynos4210_mach_id {
     MACH_SMDKC210_ID = 0xB16,
 };
 
+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);
+    }
+}
 
 static void exynos4210_init(ram_addr_t ram_size,
         const char *boot_device,
@@ -110,8 +202,12 @@ static void exynos4210_init(ram_addr_t ram_size,
     MemoryRegion *irom_alias_mem = g_new(MemoryRegion, 1);
     MemoryRegion *dram0_mem = g_new(MemoryRegion, 1);
     MemoryRegion *dram1_mem = NULL;
+    Exynos4210Irq *irqs;
+    qemu_irq *irq_table;
     qemu_irq *irqp;
     qemu_irq cpu_irq[4];
+    DeviceState *dev;
+    SysBusDevice *busdev;
     ram_addr_t mem_size;
     int n;
 
@@ -129,6 +225,12 @@ static void exynos4210_init(ram_addr_t ram_size,
         cpu_model = "cortex-a9";
     }
 
+    irqs = g_malloc0(sizeof(Exynos4210Irq));
+    if (!irqs) {
+        fprintf(stderr, "Can't allocate IRQs\n");
+        exit(1);
+    }
+
     for (n = 0; n < smp_cpus; n++) {
         env = cpu_init(cpu_model);
         if (!env) {
@@ -145,6 +247,63 @@ static void exynos4210_init(ram_addr_t ram_size,
         cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ];
     }
 
+    irq_table = exynos4210_init_irq(irqs);
+
+    /*** SMP ***/
+
+    /* Private memory region and Internal GIC */
+    dev = qdev_create(NULL, "a9mpcore_priv");
+    qdev_prop_set_uint32(dev, "num-cpu", smp_cpus);
+    qdev_init_nofail(dev);
+    busdev = sysbus_from_qdev(dev);
+    sysbus_mmio_map(busdev, 0, EXYNOS4210_SMP_PRIVATE_BASE_ADDR);
+    for (n = 0; n < smp_cpus; n++) {
+        sysbus_connect_irq(busdev, n, cpu_irq[n]);
+    }
+    for (n = 0; n < EXYNOS4210_INT_GIC_NIRQ; n++) {
+        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, cpu_irq[n]);
+    }
+    for (n = 0; n < EXYNOS4210_EXT_GIC_NIRQ; n++) {
+        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, irqs->int_gic_irq[n]);
+    }
+    exynos4210_combiner_get_gpioin(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, irqs->ext_gic_irq[n]);
+    }
+    exynos4210_combiner_get_gpioin(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(irqs);
+
     /*** Memory ***/
 
     /* Chip-ID and OMR */
@@ -224,7 +383,7 @@ static void exynos4210_init(ram_addr_t ram_size,
             continue;
         }
 
-        uart_irq = NULL;
+        uart_irq = irq_table[exynos4210_get_irq(EXYNOS4210_UART_INTG, channel)];
 
         exynos4210_uart_create(addr, fifo_size, channel, NULL, uart_irq);
     }
diff --git a/hw/exynos4210.h b/hw/exynos4210.h
index 3df7322..83c4e3c 100644
--- a/hw/exynos4210.h
+++ b/hw/exynos4210.h
@@ -32,6 +32,47 @@
 #define EXYNOS4210_MAX_CPUS                2
 
 /*
+ * exynos4210 IRQ subsystem stub definitions.
+ */
+
+#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;
+
+/* 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);
+
+/*
  * Interface for exynos4210 Clock Management Units (CMUs)
  */
 
diff --git a/hw/exynos4210_combiner.c b/hw/exynos4210_combiner.c
new file mode 100644
index 0000000..6a080d0
--- /dev/null
+++ b/hw/exynos4210_combiner.c
@@ -0,0 +1,385 @@
+/*
+ * 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, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#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..56af00d
--- /dev/null
+++ b/hw/exynos4210_gic.c
@@ -0,0 +1,415 @@
+/*
+ * 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, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "sysbus.h"
+#include "qemu-common.h"
+#include "irq.h"
+#include "exynos4210.h"
+
+//#define DEBUG_EXYNOS4210_IRQ
+//#define DEBUG_EXYNOS4210_GIC
+
+#ifdef DEBUG_EXYNOS4210_IRQ
+#define DPRINTF_EXYNOS4210_GIC(fmt, ...) \
+        do { fprintf(stdout, "EXYNOS4210_IRQ: [%24s:%5d] " fmt, __func__, \
+                     __LINE__, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF_EXYNOS4210_GIC(fmt, ...) do {} while (0)
+#endif
+
+#ifdef DEBUG_EXYNOS4210_GIC
+#define DPRINTF_EXYNOS4210_GIC(fmt, ...) \
+        do { fprintf(stdout, "EXT_GIC: [%24s:%5d] " fmt, __func__, __LINE__, \
+                     ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF_EXYNOS4210_GIC(fmt, ...) do {} while (0)
+#endif
+
+#define    EXT_GIC_ID_TVENC   127
+#define    EXT_GIC_ID_MFC 126
+#define    EXT_GIC_ID_HDMI_I2C    125
+#define    EXT_GIC_ID_HDMI    124
+#define    EXT_GIC_ID_MIXER   123
+#define    EXT_GIC_ID_PCIe    122
+#define    EXT_GIC_ID_2D  121
+#define    EXT_GIC_ID_JPEG    120
+#define    EXT_GIC_ID_FIMC3   119
+#define    EXT_GIC_ID_FIMC2   118
+#define    EXT_GIC_ID_FIMC1   117
+#define    EXT_GIC_ID_FIMC0   116
+#define    EXT_GIC_ID_ROTATOR 115
+#define    EXT_GIC_ID_ONENAND_AUDI    114
+#define    EXT_GIC_ID_MIPI_DSI_2LANE  113
+#define    EXT_GIC_ID_MIPI_CSI_2LANE  112
+#define    EXT_GIC_ID_MIPI_DSI_4LANE  111
+#define    EXT_GIC_ID_MIPI_CSI_4LANE  110
+#define    EXT_GIC_ID_SDMMC   109
+#define    EXT_GIC_ID_HSMMC3  108
+#define    EXT_GIC_ID_HSMMC2  107
+#define    EXT_GIC_ID_HSMMC1  106
+#define    EXT_GIC_ID_HSMMC0  105
+#define    EXT_GIC_ID_MODEMIF 104
+#define    EXT_GIC_ID_USB_DEVICE  103
+#define    EXT_GIC_ID_USB_HOST    102
+#define    EXT_GIC_ID_MCT_G1  101
+#define    EXT_GIC_ID_SPI2    100
+#define    EXT_GIC_ID_SPI1    99
+#define    EXT_GIC_ID_SPI0    98
+#define    EXT_GIC_ID_I2C7    97
+#define    EXT_GIC_ID_I2C6    96
+#define    EXT_GIC_ID_I2C5    95
+#define    EXT_GIC_ID_I2C4    94
+#define    EXT_GIC_ID_I2C3    93
+#define    EXT_GIC_ID_I2C2    92
+#define    EXT_GIC_ID_I2C1    91
+#define    EXT_GIC_ID_I2C0    90
+#define    EXT_GIC_ID_MCT_G0  89
+#define    EXT_GIC_ID_UART4   88
+#define    EXT_GIC_ID_UART3   87
+#define    EXT_GIC_ID_UART2   86
+#define    EXT_GIC_ID_UART1   85
+#define    EXT_GIC_ID_UART0    84
+#define    EXT_GIC_ID_NFC      83
+#define    EXT_GIC_ID_IEM_IEC 82
+#define    EXT_GIC_ID_IEM_APC 81
+#define    EXT_GIC_ID_MCT_L1  80
+#define    EXT_GIC_ID_GPIO_XA 79
+#define    EXT_GIC_ID_GPIO_XB 78
+#define    EXT_GIC_ID_RTC_TIC 77
+#define    EXT_GIC_ID_RTC_ALARM   76
+#define    EXT_GIC_ID_WDT 75
+#define    EXT_GIC_ID_MCT_L0  74
+#define    EXT_GIC_ID_TIMER4  73
+#define    EXT_GIC_ID_TIMER3  72
+#define    EXT_GIC_ID_TIMER2  71
+#define    EXT_GIC_ID_TIMER1  70
+#define    EXT_GIC_ID_TIMER0  69
+#define    EXT_GIC_ID_PDMA1   68
+#define    EXT_GIC_ID_PDMA0   67
+#define    EXT_GIC_ID_MDMA_LCD0   66
+
+enum ext_int {
+    EXT_GIC_ID_EXTINT0 = 48,
+    EXT_GIC_ID_EXTINT1,
+    EXT_GIC_ID_EXTINT2,
+    EXT_GIC_ID_EXTINT3,
+    EXT_GIC_ID_EXTINT4,
+    EXT_GIC_ID_EXTINT5,
+    EXT_GIC_ID_EXTINT6,
+    EXT_GIC_ID_EXTINT7,
+    EXT_GIC_ID_EXTINT8,
+    EXT_GIC_ID_EXTINT9,
+    EXT_GIC_ID_EXTINT10,
+    EXT_GIC_ID_EXTINT11,
+    EXT_GIC_ID_EXTINT12,
+    EXT_GIC_ID_EXTINT13,
+    EXT_GIC_ID_EXTINT14,
+    EXT_GIC_ID_EXTINT15
+};
+
+/*
+ * External GIC sources which are not from External Interrupt Combiner or
+ * External Interrupts are starting from EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ,
+ * which is INTG16 in Internal Interrupt Combiner.
+ */
+
+static uint32_t
+combiner_grp_to_gic_id[64-EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ][8] = {
+        /* int combiner groups 16-19 */
+        {}, {}, {}, {},
+        /* int combiner group 20 */
+        {0, EXT_GIC_ID_MDMA_LCD0},
+        /* int combiner group 21 */
+        {EXT_GIC_ID_PDMA0, EXT_GIC_ID_PDMA1},
+        /* int combiner group 22 */
+        {EXT_GIC_ID_TIMER0, EXT_GIC_ID_TIMER1, EXT_GIC_ID_TIMER2,
+                EXT_GIC_ID_TIMER3, EXT_GIC_ID_TIMER4},
+        /* int combiner group 23 */
+        {EXT_GIC_ID_RTC_ALARM, EXT_GIC_ID_RTC_TIC},
+        /* int combiner group 24 */
+        {EXT_GIC_ID_GPIO_XB, EXT_GIC_ID_GPIO_XA},
+        /* int combiner group 25 */
+        {EXT_GIC_ID_IEM_APC, EXT_GIC_ID_IEM_IEC},
+        /* int combiner group 26 */
+        {EXT_GIC_ID_UART0, EXT_GIC_ID_UART1, EXT_GIC_ID_UART2, EXT_GIC_ID_UART3,
+                EXT_GIC_ID_UART4},
+        /* int combiner group 27 */
+        {EXT_GIC_ID_I2C0, EXT_GIC_ID_I2C1, EXT_GIC_ID_I2C2, EXT_GIC_ID_I2C3,
+                EXT_GIC_ID_I2C4, EXT_GIC_ID_I2C5, EXT_GIC_ID_I2C6,
+                EXT_GIC_ID_I2C7},
+        /* int combiner group 28 */
+        {EXT_GIC_ID_SPI0, EXT_GIC_ID_SPI1, EXT_GIC_ID_SPI2},
+        /* int combiner group 29 */
+        {EXT_GIC_ID_HSMMC0, EXT_GIC_ID_HSMMC1, EXT_GIC_ID_HSMMC2,
+         EXT_GIC_ID_HSMMC3, EXT_GIC_ID_SDMMC},
+        /* int combiner group 30 */
+        {EXT_GIC_ID_MIPI_CSI_4LANE, EXT_GIC_ID_MIPI_CSI_2LANE},
+        /* int combiner group 31 */
+        {EXT_GIC_ID_MIPI_DSI_4LANE, EXT_GIC_ID_MIPI_DSI_2LANE},
+        /* int combiner group 32 */
+        {EXT_GIC_ID_FIMC0, EXT_GIC_ID_FIMC1},
+        /* int combiner group 33 */
+        {EXT_GIC_ID_FIMC2, EXT_GIC_ID_FIMC3},
+        /* int combiner group 34 */
+        {EXT_GIC_ID_ONENAND_AUDI, EXT_GIC_ID_NFC},
+        /* int combiner group 35 */
+        {0, 0, 0, EXT_GIC_ID_MCT_L1, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1},
+        /* int combiner group 36 */
+        {EXT_GIC_ID_MIXER},
+        /* int combiner group 37 */
+        {EXT_GIC_ID_EXTINT4, EXT_GIC_ID_EXTINT5, EXT_GIC_ID_EXTINT6,
+         EXT_GIC_ID_EXTINT7},
+        /* groups 38-50 */
+        {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},
+        /* int combiner group 51 */
+        {EXT_GIC_ID_MCT_L0, 0, 0, 0, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1},
+        /* group 52 */
+        {},
+        /* int combiner group 53 */
+        {EXT_GIC_ID_WDT, 0, 0, 0, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1},
+        /* groups 54-63 */
+        {}, {}, {}, {}, {}, {}, {}, {}, {}, {}
+};
+
+#define GIC_NIRQ 160
+#define NCPU EXYNOS4210_MAX_CPUS
+
+#define EXYNOS4210_GIC_CPU_REGION_SIZE     0x8050
+#define EXYNOS4210_GIC_DIST_REGION_SIZE    0x8F04
+
+static void exynos4210_irq_handler(void *opaque, int irq, int level)
+{
+    Exynos4210Irq *s = (Exynos4210Irq *)opaque;
+
+    /* Bypass */
+    qemu_set_irq(s->board_irqs[irq], level);
+
+    return;
+}
+
+/*
+ * Initialize exynos4210 IRQ subsystem stub.
+ */
+qemu_irq *exynos4210_init_irq(Exynos4210Irq *s)
+{
+    return qemu_allocate_irqs(exynos4210_irq_handler, s,
+            EXYNOS4210_MAX_INT_COMBINER_IN_IRQ);
+}
+
+/*
+ * Initialize board IRQs.
+ * These IRQs contain splitted Int/External Combiner and External Gic IRQs.
+ */
+void exynos4210_init_board_irqs(Exynos4210Irq *s)
+{
+    uint32_t grp, bit, irq_id, n;
+
+    for (n = 0; n < EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ; n++) {
+        s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n],
+                s->ext_combiner_irq[n]);
+
+        irq_id = 0;
+        if (n == EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 4) ||
+                n == EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 4)) {
+            /* MCT_G0 is passed to External GIC */
+            irq_id = EXT_GIC_ID_MCT_G0;
+        }
+        if (n == EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 5) ||
+                n == EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 5)) {
+            /* MCT_G1 is passed to External and GIC */
+            irq_id = EXT_GIC_ID_MCT_G1;
+        }
+        if (irq_id) {
+            s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n],
+                    s->ext_gic_irq[irq_id-32]);
+        }
+
+    }
+    for (; n < EXYNOS4210_MAX_INT_COMBINER_IN_IRQ; n++) {
+        /* these IDs are passed to Internal Combiner and External GIC */
+        grp = EXYNOS4210_COMBINER_GET_GRP_NUM(n);
+        bit = EXYNOS4210_COMBINER_GET_BIT_NUM(n);
+        irq_id =
+                combiner_grp_to_gic_id[grp -
+                                      EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ][bit];
+
+        if (irq_id) {
+            s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n],
+                    s->ext_gic_irq[irq_id-32]);
+        }
+    }
+}
+
+/*
+ * Get IRQ number from exynos4210 IRQ subsystem stub.
+ * To identify IRQ source use internal combiner group and bit number
+ *  grp - group number
+ *  bit - bit number inside group
+ */
+uint32_t exynos4210_get_irq(uint32_t grp, uint32_t bit)
+{
+    return EXYNOS4210_COMBINER_GET_IRQ_NUM(grp, bit);
+}
+
+/********* GIC part *********/
+
+static inline int
+gic_get_current_cpu(void)
+{
+    return cpu_single_env->cpu_index;
+}
+
+#include "arm_gic.c"
+
+typedef struct {
+    gic_state gic;
+    MemoryRegion cpu_container;
+    MemoryRegion dist_container;
+    uint32_t num_cpu;
+} Exynos4210GicState;
+
+static uint64_t exynos4210_gic_cpu_read(void *opaque, target_phys_addr_t offset,
+        unsigned size)
+{
+    Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+    DPRINTF_EXYNOS4210_GIC("CPU%d: read offset 0x%x\n",
+            gic_get_current_cpu(), offset);
+    return gic_cpu_read(&s->gic, gic_get_current_cpu(), offset & ~0x8000);
+}
+
+static void exynos4210_gic_cpu_write(void *opaque, target_phys_addr_t offset,
+        uint64_t value, unsigned size)
+{
+    Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+    DPRINTF_EXYNOS4210_GIC("CPU%d: write offset 0x%x, value 0x%x\n",
+            gic_get_current_cpu(), offset, value);
+    gic_cpu_write(&s->gic, gic_get_current_cpu(), offset & ~0x8000, value);
+}
+
+static uint32_t
+exynos4210_gic_dist_readb(void *opaque, target_phys_addr_t offset)
+{
+    Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+    DPRINTF_EXYNOS4210_GIC("DIST: readb offset 0x%x\n", offset);
+    return gic_dist_readb(&s->gic, offset & ~0x8000);
+}
+
+static uint32_t
+exynos4210_gic_dist_readw(void *opaque, target_phys_addr_t offset)
+{
+    Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+    DPRINTF_EXYNOS4210_GIC("DIST: readw offset 0x%x\n", offset);
+    return gic_dist_readw(&s->gic, offset & ~0x8000);
+}
+
+static uint32_t
+exynos4210_gic_dist_readl(void *opaque, target_phys_addr_t offset)
+{
+    Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+    DPRINTF_EXYNOS4210_GIC("DIST: readl offset 0x%x\n", offset);
+    return gic_dist_readl(&s->gic, offset & ~0x8000);
+}
+
+static void
+exynos4210_gic_dist_writeb(void *opaque, target_phys_addr_t offset,
+        uint32_t value)
+{
+    Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+    DPRINTF_EXYNOS4210_GIC("DIST: writeb offset 0x%x, value 0x%x\n", offset,
+            value);
+    gic_dist_writeb(&s->gic, offset & ~0x8000, value);
+}
+
+static void exynos4210_gic_dist_writew(void *opaque, target_phys_addr_t offset,
+        uint32_t value)
+{
+    Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+    DPRINTF_EXYNOS4210_GIC("DIST: writew offset 0x%x, value 0x%x\n", offset,
+            value);
+    gic_dist_writew(&s->gic, offset & ~0x8000, value);
+}
+
+static void exynos4210_gic_dist_writel(void *opaque, target_phys_addr_t offset,
+        uint32_t value)
+{
+    Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+    DPRINTF_EXYNOS4210_GIC("DIST: writel offset 0x%x, value 0x%x\n", offset,
+            value);
+    gic_dist_writel(&s->gic, offset & ~0x8000, value);
+}
+
+static const MemoryRegionOps exynos4210_gic_cpu_ops = {
+        .read = exynos4210_gic_cpu_read,
+        .write = exynos4210_gic_cpu_write,
+        .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const MemoryRegionOps exynos4210_gic_dist_ops = {
+        .old_mmio = {
+                .read = { exynos4210_gic_dist_readb,
+                        exynos4210_gic_dist_readw,
+                        exynos4210_gic_dist_readl, },
+                        .write = { exynos4210_gic_dist_writeb,
+                                exynos4210_gic_dist_writew,
+                                exynos4210_gic_dist_writel, },
+        },
+        .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int exynos4210_gic_init(SysBusDevice *dev)
+{
+    Exynos4210GicState *s = FROM_SYSBUSGIC(Exynos4210GicState, dev);
+    gic_init(&s->gic, s->num_cpu);
+
+    memory_region_init(&s->cpu_container, "exynos4210-gic-cpu_container",
+            EXYNOS4210_GIC_CPU_REGION_SIZE);
+    memory_region_init(&s->cpu_container, "exynos4210-gic-dist_container",
+            EXYNOS4210_GIC_DIST_REGION_SIZE);
+    memory_region_init_io(&s->cpu_container, &exynos4210_gic_cpu_ops, &s->gic,
+            "exynos4210-gic-cpu", EXYNOS4210_GIC_CPU_REGION_SIZE);
+    memory_region_init_io(&s->dist_container, &exynos4210_gic_dist_ops, &s->gic,
+            "exynos4210-gic-dist", EXYNOS4210_GIC_DIST_REGION_SIZE);
+
+    sysbus_init_mmio(dev, &s->cpu_container);
+    sysbus_init_mmio(dev, &s->dist_container);
+
+    gic_cpu_write(&s->gic, 1, 0, 1);
+    return 0;
+}
+
+static SysBusDeviceInfo exynos4210_gic_info = {
+        .init = exynos4210_gic_init,
+        .qdev.name  = "exynos4210.gic",
+        .qdev.size  = sizeof(Exynos4210GicState),
+        .qdev.props = (Property[]) {
+            DEFINE_PROP_UINT32("num-cpu", Exynos4210GicState, num_cpu, 1),
+                    DEFINE_PROP_END_OF_LIST(),
+        }
+};
+
+static void exynos4210_gic_register_devices(void)
+{
+    sysbus_register_withprop(&exynos4210_gic_info);
+}
+
+device_init(exynos4210_gic_register_devices)
-- 
1.7.4.1

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Qemu-devel] [PATCH 06/15] ARM: exynos4210: PWM support.
  2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
                   ` (4 preceding siblings ...)
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 05/15] ARM: exynos4210: IRQ subsystem support Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 07/15] hw/arm_boot.c: Add new secondary CPU bootloader Evgeny Voevodin
                   ` (8 subsequent siblings)
  14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin


Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 Makefile.target     |    2 +-
 hw/exynos4210.c     |   12 ++
 hw/exynos4210_pwm.c |  433 +++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 446 insertions(+), 1 deletions(-)
 create mode 100644 hw/exynos4210_pwm.c

diff --git a/Makefile.target b/Makefile.target
index 779c9d4..709e9e2 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -345,7 +345,7 @@ 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 += exynos4210.o exynos4210_cmu.o exynos4210_uart.o exynos4210_gic.o \
-             exynos4210_combiner.o
+             exynos4210_combiner.o exynos4210_pwm.o
 obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
 obj-arm-y += pl061.o
 obj-arm-y += arm-semi.o
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index a936dcf..361cdfb 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -65,6 +65,9 @@
 /* SFR Base Address for CMUs */
 #define EXYNOS4210_CMU_BASE_ADDR            0x10030000
 
+/* PWM */
+#define EXYNOS4210_PWM_BASE_ADDR            0x139D0000
+
 /* UART's definitions */
 #define EXYNOS4210_UART_BASE_ADDR           0x13800000
 #define EXYNOS4210_UART_SHIFT               0x00010000
@@ -350,6 +353,15 @@ static void exynos4210_init(ram_addr_t ram_size,
     /* CMU */
     sysbus_create_simple("exynos4210.cmu", EXYNOS4210_CMU_BASE_ADDR, NULL);
 
+    /* PWM */
+    sysbus_create_varargs("exynos4210.pwm", EXYNOS4210_PWM_BASE_ADDR,
+            irq_table[exynos4210_get_irq(22, 0)],
+            irq_table[exynos4210_get_irq(22, 1)],
+            irq_table[exynos4210_get_irq(22, 2)],
+            irq_table[exynos4210_get_irq(22, 3)],
+            irq_table[exynos4210_get_irq(22, 4)],
+            NULL);
+
     /*** UARTs ***/
     for (n = 0; n < EXYNOS4210_UARTS_NUMBER; n++) {
 
diff --git a/hw/exynos4210_pwm.c b/hw/exynos4210_pwm.c
new file mode 100644
index 0000000..1e80f10
--- /dev/null
+++ b/hw/exynos4210_pwm.c
@@ -0,0 +1,433 @@
+/*
+ * Samsung exynos4210 Pulse Width Modulation Timer
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ *
+ * Evgeny Voevodin <e.voevodin@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "sysbus.h"
+#include "qemu-timer.h"
+#include "qemu-common.h"
+#include "hw.h"
+
+#include "exynos4210.h"
+
+//#define DEBUG_PWM
+
+#ifdef DEBUG_PWM
+#define DPRINTF(fmt, ...) \
+        do { fprintf(stdout, "PWM: [%24s:%5d] " fmt, __func__, __LINE__, \
+                ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+#define     EXYNOS4210_PWM_TIMERS_NUM      5
+#define     EXYNOS4210_PWM_REG_MEM_SIZE    0x50
+
+#define     TCFG0        0x0000
+#define     TCFG1        0x0004
+#define     TCON         0x0008
+#define     TCNTB0       0x000C
+#define     TCMPB0       0x0010
+#define     TCNTO0       0x0014
+#define     TCNTB1       0x0018
+#define     TCMPB1       0x001C
+#define     TCNTO1       0x0020
+#define     TCNTB2       0x0024
+#define     TCMPB2       0x0028
+#define     TCNTO2       0x002C
+#define     TCNTB3       0x0030
+#define     TCMPB3       0x0034
+#define     TCNTO3       0x0038
+#define     TCNTB4       0x003C
+#define     TCNTO4       0x0040
+#define     TINT_CSTAT   0x0044
+
+#define     TCNTB(x)    (0xC*x)
+#define     TCMPB(x)    (0xC*x+1)
+#define     TCNTO(x)    (0xC*x+2)
+
+#define     GET_PRESCALER(reg, x)  ((reg&(0xFF<<(8*x)))>>8*x)
+#define     GET_DIVIDER(reg, x)    (1<<((0xF<<(4*x))>>(4*x)))
+
+/*
+ * Attention! Timer4 doesn't have OUTPUT_INVERTER,
+ * so Auto Reload bit is not accessible by macros!
+ */
+#define     TCON_TIMER_BASE(x)          ((x ? 1 : 0)*4 + 4*x)
+#define     TCON_TIMER_START(x)         (1<<(TCON_TIMER_BASE(x) + 0))
+#define     TCON_TIMER_MANUAL_UPD(x)    (1<<(TCON_TIMER_BASE(x) + 1))
+#define     TCON_TIMER_OUTPUT_INV(x)    (1<<(TCON_TIMER_BASE(x) + 2))
+#define     TCON_TIMER_AUTO_RELOAD(x)   (1<<(TCON_TIMER_BASE(x) + 3))
+#define     TCON_TIMER4_AUTO_RELOAD     (1<<22)
+
+#define     TINT_CSTAT_STATUS(x)        (1<<(5+x))
+#define     TINT_CSTAT_ENABLE(x)        (1<<x)
+
+/* timer struct */
+typedef struct {
+    uint32_t    id;             /* timer id */
+    qemu_irq    irq;            /* local timer irq */
+    uint32_t    freq;           /* timer frequency */
+
+    /* use ptimer.c to represent count down timer */
+    ptimer_state *ptimer;       /* timer  */
+
+    /* registers */
+    uint32_t    reg_tcntb;       /* counter register buffer */
+    uint32_t    reg_tcmpb;       /* compare register buffer */
+
+} Exynos4210_pwm;
+
+
+typedef struct Exynos4210PWMState {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+
+    uint32_t    reg_tcfg[2];
+    uint32_t    reg_tcon;
+    uint32_t    reg_tint_cstat;
+
+    Exynos4210CmuClock clk;         /* clock source for timer */
+    Exynos4210_pwm timer[EXYNOS4210_PWM_TIMERS_NUM];
+
+} Exynos4210PWMState;
+
+/*** VMState ***/
+static const VMStateDescription VMState_Exynos4210_pwm = {
+        .name = "exynos4210.pwm.pwm",
+        .version_id = 1,
+        .minimum_version_id = 1,
+        .minimum_version_id_old = 1,
+        .fields = (VMStateField[]) {
+            VMSTATE_UINT32(id, Exynos4210_pwm),
+            VMSTATE_UINT32(freq, Exynos4210_pwm),
+            VMSTATE_UINT32(reg_tcntb, Exynos4210_pwm),
+            VMSTATE_UINT32(reg_tcmpb, Exynos4210_pwm),
+            VMSTATE_END_OF_LIST()
+        }
+};
+
+static const VMStateDescription VMState_Exynos4210PWMState = {
+        .name = "exynos4210.pwm",
+        .version_id = 1,
+        .minimum_version_id = 1,
+        .minimum_version_id_old = 1,
+        .fields = (VMStateField[]) {
+            VMSTATE_UINT32_ARRAY(reg_tcfg, Exynos4210PWMState, 2),
+            VMSTATE_UINT32(reg_tcon, Exynos4210PWMState),
+            VMSTATE_UINT32(reg_tint_cstat, Exynos4210PWMState),
+            VMSTATE_STRUCT_ARRAY(timer, Exynos4210PWMState,
+                    EXYNOS4210_PWM_TIMERS_NUM, 0,
+            VMState_Exynos4210_pwm, Exynos4210_pwm),
+            VMSTATE_END_OF_LIST()
+        }
+};
+
+/*
+ * PWM update frequency
+ */
+static void exynos4210_pwm_update_freq(Exynos4210PWMState *s, uint32_t id)
+{
+    uint32_t freq;
+    freq = s->timer[id].freq;
+    if (id > 1) {
+        s->timer[id].freq = exynos4210_cmu_get_rate(s->clk) /
+        ((GET_PRESCALER(s->reg_tcfg[0], 1) + 1) *
+                (1<<GET_DIVIDER(s->reg_tcfg[1], id)));
+    } else {
+        s->timer[id].freq = exynos4210_cmu_get_rate(s->clk) /
+        ((GET_PRESCALER(s->reg_tcfg[0], 0) + 1) *
+                (1<<GET_DIVIDER(s->preg_tcfg[1], id)));
+    }
+
+    if (freq != s->timer[id].freq) {
+        ptimer_set_freq(s->timer[id].ptimer, s->timer[id].freq);
+        DPRINTF("freq=%dHz\n", s->timer[id].freq);
+    }
+}
+
+/*
+ * Counter tick handler
+ */
+static void exynos4210_pwm_tick(void *opaque, uint32_t id)
+{
+    Exynos4210PWMState *s = (Exynos4210PWMState *)opaque;
+    bool cmp;
+
+    DPRINTF("timer %d tick\n", id);
+
+    /* set irq status */
+    s->reg_tint_cstat |= TINT_CSTAT_STATUS(id);
+
+    /* raise IRQ */
+    if (s->reg_tint_cstat & TINT_CSTAT_ENABLE(id)) {
+        DPRINTF("timer %d IRQ\n", id);
+        qemu_irq_raise(s->timer[id].irq);
+    }
+
+    /* reload timer */
+    if (id != 4) {
+        cmp = s->reg_tcon & TCON_TIMER_AUTO_RELOAD(id);
+    } else {
+        cmp = s->reg_tcon & TCON_TIMER4_AUTO_RELOAD;
+    }
+
+    if (cmp) {
+        DPRINTF("auto reload timer %d count to %x\n", id,
+                s->timer[id].reg_tcntb);
+        ptimer_set_count(s->timer[id].ptimer, s->timer[id].reg_tcntb);
+        ptimer_run(s->timer[id].ptimer, 1);
+    } else {
+        /* stop timer, set status to STOP, see Basic Timer Operation */
+        s->reg_tcon = ~TCON_TIMER_START(id);
+        ptimer_stop(s->timer[id].ptimer);
+    }
+}
+
+static void exynos4210_pwm_tick0(void *opaque)
+{
+    exynos4210_pwm_tick(opaque, 0);
+}
+static void exynos4210_pwm_tick1(void *opaque)
+{
+    exynos4210_pwm_tick(opaque, 1);
+}
+static void exynos4210_pwm_tick2(void *opaque)
+{
+    exynos4210_pwm_tick(opaque, 2);
+}
+static void exynos4210_pwm_tick3(void *opaque)
+{
+    exynos4210_pwm_tick(opaque, 3);
+}
+static void exynos4210_pwm_tick4(void *opaque)
+{
+    exynos4210_pwm_tick(opaque, 4);
+}
+
+/*
+ * PWM Read
+ */
+static uint64_t exynos4210_pwm_read(void *opaque, target_phys_addr_t offset,
+        unsigned size)
+{
+    Exynos4210PWMState *s = (Exynos4210PWMState *)opaque;
+    uint32_t value = 0;
+    int index;
+
+    switch (offset) {
+    case TCFG0: case TCFG1:
+        index = (offset - TCFG0)>>2;
+        value = s->reg_tcfg[index];
+        break;
+
+    case TCON:
+        value = s->reg_tcon;
+        break;
+
+    case TCNTB0: case TCNTB1:
+    case TCNTB2: case TCNTB3: case TCNTB4:
+        index = (offset - TCNTB0)/0xC;
+        value = s->timer[index].reg_tcntb;
+        break;
+
+    case TCMPB0: case TCMPB1:
+    case TCMPB2: case TCMPB3:
+        index = (offset - TCMPB0)/0xC;
+        value = s->timer[index].reg_tcmpb;
+        break;
+
+    case TCNTO0: case TCNTO1:
+    case TCNTO2: case TCNTO3: case TCNTO4:
+        index = (offset == TCNTO4) ? 4 : (offset - TCNTO0)/0xC;
+        value = ptimer_get_count(s->timer[index].ptimer);
+        break;
+
+    case TINT_CSTAT:
+        value = s->reg_tint_cstat;
+        break;
+
+    default:
+        fprintf(stderr,
+                "[exynos4210.pwm: bad read offset " TARGET_FMT_plx "]\n",
+                offset);
+        break;
+    }
+    return value;
+}
+
+/*
+ * PWM Write
+ */
+static void exynos4210_pwm_write(void *opaque, target_phys_addr_t offset,
+        uint64_t value, unsigned size)
+{
+    Exynos4210PWMState *s = (Exynos4210PWMState *)opaque;
+    int index;
+    uint32_t new_val;
+    int i;
+
+    switch (offset) {
+    case TCFG0: case TCFG1:
+        index = (offset - TCFG0)>>2;
+        s->reg_tcfg[index] = value;
+
+        /* update timers frequencies */
+        for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
+            exynos4210_pwm_update_freq(s, s->timer[i].id);
+        }
+        break;
+
+    case TCON:
+        for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
+            if ((value & TCON_TIMER_MANUAL_UPD(i)) >
+            (s->reg_tcon & TCON_TIMER_MANUAL_UPD(i))) {
+                /*
+                 * TCNTB and TCMPB are loaded into TCNT and TCMP.
+                 * Update timers.
+                 */
+
+                /* this will start timer to run, this ok, because
+                 * during processing start bit timer will be stopped
+                 * if needed */
+                ptimer_set_count(s->timer[i].ptimer, s->timer[i].reg_tcntb);
+                DPRINTF("set timer %d count to %x\n", i,
+                        s->timer[i].reg_tcntb);
+            }
+
+            if ((value & TCON_TIMER_START(i)) >
+            (s->reg_tcon & TCON_TIMER_START(i))) {
+                /* changed to start */
+                ptimer_run(s->timer[i].ptimer, 1);
+                DPRINTF("run timer %d\n", i);
+            }
+
+            if ((value & TCON_TIMER_START(i)) <
+                    (s->reg_tcon & TCON_TIMER_START(i))) {
+                /* changed to stop */
+                ptimer_stop(s->timer[i].ptimer);
+                DPRINTF("stop timer %d\n", i);
+            }
+        }
+        s->reg_tcon = value;
+        break;
+
+    case TCNTB0: case TCNTB1:
+    case TCNTB2: case TCNTB3: case TCNTB4:
+        index = (offset - TCNTB0)/0xC;
+        s->timer[index].reg_tcntb = value;
+        break;
+
+    case TCMPB0: case TCMPB1:
+    case TCMPB2: case TCMPB3:
+        index = (offset - TCMPB0)/0xC;
+        s->timer[index].reg_tcmpb = value;
+        break;
+
+    case TINT_CSTAT:
+        new_val = (s->reg_tint_cstat&0x3E0) + (0x1F & value);
+        new_val &= ~(0x3E0 & value);
+
+        for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
+            if ((new_val & TINT_CSTAT_STATUS(i)) <
+                    (s->reg_tint_cstat & TINT_CSTAT_STATUS(i))) {
+                qemu_irq_lower(s->timer[i].irq);
+            }
+        }
+
+        s->reg_tint_cstat = new_val;
+        break;
+
+    default:
+        fprintf(stderr,
+                "[exynos4210.pwm: bad write offset " TARGET_FMT_plx "]\n",
+                offset);
+        break;
+
+    }
+}
+
+/*
+ * Set default values to timer fields and registers
+ */
+static void exynos4210_pwm_reset(Exynos4210PWMState *s)
+{
+    int i;
+    s->reg_tcfg[0] = 0x0101;
+    s->reg_tcfg[1] = 0x0;
+    s->reg_tcon = 0;
+    s->reg_tint_cstat = 0;
+    for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
+        s->timer[i].reg_tcmpb = 0;
+        s->timer[i].reg_tcntb = 0;
+
+        exynos4210_pwm_update_freq(s, s->timer[i].id);
+        ptimer_stop(s->timer[i].ptimer);
+    }
+}
+
+static const MemoryRegionOps exynos4210_pwm_ops = {
+        .read = exynos4210_pwm_read,
+        .write = exynos4210_pwm_write,
+        .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/*
+ * PWM timer initialization
+ */
+static int exynos4210_pwm_init(SysBusDevice *dev)
+{
+    Exynos4210PWMState *s = FROM_SYSBUS(Exynos4210PWMState, dev);
+    int i;
+    QEMUBH * bh[EXYNOS4210_PWM_TIMERS_NUM];
+
+    s->clk = ACLK_100;
+
+    bh[0] = qemu_bh_new(exynos4210_pwm_tick0, s);
+    bh[1] = qemu_bh_new(exynos4210_pwm_tick1, s);
+    bh[2] = qemu_bh_new(exynos4210_pwm_tick2, s);
+    bh[3] = qemu_bh_new(exynos4210_pwm_tick3, s);
+    bh[4] = qemu_bh_new(exynos4210_pwm_tick4, s);
+
+    for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
+        sysbus_init_irq(dev, &s->timer[i].irq);
+        s->timer[i].ptimer = ptimer_init(bh[i]);
+        s->timer[i].id = i;
+    }
+
+    memory_region_init_io(&s->iomem, &exynos4210_pwm_ops, s, "exynos4210-pwm",
+            EXYNOS4210_PWM_REG_MEM_SIZE);
+    sysbus_init_mmio(dev, &s->iomem);
+
+    exynos4210_pwm_reset(s);
+
+    qemu_register_reset((QEMUResetHandler *)exynos4210_pwm_reset, s);
+    vmstate_register(NULL, -1, &VMState_Exynos4210PWMState, s);
+    return 0;
+}
+
+static void exynos4210_pwm_register_devices(void)
+{
+    sysbus_register_dev("exynos4210.pwm", sizeof(Exynos4210PWMState),
+            exynos4210_pwm_init);
+}
+
+device_init(exynos4210_pwm_register_devices)
-- 
1.7.4.1

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Qemu-devel] [PATCH 07/15] hw/arm_boot.c: Add new secondary CPU bootloader.
  2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
                   ` (5 preceding siblings ...)
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 06/15] ARM: exynos4210: PWM support Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 08/15] hw/arm_gic.c: lower IRQ only on changing of enable bit Evgeny Voevodin
                   ` (7 subsequent siblings)
  14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin

Secondary CPU bootloader enables interrupt and issues wfi until start address
is written to system controller. The position where to find this start
address is hardcoded to 0x10000030. This commit adds new bootloader for
secondary CPU which allows a target board to cpecify a position where
to find start address. If target board doesn't specify start address then
default 0x10000030 is used

Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 hw/arm-misc.h |    1 +
 hw/arm_boot.c |   22 +++++++++++++++-------
 2 files changed, 16 insertions(+), 7 deletions(-)

diff --git a/hw/arm-misc.h b/hw/arm-misc.h
index af403a1..6e8ae6b 100644
--- a/hw/arm-misc.h
+++ b/hw/arm-misc.h
@@ -31,6 +31,7 @@ struct arm_boot_info {
     const char *initrd_filename;
     target_phys_addr_t loader_start;
     target_phys_addr_t smp_loader_start;
+    target_phys_addr_t smp_bootreg_addr;
     target_phys_addr_t smp_priv_base;
     int nb_cpus;
     int board_id;
diff --git a/hw/arm_boot.c b/hw/arm_boot.c
index 215d5de..ecaac22 100644
--- a/hw/arm_boot.c
+++ b/hw/arm_boot.c
@@ -31,17 +31,17 @@ static uint32_t bootloader[] = {
 /* Entry point for secondary CPUs.  Enable interrupt controller and
    Issue WFI until start address is written to system controller.  */
 static uint32_t smpboot[] = {
-  0xe59f0020, /* ldr     r0, privbase */
-  0xe3a01001, /* mov     r1, #1 */
-  0xe5801100, /* str     r1, [r0, #0x100] */
-  0xe3a00201, /* mov     r0, #0x10000000 */
-  0xe3800030, /* orr     r0, #0x30 */
+  0xe59f201c, /* ldr r2, privbase */
+  0xe59f001c, /* ldr r0, startaddr */
+  0xe3a01001, /* mov r1, #1 */
+  0xe5821100, /* str r1, [r2, #256] */
   0xe320f003, /* wfi */
   0xe5901000, /* ldr     r1, [r0] */
   0xe1110001, /* tst     r1, r1 */
   0x0afffffb, /* beq     <wfi> */
   0xe12fff11, /* bx      r1 */
-  0 /* privbase: Private memory region base address.  */
+  0,          /* privbase: Private memory region base address.  */
+  0           /* bootreg: Boot register address is hold here */
 };
 
 #define WRITE_WORD(p, value) do { \
@@ -179,6 +179,7 @@ static void do_cpu_reset(void *opaque)
 {
     CPUState *env = opaque;
     const struct arm_boot_info *info = env->boot_info;
+    uint8_t smp_bootreg_addr[4] = {0};
 
     cpu_reset(env);
     if (info) {
@@ -197,6 +198,8 @@ static void do_cpu_reset(void *opaque)
                                     info->loader_start);
                 }
             } else {
+                cpu_physical_memory_rw(info->smp_bootreg_addr, smp_bootreg_addr,
+                        sizeof(smp_bootreg_addr), 1);
                 env->regs[15] = info->smp_loader_start;
             }
         }
@@ -262,6 +265,7 @@ void arm_load_kernel(CPUState *env, struct arm_boot_info *info)
         } else {
             initrd_size = 0;
         }
+
         bootloader[1] |= info->board_id & 0xff;
         bootloader[2] |= (info->board_id >> 8) & 0xff;
         bootloader[5] = info->loader_start + KERNEL_ARGS_ADDR;
@@ -272,7 +276,11 @@ void arm_load_kernel(CPUState *env, struct arm_boot_info *info)
         rom_add_blob_fixed("bootloader", bootloader, sizeof(bootloader),
                            info->loader_start);
         if (info->nb_cpus > 1) {
-            smpboot[10] = info->smp_priv_base;
+            if (!info->smp_bootreg_addr) {
+                info->smp_bootreg_addr = 0x10000030;
+            }
+            smpboot[(sizeof(smpboot) - 8)/4] = info->smp_priv_base;
+            smpboot[(sizeof(smpboot) - 4)/4] = info->smp_bootreg_addr;
             for (n = 0; n < sizeof(smpboot) / 4; n++) {
                 smpboot[n] = tswap32(smpboot[n]);
             }
-- 
1.7.4.1

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Qemu-devel] [PATCH 08/15] hw/arm_gic.c: lower IRQ only on changing of enable bit.
  2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
                   ` (6 preceding siblings ...)
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 07/15] hw/arm_boot.c: Add new secondary CPU bootloader Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
  2011-12-09 15:55   ` Paul Brook
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 09/15] ARM: exynos4210: MCT support Evgeny Voevodin
                   ` (6 subsequent siblings)
  14 siblings, 1 reply; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin

In previous version IRQ was lowered every time if enable bits were
not set. If platform has splitted IRQ source to pass IRQ to two
identical GICs simultaneously in first of which IRQ passing is
enabled but in second is disabled, handling IRQ by second GIC would
lower IRQ previously raised by first GIC.
Linux kernel v3.0 faces this problem.
The problem is avoided if IRQ is only lowered as result of
transitioning enable bits to zeroes.

Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 hw/arm_gic.c |   20 +++++++++++++++++++-
 1 files changed, 19 insertions(+), 1 deletions(-)

diff --git a/hw/arm_gic.c b/hw/arm_gic.c
index 527c9ce..7e3db4f 100644
--- a/hw/arm_gic.c
+++ b/hw/arm_gic.c
@@ -84,7 +84,9 @@ typedef struct gic_state
     SysBusDevice busdev;
     qemu_irq parent_irq[NCPU];
     int enabled;
+    int enabled_prev;
     int cpu_enabled[NCPU];
+    int cpu_enabled_prev[NCPU];
 
     gic_irq_state irq_state[GIC_NIRQ];
 #ifndef NVIC
@@ -116,12 +118,22 @@ static void gic_update(gic_state *s)
     int level;
     int cpu;
     int cm;
+    int enabled_prev;
+    int cpu_enabled_prev;
 
+    enabled_prev = s->enabled_prev;
+    s->enabled_prev = s->enabled;
     for (cpu = 0; cpu < NUM_CPU(s); cpu++) {
+        cpu_enabled_prev = s->cpu_enabled_prev[cpu];
+        s->cpu_enabled_prev[cpu] = s->cpu_enabled[cpu];
         cm = 1 << cpu;
         s->current_pending[cpu] = 1023;
         if (!s->enabled || !s->cpu_enabled[cpu]) {
-	    qemu_irq_lower(s->parent_irq[cpu]);
+            /* lower IRQ only if enable bit was changed */
+            if (enabled_prev != s->enabled
+                    || cpu_enabled_prev != s->cpu_enabled[cpu]) {
+                qemu_irq_lower(s->parent_irq[cpu]);
+            }
             return;
         }
         best_prio = 0x100;
@@ -650,6 +662,7 @@ static void gic_reset(gic_state *s)
 #else
         s->cpu_enabled[i] = 0;
 #endif
+        s->cpu_enabled_prev[i] = s->cpu_enabled[i];
     }
     for (i = 0; i < 16; i++) {
         GIC_SET_ENABLED(i, ALL_CPU_MASK);
@@ -661,6 +674,7 @@ static void gic_reset(gic_state *s)
 #else
     s->enabled = 0;
 #endif
+    s->enabled_prev = s->enabled;
 }
 
 static void gic_save(QEMUFile *f, void *opaque)
@@ -669,8 +683,10 @@ static void gic_save(QEMUFile *f, void *opaque)
     int i;
     int j;
 
+    qemu_put_be32(f, s->enabled_prev);
     qemu_put_be32(f, s->enabled);
     for (i = 0; i < NUM_CPU(s); i++) {
+        qemu_put_be32(f, s->cpu_enabled_prev[i]);
         qemu_put_be32(f, s->cpu_enabled[i]);
         for (j = 0; j < 32; j++)
             qemu_put_be32(f, s->priority1[j][i]);
@@ -706,8 +722,10 @@ static int gic_load(QEMUFile *f, void *opaque, int version_id)
     if (version_id != 2)
         return -EINVAL;
 
+    s->enabled_prev = qemu_get_be32(f);
     s->enabled = qemu_get_be32(f);
     for (i = 0; i < NUM_CPU(s); i++) {
+        s->cpu_enabled_prev[i] = qemu_get_be32(f);
         s->cpu_enabled[i] = qemu_get_be32(f);
         for (j = 0; j < 32; j++)
             s->priority1[j][i] = qemu_get_be32(f);
-- 
1.7.4.1

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Qemu-devel] [PATCH 09/15] ARM: exynos4210: MCT support.
  2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
                   ` (7 preceding siblings ...)
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 08/15] hw/arm_gic.c: lower IRQ only on changing of enable bit Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 10/15] hw/exynos4210.c: Boot secondary CPU Evgeny Voevodin
                   ` (5 subsequent siblings)
  14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin


Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 Makefile.target     |    2 +-
 hw/exynos4210.c     |   19 +
 hw/exynos4210_mct.c | 1486 +++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1506 insertions(+), 1 deletions(-)
 create mode 100644 hw/exynos4210_mct.c

diff --git a/Makefile.target b/Makefile.target
index 709e9e2..691582b 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -345,7 +345,7 @@ 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 += exynos4210.o exynos4210_cmu.o exynos4210_uart.o exynos4210_gic.o \
-             exynos4210_combiner.o exynos4210_pwm.o
+             exynos4210_combiner.o exynos4210_pwm.o exynos4210_mct.o
 obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
 obj-arm-y += pl061.o
 obj-arm-y += arm-semi.o
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index 361cdfb..f172346 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -68,6 +68,9 @@
 /* PWM */
 #define EXYNOS4210_PWM_BASE_ADDR            0x139D0000
 
+/* MCT */
+#define EXYNOS4210_MCT_BASE_ADDR            0x10050000
+
 /* UART's definitions */
 #define EXYNOS4210_UART_BASE_ADDR           0x13800000
 #define EXYNOS4210_UART_SHIFT               0x00010000
@@ -362,6 +365,22 @@ static void exynos4210_init(ram_addr_t ram_size,
             irq_table[exynos4210_get_irq(22, 4)],
             NULL);
 
+    /* Multi Core Timer */
+    dev = qdev_create(NULL, "exynos4210.mct");
+    qdev_init_nofail(dev);
+    busdev = sysbus_from_qdev(dev);
+    for (n = 0; n < 4; n++) {
+        /* Connect global timer interrupts to Combiner gpio_in */
+        sysbus_connect_irq(busdev, n,
+                irq_table[exynos4210_get_irq(1, 4 + n)]);
+    }
+    /* Connect local timer interrupts to Combiner gpio_in */
+    sysbus_connect_irq(busdev, 4,
+            irq_table[exynos4210_get_irq(51, 0)]);
+    sysbus_connect_irq(busdev, 5,
+            irq_table[exynos4210_get_irq(35, 3)]);
+    sysbus_mmio_map(busdev, 0, EXYNOS4210_MCT_BASE_ADDR);
+
     /*** UARTs ***/
     for (n = 0; n < EXYNOS4210_UARTS_NUMBER; n++) {
 
diff --git a/hw/exynos4210_mct.c b/hw/exynos4210_mct.c
new file mode 100644
index 0000000..d754a94
--- /dev/null
+++ b/hw/exynos4210_mct.c
@@ -0,0 +1,1486 @@
+/*
+ * Samsung exynos4210 Multi Core timer
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ *
+ * Evgeny Voevodin <e.voevodin@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * Global Timer:
+ *
+ * Consists of two timers. First represents Free Running Counter and second
+ * is used to measure interval from FRC to nearest comparator.
+ *
+ *        0                                                            0xFFFF...
+ *        |                              timer0                             |
+ *        | <-------------------------------------------------------------- |
+ *        | --------------------------------------------frc---------------> |
+ *        |______________________________________________|__________________|
+ *                CMP0          CMP1             CMP2    |           CMP3
+ *                                                     __|            |_
+ *                                                     |     timer1     |
+ *                                                     | -------------> |
+ *                                                    frc              CMPx
+ *
+ * Problem: when implementing global timer as is, overflow arises.
+ * next_time = cur_time + period*count;
+ * period and count are 64 bits width and count == 0xFF..FF 64its.
+ * Lets arm timer for 0xFFFFFFFF count and update internal G_CNT register
+ * during each event.
+ *
+ * Problem: both timers need to be implemented using MCT_XT_COUNTER_STEP because
+ * local timer contains two counters: TCNT and ICNT. TCNT == 0 -> ICNT--.
+ * IRQ is generated when ICNT riches zero. If make timer for every TCNT == 0
+ * possible too frequently events (yes, if ICNT == 0 both, we got no luck).
+ * So, better to have one uint64_t counter equal to TCNT*ICNT and arm ptimer.c
+ * for a minimum(TCNT*ICNT, MCT_GT_COUNTER_STEP);
+ */
+
+#include "sysbus.h"
+#include "qemu-timer.h"
+#include "qemu-common.h"
+#include "osdep.h"
+
+#include "exynos4210.h"
+
+//#define DEBUG_MCT
+
+#ifdef DEBUG_MCT
+#define DPRINTF(fmt, ...) \
+        do { fprintf(stdout, "MCT: [%24s:%5d] " fmt, __func__, __LINE__, \
+                     ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+#define    MCT_CFG                0x000
+#define    G_CNT_L                0x100
+#define    G_CNT_U                0x104
+#define    G_CNT_WSTAT            0x110
+#define    G_COMP0_L            0x200
+#define    G_COMP0_U            0x204
+#define    G_COMP0_ADD_INCR    0x208
+#define    G_COMP1_L            0x210
+#define    G_COMP1_U            0x214
+#define    G_COMP1_ADD_INCR    0x218
+#define    G_COMP2_L            0x220
+#define    G_COMP2_U            0x224
+#define    G_COMP2_ADD_INCR    0x228
+#define    G_COMP3_L            0x230
+#define    G_COMP3_U            0x234
+#define    G_COMP3_ADD_INCR    0x238
+#define    G_TCON                0x240
+#define    G_INT_CSTAT            0x244
+#define    G_INT_ENB            0x248
+#define    G_WSTAT                0x24C
+#define    L0_TCNTB            0x300
+#define    L0_TCNTO            0x304
+#define    L0_ICNTB            0x308
+#define    L0_ICNTO            0x30C
+#define    L0_FRCNTB            0x310
+#define    L0_FRCNTO            0x314
+#define    L0_TCON                0x320
+#define    L0_INT_CSTAT        0x330
+#define    L0_INT_ENB            0x334
+#define    L0_WSTAT            0x340
+#define    L1_TCNTB            0x400
+#define    L1_TCNTO            0x404
+#define    L1_ICNTB            0x408
+#define    L1_ICNTO            0x40C
+#define    L1_FRCNTB            0x410
+#define    L1_FRCNTO            0x414
+#define    L1_TCON                0x420
+#define    L1_INT_CSTAT        0x430
+#define    L1_INT_ENB            0x434
+#define    L1_WSTAT            0x440
+
+#define MCT_CFG_GET_PRESCALER(x)    (x&0xFF)
+#define MCT_CFG_GET_DIVIDER(x)      (1<<(x>>8 & 7))
+
+#define GET_G_COMP_IDX(offset)          ((offset - G_COMP0_L)/0x10)
+#define GET_G_COMP_ADD_INCR_IDX(offset) ((offset - G_COMP0_ADD_INCR)/0x10)
+
+#define G_COMP_L(x)         (G_COMP0_L+x*0x10)
+#define G_COMP_U(x)         (G_COMP0_U+x*0x10)
+
+#define G_COMP_ADD_INCR(x)  (G_COMP0_ADD_INCR+x*0x10)
+
+/* MCT bits */
+#define G_TCON_COMP_ENABLE(x)   (1<<2*x)
+#define G_TCON_AUTO_ICREMENT(x) (1<<(2*x + 1))
+#define G_TCON_TIMER_ENABLE     (1<<8)
+
+#define G_INT_ENABLE(x)         (1<<x)
+#define G_INT_CSTAT_COMP(x)     (1<<x)
+
+#define G_CNT_WSTAT_L           1
+#define G_CNT_WSTAT_U           2
+
+#define G_WSTAT_COMP_L(x)       (1<<4*x)
+#define G_WSTAT_COMP_U(x)       (1<<((4*x)+1))
+#define G_WSTAT_COMP_ADDINCR(x) (1<<((4*x)+2))
+#define G_WSTAT_TCON_WRITE      (1<<16)
+
+#define GET_L_TIMER_IDX(offset) (((offset&0xF00) - L0_TCNTB)/0x100)
+#define GET_L_TIMER_CNT_REG_IDX(offset, lt_i) \
+        ((offset - (L0_TCNTB+0x100*lt_i))>>2)
+
+#define L_ICNTB_MANUAL_UPDATE   (1<<31)
+
+#define L_TCON_TICK_START       (1)
+#define L_TCON_INT_START        (1<<1)
+#define L_TCON_INTERVAL_MODE    (1<<2)
+#define L_TCON_FRC_START        (1<<3)
+
+#define L_INT_CSTAT_INTCNT      (1<<0)
+#define L_INT_CSTAT_FRCCNT      (1<<1)
+
+#define L_INT_INTENB_ICNTEIE    (1<<0)
+#define L_INT_INTENB_FRCEIE     (1<<1)
+
+#define L_WSTAT_TCNTB_WRITE     (1<<0)
+#define L_WSTAT_ICNTB_WRITE     (1<<1)
+#define L_WSTAT_FRCCNTB_WRITE   (1<<2)
+#define L_WSTAT_TCON_WRITE      (1<<3)
+
+enum local_timer_reg_cnt_indexes {
+    L_REG_CNT_TCNTB,
+    L_REG_CNT_TCNTO,
+    L_REG_CNT_ICNTB,
+    L_REG_CNT_ICNTO,
+    L_REG_CNT_FRCCNTB,
+    L_REG_CNT_FRCCNTO,
+
+    L_REG_CNT_AMOUNT
+};
+
+#define MCT_NIRQ                6
+#define MCT_SFR_SIZE            0x444
+
+#define MCT_GT_CMP_NUM          4
+
+#define MCT_GT_MAX_VAL          0xFFFFFFFFFFFFFFFF
+
+#define MCT_GT_COUNTER_STEP     0x100000000
+#define MCT_LT_COUNTER_STEP     0x100000000
+#define MCT_LT_CNT_LOW_LIMIT    0x100
+
+/* global timer */
+typedef struct {
+    qemu_irq  irq[MCT_GT_CMP_NUM];
+
+    struct gregs {
+        uint64_t cnt;
+        uint32_t cnt_wstat;
+        uint32_t tcon;
+        uint32_t int_cstat;
+        uint32_t int_enb;
+        uint32_t wstat;
+        uint64_t comp[MCT_GT_CMP_NUM];
+        uint32_t comp_add_incr[MCT_GT_CMP_NUM];
+    } reg;
+
+    uint64_t count;            /* Value FRC was armed with */
+    int32_t curr_comp;             /* Current comparator FRC is running to */
+
+    ptimer_state *ptimer_frc;                   /* FRC timer */
+
+} exynos4210_mct_gt;
+
+/* local timer */
+typedef struct {
+    int         id;             /* timer id */
+    qemu_irq    irq;            /* local timer irq */
+
+    struct tick_timer {
+        uint32_t cnt_run;           /* cnt timer is running */
+        uint32_t int_run;           /* int timer is running */
+
+        uint32_t last_icnto;
+        uint32_t last_tcnto;
+        uint32_t tcntb;             /* initial value for TCNTB */
+        uint32_t icntb;             /* initial value for ICNTB */
+
+        /* for step mode */
+        uint64_t    distance;       /* distance to count to the next event */
+        uint64_t    progress;       /* progress when counting by steps */
+        uint64_t    count;          /* count to arm timer with */
+
+        ptimer_state *ptimer_tick;  /* timer for tick counter */
+    } tick_timer;
+
+    /* use ptimer.c to represent count down timer */
+
+    ptimer_state *ptimer_frc;   /* timer for free running counter */
+
+    /* registers */
+    struct lregs {
+        uint32_t    cnt[L_REG_CNT_AMOUNT];
+        uint32_t    tcon;
+        uint32_t    int_cstat;
+        uint32_t    int_enb;
+        uint32_t    wstat;
+    } reg;
+
+} exynos4210_mct_lt;
+
+
+
+typedef struct Exynos4210MCTState {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+
+    /* Registers */
+    uint32_t    reg_mct_cfg;
+
+    exynos4210_mct_lt l_timer[2];
+    exynos4210_mct_gt g_timer;
+
+    uint32_t    freq;                   /* all timers tick frequency, TCLK */
+} Exynos4210MCTState;
+
+/*** VMState ***/
+static const VMStateDescription VMState_tick_timer = {
+        .name = "exynos4210.mct.tick_timer",
+        .version_id = 1,
+        .minimum_version_id = 1,
+        .minimum_version_id_old = 1,
+        .fields = (VMStateField[]) {
+            VMSTATE_UINT32(cnt_run, struct tick_timer),
+            VMSTATE_UINT32(int_run, struct tick_timer),
+            VMSTATE_UINT32(last_icnto, struct tick_timer),
+            VMSTATE_UINT32(last_tcnto, struct tick_timer),
+            VMSTATE_UINT32(tcntb, struct tick_timer),
+            VMSTATE_UINT32(icntb, struct tick_timer),
+            VMSTATE_UINT64(distance, struct tick_timer),
+            VMSTATE_UINT64(progress, struct tick_timer),
+            VMSTATE_UINT64(count, struct tick_timer),
+            VMSTATE_END_OF_LIST()
+        }
+};
+
+static const VMStateDescription VMState_lregs = {
+        .name = "exynos4210.mct.lregs",
+        .version_id = 1,
+        .minimum_version_id = 1,
+        .minimum_version_id_old = 1,
+        .fields = (VMStateField[]) {
+            VMSTATE_UINT32_ARRAY(cnt, struct lregs, L_REG_CNT_AMOUNT),
+            VMSTATE_UINT32(tcon, struct lregs),
+            VMSTATE_UINT32(int_cstat, struct lregs),
+            VMSTATE_UINT32(int_enb, struct lregs),
+            VMSTATE_UINT32(wstat, struct lregs),
+            VMSTATE_END_OF_LIST()
+        }
+};
+
+static const VMStateDescription VMState_Exynos4210_mct_lt = {
+        .name = "exynos4210.mct.lt",
+        .version_id = 1,
+        .minimum_version_id = 1,
+        .minimum_version_id_old = 1,
+        .fields = (VMStateField[]) {
+            VMSTATE_INT32(id, exynos4210_mct_lt),
+            VMSTATE_STRUCT(tick_timer, exynos4210_mct_lt, 0,
+                    VMState_tick_timer,
+                    struct tick_timer),
+            VMSTATE_STRUCT(reg, exynos4210_mct_lt, 0,
+                    VMState_lregs,
+                    struct lregs),
+            VMSTATE_END_OF_LIST()
+        }
+};
+
+static const VMStateDescription VMState_gregs = {
+        .name = "exynos4210.mct.lregs",
+        .version_id = 1,
+        .minimum_version_id = 1,
+        .minimum_version_id_old = 1,
+        .fields = (VMStateField[]) {
+            VMSTATE_UINT64(cnt, struct gregs),
+            VMSTATE_UINT32(cnt_wstat, struct gregs),
+            VMSTATE_UINT32(tcon, struct gregs),
+            VMSTATE_UINT32(int_cstat, struct gregs),
+            VMSTATE_UINT32(int_enb, struct gregs),
+            VMSTATE_UINT32(wstat, struct gregs),
+            VMSTATE_UINT64_ARRAY(comp, struct gregs, MCT_GT_CMP_NUM),
+            VMSTATE_UINT32_ARRAY(comp_add_incr, struct gregs,
+                    MCT_GT_CMP_NUM),
+            VMSTATE_END_OF_LIST()
+        }
+};
+
+static const VMStateDescription VMState_Exynos4210_mct_gt = {
+        .name = "exynos4210.mct.lt",
+        .version_id = 1,
+        .minimum_version_id = 1,
+        .minimum_version_id_old = 1,
+        .fields = (VMStateField[]) {
+            VMSTATE_STRUCT(reg, exynos4210_mct_gt, 0, VMState_gregs,
+                    struct gregs),
+            VMSTATE_UINT64(count, exynos4210_mct_gt),
+            VMSTATE_INT32(curr_comp, exynos4210_mct_gt),
+            VMSTATE_END_OF_LIST()
+        }
+};
+
+static const VMStateDescription VMState_Exynos4210MCTState = {
+        .name = "exynos4210.mct",
+        .version_id = 1,
+        .minimum_version_id = 1,
+        .minimum_version_id_old = 1,
+        .fields = (VMStateField[]) {
+            VMSTATE_UINT32(reg_mct_cfg, Exynos4210MCTState),
+            VMSTATE_STRUCT_ARRAY(l_timer, Exynos4210MCTState, 2, 0,
+                VMState_Exynos4210_mct_lt, exynos4210_mct_lt),
+            VMSTATE_STRUCT(g_timer, Exynos4210MCTState, 0,
+                VMState_Exynos4210_mct_gt, exynos4210_mct_gt),
+            VMSTATE_UINT32(freq, Exynos4210MCTState),
+                VMSTATE_END_OF_LIST()
+        }
+};
+
+static void exynos4210_mct_update_freq(Exynos4210MCTState *s);
+
+/*
+ * Set counter of FRC global timer.
+ */
+static void exynos4210_gfrc_set_count(exynos4210_mct_gt *s, uint64_t count)
+{
+    s->count = count;
+    DPRINTF("global timer frc set count 0x%llx\n", count);
+    ptimer_set_count(s->ptimer_frc, count);
+}
+
+/*
+ * Get counter of FRC global timer.
+ */
+static uint64_t exynos4210_gfrc_get_count(exynos4210_mct_gt *s)
+{
+    uint64_t count = 0;
+    count = ptimer_get_count(s->ptimer_frc);
+    if (!count) {
+        /* Timer event was generated and s->reg.cnt holds adequate value */
+        return s->reg.cnt;
+    }
+    count = s->count - count;
+    return s->reg.cnt + count;
+}
+
+/*
+ * Stop global FRC timer
+ */
+static void exynos4210_gfrc_stop(exynos4210_mct_gt *s)
+{
+    DPRINTF("global timer frc stop\n");
+
+    ptimer_stop(s->ptimer_frc);
+}
+
+/*
+ * Start global FRC timer
+ */
+static void exynos4210_gfrc_start(exynos4210_mct_gt *s)
+{
+    DPRINTF("global timer frc start\n");
+
+    ptimer_run(s->ptimer_frc, 1);
+}
+
+/*
+ * Find next nearest Comparator. If current Comparator value equals to other
+ * Comparator value, skip them both
+ */
+static int32_t exynos4210_gcomp_find(Exynos4210MCTState *s)
+{
+    int res;
+    int i;
+    int enabled;
+    uint64_t min;
+    int min_comp_i;
+    uint64_t gfrc;
+    uint64_t distance;
+    uint64_t distance_min;
+    int comp_i;
+
+    /* get gfrc count */
+    gfrc = exynos4210_gfrc_get_count(&s->g_timer);
+
+    min = 0xFFFFFFFFFFFFFFFF;
+    distance_min = 0xFFFFFFFFFFFFFFFF;
+    comp_i = MCT_GT_CMP_NUM;
+    min_comp_i = MCT_GT_CMP_NUM;
+    enabled = 0;
+
+    /* lookup for nearest comparator */
+    for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+
+        if (s->g_timer.reg.tcon & G_TCON_COMP_ENABLE(i)) {
+
+            enabled = 1;
+
+            if (s->g_timer.reg.comp[i] > gfrc) {
+                /* Comparator is upper then FRC */
+                distance = s->g_timer.reg.comp[i] - gfrc;
+
+                if (distance <= distance_min) {
+                    distance_min = distance;
+                    comp_i = i;
+                }
+            } else {
+                /* Comparator is below FRC, find the smallest */
+
+                if (s->g_timer.reg.comp[i] <= min) {
+                    min = s->g_timer.reg.comp[i];
+                    min_comp_i = i;
+                }
+            }
+        }
+    }
+
+    if (!enabled) {
+        /* All Comparators disabled */
+        res = -1;
+    } else if (comp_i < MCT_GT_CMP_NUM) {
+        /* Found upper Comparator */
+        res = comp_i;
+    } else {
+        /* All Comparators are below or equal to FRC  */
+        res = min_comp_i;
+    }
+
+    DPRINTF("found comparator %d: comp 0x%llx distance 0x%llx, gfrc 0x%llx\n",
+            res,
+            s->g_timer.reg.comp[res],
+            distance_min,
+            gfrc);
+
+    return res;
+}
+
+/*
+ * Get distance to nearest Comparator
+ */
+static uint64_t exynos4210_gcomp_get_distance(Exynos4210MCTState *s, int32_t id)
+{
+    if (id == -1) {
+        /* no enabled Comparators, choose max distance */
+        return MCT_GT_COUNTER_STEP;
+    }
+    if (s->g_timer.reg.comp[id] - s->g_timer.reg.cnt < MCT_GT_COUNTER_STEP) {
+        return s->g_timer.reg.comp[id] - s->g_timer.reg.cnt;
+    } else {
+        return MCT_GT_COUNTER_STEP;
+    }
+}
+
+/*
+ * Restart global FRC timer
+ */
+static void exynos4210_gfrc_restart(Exynos4210MCTState *s)
+{
+    uint64_t distance;
+
+    exynos4210_gfrc_stop(&s->g_timer);
+
+    s->g_timer.curr_comp = exynos4210_gcomp_find(s);
+
+    distance = exynos4210_gcomp_get_distance(s, s->g_timer.curr_comp);
+
+    if (distance > MCT_GT_COUNTER_STEP || !distance) {
+        distance = MCT_GT_COUNTER_STEP;
+    }
+
+    exynos4210_gfrc_set_count(&s->g_timer, distance);
+    exynos4210_gfrc_start(&s->g_timer);
+}
+
+/*
+ * Raise global timer CMP IRQ
+ */
+static void exynos4210_gcomp_raise_irq(void *opaque, uint32_t id)
+{
+    exynos4210_mct_gt *s = opaque;
+
+    /* If CSTAT is pending and IRQ is enabled */
+    if ((s->reg.int_cstat & G_INT_CSTAT_COMP(id)) &&
+            (s->reg.int_enb & G_INT_ENABLE(id))) {
+        DPRINTF("gcmp timer[%d] IRQ\n", id);
+        qemu_irq_raise(s->irq[id]);
+    }
+}
+
+/*
+ * Lower global timer CMP IRQ
+ */
+static void exynos4210_gcomp_lower_irq(void *opaque, uint32_t id)
+{
+    exynos4210_mct_gt *s = opaque;
+    qemu_irq_lower(s->irq[id]);
+}
+
+/*
+ * Global timer FRC event handler.
+ * Each event occurs when internal counter reaches counter + MCT_GT_COUNTER_STEP
+ * Every time we arm global FRC timer to count for MCT_GT_COUNTER_STEP value
+ */
+static void exynos4210_gfrc_event(void *opaque)
+{
+    Exynos4210MCTState *s = (Exynos4210MCTState *)opaque;
+    int i;
+    uint64_t distance;
+
+    DPRINTF("\n");
+
+    s->g_timer.reg.cnt += s->g_timer.count;
+
+    /* Process all comparators */
+    for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+
+        if (s->g_timer.reg.cnt == s->g_timer.reg.comp[i]) {
+            /* reached nearest comparator */
+
+            s->g_timer.reg.int_cstat |= G_INT_CSTAT_COMP(i);
+
+            /* Auto increment */
+            if (s->g_timer.reg.tcon & G_TCON_AUTO_ICREMENT(i)) {
+                s->g_timer.reg.comp[i] += s->g_timer.reg.comp_add_incr[i];
+            }
+
+            /* IRQ */
+            exynos4210_gcomp_raise_irq(&s->g_timer, i);
+        }
+    }
+
+    /* Reload FRC to reach nearest comparator */
+    s->g_timer.curr_comp = exynos4210_gcomp_find(s);
+    distance = exynos4210_gcomp_get_distance(s, s->g_timer.curr_comp);
+    if (distance > MCT_GT_COUNTER_STEP) {
+        distance = MCT_GT_COUNTER_STEP;
+    }
+    exynos4210_gfrc_set_count(&s->g_timer, distance);
+
+    exynos4210_gfrc_start(&s->g_timer);
+
+    return;
+}
+
+/*
+ * Get counter of FRC local timer.
+ */
+static uint64_t exynos4210_lfrc_get_count(exynos4210_mct_lt *s)
+{
+    return ptimer_get_count(s->ptimer_frc);
+}
+
+/*
+ * Set counter of FRC local timer.
+ */
+static void exynos4210_lfrc_update_count(exynos4210_mct_lt *s)
+{
+    if (!s->reg.cnt[L_REG_CNT_FRCCNTB]) {
+        ptimer_set_count(s->ptimer_frc, MCT_LT_COUNTER_STEP);
+    } else {
+        ptimer_set_count(s->ptimer_frc, s->reg.cnt[L_REG_CNT_FRCCNTB]);
+    }
+}
+
+/*
+ * Start local FRC timer
+ */
+static void exynos4210_lfrc_start(exynos4210_mct_lt *s)
+{
+    ptimer_run(s->ptimer_frc, 1);
+}
+
+/*
+ * Stop local FRC timer
+ */
+static void exynos4210_lfrc_stop(exynos4210_mct_lt *s)
+{
+    ptimer_stop(s->ptimer_frc);
+}
+
+/*
+ * Local timer free running counter tick handler
+ */
+static void exynos4210_lfrc_event(void *opaque)
+{
+    exynos4210_mct_lt * s = (exynos4210_mct_lt *)opaque;
+
+    /* local frc expired */
+
+    DPRINTF("\n");
+
+    s->reg.int_cstat |= L_INT_CSTAT_FRCCNT;
+
+    /* update frc counter */
+    exynos4210_lfrc_update_count(s);
+
+    /* raise irq */
+    if (s->reg.int_enb & L_INT_INTENB_FRCEIE) {
+        qemu_irq_raise(s->irq);
+    }
+
+    /*  we reached here, this means that timer is enabled */
+    exynos4210_lfrc_start(s);
+}
+
+
+static uint32_t exynos4210_ltick_int_get_cnto(struct tick_timer *s);
+static void exynos4210_ltick_cnt_start(struct tick_timer *s);
+static void exynos4210_ltick_cnt_stop(struct tick_timer *s);
+static uint32_t exynos4210_ltick_cnt_get_cnto(struct tick_timer *s);
+static void exynos4210_ltick_recalc_count(struct tick_timer *s);
+
+
+
+/*
+ * Action on enabling local tick int timer
+ */
+static void exynos4210_ltick_int_start(struct tick_timer *s)
+{
+    if (!s->int_run) {
+        s->int_run = 1;
+    }
+}
+
+/*
+ * Action on disabling local tick int timer
+ */
+static void exynos4210_ltick_int_stop(struct tick_timer *s)
+{
+    if (s->int_run) {
+        s->last_icnto = exynos4210_ltick_int_get_cnto(s);
+        s->int_run = 0;
+    }
+}
+
+/*
+ * Get count for INT timer
+ */
+static uint32_t exynos4210_ltick_int_get_cnto(struct tick_timer *s)
+{
+    uint32_t icnto;
+    uint64_t remain;
+    uint64_t count;
+    uint64_t counted;
+    uint64_t cur_progress;
+
+    count = ptimer_get_count(s->ptimer_tick);
+    if (count) {
+        /* timer is still counting, called not from event */
+        counted = s->count - ptimer_get_count(s->ptimer_tick);
+        cur_progress = s->progress + counted;
+    } else {
+        /* timer expired earlier */
+        cur_progress = s->progress;
+    }
+
+    remain = s->distance - cur_progress;
+
+    if (!s->int_run) {
+        /* INT is stopped. */
+        icnto = s->last_icnto;
+    } else {
+        /* Both are counting */
+        icnto = remain / s->tcntb;
+    }
+
+    return icnto;
+}
+
+/*
+ * Start local tick cnt timer.
+ */
+static void exynos4210_ltick_cnt_start(struct tick_timer *s)
+{
+    if (!s->cnt_run) {
+
+        exynos4210_ltick_recalc_count(s);
+        ptimer_set_count(s->ptimer_tick, s->count);
+        ptimer_run(s->ptimer_tick, 1);
+
+        s->cnt_run = 1;
+    }
+}
+
+/*
+ * Stop local tick cnt timer.
+ */
+static void exynos4210_ltick_cnt_stop(struct tick_timer *s)
+{
+    if (s->cnt_run) {
+
+        s->last_tcnto = exynos4210_ltick_cnt_get_cnto(s);
+
+        if (s->int_run) {
+            exynos4210_ltick_int_stop(s);
+        }
+
+        ptimer_stop(s->ptimer_tick);
+
+        s->cnt_run = 0;
+    }
+}
+
+/*
+ * Get counter for CNT timer
+ */
+static uint32_t exynos4210_ltick_cnt_get_cnto(struct tick_timer *s)
+{
+    uint32_t tcnto;
+    uint32_t icnto;
+    uint64_t remain;
+    uint64_t counted;
+    uint64_t count;
+    uint64_t cur_progress;
+
+    count = ptimer_get_count(s->ptimer_tick);
+    if (count) {
+        /* timer is still counting, called not from event */
+        counted = s->count - ptimer_get_count(s->ptimer_tick);
+        cur_progress = s->progress + counted;
+    } else {
+        /* timer expired earlier */
+        cur_progress = s->progress;
+    }
+
+    remain = s->distance - cur_progress;
+
+    if (!s->cnt_run) {
+        /* Both are stopped. */
+        tcnto = s->last_tcnto;
+    } else if (!s->int_run) {
+        /* INT counter is stopped, progress is by CNT timer */
+        tcnto = remain % s->tcntb;
+    } else {
+        /* Both are counting */
+        icnto = remain / s->tcntb;
+        if (icnto) {
+            tcnto = remain % (icnto * s->tcntb);
+        } else {
+            tcnto = remain % s->tcntb;
+        }
+    }
+
+    return tcnto;
+}
+
+/*
+ * Set new values of counters for CNT and INT timers
+ */
+static void exynos4210_ltick_set_cntb(struct tick_timer *s, uint32_t new_cnt,
+        uint32_t new_int)
+{
+    uint32_t cnt_stopped = 0;
+    uint32_t int_stopped = 0;
+
+    if (s->cnt_run) {
+        exynos4210_ltick_cnt_stop(s);
+        cnt_stopped = 1;
+    }
+
+    if (s->int_run) {
+        exynos4210_ltick_int_stop(s);
+        int_stopped = 1;
+    }
+
+    s->tcntb = new_cnt + 1;
+    s->icntb = new_int + 1;
+
+    if (cnt_stopped) {
+        exynos4210_ltick_cnt_start(s);
+    }
+    if (int_stopped) {
+        exynos4210_ltick_int_start(s);
+    }
+
+}
+
+/*
+ * Calculate new counter value for tick timer
+ */
+static void exynos4210_ltick_recalc_count(struct tick_timer *s)
+{
+    uint64_t to_count;
+
+    if ((s->cnt_run && s->last_tcnto) || (s->int_run && s->last_icnto)) {
+        /*
+         * one or both timers run and not counted to the end;
+         * distance is not passed, recalculate with last_tcnto * last_icnto
+         */
+
+        if (s->last_tcnto) {
+            to_count = s->last_tcnto * s->last_icnto;
+        } else {
+            to_count = s->last_icnto;
+        }
+    } else {
+        /* distance is passed, recalculate with tcnto * icnto */
+        if (s->icntb) {
+            s->distance = s->tcntb * s->icntb;
+        } else {
+            s->distance = s->tcntb;
+        }
+
+        to_count = s->distance;
+        s->progress = 0;
+    }
+
+    if (to_count > MCT_LT_COUNTER_STEP) {
+        /* count by step */
+        s->count = MCT_LT_COUNTER_STEP;
+    } else {
+        s->count = to_count;
+    }
+}
+
+/*
+ * Initialize tick_timer
+ */
+static void exynos4210_ltick_timer_init(struct tick_timer *s)
+{
+    exynos4210_ltick_int_stop(s);
+    exynos4210_ltick_cnt_stop(s);
+
+    s->count = 0;
+    s->distance = 0;
+    s->progress = 0;
+    s->icntb = 0;
+    s->tcntb = 0;
+}
+
+/*
+ * tick_timer event.
+ * Raises when abstract tick_timer expires.
+ */
+static void exynos4210_ltick_timer_event(struct tick_timer *s)
+{
+    s->progress += s->count;
+}
+
+/*
+ * Local timer tick counter handler.
+ * Don't use reloaded timers. If timer counter = zero
+ * then handler called but after handler finished no
+ * timer reload occurs.
+ */
+static void exynos4210_ltick_event(void *opaque)
+{
+    exynos4210_mct_lt * s = (exynos4210_mct_lt *)opaque;
+    uint32_t tcnto;
+    uint32_t icnto;
+#ifdef DEBUG_MCT
+    static uint64_t time1[2] = {0};
+    static uint64_t time2[2] = {0};
+#endif
+
+    /* Call tick_timer event handler, it will update it's tcntb and icntb */
+    exynos4210_ltick_timer_event(&s->tick_timer);
+
+    /* get tick_timer cnt */
+    tcnto = exynos4210_ltick_cnt_get_cnto(&s->tick_timer);
+
+    /* get tick_timer int */
+    icnto = exynos4210_ltick_int_get_cnto(&s->tick_timer);
+
+    /* raise IRQ if needed */
+    if (!icnto && s->reg.tcon & L_TCON_INT_START) {
+        /* INT counter enabled and expired */
+
+        s->reg.int_cstat |= L_INT_CSTAT_INTCNT;
+
+        /* raise interrupt if enabled */
+        if (s->reg.int_enb & L_INT_INTENB_ICNTEIE) {
+#ifdef DEBUG_MCT
+            time2[s->id] = qemu_get_clock_ns(vm_clock);
+            DPRINTF("local timer[%d] IRQ: %llx\n", s->id,
+                    time2[s->id] - time1[s->id]);
+            time1[s->id] = time2[s->id];
+#endif
+            qemu_irq_raise(s->irq);
+        }
+
+        /* reload ICNTB */
+        if (s->reg.tcon & L_TCON_INTERVAL_MODE) {
+            exynos4210_ltick_set_cntb(&s->tick_timer,
+                    s->reg.cnt[L_REG_CNT_TCNTB],
+                    s->reg.cnt[L_REG_CNT_ICNTB]);
+        }
+    } else {
+        /* reload TCNTB */
+        if (!tcnto) {
+            exynos4210_ltick_set_cntb(&s->tick_timer,
+                    s->reg.cnt[L_REG_CNT_TCNTB],
+                    icnto);
+        }
+    }
+
+    /* start tick_timer cnt */
+    exynos4210_ltick_cnt_start(&s->tick_timer);
+
+    /* start tick_timer int */
+    exynos4210_ltick_int_start(&s->tick_timer);
+}
+
+/* update timer frequency */
+static void exynos4210_mct_update_freq(Exynos4210MCTState *s)
+{
+    uint32_t freq = s->freq;
+    s->freq = exynos4210_cmu_get_rate(XXTI) /
+            ((MCT_CFG_GET_PRESCALER(s->reg_mct_cfg)+1) *
+                    MCT_CFG_GET_DIVIDER(s->reg_mct_cfg));
+
+    if (freq != s->freq) {
+        DPRINTF("freq=%dHz\n", s->freq);
+
+        /* global timer */
+        ptimer_set_freq(s->g_timer.ptimer_frc, s->freq);
+
+        /* local timer */
+        ptimer_set_freq(s->l_timer[0].tick_timer.ptimer_tick, s->freq);
+        ptimer_set_freq(s->l_timer[0].ptimer_frc, s->freq);
+        ptimer_set_freq(s->l_timer[1].tick_timer.ptimer_tick, s->freq);
+        ptimer_set_freq(s->l_timer[1].ptimer_frc, s->freq);
+    }
+}
+
+/* set defaul_timer values for all fields */
+static void exynos4210_mct_reset(void *opaque)
+{
+    Exynos4210MCTState *s = (Exynos4210MCTState *) opaque;
+    uint32_t i;
+
+    s->reg_mct_cfg = 0;
+
+    /* global timer */
+    memset(&s->g_timer.reg, 0, sizeof(s->g_timer.reg));
+    exynos4210_gfrc_stop(&s->g_timer);
+
+    /* local timer */
+    memset(s->l_timer[0].reg.cnt, 0, sizeof(s->l_timer[0].reg.cnt));
+    memset(s->l_timer[1].reg.cnt, 0, sizeof(s->l_timer[1].reg.cnt));
+    for (i = 0; i < 2; i++) {
+        s->l_timer[i].reg.int_cstat = 0;
+        s->l_timer[i].reg.int_enb = 0;
+        s->l_timer[i].reg.tcon = 0;
+        s->l_timer[i].reg.wstat = 0;
+        s->l_timer[i].tick_timer.count = 0;
+        s->l_timer[i].tick_timer.distance = 0;
+        s->l_timer[i].tick_timer.progress = 0;
+        ptimer_stop(s->l_timer[i].ptimer_frc);
+
+        exynos4210_ltick_timer_init(&s->l_timer[i].tick_timer);
+    }
+
+    exynos4210_mct_update_freq(s);
+
+}
+
+/* Multi Core Timer read */
+static uint64_t exynos4210_mct_read(void *opaque, target_phys_addr_t offset,
+        unsigned size)
+{
+    Exynos4210MCTState *s = (Exynos4210MCTState *)opaque;
+    int index;
+    int shift;
+    uint64_t count;
+    uint32_t value;
+    int lt_i;
+
+    switch (offset) {
+
+    case MCT_CFG:
+        value = s->reg_mct_cfg;
+        break;
+
+    case G_CNT_L: case G_CNT_U:
+        shift = 8 * (offset & 0x4);
+        count = exynos4210_gfrc_get_count(&s->g_timer);
+        value = 0xFFFFFFFF & (count >> shift);
+        DPRINTF("read FRC=0x%llx\n", count);
+        break;
+
+    case G_CNT_WSTAT:
+        value = s->g_timer.reg.cnt_wstat;
+        break;
+
+    case G_COMP_L(0): case G_COMP_L(1): case G_COMP_L(2): case G_COMP_L(3):
+    case G_COMP_U(0): case G_COMP_U(1): case G_COMP_U(2): case G_COMP_U(3):
+    index = GET_G_COMP_IDX(offset);
+    shift = 8 * (offset & 0x4);
+    value = 0xFFFFFFFF & (s->g_timer.reg.comp[index] >> shift);
+    break;
+
+    case G_TCON:
+        value = s->g_timer.reg.tcon;
+        break;
+
+    case G_INT_CSTAT:
+        value = s->g_timer.reg.int_cstat;
+        break;
+
+    case G_INT_ENB:
+        value = s->g_timer.reg.int_enb;
+        break;
+        break;
+    case G_WSTAT:
+        value = s->g_timer.reg.wstat;
+        break;
+
+    case G_COMP0_ADD_INCR: case G_COMP1_ADD_INCR:
+    case G_COMP2_ADD_INCR: case G_COMP3_ADD_INCR:
+        value = s->g_timer.reg.comp_add_incr[GET_G_COMP_ADD_INCR_IDX(offset)];
+        break;
+
+        /* Local timers */
+    case L0_TCNTB: case L0_ICNTB: case L0_FRCNTB:
+    case L1_TCNTB: case L1_ICNTB: case L1_FRCNTB:
+        lt_i = GET_L_TIMER_IDX(offset);
+        index = GET_L_TIMER_CNT_REG_IDX(offset, lt_i);
+        value = s->l_timer[lt_i].reg.cnt[index];
+        break;
+
+    case L0_TCNTO: case L1_TCNTO:
+        lt_i = GET_L_TIMER_IDX(offset);
+
+        value = exynos4210_ltick_cnt_get_cnto(&s->l_timer[lt_i].tick_timer);
+        DPRINTF("local timer[%d] read TCNTO %x\n", lt_i, value);
+        break;
+
+    case L0_ICNTO: case L1_ICNTO:
+        lt_i = GET_L_TIMER_IDX(offset);
+
+        value = exynos4210_ltick_int_get_cnto(&s->l_timer[lt_i].tick_timer);
+        DPRINTF("local timer[%d] read ICNTO %x\n", lt_i, value);
+        break;
+
+    case L0_FRCNTO: case L1_FRCNTO:
+        lt_i = GET_L_TIMER_IDX(offset);
+
+        value = exynos4210_lfrc_get_count(&s->l_timer[lt_i]);
+
+        break;
+
+    case L0_TCON: case L1_TCON:
+        lt_i = ((offset&0xF00) - L0_TCNTB)/0x100;
+        value = s->l_timer[lt_i].reg.tcon;
+        break;
+
+    case L0_INT_CSTAT: case L1_INT_CSTAT:
+        lt_i = ((offset&0xF00) - L0_TCNTB)/0x100;
+        value = s->l_timer[lt_i].reg.int_cstat;
+        break;
+
+    case L0_INT_ENB: case L1_INT_ENB:
+        lt_i = ((offset&0xF00) - L0_TCNTB)/0x100;
+        value = s->l_timer[lt_i].reg.int_enb;
+        break;
+
+    case L0_WSTAT: case L1_WSTAT:
+        lt_i = ((offset&0xF00) - L0_TCNTB)/0x100;
+        value = s->l_timer[lt_i].reg.wstat;
+        break;
+
+    default:
+        hw_error("exynos4210.mct: bad read offset "
+                TARGET_FMT_plx "\n", offset);
+        break;
+    }
+    return value;
+}
+
+/* MCT write */
+static void exynos4210_mct_write(void *opaque, target_phys_addr_t offset,
+        uint64_t value, unsigned size)
+{
+    Exynos4210MCTState *s = (Exynos4210MCTState *)opaque;
+    int index;  /* index in buffer which represents register set */
+    int shift;
+    int lt_i;
+    uint64_t new_frc;
+    uint32_t i;
+    uint32_t old_val;
+#ifdef DEBUG_MCT
+    static uint32_t icntb_max[2] = {0};
+    static uint32_t icntb_min[2] = {0xFFFFFFFF, 0xFFFFFFFF};
+    static uint32_t tcntb_max[2] = {0};
+    static uint32_t tcntb_min[2] = {0xFFFFFFFF, 0xFFFFFFFF};
+#endif
+
+    new_frc = s->g_timer.reg.cnt;
+
+    switch (offset) {
+
+    case MCT_CFG:
+        s->reg_mct_cfg = value;
+        exynos4210_mct_update_freq(s);
+        break;
+
+    case G_CNT_L:
+    case G_CNT_U:
+        if (offset == G_CNT_L) {
+
+            DPRINTF("global timer write to reg.cntl %llx\n", value);
+
+            new_frc = (s->g_timer.reg.cnt & 0xFFFFFFFF00000000) +
+                    value;
+            s->g_timer.reg.cnt_wstat |= G_CNT_WSTAT_L;
+        }
+        if (offset == G_CNT_U) {
+
+            DPRINTF("global timer write to reg.cntu %llx\n", value);
+
+            new_frc = (s->g_timer.reg.cnt & 0xFFFFFFFF) +
+                    ((uint64_t)value<<32);
+            s->g_timer.reg.cnt_wstat |= G_CNT_WSTAT_U;
+        }
+
+        s->g_timer.reg.cnt = new_frc;
+        exynos4210_gfrc_restart(s);
+        break;
+
+    case G_CNT_WSTAT:
+        s->g_timer.reg.cnt_wstat &= ~(value);
+        break;
+
+    case G_COMP_L(0): case G_COMP_L(1): case G_COMP_L(2): case G_COMP_L(3):
+    case G_COMP_U(0): case G_COMP_U(1): case G_COMP_U(2): case G_COMP_U(3):
+    index = GET_G_COMP_IDX(offset);
+    shift = 8*(offset&0x4);
+    s->g_timer.reg.comp[index] =
+            (s->g_timer.reg.comp[index] & (0xFFFFFFFF00000000>>shift)) +
+            (value<<shift);
+
+    DPRINTF("comparator %d write 0x%llx val << %d\n", index, value, shift);
+
+    if (offset&0x4) {
+        s->g_timer.reg.wstat |= G_WSTAT_COMP_U(index);
+    } else {
+        s->g_timer.reg.wstat |= G_WSTAT_COMP_L(index);
+    }
+
+    exynos4210_gfrc_restart(s);
+    break;
+
+    case G_TCON:
+        old_val = s->g_timer.reg.tcon;
+        s->g_timer.reg.tcon = value;
+        s->g_timer.reg.wstat |= G_WSTAT_TCON_WRITE;
+
+        DPRINTF("global timer write to reg.g_tcon %llx\n", value);
+
+        /* Start FRC if transition from disabled to enabled */
+        if ((value & G_TCON_TIMER_ENABLE) > (old_val &
+                G_TCON_TIMER_ENABLE)) {
+            exynos4210_gfrc_start(&s->g_timer);
+        }
+        if ((value & G_TCON_TIMER_ENABLE) < (old_val &
+                G_TCON_TIMER_ENABLE)) {
+            exynos4210_gfrc_stop(&s->g_timer);
+        }
+
+        /* Start CMP if transition from disabled to enabled */
+        for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+            if ((value & G_TCON_COMP_ENABLE(i)) != (old_val &
+                    G_TCON_COMP_ENABLE(i))) {
+                exynos4210_gfrc_restart(s);
+            }
+        }
+        break;
+
+    case G_INT_CSTAT:
+        s->g_timer.reg.int_cstat &= ~(value);
+        for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+            if (value & G_INT_CSTAT_COMP(i)) {
+                exynos4210_gcomp_lower_irq(&s->g_timer, i);
+            }
+        }
+        break;
+
+    case G_INT_ENB:
+
+        /* Raise IRQ if transition from disabled to enabled and CSTAT pending */
+        for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+            if ((value & G_INT_ENABLE(i)) > (s->g_timer.reg.tcon &
+                    G_INT_ENABLE(i))) {
+                if (s->g_timer.reg.int_cstat & G_INT_CSTAT_COMP(i)) {
+                    exynos4210_gcomp_raise_irq(&s->g_timer, i);
+                }
+            }
+
+            if ((value & G_INT_ENABLE(i)) < (s->g_timer.reg.tcon &
+                    G_INT_ENABLE(i))) {
+                exynos4210_gcomp_lower_irq(&s->g_timer, i);
+            }
+        }
+
+        DPRINTF("global timer INT enable %llx\n", value);
+        s->g_timer.reg.int_enb = value;
+        break;
+
+    case G_WSTAT:
+        s->g_timer.reg.wstat &= ~(value);
+        break;
+
+    case G_COMP0_ADD_INCR: case G_COMP1_ADD_INCR:
+    case G_COMP2_ADD_INCR: case G_COMP3_ADD_INCR:
+        index = GET_G_COMP_ADD_INCR_IDX(offset);
+        s->g_timer.reg.comp_add_incr[index] = value;
+        s->g_timer.reg.wstat |= G_WSTAT_COMP_ADDINCR(index);
+        break;
+
+        /* Local timers */
+    case L0_TCON: case L1_TCON:
+        lt_i = GET_L_TIMER_IDX(offset);
+        old_val = s->l_timer[lt_i].reg.tcon;
+
+        s->l_timer[lt_i].reg.wstat |= L_WSTAT_TCON_WRITE;
+        s->l_timer[lt_i].reg.tcon = value;
+
+        /* Stop local CNT */
+        if ((value & L_TCON_TICK_START) <
+                (old_val & L_TCON_TICK_START)) {
+            DPRINTF("local timer[%d] stop cnt\n", lt_i);
+            exynos4210_ltick_cnt_stop(&s->l_timer[lt_i].tick_timer);
+        }
+
+        /* Stop local INT */
+        if ((value & L_TCON_INT_START) <
+                (old_val & L_TCON_INT_START)) {
+            DPRINTF("local timer[%d] stop int\n", lt_i);
+            exynos4210_ltick_int_stop(&s->l_timer[lt_i].tick_timer);
+        }
+
+        /* Start local CNT */
+        if ((value & L_TCON_TICK_START) >
+        (old_val & L_TCON_TICK_START)) {
+            DPRINTF("local timer[%d] start cnt\n", lt_i);
+            exynos4210_ltick_cnt_start(&s->l_timer[lt_i].tick_timer);
+        }
+
+        /* Start local INT */
+        if ((value & L_TCON_INT_START) >
+        (old_val & L_TCON_INT_START)) {
+            DPRINTF("local timer[%d] start int\n", lt_i);
+            exynos4210_ltick_int_start(&s->l_timer[lt_i].tick_timer);
+        }
+
+        /* Start or Stop local FRC if TCON changed */
+        if ((value & L_TCON_FRC_START) >
+        (s->l_timer[lt_i].reg.tcon & L_TCON_FRC_START)) {
+            DPRINTF("local timer[%d] start frc\n", lt_i);
+            exynos4210_lfrc_start(&s->l_timer[lt_i]);
+        }
+        if ((value & L_TCON_FRC_START) <
+                (s->l_timer[lt_i].reg.tcon & L_TCON_FRC_START)) {
+            DPRINTF("local timer[%d] stop frc\n", lt_i);
+            exynos4210_lfrc_stop(&s->l_timer[lt_i]);
+        }
+        break;
+
+    case L0_TCNTB: case L1_TCNTB:
+
+        lt_i = GET_L_TIMER_IDX(offset);
+        index = GET_L_TIMER_CNT_REG_IDX(offset, lt_i);
+
+        /*
+         * TCNTB is updated to internal register only after CNT expired.
+         * Due to this we should reload timer to nearest moment when CNT is
+         * expired and then in event handler update tcntb to new TCNTB value.
+         */
+        exynos4210_ltick_set_cntb(&s->l_timer[lt_i].tick_timer, value,
+                s->l_timer[lt_i].tick_timer.icntb);
+
+        s->l_timer[lt_i].reg.wstat |= L_WSTAT_TCNTB_WRITE;
+        s->l_timer[lt_i].reg.cnt[L_REG_CNT_TCNTB] = value;
+
+#ifdef DEBUG_MCT
+        if (tcntb_min[lt_i] > value) {
+            tcntb_min[lt_i] = value;
+        }
+        if (tcntb_max[lt_i] < value) {
+            tcntb_max[lt_i] = value;
+        }
+        DPRINTF("local timer[%d] TCNTB write %llx; max=%x, min=%x\n",
+                lt_i, value, tcntb_max[lt_i], tcntb_min[lt_i]);
+#endif
+        break;
+
+    case L0_ICNTB: case L1_ICNTB:
+
+        lt_i = GET_L_TIMER_IDX(offset);
+        index = GET_L_TIMER_CNT_REG_IDX(offset, lt_i);
+
+        s->l_timer[lt_i].reg.wstat |= L_WSTAT_ICNTB_WRITE;
+        s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB] = value &
+                ~L_ICNTB_MANUAL_UPDATE;
+
+        /*
+         * FIXME: this stub should be removed
+         * We need to avoid too small values for TCNTB*ICNTB. If not, IRQ event
+         * could raise too fast disallowing QEMU to execute target code.
+         * One way is to use -icount option, but in that case target 1 second
+         * could turn in host 100 seconds which leads to enormous sleep(n)
+         * awaiting. So let just enlarge small values of TCNTB*ICNTB to avoid
+         * too fast IRQs.
+         */
+        if (s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB] *
+            s->l_timer[lt_i].reg.cnt[L_REG_CNT_TCNTB] < MCT_LT_CNT_LOW_LIMIT) {
+            if (!s->l_timer[lt_i].reg.cnt[L_REG_CNT_TCNTB]) {
+                s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB] =
+                        MCT_LT_CNT_LOW_LIMIT;
+            } else {
+                s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB] =
+                        MCT_LT_CNT_LOW_LIMIT /
+                        s->l_timer[lt_i].reg.cnt[L_REG_CNT_TCNTB];
+            }
+        }
+
+
+        if (value & L_ICNTB_MANUAL_UPDATE) {
+            exynos4210_ltick_set_cntb(&s->l_timer[lt_i].tick_timer,
+                    s->l_timer[lt_i].tick_timer.tcntb,
+                    s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB]);
+        }
+
+#ifdef DEBUG_MCT
+        if (icntb_min[lt_i] > value) {
+            icntb_min[lt_i] = value;
+        }
+        if (icntb_max[lt_i] < value) {
+            icntb_max[lt_i] = value;
+        }
+DPRINTF("local timer[%d] ICNTB write %llx; max=%x, min=%x\n\n",
+        lt_i, value, icntb_max[lt_i], icntb_min[lt_i]);
+#endif
+break;
+
+    case L0_FRCNTB: case L1_FRCNTB:
+
+        lt_i = GET_L_TIMER_IDX(offset);
+        index = GET_L_TIMER_CNT_REG_IDX(offset, lt_i);
+
+        DPRINTF("local timer[%d] FRCNTB write %llx\n", lt_i, value);
+
+        s->l_timer[lt_i].reg.wstat |= L_WSTAT_FRCCNTB_WRITE;
+        s->l_timer[lt_i].reg.cnt[L_REG_CNT_FRCCNTB] = value;
+
+        break;
+
+    case L0_TCNTO: case L1_TCNTO:
+    case L0_ICNTO: case L1_ICNTO:
+    case L0_FRCNTO: case L1_FRCNTO:
+        fprintf(stderr, "\n[exynos4210.mct: write to RO register "
+                TARGET_FMT_plx "]\n\n", offset);
+        break;
+
+    case L0_INT_CSTAT: case L1_INT_CSTAT:
+        lt_i = GET_L_TIMER_IDX(offset);
+
+        DPRINTF("local timer[%d] CSTAT write %llx\n", lt_i, value);
+
+        s->l_timer[lt_i].reg.int_cstat &= ~value;
+        if (!s->l_timer[lt_i].reg.int_cstat) {
+            qemu_irq_lower(s->l_timer[lt_i].irq);
+        }
+        break;
+
+    case L0_INT_ENB: case L1_INT_ENB:
+        lt_i = GET_L_TIMER_IDX(offset);
+        old_val = s->l_timer[lt_i].reg.int_enb;
+
+        /* Raise Local timer IRQ if cstat is pending */
+        if ((value & L_INT_INTENB_ICNTEIE) > (old_val & L_INT_INTENB_ICNTEIE)) {
+            if (s->l_timer[lt_i].reg.int_cstat & L_INT_CSTAT_INTCNT) {
+                qemu_irq_raise(s->l_timer[lt_i].irq);
+            }
+        }
+
+        s->l_timer[lt_i].reg.int_enb = value;
+
+        break;
+
+    case L0_WSTAT: case L1_WSTAT:
+        lt_i = GET_L_TIMER_IDX(offset);
+
+        s->l_timer[lt_i].reg.wstat &= ~value;
+        break;
+
+    default:
+        hw_error("exynos4210.mct: bad write offset "
+                TARGET_FMT_plx "\n", offset);
+        break;
+    }
+}
+
+static const MemoryRegionOps exynos4210_mct_ops = {
+        .read = exynos4210_mct_read,
+        .write = exynos4210_mct_write,
+        .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/* MCT init */
+static int exynos4210_mct_init(SysBusDevice *dev)
+{
+    int i;
+    Exynos4210MCTState *s = FROM_SYSBUS(Exynos4210MCTState, dev);
+    QEMUBH * bh[2];
+
+    /* Global timer */
+    bh[0] = qemu_bh_new(exynos4210_gfrc_event, s);
+    s->g_timer.ptimer_frc = ptimer_init(bh[0]);
+    memset(&s->g_timer.reg, 0, sizeof(struct gregs));
+
+    /* Local timers */
+    for (i = 0; i < 2; i++) {
+        bh[0] = qemu_bh_new(exynos4210_ltick_event, &s->l_timer[i]);
+        bh[1] = qemu_bh_new(exynos4210_lfrc_event, &s->l_timer[i]);
+        s->l_timer[i].tick_timer.ptimer_tick = ptimer_init(bh[0]);
+        s->l_timer[i].ptimer_frc = ptimer_init(bh[1]);
+        s->l_timer[i].id = i;
+    }
+
+    /* IRQs */
+    for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+        sysbus_init_irq(dev, &s->g_timer.irq[i]);
+    }
+    for (i = 0; i < 2; i++) {
+        sysbus_init_irq(dev, &s->l_timer[i].irq);
+    }
+
+    memory_region_init_io(&s->iomem, &exynos4210_mct_ops, s, "exynos4210-mct",
+            MCT_SFR_SIZE);
+    sysbus_init_mmio(dev, &s->iomem);
+
+    exynos4210_mct_reset(s);
+
+    qemu_register_reset(exynos4210_mct_reset, s);
+    vmstate_register(NULL, -1, &VMState_Exynos4210MCTState, s);
+    return 0;
+}
+
+static void exynos4210_mct_register_devices(void)
+{
+    sysbus_register_dev("exynos4210.mct", sizeof(Exynos4210MCTState),
+            exynos4210_mct_init);
+}
+
+device_init(exynos4210_mct_register_devices)
-- 
1.7.4.1

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Qemu-devel] [PATCH 10/15] hw/exynos4210.c: Boot secondary CPU.
  2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
                   ` (8 preceding siblings ...)
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 09/15] ARM: exynos4210: MCT support Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 11/15] hw/lan9118: Add basic 16-bit mode support Evgeny Voevodin
                   ` (4 subsequent siblings)
  14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin


Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 hw/exynos4210.c |   28 +++++++++++++++++++++++++++-
 1 files changed, 27 insertions(+), 1 deletions(-)

diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index f172346..8d8a12c 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -93,6 +93,12 @@
 #define EXYNOS4210_EXT_COMBINER_BASE_ADDR   0x10440000
 #define EXYNOS4210_INT_COMBINER_BASE_ADDR   0x10448000
 
+/* Secondary CPU polling address to get loader start from */
+#define EXYNOS4210_SECOND_CPU_BOOTREG       0x10020814
+
+/* Secondary CPU startup code is in IROM memory */
+#define EXYNOS4210_SMP_BOOT_ADDR            EXYNOS4210_IROM_BASE_ADDR
+
 static struct arm_boot_info exynos4210_binfo = {
         .loader_start     = EXYNOS4210_BASE_BOOT_ADDR,
 };
@@ -208,6 +214,8 @@ static void exynos4210_init(ram_addr_t ram_size,
     MemoryRegion *irom_alias_mem = g_new(MemoryRegion, 1);
     MemoryRegion *dram0_mem = g_new(MemoryRegion, 1);
     MemoryRegion *dram1_mem = NULL;
+    MemoryRegion *hack_mem = g_new(MemoryRegion, 1);
+    MemoryRegion *bootreg_mem = g_new(MemoryRegion, 1);
     Exynos4210Irq *irqs;
     qemu_irq *irq_table;
     qemu_irq *irqp;
@@ -219,10 +227,12 @@ static void exynos4210_init(ram_addr_t ram_size,
 
     switch (board_type) {
     case BOARD_EXYNOS4210_NURI:
-        exynos4210_binfo.board_id      = MACH_NURI_ID;
+        exynos4210_binfo.board_id = MACH_NURI_ID;
+        exynos4210_binfo.smp_bootreg_addr = EXYNOS4210_SECOND_CPU_BOOTREG;
     break;
     case BOARD_EXYNOS4210_SMDKC210:
         exynos4210_binfo.board_id = MACH_SMDKC210_ID;
+        exynos4210_binfo.smp_bootreg_addr = EXYNOS4210_SECOND_CPU_BOOTREG;
     break;
     default:
     break;
@@ -353,6 +363,21 @@ static void exynos4210_init(ram_addr_t ram_size,
     memory_region_add_subregion(system_mem, EXYNOS4210_DRAM0_BASE_ADDR,
             dram0_mem);
 
+    /*
+     * Secondary CPU startup code will be placed here.
+     */
+    memory_region_init_ram(hack_mem, NULL, "exynos4210.hack", 0x1000);
+    memory_region_add_subregion(system_mem, EXYNOS4210_SMP_BOOT_ADDR,
+            hack_mem);
+
+   /*
+    * Hack: Map SECOND_CPU_BOOTREG, because it is in PMU USER5 register.
+    */
+   memory_region_init_ram(bootreg_mem, NULL, "exynos4210.bootreg", 0x4);
+   memory_region_add_subregion(system_mem, EXYNOS4210_SECOND_CPU_BOOTREG,
+           bootreg_mem);
+
+
     /* CMU */
     sysbus_create_simple("exynos4210.cmu", EXYNOS4210_CMU_BASE_ADDR, NULL);
 
@@ -427,6 +452,7 @@ static void exynos4210_init(ram_addr_t ram_size,
     exynos4210_binfo.kernel_filename = kernel_filename;
     exynos4210_binfo.initrd_filename = initrd_filename;
     exynos4210_binfo.kernel_cmdline = kernel_cmdline;
+    exynos4210_binfo.smp_priv_base = EXYNOS4210_SMP_PRIVATE_BASE_ADDR;
 
     arm_load_kernel(first_cpu, &exynos4210_binfo);
 }
-- 
1.7.4.1

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Qemu-devel] [PATCH 11/15] hw/lan9118: Add basic 16-bit mode support.
  2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
                   ` (9 preceding siblings ...)
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 10/15] hw/exynos4210.c: Boot secondary CPU Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 12/15] hw/exynos4210.c: Add LAN support for SMDKC210 Evgeny Voevodin
                   ` (3 subsequent siblings)
  14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin


Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 hw/devices.h |    2 +-
 hw/lan9118.c |  115 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 2 files changed, 109 insertions(+), 8 deletions(-)

diff --git a/hw/devices.h b/hw/devices.h
index 1a55c1e..24cae4c 100644
--- a/hw/devices.h
+++ b/hw/devices.h
@@ -10,7 +10,7 @@ struct MemoryRegion;
 void smc91c111_init(NICInfo *, uint32_t, qemu_irq);
 
 /* lan9118.c */
-void lan9118_init(NICInfo *, uint32_t, qemu_irq);
+DeviceState *lan9118_init(NICInfo *, uint32_t, qemu_irq);
 
 /* tsc210x.c */
 uWireSlave *tsc2102_init(qemu_irq pint);
diff --git a/hw/lan9118.c b/hw/lan9118.c
index ee8b2ea..e44e5f8 100644
--- a/hw/lan9118.c
+++ b/hw/lan9118.c
@@ -212,6 +212,17 @@ typedef struct {
     int rxp_offset;
     int rxp_size;
     int rxp_pad;
+
+    uint32_t write_word_prev_offset;
+    uint32_t write_word_n;
+    uint16_t write_word_l;
+    uint16_t write_word_h;
+    uint32_t read_word_prev_offset;
+    uint32_t read_word_n;
+    uint32_t read_long;
+
+    uint32_t mode_16bit;
+
 } lan9118_state;
 
 static void lan9118_update(lan9118_state *s)
@@ -345,6 +356,9 @@ static void lan9118_reset(DeviceState *d)
     s->mac_mii_data = 0;
     s->mac_flow = 0;
 
+    s->read_word_n = 0;
+    s->write_word_n = 0;
+
     phy_reset(s);
 
     s->eeprom_writable = 0;
@@ -896,11 +910,11 @@ static void lan9118_tick(void *opaque)
 }
 
 static void lan9118_writel(void *opaque, target_phys_addr_t offset,
-                           uint64_t val, unsigned size)
+                           uint32_t val)
 {
     lan9118_state *s = (lan9118_state *)opaque;
     offset &= 0xff;
-    
+
     //DPRINTF("Write reg 0x%02x = 0x%08x\n", (int)offset, val);
     if (offset >= 0x20 && offset < 0x40) {
         /* TX FIFO */
@@ -1029,8 +1043,47 @@ static void lan9118_writel(void *opaque, target_phys_addr_t offset,
     lan9118_update(s);
 }
 
-static uint64_t lan9118_readl(void *opaque, target_phys_addr_t offset,
-                              unsigned size)
+static void lan9118_writew(void *opaque, target_phys_addr_t offset,
+                           uint32_t val)
+{
+    lan9118_state *s = (lan9118_state *)opaque;
+    offset &= 0xff;
+
+    if (s->write_word_prev_offset != (offset & ~0x3)) {
+        /* New offset, reset word counter */
+        s->write_word_n = 0;
+        s->write_word_prev_offset = offset & ~0x3;
+    }
+
+    if (offset & 0x2) {
+        s->write_word_h = val;
+    } else {
+        s->write_word_l = val;
+    }
+
+    //DPRINTF("Writew reg 0x%02x = 0x%08x\n", (int)offset, val);
+    s->write_word_n++;
+    if (s->write_word_n == 2) {
+        s->write_word_n = 0;
+        lan9118_writel(s, offset & ~3, s->write_word_l +
+                (s->write_word_h << 16));
+    }
+}
+
+static void lan9118_write(void *opaque, target_phys_addr_t offset,
+                          uint64_t val, unsigned size)
+{
+    switch (size) {
+    case 2:
+        return lan9118_writew(opaque, offset, (uint32_t)val);
+    case 4:
+        return lan9118_writel(opaque, offset, (uint32_t)val);
+    }
+
+    hw_error("lan9118_write: Bad size 0x%x\n", size);
+}
+
+static uint32_t lan9118_readl(void *opaque, target_phys_addr_t offset)
 {
     lan9118_state *s = (lan9118_state *)opaque;
 
@@ -1065,6 +1118,9 @@ static uint64_t lan9118_readl(void *opaque, target_phys_addr_t offset,
     case CSR_TX_CFG:
         return s->tx_cfg;
     case CSR_HW_CFG:
+        if (s->mode_16bit) {
+            return s->hw_cfg & ~0x4;
+        }
         return s->hw_cfg | 0x4;
     case CSR_RX_DP_CTRL:
         return 0;
@@ -1103,9 +1159,51 @@ static uint64_t lan9118_readl(void *opaque, target_phys_addr_t offset,
     return 0;
 }
 
+static uint32_t lan9118_readw(void *opaque, target_phys_addr_t offset)
+{
+    lan9118_state *s = (lan9118_state *)opaque;
+    uint32_t val;
+
+    if (s->read_word_prev_offset != (offset & ~0x3)) {
+        /* New offset, reset word counter */
+        s->read_word_n = 0;
+        s->read_word_prev_offset = offset & ~0x3;
+    }
+
+    s->read_word_n++;
+    if (s->read_word_n == 1) {
+        s->read_long = lan9118_readl(s, offset & ~3);
+    } else {
+        s->read_word_n = 0;
+    }
+
+    if (offset & 2) {
+        val = s->read_long >> 16;
+    } else {
+        val = s->read_long & 0xFFFF;
+    }
+
+    //DPRINTF("Readw reg 0x%02x, val 0x%x\n", (int)offset, val);
+    return val;
+}
+
+static uint64_t lan9118_read(void *opaque, target_phys_addr_t offset,
+                             unsigned size)
+{
+    switch (size) {
+    case 2:
+        return lan9118_readw(opaque, offset);
+    case 4:
+        return lan9118_readl(opaque, offset);
+    }
+
+    hw_error("lan9118_read: Bad size 0x%x\n", size);
+    return 0;
+}
+
 static const MemoryRegionOps lan9118_mem_ops = {
-    .read = lan9118_readl,
-    .write = lan9118_writel,
+    .read = lan9118_read,
+    .write = lan9118_write,
     .endianness = DEVICE_NATIVE_ENDIAN,
 };
 
@@ -1162,6 +1260,7 @@ static SysBusDeviceInfo lan9118_info = {
     .qdev.reset = lan9118_reset,
     .qdev.props = (Property[]) {
         DEFINE_NIC_PROPERTIES(lan9118_state, conf),
+        DEFINE_PROP_UINT32("mode_16bit", lan9118_state, mode_16bit, 0),
         DEFINE_PROP_END_OF_LIST(),
     }
 };
@@ -1173,7 +1272,7 @@ static void lan9118_register_devices(void)
 
 /* Legacy helper function.  Should go away when machine config files are
    implemented.  */
-void lan9118_init(NICInfo *nd, uint32_t base, qemu_irq irq)
+DeviceState *lan9118_init(NICInfo *nd, uint32_t base, qemu_irq irq)
 {
     DeviceState *dev;
     SysBusDevice *s;
@@ -1185,6 +1284,8 @@ void lan9118_init(NICInfo *nd, uint32_t base, qemu_irq irq)
     s = sysbus_from_qdev(dev);
     sysbus_mmio_map(s, 0, base);
     sysbus_connect_irq(s, 0, irq);
+
+    return dev;
 }
 
 device_init(lan9118_register_devices)
-- 
1.7.4.1

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Qemu-devel] [PATCH 12/15] hw/exynos4210.c: Add LAN support for SMDKC210.
  2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
                   ` (10 preceding siblings ...)
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 11/15] hw/lan9118: Add basic 16-bit mode support Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 13/15] hw/sd.c, hw/sd.h: add receive ready query routine to SD/MMC API Evgeny Voevodin
                   ` (2 subsequent siblings)
  14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin

SMDKC210 uses lan9215 chip, but lan9118 in 16-bit mode seems to
be enough.

Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 hw/exynos4210.c |   18 ++++++++++++++++++
 1 files changed, 18 insertions(+), 0 deletions(-)

diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index 8d8a12c..5b18b68 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -26,6 +26,8 @@
 #include "sysemu.h"
 #include "sysbus.h"
 #include "arm-misc.h"
+#include "net.h"
+#include "devices.h"
 #include "exec-memory.h"
 #include "exynos4210.h"
 
@@ -224,6 +226,8 @@ static void exynos4210_init(ram_addr_t ram_size,
     SysBusDevice *busdev;
     ram_addr_t mem_size;
     int n;
+    NICInfo *nd;
+    int done_nic = 0;
 
     switch (board_type) {
     case BOARD_EXYNOS4210_NURI:
@@ -444,6 +448,20 @@ static void exynos4210_init(ram_addr_t ram_size,
         exynos4210_uart_create(addr, fifo_size, channel, NULL, uart_irq);
     }
 
+    /*** LAN adapter: this should be a 9215 but the 9118 is close enough ***/
+    if (board_type == BOARD_EXYNOS4210_SMDKC210) {
+        for (n = 0; n < nb_nics; n++) {
+            nd = &nd_table[n];
+
+            if (!done_nic && (!nd->model ||
+                    strcmp(nd->model, "lan9118") == 0)) {
+                dev = lan9118_init(nd, 0x05000000,
+                        qemu_irq_invert(irq_table[exynos4210_get_irq(37, 1)]));
+                qdev_prop_set_uint32(dev, "mode_16bit", 1);
+                done_nic = 1;
+            }
+        }
+    }
 
     /*** Load kernel ***/
 
-- 
1.7.4.1

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Qemu-devel] [PATCH 13/15] hw/sd.c, hw/sd.h: add receive ready query routine to SD/MMC API
  2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
                   ` (11 preceding siblings ...)
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 12/15] hw/exynos4210.c: Add LAN support for SMDKC210 Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 14/15] ARM: exynos4210: added SD/MMC host controller Evgeny Voevodin
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 15/15] ARM: exynos4210: added display controller implementation Evgeny Voevodin
  14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: m.kozlov, Mitsyanko Igor, d.solodkiy, Evgeny Voevodin

From: Mitsyanko Igor <i.mitsyanko@samsung.com>

Data transfer direction between host controller and SD/MMC card is selected by
host controller configuration registers, but whether we actually need or need
not perform data transfer depends on type of last issued command. To avoid
memorization of which type of command host controller issued the last time, we
can use simple query procedures, to make sure that SD/MMC card is in the
right state. The only query routine currently presented in SD/MMC card
emulation is sd_data_ready(), this patch adds sd_receive_ready() routine.

Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 hw/sd.c |    5 +++++
 hw/sd.h |    1 +
 2 files changed, 6 insertions(+), 0 deletions(-)

diff --git a/hw/sd.c b/hw/sd.c
index 10e26ad..b0a8557 100644
--- a/hw/sd.c
+++ b/hw/sd.c
@@ -1670,3 +1670,8 @@ void sd_enable(SDState *sd, int enable)
 {
     sd->enable = enable;
 }
+
+int sd_receive_ready(SDState *sd)
+{
+    return sd->state == sd_receivingdata_state;
+}
diff --git a/hw/sd.h b/hw/sd.h
index ac4b7c4..71ab781 100644
--- a/hw/sd.h
+++ b/hw/sd.h
@@ -75,5 +75,6 @@ uint8_t sd_read_data(SDState *sd);
 void sd_set_cb(SDState *sd, qemu_irq readonly, qemu_irq insert);
 int sd_data_ready(SDState *sd);
 void sd_enable(SDState *sd, int enable);
+int sd_receive_ready(SDState *sd);
 
 #endif	/* __hw_sd_h */
-- 
1.7.4.1

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Qemu-devel] [PATCH 14/15] ARM: exynos4210: added SD/MMC host controller
  2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
                   ` (12 preceding siblings ...)
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 13/15] hw/sd.c, hw/sd.h: add receive ready query routine to SD/MMC API Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 15/15] ARM: exynos4210: added display controller implementation Evgeny Voevodin
  14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: m.kozlov, Mitsyanko Igor, d.solodkiy, Evgeny Voevodin

From: Mitsyanko Igor <i.mitsyanko@samsung.com>


Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 Makefile.target      |    3 +-
 hw/exynos4210.c      |   20 +
 hw/exynos4210_sdhc.c | 1666 ++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1688 insertions(+), 1 deletions(-)
 create mode 100644 hw/exynos4210_sdhc.c

diff --git a/Makefile.target b/Makefile.target
index 691582b..a5d67c9 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -345,7 +345,8 @@ 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 += exynos4210.o exynos4210_cmu.o exynos4210_uart.o exynos4210_gic.o \
-             exynos4210_combiner.o exynos4210_pwm.o exynos4210_mct.o
+             exynos4210_combiner.o exynos4210_pwm.o exynos4210_mct.o \
+             exynos4210_sdhc.o
 obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
 obj-arm-y += pl061.o
 obj-arm-y += arm-semi.o
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index 5b18b68..62c90e9 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -95,6 +95,12 @@
 #define EXYNOS4210_EXT_COMBINER_BASE_ADDR   0x10440000
 #define EXYNOS4210_INT_COMBINER_BASE_ADDR   0x10448000
 
+/* SD/MMC host controllers SFR base addresses */
+#define EXYNOS4210_SDHC0_BASE_ADDR          0x12510000
+#define EXYNOS4210_SDHC1_BASE_ADDR          0x12520000
+#define EXYNOS4210_SDHC2_BASE_ADDR          0x12530000
+#define EXYNOS4210_SDHC3_BASE_ADDR          0x12540000
+
 /* Secondary CPU polling address to get loader start from */
 #define EXYNOS4210_SECOND_CPU_BOOTREG       0x10020814
 
@@ -448,6 +454,20 @@ static void exynos4210_init(ram_addr_t ram_size,
         exynos4210_uart_create(addr, fifo_size, channel, NULL, uart_irq);
     }
 
+    /*** SD/MMC host controllers ***/
+
+    sysbus_create_simple("exynos4210.sdhc", EXYNOS4210_SDHC0_BASE_ADDR,
+            irq_table[exynos4210_get_irq(29, 0)]);
+
+    sysbus_create_simple("exynos4210.sdhc", EXYNOS4210_SDHC1_BASE_ADDR,
+            irq_table[exynos4210_get_irq(29, 1)]);
+
+    sysbus_create_simple("exynos4210.sdhc", EXYNOS4210_SDHC2_BASE_ADDR,
+            irq_table[exynos4210_get_irq(29, 2)]);
+
+    sysbus_create_simple("exynos4210.sdhc", EXYNOS4210_SDHC3_BASE_ADDR,
+            irq_table[exynos4210_get_irq(29, 3)]);
+
     /*** LAN adapter: this should be a 9215 but the 9118 is close enough ***/
     if (board_type == BOARD_EXYNOS4210_SMDKC210) {
         for (n = 0; n < nb_nics; n++) {
diff --git a/hw/exynos4210_sdhc.c b/hw/exynos4210_sdhc.c
new file mode 100644
index 0000000..05dd05c
--- /dev/null
+++ b/hw/exynos4210_sdhc.c
@@ -0,0 +1,1666 @@
+/*
+ * Samsung exynos4210 SD/MMC host controller
+ * (SD host controller specification ver. 2.0 compliant)
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ * Contributed by Mitsyanko Igor <i.mitsyanko@samsung.com>
+ *
+ * Based on MMC controller for Samsung S5PC1xx-based board emulation
+ * by Alexey Merkulov and Vladimir Monakhov.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "hw.h"
+#include "sd.h"
+#include "qemu-common.h"
+#include "block_int.h"
+#include "blockdev.h"
+#include "sysbus.h"
+#include "qemu-timer.h"
+
+/* host controller debug messages */
+#define DEBUG_SDHC 0
+
+#if DEBUG_SDHC
+#define DPRINTF(fmt, args...) \
+do { fprintf(stderr, "QEMU SDHC: " fmt , ##args); } while (0)
+#else
+#define DPRINTF(fmt, args...) do {} while (0)
+#endif
+
+#define TARGET_WORD_SIZE         (TARGET_PHYS_ADDR_SPACE_BITS / 8)
+#define READ_BUFFER_DELAY        5
+#define WRITE_BUFFER_DELAY       6
+#define INSERTION_DELAY          (get_ticks_per_sec())
+#define CMD_RESPONSE             (3 << 0)
+
+/* Default SD/MMC host controller features information, which will be
+ * presented in host's CAPABILITIES REGISTER at reset.
+ * If not stated otherwise:
+ * 0 - not supported, 1 - supported, other - prohibited.
+ */
+#define SDHC_CAPAB_64BITBUS       0ul        /* 64-bit System Bus Support */
+#define SDHC_CAPAB_18V            1ul        /* Voltage support 1.8v */
+#define SDHC_CAPAB_30V            0ul        /* Voltage support 3.0v */
+#define SDHC_CAPAB_33V            1ul        /* Voltage support 3.3v */
+#define SDHC_CAPAB_SUSPRESUME     0ul        /* Suspend/resume support */
+#define SDHC_CAPAB_SDMA           1ul        /* SDMA support */
+#define SDHC_CAPAB_HIGHSPEED      1ul        /* High speed support */
+#define SDHC_CAPAB_ADMA           1ul        /* ADMA2 support */
+/* Maximum host controller R/W buffers size
+ * Possible values: 512, 1024, 2048 bytes */
+#define SDHC_CAPAB_MAXBLOCKLENGTH 512ul
+/* Maximum clock frequency for SDclock in MHz
+ * value in range 10-63 MHz, 0 - not defined */
+#define SDHC_CAPAB_BASECLKFREQ    0ul
+#define SDHC_CAPAB_TOUNIT         1ul  /* Timeout clock unit 0 - kHz, 1 - MHz */
+/* Timeout clock frequency 1-63, 0 - not defined */
+#define SDHC_CAPAB_TOCLKFREQ      0ul
+
+/* Now check all parameters and calculate CAPABILITIES REGISTER value */
+#if SDHC_CAPAB_64BITBUS > 1 || SDHC_CAPAB_18V > 1 || SDHC_CAPAB_30V > 1 ||\
+    SDHC_CAPAB_33V > 1 || SDHC_CAPAB_SUSPRESUME > 1 || SDHC_CAPAB_SDMA > 1 ||\
+    SDHC_CAPAB_HIGHSPEED > 1 || SDHC_CAPAB_ADMA > 1 || SDHC_CAPAB_TOUNIT > 1
+#error Capabilities features SDHC_CAPAB_x must have value 0 or 1!
+#endif
+
+#if SDHC_CAPAB_MAXBLOCKLENGTH == 512
+#define MAX_BLOCK_LENGTH 0ul
+#elif SDHC_CAPAB_MAXBLOCKLENGTH == 1024
+#define MAX_BLOCK_LENGTH 1ul
+#elif SDHC_CAPAB_MAXBLOCKLENGTH == 2048
+#define MAX_BLOCK_LENGTH 2ul
+#else
+#error Max host controller block size can have value 512, 1024 or 2048 only!
+#endif
+
+#if (SDHC_CAPAB_BASECLKFREQ > 0 && SDHC_CAPAB_BASECLKFREQ < 10) || \
+    SDHC_CAPAB_BASECLKFREQ > 63
+#error SDclock frequency can have value in range 0, 10-63 only!
+#endif
+
+#if SDHC_CAPAB_TOCLKFREQ > 63
+#error Timeout clock frequency can have value in range 0-63 only!
+#endif
+
+#define SDHC_CAPAB_REG_DEFAULT ((SDHC_CAPAB_64BITBUS<<28)|(SDHC_CAPAB_18V<<26)|\
+   (SDHC_CAPAB_30V<<25)|(SDHC_CAPAB_33V<<24)|(SDHC_CAPAB_SUSPRESUME<<23)|\
+   (SDHC_CAPAB_SDMA<<22)|(SDHC_CAPAB_HIGHSPEED<<21)|(SDHC_CAPAB_ADMA<<19)|\
+   (MAX_BLOCK_LENGTH<<16)|(SDHC_CAPAB_BASECLKFREQ<<8)|(SDHC_CAPAB_TOUNIT<<7)|\
+   (SDHC_CAPAB_TOCLKFREQ))
+
+/****************************************************
+ * SD host controller registers offsets
+ ***************************************************/
+
+/* R/W SDMA System Address register 0x0 */
+#define SDHC_SYSAD                     0x00
+
+/* R/W Host DMA Buffer Boundary and Transfer Block Size Register 0x0 */
+#define SDHC_BLKSIZE                   0x04
+
+/* R/W Blocks count for current transfer 0x0 */
+#define SDHC_BLKCNT                    0x06
+
+/* R/W Command Argument Register 0x0 */
+#define SDHC_ARGUMENT                  0x08
+
+/* R/W Transfer Mode Setting Register 0x0 */
+#define SDHC_TRNMOD                    0x0C
+#define SDHC_TRNS_DMA                  0x0001
+#define SDHC_TRNS_BLK_CNT_EN           0x0002
+#define SDHC_TRNS_ACMD12               0x0004
+#define SDHC_TRNS_READ                 0x0010
+#define SDHC_TRNS_MULTI                0x0020
+#define SDHC_TRNS_CEATA                0x0300
+#define SDHC_TRNS_BOOTCMD              0x1000
+#define SDHC_TRNS_BOOTACK              0x2000
+
+/* R/W Command Register 0x0 */
+#define SDHC_CMDREG                    0x0E
+#define SDHC_CMD_RSP_WITH_BUSY         (3 << 0)
+#define SDHC_CMD_DATA_PRESENT          (1 << 5)
+#define SDHC_CMD_SUSPEND_MASK          (1 << 6)
+#define SDHC_CMD_RESUME_MASK           (1 << 7)
+#define SDHC_CMD_ABORT_MASK            ((1 << 6)|(1 << 7))
+#define SDHC_CMD_TYPE_MASK             ((1 << 6)|(1 << 7))
+
+/* ROC Response Register 0 0x0 */
+#define SDHC_RSPREG0                   0x10
+/* ROC Response Register 1 0x0 */
+#define SDHC_RSPREG1                   0x14
+/* ROC Response Register 2 0x0 */
+#define SDHC_RSPREG2                   0x18
+/* ROC Response Register 3 0x0 */
+#define SDHC_RSPREG3                   0x1C
+#define SDHC_CMD_RESP_MASK             0x00000003
+#define SDHC_CMD_CRC                   0x00000008
+#define SDHC_CMD_INDEX                 0x00000010
+#define SDHC_CMD_DATA                  0x00000020
+#define SDHC_CMD_RESP_NONE             0x00000000
+#define SDHC_CMD_RESP_LONG             0x00000001
+#define SDHC_CMD_RESP_SHORT            0x00000002
+#define SDHC_CMD_RESP_SHORT_BUSY       0x00000003
+#define SDHC_MAKE_CMD(c, f)            (((c & 0xFF) << 8) | (f & 0xFF))
+
+/* R/W Buffer Data Register 0x0 */
+#define SDHC_BDATA                     0x20
+
+/* R/ROC Present State Register 0x000A0000 */
+#define SDHC_PRNSTS                    0x24
+#define SDHC_CMD_INHIBIT               0x00000001
+#define SDHC_DATA_INHIBIT              0x00000002
+#define SDHC_DAT_LINE_ACTIVE           0x00000004
+#define SDHC_DOING_WRITE               0x00000100
+#define SDHC_DOING_READ                0x00000200
+#define SDHC_SPACE_AVAILABLE           0x00000400
+#define SDHC_DATA_AVAILABLE            0x00000800
+#define SDHC_CARD_PRESENT              0x00010000
+#define SDHC_WRITE_PROTECT             0x00080000
+
+/* R/W Host control Register 0x0 */
+#define SDHC_HOSTCTL                   0x28
+#define SDHC_CTRL_LED                  0x01
+#define SDHC_CTRL_4BITBUS              0x02
+#define SDHC_CTRL_HIGHSPEED            0x04
+#define SDHC_CTRL_1BIT                 0x00
+#define SDHC_CTRL_4BIT                 0x02
+#define SDHC_CTRL_8BIT                 0x20
+#define SDHC_CTRL_DMA_CHECK_MASK       0x18
+#define SDHC_CTRL_SDMA                 0x00
+#define SDHC_CTRL_ADMA2_32             0x10
+
+/* R/W Power Control Register 0x0 */
+#define SDHC_PWRCON                    0x29
+#define SDHC_POWER_OFF                 0x00
+#define SDHC_POWER_ON                  0x01
+#define SDHC_POWER_180                 0x0A
+#define SDHC_POWER_300                 0x0C
+#define SDHC_POWER_330                 0x0E
+#define SDHC_POWER_ON_ALL              0xFF
+
+/* R/W Block Gap Control Register 0x0 */
+#define SDHC_BLKGAP                    0x2A
+#define SDHC_STOP_AT_GAP_REQ           0x01
+#define SDHC_CONTINUE_REQ              0x02
+
+/* R/W WakeUp Control Register 0x0 */
+#define SDHC_WAKCON                    0x2B
+#define SDHC_STAWAKEUP                 0x08
+
+/*
+ * CLKCON
+ * SELFREQ[15:8]    : base clock divided by value
+ * ENSDCLK[2]        : SD Clock Enable
+ * STBLINTCLK[1]    : Internal Clock Stable
+ * ENINTCLK[0]        : Internal Clock Enable
+ */
+#define SDHC_CLKCON                    0x2C
+#define SDHC_DIVIDER_SHIFT             0x0008
+#define SDHC_CLOCK_EXT_STABLE          0x0008
+#define SDHC_CLOCK_CARD_EN             0x0004
+#define SDHC_CLOCK_INT_STABLE          0x0002
+#define SDHC_CLOCK_INT_EN              0x0001
+#define SDHC_CLOCK_CHK_MASK            0x000F
+
+/* R/W Timeout Control Register 0x0 */
+#define SDHC_TIMEOUTCON                0x2E
+#define SDHC_TIMEOUT_MAX               0x0E
+
+/* R/W Software Reset Register 0x0 */
+#define SDHC_SWRST                     0x2F
+#define SDHC_RESET_ALL                 0x01
+#define SDHC_RESET_CMD                 0x02
+#define SDHC_RESET_DATA                0x04
+
+/* ROC/RW1C Normal Interrupt Status Register 0x0 */
+#define SDHC_NORINTSTS                 0x30
+#define SDHC_NIS_ERR                   0x8000
+#define SDHC_NIS_CMDCMP                0x0001
+#define SDHC_NIS_TRSCMP                0x0002
+#define SDHC_NIS_BLKGAP                0x0004
+#define SDHC_NIS_DMA                   0x0008
+#define SDHC_NIS_WBUFRDY               0x0010
+#define SDHC_NIS_RBUFRDY               0x0020
+#define SDHC_NIS_INSERT                0x0040
+#define SDHC_NIS_REMOVE                0x0080
+#define SDHC_NIS_CARDINT               0x0100
+
+/* ROC/RW1C Error Interrupt Status Register 0x0 */
+#define SDHC_ERRINTSTS                 0x32
+#define SDHC_EIS_CMDTIMEOUT            0x0001
+#define SDHC_EIS_BLKGAP                0x0004
+#define SDHC_EIS_CMDERR                0x000E
+#define SDHC_EIS_DATATIMEOUT           0x0010
+#define SDHC_EIS_DATAERR               0x0060
+#define SDHC_EIS_CMD12ERR              0x0100
+#define SDHC_EIS_ADMAERR               0x0200
+#define SDHC_EIS_STABOOTACKERR         0x0400
+
+/* R/W Normal Interrupt Status Enable Register 0x0 */
+#define SDHC_NORINTSTSEN               0x34
+#define SDHC_NISEN_CMDCMP              0x0001
+#define SDHC_NISEN_TRSCMP              0x0002
+#define SDHC_NISEN_DMA                 0x0008
+#define SDHC_NISEN_WBUFRDY             0x0010
+#define SDHC_NISEN_RBUFRDY             0x0020
+#define SDHC_NISEN_INSERT              0x0040
+#define SDHC_NISEN_REMOVE              0x0080
+#define SDHC_NISEN_CARDINT             0x0100
+
+/* R/W Error Interrupt Status Enable Register 0x0 */
+#define SDHC_ERRINTSTSEN               0x36
+#define SDHC_EISEN_CMDTIMEOUT          0x0001
+#define SDHC_EISEN_BLKGAP              0x0004
+#define SDHC_EISEN_ADMAERR             0x0200
+#define SDHC_EISEN_STABOOTACKERR       0x0400
+
+/* R/W Normal Interrupt Signal Enable Register 0x0 */
+#define SDHC_NORINTSIGEN               0x38
+#define SDHC_NISSIG_MASK_ALL           0x00
+#define SDHC_NISSIG_RESPONSE           0x0001
+#define SDHC_NISSIG_DATA_END           0x0002
+#define SDHC_NISSIG_DMA_END            0x0008
+#define SDHC_NISSIG_SPACE_AVAIL        0x0010
+#define SDHC_NISSIG_DATA_AVAIL         0x0020
+#define SDHC_NISSIG_CARD_INSERT        0x0040
+#define SDHC_NISSIG_CARD_REMOVE        0x0080
+#define SDHC_NISSIG_CARD_CHANGE        0x00C0
+#define SDHC_NISSIG_CARD_INT           0x0100
+
+/* R/W Error Interrupt Signal Enable Register 0x0 */
+#define SDHC_ERRINTSIGEN               0x3A
+#define SDHC_EISSIG_STABOOTACKERR      0x0400
+#define SDHC_EISSIG_ADMAERR            0x0200
+
+/* ROC Auto CMD12 error status register 0x0 */
+#define SDHC_ACMD12ERRSTS              0x3C
+
+/* HWInit Capabilities Register 0x05E80080 */
+#define SDHC_CAPAREG                   0x40
+#define SDHC_TIMEOUT_CLK_MASK          0x0000003F
+#define SDHC_TIMEOUT_CLK_SHIFT         0x0
+#define SDHC_TIMEOUT_CLK_UNIT          0x00000080
+#define SDHC_CLOCK_BASE_MASK           0x00003F00
+#define SDHC_CLOCK_BASE_SHIFT          0x8
+#define SDHC_MAX_BLOCK_MASK            0x00030000
+#define SDHC_MAX_BLOCK_SHIFT           0x10
+#define SDHC_CAN_DO_DMA                0x00400000
+#define SDHC_CAN_DO_ADMA2              0x00080000
+#define SDHC_CAN_VDD_330               0x01000000
+#define SDHC_CAN_VDD_300               0x02000000
+#define SDHC_CAN_VDD_180               0x04000000
+
+/* HWInit Maximum Current Capabilities Register 0x0 */
+#define SDHC_MAXCURR                   0x48
+
+/* For ADMA2 */
+
+/* W Force Event Auto CMD12 Error Interrupt Register 0x0000 */
+#define SDHC_FEAER                     0x50
+/* W Force Event Error Interrupt Register Error Interrupt 0x0000 */
+#define SDHC_FEERR                     0x52
+
+/* R/W ADMA Error Status Register 0x00 */
+#define SDHC_ADMAERR                   0x54
+#define SDHC_ADMAERR_FINAL_BLOCK       (1 << 10)
+#define SDHC_ADMAERR_CONTINUE_REQUEST  (1 << 9)
+#define SDHC_ADMAERR_INTRRUPT_STATUS   (1 << 8)
+#define SDHC_ADMAERR_LENGTH_MISMATCH   (1 << 2)
+#define SDHC_ADMAERR_STATE_ST_STOP     (0 << 0)
+#define SDHC_ADMAERR_STATE_ST_FDS      (1 << 0)
+#define SDHC_ADMAERR_STATE_ST_TFR      (3 << 0)
+#define SDHC_ADMAERR_STATE_MASK        (3 << 0)
+
+/* R/W ADMA System Address Register 0x00 */
+#define SDHC_ADMASYSADDR               0x58
+#define SDHC_ADMA_ATTR_MSK             0x3F
+#define SDHC_ADMA_ATTR_ACT_NOP         (0 << 4)
+#define SDHC_ADMA_ATTR_ACT_RSV         (1 << 4)
+#define SDHC_ADMA_ATTR_ACT_TRAN        (1 << 5)
+#define SDHC_ADMA_ATTR_ACT_LINK        (3 << 4)
+#define SDHC_ADMA_ATTR_INT             (1 << 2)
+#define SDHC_ADMA_ATTR_END             (1 << 1)
+#define SDHC_ADMA_ATTR_VALID           (1 << 0)
+#define SDHC_ADMA_ATTR_ACT_MASK        ((1 << 4)|(1 << 5))
+
+/* R/W Control register 2 0x0 */
+#define SDHC_CONTROL2                   0x80
+#define SDHC_HWINITFIN                  0x00000001
+#define SDHC_SDOPSIGPC                  0x00001000
+#define SDHC_ENCLKOUTHOLD               0x00000100
+#define SDHC_SDINPSIGPC                 0x00000008
+#define SDHC_DISBUFRD                   0x00000040
+#define SDHC_ENCMDCNFMSK                0x40000000
+
+/* R/W FIFO Interrupt Control (Control Register 3) 0x7F5F3F1F */
+#define SDHC_CONTROL3                   0x84
+
+/* R/W Control register 4 0x0 */
+#define SDHC_CONTROL4                   0x8C
+
+/* Magic register which is used from kernel! */
+#define SDHC_SLOT_INT_STATUS            0xFC
+
+/* HWInit Host Controller Version Register 0x0401 */
+#define SDHC_HCVER                      0xFE
+#define SDHC_VENDOR_VER_MASK            0xFF00
+#define SDHC_VENDOR_VER_SHIFT           0x8
+#define SDHC_SPEC_VER_MASK              0x00FF
+#define SDHC_SPEC_VER_SHIFT             0x0
+
+#define SDHC_REG_SIZE                   0x100
+
+enum {
+    not_stoped = 0,       /* normal SDHC state */
+    gap_read = 1,         /* SDHC stopped at block gap during read operation */
+    gap_write = 2,        /* SDHC stopped at block gap during write operation */
+    adma_intr = 3         /* SDHC stopped after ADMA interrupt occurred */
+};
+
+/* SD/MMC host controller state */
+typedef struct Exynos4210SDHCState {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    SDState *card;
+
+    QEMUTimer *insert_timer; /* timer for 'changing' sd card. */
+    QEMUTimer *read_buffer_timer; /* read block of data to controller FIFO */
+    QEMUTimer *write_buffer_timer; /* write block of data to card from FIFO */
+    QEMUTimer *transfer_complete_timer; /* raise transfer complete irq */
+    qemu_irq eject;
+    qemu_irq irq;
+
+    uint32_t sdmasysad;    /* SDMA System Address register */
+    uint16_t blksize;      /* Host DMA Buff Boundary and Transfer BlkSize Reg */
+    uint16_t blkcnt;       /* Blocks count for current transfer */
+    uint32_t argument;     /* Command Argument Register */
+    uint16_t trnmod;       /* Transfer Mode Setting Register */
+    uint16_t cmdreg;       /* Command Register */
+    uint32_t rspreg[4];    /* Response Registers 0-3 */
+/*  32 bit bdata Buffer Data Register - access point to R and W buffers */
+    uint32_t prnsts;       /* Present State Register */
+    uint8_t hostctl;       /* Present State Register */
+    uint8_t pwrcon;        /* Present State Register */
+    uint8_t blkgap;        /* Block Gap Control Register */
+    uint8_t wakcon;        /* WakeUp Control Register */
+    uint16_t clkcon;       /* Command Register */
+    uint8_t timeoutcon;    /* Timeout Control Register */
+    uint8_t stoped_state;  /* Current SDHC state */
+    /* SD host i/o FIFO buffer */
+    uint32_t fifo_buffer[SDHC_CAPAB_MAXBLOCKLENGTH / TARGET_WORD_SIZE];
+    uint16_t data_count;   /* current element in FIFO buffer */
+/*  8 bit swrst Software Reset Register - read as 0 */
+    uint16_t norintsts;    /* Normal Interrupt Status Register */
+    uint16_t errintsts;    /* Error Interrupt Status Register */
+    uint16_t norintstsen;  /* Normal Interrupt Status Enable Register */
+    uint16_t errintstsen;  /* Error Interrupt Status Enable Register */
+    uint16_t norintsigen;  /* Normal Interrupt Signal Enable Register */
+    uint16_t errintsigen;  /* Error Interrupt Signal Enable Register */
+    uint16_t acmd12errsts;   /* Auto CMD12 error status register */
+    uint32_t capareg;      /* Capabilities Register */
+    uint32_t maxcurr;      /* Maximum Current Capabilities Register */
+/*  16 bit feaer Force Event Auto CMD12 Error Interrupt Reg - write only */
+/*  16 bit feerr Force Event Error Interrupt Register- write only */
+    uint32_t admaerr;      /* ADMA Error Status Register */
+    uint32_t admasysaddr;  /* ADMA System Address Register */
+    uint32_t control2;     /* Control Register 2 */
+    uint32_t control3;     /* FIFO Interrupt Control (Control Register 3) */
+/*  32 bit control4 makes no sense in emulation so always return 0 */
+/*  32 bit hcver Host Controller Ver Reg 0x2401 -SD specification v2 */
+} Exynos4210SDHCState;
+
+static void sdhc_raise_transfer_complete_irq(void *opaque)
+{
+    Exynos4210SDHCState *s = (Exynos4210SDHCState *)opaque;
+
+    /* free data transfer line */
+    s->prnsts &= ~(SDHC_DOING_READ | SDHC_DOING_WRITE |
+            SDHC_DAT_LINE_ACTIVE | SDHC_DATA_INHIBIT |
+            SDHC_SPACE_AVAILABLE | SDHC_DATA_AVAILABLE);
+
+    if (s->norintstsen & SDHC_NISEN_TRSCMP) {
+        s->norintsts |= SDHC_NIS_TRSCMP;
+    }
+
+    qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+}
+
+/* raise command response received interrupt */
+static inline void sdhc_raise_response_recieved_irq(Exynos4210SDHCState *s)
+{
+    DPRINTF("raise IRQ response\n");
+
+    if (s->norintstsen & SDHC_NISEN_CMDCMP) {
+        s->norintsts |= SDHC_NIS_CMDCMP;
+    }
+
+    qemu_set_irq(s->irq, ((s->norintsts & s->norintsigen) ||
+            (s->errintsts & s->errintsigen)));
+}
+
+static void sdhc_raise_insertion_irq(void *opaque)
+{
+    Exynos4210SDHCState *s = (Exynos4210SDHCState *)opaque;
+    DPRINTF("raise IRQ response\n");
+
+    if (s->norintsts & SDHC_NIS_REMOVE) {
+        DPRINTF("sdhc_raise_insertion_irq: set timer!\n");
+        qemu_mod_timer(s->insert_timer,
+                       qemu_get_clock_ns(vm_clock) + INSERTION_DELAY);
+    } else {
+        DPRINTF("sdhc_raise_insertion_irq: raise irq!\n");
+        if (s->norintstsen & SDHC_NIS_INSERT) {
+            s->norintsts |= SDHC_NIS_INSERT;
+        }
+        qemu_set_irq(s->irq, (s->norintsts & s->norintsigen));
+    }
+}
+
+static void sdhc_insert_eject(void *opaque, int irq, int level)
+{
+    Exynos4210SDHCState *s = (Exynos4210SDHCState *)opaque;
+    DPRINTF("change card state: %s!\n", level ? "insert" : "eject");
+
+    if (s->norintsts & SDHC_NIS_REMOVE) {
+        if (level) {
+            DPRINTF("change card state: timer set!\n");
+            qemu_mod_timer(s->insert_timer,
+                           qemu_get_clock_ns(vm_clock) + INSERTION_DELAY);
+        }
+    } else {
+        if (level) {
+            s->prnsts = 0x1ff0000;
+            if (s->norintstsen & SDHC_NIS_INSERT) {
+                s->norintsts |= SDHC_NIS_INSERT;
+            }
+        } else {
+            s->prnsts = 0x1fa0000;
+            if (s->norintstsen & SDHC_NIS_REMOVE) {
+                s->norintsts |= SDHC_NIS_REMOVE;
+            }
+        }
+        qemu_set_irq(s->irq, (s->norintsts & s->norintsigen));
+    }
+}
+
+static void exynos4210_sdhc_reset(DeviceState *d)
+{
+    Exynos4210SDHCState *s = container_of(d, Exynos4210SDHCState, busdev.qdev);
+    unsigned long begin = (unsigned long)s + offsetof(Exynos4210SDHCState,
+            sdmasysad);
+    unsigned long len = ((unsigned long)s + offsetof(Exynos4210SDHCState,
+            control3)) - begin;
+
+    /* Set all registers to 0 and then set appropriate values
+     * for hardware-initialize registers */
+    memset((void *)begin, 0, len);
+    (s->card) ? (s->prnsts = 0x1ff0000) : (s->prnsts = 0x1fa0000);
+    s->stoped_state = not_stoped;
+    s->data_count = 0;
+    s->capareg = SDHC_CAPAB_REG_DEFAULT;
+    s->control3 = 0x7F5F3F1F;
+}
+
+static void sdhc_send_command(Exynos4210SDHCState *s)
+{
+    SDRequest request;
+    uint8_t response[16];
+    int rlen;
+    s->errintsts = 0;
+    if (!s->card) {
+        goto error;
+    }
+
+    request.cmd = s->cmdreg >> 8;
+    request.arg = s->argument;
+    DPRINTF("Command %d %08x\n", request.cmd, request.arg);
+    rlen = sd_do_command(s->card, &request, response);
+    if (rlen < 0) {
+        goto error;
+    }
+    if ((s->cmdreg & CMD_RESPONSE) != 0) {
+#define RWORD(n) ((n >= 0 ? (response[n] << 24) : 0) \
+                  | (response[n + 1] << 16) \
+                  | (response[n + 2] << 8) \
+                  |  response[n + 3])
+
+        if ((rlen == 0) || (rlen != 4 && rlen != 16)) {
+            goto error;
+        }
+
+        s->rspreg[0] = RWORD(0);
+        if (rlen == 4) {
+            s->rspreg[1] = s->rspreg[2] = s->rspreg[3] = 0;
+        } else {
+            s->rspreg[0] = RWORD(11);
+            s->rspreg[1] = RWORD(7);
+            s->rspreg[2] = RWORD(3);
+            s->rspreg[3] = RWORD(-1);
+        }
+        DPRINTF("Response received\n");
+#undef RWORD
+    } else {
+        DPRINTF("Command sent\n");
+    }
+    return;
+
+error:
+    DPRINTF("Timeout\n");
+    if (s->errintstsen & SDHC_EISEN_CMDTIMEOUT) {
+        s->errintsts |= SDHC_EIS_CMDTIMEOUT;
+        s->norintsts |= SDHC_NIS_ERR;
+    }
+}
+
+static void sdhc_do_transfer_complete(Exynos4210SDHCState *s)
+{
+    /* Automatically send CMD12 to stop transfer if AutoCMD12 enabled */
+    if ((s->trnmod & SDHC_TRNS_ACMD12) != 0) {
+        SDRequest request;
+        uint8_t response[16];
+
+        request.cmd = 0x0C;
+        request.arg = 0;
+        DPRINTF("Command Auto%d %08x\n", request.cmd, request.arg);
+        sd_do_command(s->card, &request, response);
+    }
+    /* pend a timer which will raise a transfer complete irq */
+    qemu_mod_timer(s->transfer_complete_timer,
+        qemu_get_clock_ns(vm_clock) + 1);
+}
+
+/*
+ * Programmed i/o data transfer
+ */
+
+/* Fill host controlerr's read buffer with BLKSIZE bytes of data from card */
+static void sdhc_read_block_from_card(void *opaque)
+{
+    Exynos4210SDHCState *s = (Exynos4210SDHCState *)opaque;
+    uint32_t value = 0;
+    int n = 0, index = 0;
+    uint16_t datacount = s->blksize & 0x0fff;
+
+    if ((s->trnmod & SDHC_TRNS_MULTI) &&
+            (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0)) {
+        return;
+    }
+
+    while (datacount) {
+        value |= (uint32_t)sd_read_data(s->card) << (n * 8);
+        n++;
+        if (n == 4) {
+            s->fifo_buffer[index] = value;
+            value = 0;
+            n = 0;
+            index++;
+        }
+        datacount--;
+    }
+
+    /* New data now available for READ through Buffer Port Register */
+    s->prnsts |= SDHC_DATA_AVAILABLE;
+    if (s->norintstsen & SDHC_NISEN_RBUFRDY) {
+        s->norintsts |= SDHC_NIS_RBUFRDY;
+    }
+
+    /* Clear DAT line active status if that was the last block */
+    if ((s->trnmod & SDHC_TRNS_MULTI) == 0 ||
+            ((s->trnmod & SDHC_TRNS_MULTI) && s->blkcnt == 1)) {
+        s->prnsts &= ~SDHC_DAT_LINE_ACTIVE;
+    }
+
+    /* If stop at block gap request was set and it's not the last block of
+     * data - generate Block Event interrupt */
+    if (s->stoped_state == gap_read && (s->trnmod & SDHC_TRNS_MULTI) &&
+            s->blkcnt != 1)    {
+        s->prnsts &= ~SDHC_DAT_LINE_ACTIVE;
+        if (s->norintstsen & SDHC_EISEN_BLKGAP) {
+            s->norintsts |= SDHC_EIS_BLKGAP;
+        }
+    }
+
+    qemu_set_irq(s->irq, (s->norintsts & s->norintsigen));
+}
+
+/* Read data from host controller BUFFER DATA PORT register */
+static inline uint32_t sdhc_read_dataport(Exynos4210SDHCState *s)
+{
+    /* first check if a valid data exists in host controller input buffer
+     * and that buffer read is not disabled */
+    if ((s->prnsts & SDHC_DATA_AVAILABLE) == 0 ||
+            (s->control2 & SDHC_DISBUFRD)) {
+        DPRINTF("Trying to read from empty buffer or read disabled\n");
+        return 0;
+    }
+
+    uint32_t value = s->fifo_buffer[s->data_count];
+    s->data_count++;
+    /* check if we've read all valid data (blksize bytes) from buffer */
+    if (((s->data_count) * 4) >= (s->blksize & 0x0fff)) {
+        s->prnsts &= ~SDHC_DATA_AVAILABLE; /* no more data in a buffer */
+        s->data_count = 0;  /* next buff read must start at position [0] */
+
+        if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
+            s->blkcnt--;
+        }
+
+        /* if that was the last block of data */
+        if ((s->trnmod & SDHC_TRNS_MULTI) == 0 ||
+                ((s->trnmod & SDHC_TRNS_MULTI) &&
+                (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0))) {
+            sdhc_do_transfer_complete(s);
+        } else if (s->stoped_state == gap_read &&  /* stop at gap request */
+                !(s->prnsts & SDHC_DAT_LINE_ACTIVE)) {
+            sdhc_raise_transfer_complete_irq(s);
+        } else { /* if there are more data, read next block from card */
+            qemu_mod_timer(s->read_buffer_timer,
+                qemu_get_clock_ns(vm_clock) + READ_BUFFER_DELAY);
+        }
+    }
+    return value;
+}
+
+/* Write data from host controller FIFO to card */
+static void sdhc_write_block_to_card(void *opaque)
+{
+    Exynos4210SDHCState *s = (Exynos4210SDHCState *)opaque;
+    uint32_t value = 0;
+    int n = 0, index = 0;
+    uint16_t datacount = s->blksize & 0x0fff;
+
+    if (s->prnsts & SDHC_SPACE_AVAILABLE) {
+        if (s->norintstsen & SDHC_NISEN_WBUFRDY) {
+            s->norintsts |= SDHC_NIS_WBUFRDY;
+        }
+        qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+        return;
+    }
+
+    if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
+        if (s->blkcnt == 0) {
+            return;
+        } else {
+            s->blkcnt--;
+        }
+    }
+
+    while (datacount) {
+        if (n == 0) {
+            value = s->fifo_buffer[index];
+            n = 4;
+            index++;
+        }
+        sd_write_data(s->card, (uint8_t)(value & 0xff));
+        value >>= 8;
+        n--;
+        datacount--;
+    }
+
+    /* Next data can be written through BUFFER DATORT register */
+    s->prnsts |= SDHC_SPACE_AVAILABLE;
+    if (s->norintstsen & SDHC_NISEN_WBUFRDY) {
+        s->norintsts |= SDHC_NIS_WBUFRDY;
+    }
+
+    /* Finish transfer if that was the last block of data */
+    if ((s->trnmod & SDHC_TRNS_MULTI) == 0 ||
+            ((s->trnmod & SDHC_TRNS_MULTI) &&
+            (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0))) {
+        sdhc_do_transfer_complete(s);
+    }
+
+    /* Generate Block Gap Event if requested and if not the last block */
+    if (s->stoped_state == gap_write && (s->trnmod & SDHC_TRNS_MULTI) &&
+            s->blkcnt > 0) {
+        s->prnsts &= ~SDHC_DOING_WRITE;
+        if (s->norintstsen & SDHC_EISEN_BLKGAP) {
+            s->norintsts |= SDHC_EIS_BLKGAP;
+        }
+        qemu_mod_timer(s->transfer_complete_timer,
+            qemu_get_clock_ns(vm_clock)+1);
+    }
+
+    qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+}
+
+/* Write data to host controller BUFFER DATA PORT register */
+static inline void sdhc_write_dataport(Exynos4210SDHCState *s, uint32_t value)
+{
+    if ((s->prnsts & SDHC_SPACE_AVAILABLE) == 0) {
+        DPRINTF("exynos4210.sdhc: Trying to write to full buffer\n");
+        return;
+    }
+
+    s->fifo_buffer[s->data_count] = value;
+    s->data_count++;
+
+    if (((s->data_count) * 4) >= (s->blksize & 0x0fff)) {
+        s->data_count = 0;
+        s->prnsts &= ~SDHC_SPACE_AVAILABLE;
+        if (s->prnsts & SDHC_DOING_WRITE) {
+            qemu_mod_timer(s->write_buffer_timer,
+                qemu_get_clock_ns(vm_clock) + WRITE_BUFFER_DELAY);
+        }
+    }
+}
+
+/*
+ * Single DMA data transfer
+ */
+
+/* Multi block SDMA transfer */
+static void sdhc_sdma_transfer_multi_blocks(Exynos4210SDHCState *s)
+{
+    uint32_t value = 0, datacnt, saved_datacnt, pos = 0;
+    int n = 0, page_aligned = 0;
+    int is_read = (s->trnmod & SDHC_TRNS_READ) != 0;
+    uint32_t dma_buf_boundary = (s->blksize & 0xf000) >> 12;
+    uint32_t boundary_chk = 1 << (dma_buf_boundary+12);
+    uint32_t boundary_count = boundary_chk - (s->sdmasysad % boundary_chk);
+
+    /* XXX: Some sd/mmc drivers (for example, u-boot-slp) do not account for
+     * possible stop at page boundary if initial address is not page aligned.
+     * Documentation is vague on how this really behaves in hardware, so allow
+     * them to work properly */
+    if ((s->sdmasysad % boundary_chk) == 0) {
+        page_aligned = 1;
+    }
+
+    if (is_read) {
+        s->prnsts |= SDHC_DOING_READ | SDHC_DATA_INHIBIT |
+                SDHC_DAT_LINE_ACTIVE;
+    } else {
+        s->prnsts |= SDHC_DOING_WRITE | SDHC_DATA_INHIBIT |
+                SDHC_DAT_LINE_ACTIVE;
+    }
+
+    /* Transfer data left from last SDMA transaction loop, after controller
+     * stopped at DMA buffer boundary. This is required only if transfers
+     * are not aligned to DMA buffer boundary */
+    saved_datacnt = s->data_count;
+    while (saved_datacnt) {
+        if (is_read) {
+            value |= (uint32_t)sd_read_data(s->card) << (n * 8);
+            n++;
+            if (n == 4) {
+                cpu_physical_memory_write(s->sdmasysad + pos,
+                        (uint8_t *)(&value), 4);
+                value = 0;
+                n = 0;
+                pos += 4;
+            }
+        } else {
+            if (n == 0) {
+                cpu_physical_memory_read(s->sdmasysad + pos,
+                        (uint8_t *)(&value), 4);
+                n = 4;
+                pos += 4;
+            }
+            sd_write_data(s->card, value & 0xff);
+            value >>= 8;
+            n--;
+        }
+        saved_datacnt--;
+    }
+    if (s->data_count) {
+        s->blkcnt--;
+        boundary_count -= s->data_count--;
+        s->sdmasysad += s->data_count--;
+        s->data_count = 0;
+    }
+
+    n = value = 0;
+    while (s->blkcnt) {
+        if ((boundary_count < (s->blksize & 0x0fff)) && page_aligned) {
+            datacnt = boundary_count;
+            s->data_count = (s->blksize & 0x0fff) - boundary_count;
+        } else {
+            datacnt = s->blksize & 0x0fff;
+        }
+        pos = 0;
+        saved_datacnt = datacnt;
+
+        if (is_read) {
+            while (datacnt) {
+                value |= (uint32_t)sd_read_data(s->card) << (n * 8);
+                n++;
+                if (n == 4) {
+                    cpu_physical_memory_write(s->sdmasysad + pos,
+                            (uint8_t *)(&value), 4);
+                    value = 0;
+                    n = 0;
+                    pos += 4;
+                }
+                datacnt--;
+            }
+        } else {
+            while (datacnt) {
+                if (n == 0) {
+                    cpu_physical_memory_read(s->sdmasysad + pos,
+                            (uint8_t *)(&value), 4);
+                    n = 4;
+                    pos += 4;
+                }
+                sd_write_data(s->card, value & 0xff);
+                value >>= 8;
+                n--;
+                datacnt--;
+            }
+        }
+
+        s->sdmasysad += saved_datacnt;
+        boundary_count -= saved_datacnt;
+        if (s->data_count == 0) {
+            s->blkcnt--;
+        }
+
+        if (boundary_count == 0) {
+            break;
+        }
+    }
+
+    if (s->blkcnt == 0) {
+        sdhc_do_transfer_complete(s);
+    } else {
+        if (s->norintstsen & SDHC_NISEN_DMA) {
+            s->norintsts |= SDHC_NIS_DMA;
+        }
+        qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+    }
+}
+
+/* single block SDMA transfer */
+static void sdhc_sdma_transfer_single_block(Exynos4210SDHCState *s)
+{
+    int is_read = (s->trnmod & SDHC_TRNS_READ) != 0;
+    int n = 0;
+    uint32_t value = 0, pos = 0;
+    uint32_t datacnt = s->blksize & 0x0fff;
+
+    while (datacnt) {
+        if (is_read) {
+            value |= (uint32_t)sd_read_data(s->card) << (n * 8);
+            n++;
+            if (n == 4) {
+                cpu_physical_memory_write(s->sdmasysad + pos,
+                        (uint8_t *)(&value), 4);
+                value = 0;
+                n = 0;
+                pos += 4;
+            }
+        } else {
+            if (n == 0) {
+                cpu_physical_memory_read(s->sdmasysad + pos,
+                        (uint8_t *)(&value), 4);
+                n = 4;
+                pos += 4;
+            }
+            sd_write_data(s->card, value & 0xff);
+            value >>= 8;
+            n--;
+        }
+        datacnt--;
+    }
+
+    if ((s->trnmod & SDHC_TRNS_BLK_CNT_EN) != 0) {
+        s->blkcnt--;
+    }
+
+    sdhc_do_transfer_complete(s);
+}
+
+/* Advanced DMA data transfer */
+static void sdhc_run_adma(Exynos4210SDHCState *s)
+{
+    uint32_t value;
+    int n, pos, length;
+    uint32_t address, datacnt;
+    uint16_t length_table;
+    uint8_t attributes;
+
+    int is_read = (s->trnmod & SDHC_TRNS_READ) != 0;
+    s->admaerr &= ~(SDHC_ADMAERR_FINAL_BLOCK | SDHC_ADMAERR_LENGTH_MISMATCH);
+
+    while (1) {
+        address = length_table = length = attributes = 0;
+        value = n = pos  = 0;
+
+        /* fetch next entry from descriptor table */
+        cpu_physical_memory_read(s->admasysaddr+4, (uint8_t *)(&address), 4);
+        cpu_physical_memory_read(s->admasysaddr+2,
+                (uint8_t *)(&length_table), 2);
+        cpu_physical_memory_read(s->admasysaddr, (uint8_t *)(&attributes), 1);
+        DPRINTF("ADMA loop: addr=0x%x, len=%d, attr=%x\n", address,
+                length_table, attributes);
+
+        if ((attributes & SDHC_ADMA_ATTR_VALID) == 0) {
+            /* Indicate that error occurred in ST_FDS state */
+            s->admaerr &= ~SDHC_ADMAERR_STATE_MASK;
+            s->admaerr |= SDHC_ADMAERR_STATE_ST_FDS;
+
+            /* Generate ADMA error interrupt */
+            if (s->errintstsen & SDHC_EISEN_ADMAERR) {
+                s->errintsts |= SDHC_EIS_ADMAERR;
+                s->norintsts |= SDHC_NIS_ERR;
+            }
+            qemu_set_irq(s->irq, (s->errintsigen & s->errintsts));
+            break;
+        }
+
+        if (length_table == 0) {
+            length = 65536;
+        } else {
+            length = length_table;
+        }
+
+        address &= 0xfffffffc;  /* minimum unit of address is 4 byte */
+
+        switch (attributes & SDHC_ADMA_ATTR_ACT_MASK) {
+        case (SDHC_ADMA_ATTR_ACT_TRAN):  /* data transfer */
+            for (;;) {
+                if (s->data_count) {
+                    datacnt = s->data_count;
+                    s->data_count = 0;
+                } else {
+                    datacnt = s->blksize & 0x0fff;
+                }
+
+                length -= datacnt;
+
+                if (length < 0) {
+                    s->data_count = (uint16_t)(-length);
+                    datacnt += length;
+                }
+
+                while (datacnt) {
+                    if (is_read) {
+                        value |= (uint32_t)sd_read_data(s->card) << (n * 8);
+                        n++;
+                        if (n == 4) {
+                            cpu_physical_memory_write(address + pos,
+                                    (uint8_t *)(&value), 4);
+                            value = 0;
+                            n = 0;
+                            pos += 4;
+                        }
+                    } else {
+                        if (n == 0) {
+                            cpu_physical_memory_read(address + pos,
+                                    (uint8_t *)(&value), 4);
+                            n = 4;
+                            pos += 4;
+                        }
+                        sd_write_data(s->card, value & 0xff);
+                        value >>= 8;
+                        n--;
+                    }
+                    datacnt--;
+                }
+
+                if ((s->trnmod & SDHC_TRNS_BLK_CNT_EN) &&
+                        (!s->data_count)) {
+                    s->blkcnt--;
+                    if (s->blkcnt == 0) {
+                        break;
+                    }
+                }
+
+                if (length == 0 || s->data_count) {
+                    break;
+                }
+            }
+            s->admasysaddr += 8;
+            break;
+        case (SDHC_ADMA_ATTR_ACT_LINK):   /* link to next descriptor table */
+            s->admasysaddr = address;
+            DPRINTF("ADMA link: admasysaddr=0x%x\n", s->admasysaddr);
+            break;
+        default:
+            s->admasysaddr += 8;
+            break;
+        }
+
+        /* ADMA transfer terminates if blkcnt == 0 or by END attribute */
+        if (((s->trnmod & SDHC_TRNS_BLK_CNT_EN) &&
+                    (s->blkcnt == 0)) || (attributes & SDHC_ADMA_ATTR_END)) {
+            DPRINTF("ADMA transfer completed\n");
+            if (length || ((attributes & SDHC_ADMA_ATTR_END) &&
+                (s->trnmod & SDHC_TRNS_BLK_CNT_EN) &&
+                s->blkcnt != 0) || ((s->trnmod & SDHC_TRNS_BLK_CNT_EN) &&
+                s->blkcnt == 0 && (attributes & SDHC_ADMA_ATTR_END) == 0)) {
+                DPRINTF("SD/MMC host ADMA length mismatch\n");
+                s->admaerr |= SDHC_ADMAERR_LENGTH_MISMATCH |
+                        SDHC_ADMAERR_STATE_ST_TFR;
+                if (s->errintstsen & SDHC_EISEN_ADMAERR) {
+                    DPRINTF("Set ADMA error flag\n");
+                    s->errintsts |= SDHC_EIS_ADMAERR;
+                    s->norintsts |= SDHC_NIS_ERR;
+                }
+                qemu_set_irq(s->irq, (s->errintsigen & s->errintsts));
+            }
+
+            s->admaerr |= SDHC_ADMAERR_FINAL_BLOCK;
+            sdhc_do_transfer_complete(s);
+            break;
+        }
+
+        if (attributes & SDHC_ADMA_ATTR_INT) {
+            DPRINTF("ADMA interrupt: admasysaddr=0x%x\n", s->admasysaddr);
+            s->admaerr |= SDHC_ADMAERR_INTRRUPT_STATUS;
+            s->stoped_state = adma_intr;
+            if (s->norintstsen & SDHC_NISEN_DMA) {
+                s->norintsts |= SDHC_NIS_DMA;
+            }
+            qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+            break;
+        }
+    }
+}
+
+/* Perform data transfer according to controller configuration */
+static void sdhc_process_command(Exynos4210SDHCState *s)
+{
+    int is_read = (s->trnmod & SDHC_TRNS_READ) != 0;
+    if (s->trnmod & SDHC_TRNS_DMA) {
+        if (!((is_read && sd_data_ready(s->card)) ||
+                (!is_read && sd_receive_ready(s->card)))) {
+            return;
+        }
+
+        switch (s->hostctl & SDHC_CTRL_DMA_CHECK_MASK) {
+        case SDHC_CTRL_SDMA:
+            if ((s->trnmod & SDHC_TRNS_MULTI) &&
+                    ((s->trnmod & SDHC_TRNS_BLK_CNT_EN) == 0
+                            || s->blkcnt == 0)) {
+                break;
+            }
+
+            if ((s->blkcnt == 1) || !(s->trnmod & SDHC_TRNS_MULTI)) {
+                sdhc_sdma_transfer_single_block(s);
+            } else {
+                s->data_count = 0;  /* # of bytes from last transfer */
+                sdhc_sdma_transfer_multi_blocks(s);
+            }
+            break;
+        case SDHC_CTRL_ADMA2_32:
+            s->data_count = 0;  /* # of bytes from last transfer */
+            sdhc_run_adma(s);
+            break;
+        default:
+            DPRINTF("Unsupported DMA type\n");
+            break;
+        }
+    } else {
+        if (is_read && sd_data_ready(s->card)) {
+                s->prnsts |= SDHC_DOING_READ | SDHC_DATA_INHIBIT |
+                        SDHC_DAT_LINE_ACTIVE;
+                s->data_count = 0;
+                qemu_mod_timer(s->read_buffer_timer,
+                    qemu_get_clock_ns(vm_clock) + READ_BUFFER_DELAY);
+        } else if (!is_read && sd_receive_ready(s->card)) {
+                s->prnsts |= SDHC_DOING_WRITE | SDHC_DAT_LINE_ACTIVE |
+                        SDHC_SPACE_AVAILABLE | SDHC_DATA_INHIBIT;
+                s->data_count = 0;
+                qemu_mod_timer(s->write_buffer_timer,
+                    qemu_get_clock_ns(vm_clock) + WRITE_BUFFER_DELAY);
+        }
+    }
+}
+
+/* Read byte from SD/MMC host controller registers */
+static uint32_t exynos4210_sdhc_read_1(Exynos4210SDHCState *s,
+        target_phys_addr_t offset)
+{
+    switch (offset) {
+    case SDHC_HOSTCTL:
+        return s->hostctl;
+    case SDHC_PWRCON:
+        return s->pwrcon;
+    case SDHC_BLKGAP:
+        return s->blkgap;
+    case SDHC_WAKCON:
+        return s->wakcon;
+    case SDHC_TIMEOUTCON:
+        return s->timeoutcon;
+    case SDHC_SWRST:
+        return 0;
+    case SDHC_RSPREG0 + 0x3:
+        return (uint8_t)(s->rspreg[0] >> 24);
+    case SDHC_RSPREG1 + 0x3:
+        return (uint8_t)(s->rspreg[1] >> 24);
+    case SDHC_RSPREG2 + 0x3:
+        return (uint8_t)(s->rspreg[2] >> 24);
+    default:
+        DPRINTF("bad 1 byte read offset " TARGET_FMT_plx "\n", offset);
+        return 0xBAADBAAD;
+    }
+}
+
+/* Read two bytes from SD/MMC host controller registers */
+static uint32_t exynos4210_sdhc_read_2(Exynos4210SDHCState *s,
+        target_phys_addr_t offset)
+{
+    switch (offset) {
+    case SDHC_BLKSIZE:
+        return s->blksize;
+    case SDHC_BLKCNT:
+        return s->blkcnt;
+    case SDHC_TRNMOD:
+        return s->trnmod;
+    case SDHC_CMDREG:
+        return s->cmdreg;
+    case SDHC_CLKCON:
+        return s->clkcon;
+    case SDHC_NORINTSTS:
+        qemu_set_irq(s->irq, 0);
+        return s->norintsts;
+    case SDHC_ERRINTSTS:
+        qemu_set_irq(s->irq, 0);
+        return s->errintsts;
+    case SDHC_NORINTSTSEN:
+        return s->norintstsen;
+    case SDHC_ERRINTSTSEN:
+        return s->errintstsen;
+    case SDHC_NORINTSIGEN:
+        return s->norintsigen;
+    case SDHC_ERRINTSIGEN:
+        return s->errintsigen;
+    case SDHC_ACMD12ERRSTS:
+        return s->acmd12errsts;
+    case SDHC_SLOT_INT_STATUS:
+        return 0;
+    case SDHC_FEAER: case SDHC_FEERR:
+        DPRINTF("Reading from WO register\n");
+        return 0;
+    case SDHC_HCVER:
+        return 0x2401;  /* SD Host Specification Version 2.0 */
+    default:
+        DPRINTF("bad 2 byte read offset " TARGET_FMT_plx "\n", offset);
+        return 0xBAADBAAD;
+    }
+}
+
+/* MMC read 4 bytes function */
+static uint32_t exynos4210_sdhc_read_4(Exynos4210SDHCState *s,
+        target_phys_addr_t offset)
+{
+    switch (offset) {
+    case SDHC_SYSAD:
+        return s->sdmasysad;
+    case SDHC_ARGUMENT:
+        return s->argument;
+    case SDHC_RSPREG0:
+        return s->rspreg[0];
+    case SDHC_RSPREG1:
+        return s->rspreg[1];
+    case SDHC_RSPREG2:
+        return s->rspreg[2];
+    case SDHC_RSPREG3:
+        return s->rspreg[3];
+    case SDHC_BDATA:
+        return sdhc_read_dataport(s);
+    case SDHC_PRNSTS:
+        return s->prnsts;
+    case SDHC_NORINTSTS:
+        qemu_set_irq(s->irq, 0);
+        return (s->errintsts << 16) | (s->norintsts);
+    case SDHC_NORINTSTSEN:
+        return (s->errintstsen << 16) | s->norintstsen;
+    case SDHC_NORINTSIGEN:
+        return (s->errintsigen << 16) | s->norintsigen;
+    case SDHC_CAPAREG:
+        return s->capareg;
+    case SDHC_MAXCURR:
+        return s->maxcurr;
+    case SDHC_ADMAERR:
+        return s->admaerr;
+    case SDHC_ADMASYSADDR:
+        return s->admasysaddr;
+    case SDHC_CONTROL2:
+        return s->control2;
+    case SDHC_CONTROL3:
+        return s->control3;
+    case SDHC_CONTROL4:
+        return 0;  /* makes no sense in emulation so return 0 */
+    default:
+        DPRINTF("bad 4 byte read offset " TARGET_FMT_plx "\n", offset);
+        return 0xBAADBAAD;
+    }
+}
+
+/* MMC write (byte) function */
+static void
+exynos4210_sdhc_write_1(Exynos4210SDHCState *s, target_phys_addr_t offset,
+        uint32_t value)
+{
+    switch (offset) {
+    case SDHC_HOSTCTL:
+        s->hostctl = value & 0x3F;
+        break;
+    case SDHC_PWRCON:
+        s->pwrcon = value & 0x0F;
+        break;
+    case SDHC_BLKGAP:
+        if (value & 0x0C) {
+            error_report("SDHC: ReadWait & IntAtBlockGap not implemented\n");
+        }
+
+        if ((value & SDHC_STOP_AT_GAP_REQ) &&
+                (s->blkgap & SDHC_STOP_AT_GAP_REQ)) {
+            break;
+        }
+        s->blkgap = value & (SDHC_STOP_AT_GAP_REQ);
+
+        if ((value & SDHC_CONTINUE_REQ) && s->stoped_state &&
+                (s->blkgap & SDHC_STOP_AT_GAP_REQ) == 0) {
+            if (s->stoped_state == gap_read) {
+                s->prnsts |= SDHC_DAT_LINE_ACTIVE | SDHC_DOING_READ;
+                qemu_mod_timer(s->read_buffer_timer,
+                    qemu_get_clock_ns(vm_clock) + READ_BUFFER_DELAY);
+            } else {
+            s->prnsts |= SDHC_DAT_LINE_ACTIVE | SDHC_DOING_WRITE;
+            qemu_mod_timer(s->write_buffer_timer,
+                qemu_get_clock_ns(vm_clock) + READ_BUFFER_DELAY);
+            }
+            s->stoped_state = not_stoped;
+        } else if (!s->stoped_state && (value & SDHC_STOP_AT_GAP_REQ)) {
+            if (s->prnsts & SDHC_DOING_READ) {
+                s->stoped_state = gap_read;
+            } else if (s->prnsts & SDHC_DOING_WRITE) {
+                s->stoped_state = gap_write;
+            }
+        }
+        break;
+    case SDHC_WAKCON:
+        s->wakcon = value & 0x07;
+        s->wakcon &= ~(value & SDHC_STAWAKEUP);
+        break;
+    case SDHC_TIMEOUTCON:
+        s->timeoutcon = value & 0x0F;
+        break;
+    case SDHC_SWRST:
+        switch (value) {
+        case SDHC_RESET_ALL:
+            exynos4210_sdhc_reset(&s->busdev.qdev);
+            break;
+        case SDHC_RESET_CMD:
+            s->prnsts &= ~SDHC_CMD_INHIBIT;
+            s->norintsts &= ~SDHC_NIS_CMDCMP;
+            break;
+        case SDHC_RESET_DATA:
+            s->data_count = 0;
+            qemu_del_timer(s->read_buffer_timer);
+            qemu_del_timer(s->write_buffer_timer);
+            s->prnsts &= ~(SDHC_SPACE_AVAILABLE | SDHC_DATA_AVAILABLE |
+                    SDHC_DOING_READ | SDHC_DOING_WRITE |
+                    SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE);
+            s->blkgap &= ~(SDHC_STOP_AT_GAP_REQ | SDHC_CONTINUE_REQ);
+            s->stoped_state = not_stoped;
+            s->norintsts &= ~(SDHC_NIS_WBUFRDY | SDHC_NIS_RBUFRDY |
+                    SDHC_NIS_DMA | SDHC_NIS_TRSCMP | SDHC_NIS_BLKGAP);
+            break;
+        }
+        break;
+    case (SDHC_NORINTSTS+1):
+        if (s->norintstsen & SDHC_NISEN_CARDINT) {
+            value &= ~1;
+        }
+        s->norintsts &= (s->norintsts & 0x8000) | (~((value << 8) & 0xFF00));
+        break;
+    case (SDHC_NORINTSTS):
+        s->norintsts &= ~(((uint16_t)value) & 0x00FF);
+        break;
+    default:
+        DPRINTF("bad 1 byte write offset " TARGET_FMT_plx "\n", offset);
+        break;
+    }
+}
+
+/* MMC write 2 bytes function */
+static void
+exynos4210_sdhc_write_2(Exynos4210SDHCState *s, target_phys_addr_t offset,
+        uint32_t value)
+{
+    switch (offset) {
+    case SDHC_BLKSIZE:
+        if (!(s->prnsts & (SDHC_DOING_READ | SDHC_DOING_WRITE))) {
+            s->blksize = value & 0x7FFF;
+        }
+        break;
+    case SDHC_BLKCNT:
+        if (!(s->prnsts & (SDHC_DOING_READ | SDHC_DOING_WRITE))) {
+            s->blkcnt = value;
+        }
+        break;
+    case SDHC_TRNMOD:
+        if (value & (SDHC_TRNS_BOOTCMD | SDHC_TRNS_BOOTACK |
+                SDHC_TRNS_CEATA)) {
+            error_report("QEMU SDHC: CEATA mode not implemented\n");
+        }
+        if ((s->capareg & SDHC_CAN_DO_DMA) ==  0) {
+            value &= ~SDHC_TRNS_DMA;
+        }
+        s->trnmod = value & 0x0037;
+        break;
+    case SDHC_CMDREG: /* Command */
+        s->cmdreg = value & 0x3FFB;
+
+        if (((s->control2 & SDHC_SDOPSIGPC) || (s->control2 & SDHC_SDINPSIGPC))
+                && !(s->pwrcon & SDHC_POWER_ON)) {
+            DPRINTF("Can't issue command with power off\n");
+            break;
+        }
+
+        if ((s->clkcon & SDHC_CLOCK_CHK_MASK) != SDHC_CLOCK_CHK_MASK) {
+            DPRINTF("Can't issue command with clock off\n");
+            break;
+        }
+
+        if ((value & SDHC_CMD_TYPE_MASK) == SDHC_CMD_RESUME_MASK ||
+                (value & SDHC_CMD_TYPE_MASK) == SDHC_CMD_SUSPEND_MASK) {
+            DPRINTF("sdhc: suspend/resume commands not implemented\n");
+            break;
+        }
+
+        if ((s->stoped_state || (s->prnsts & SDHC_DATA_INHIBIT)) &&
+        ((value & SDHC_CMD_DATA_PRESENT) || ((value & SDHC_CMD_RSP_WITH_BUSY) &&
+                    !(value & SDHC_CMD_ABORT_MASK)))) {
+            DPRINTF("Can't issue command which uses DAT line\n");
+            break;
+        }
+
+        if ((value & SDHC_CMD_TYPE_MASK) == SDHC_CMD_ABORT_MASK &&
+            ((s->prnsts & SDHC_DOING_READ) || (s->prnsts & SDHC_DOING_WRITE))) {
+            DPRINTF("ABORT command\n");
+            qemu_del_timer(s->read_buffer_timer);   /* stop reading data */
+            qemu_del_timer(s->write_buffer_timer);  /* stop writing data */
+            qemu_mod_timer(s->transfer_complete_timer,
+                    qemu_get_clock_ns(vm_clock) + 1);
+        }
+
+        sdhc_send_command(s);
+        sdhc_raise_response_recieved_irq(s);
+
+        if (s->blksize == 0) {
+            break;
+        }
+        sdhc_process_command(s);
+        break;
+    case SDHC_CLKCON:
+        s->clkcon = value;
+        if (SDHC_CLOCK_INT_EN & s->clkcon) {
+            s->clkcon |= SDHC_CLOCK_INT_STABLE;
+        } else {
+            s->clkcon &= ~SDHC_CLOCK_INT_STABLE;
+        }
+        if (SDHC_CLOCK_CARD_EN & s->clkcon) {
+            s->clkcon |= SDHC_CLOCK_EXT_STABLE;
+        } else {
+            s->clkcon &= ~SDHC_CLOCK_EXT_STABLE;
+        }
+        break;
+    case SDHC_NORINTSTS:
+        if (s->norintstsen & SDHC_NISEN_CARDINT) {
+            value &= ~SDHC_NIS_CARDINT;
+        }
+        s->norintsts &= (s->norintsts & 0x8000) | (uint16_t)(~(value & 0xFFFF));
+        break;
+    case SDHC_ERRINTSTS:
+        s->errintsts &= ~((value & 0x077F) | 0xF880);
+        s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) :
+                (s->norintsts &= ~SDHC_NIS_ERR);
+        break;
+    case SDHC_NORINTSTSEN:
+        s->norintstsen = value & 0x7FFF;
+        break;
+    case SDHC_ERRINTSTSEN:
+        s->errintstsen = value & 0x07FF;
+        break;
+    case SDHC_NORINTSIGEN:
+        s->norintsigen = value & 0x7FFF;
+        break;
+    case SDHC_ERRINTSIGEN:
+        s->errintsigen = value & 0x07FF;
+        break;
+    case SDHC_ACMD12ERRSTS: case SDHC_FEAER: case SDHC_HCVER:
+        break;
+    case SDHC_FEERR:
+        s->norintsts |= (value & 0x073F) & s->norintstsen;
+        qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+        break;
+    default:
+        DPRINTF("bad 2 byte write offset " TARGET_FMT_plx "\n", offset);
+        break;
+    }
+}
+
+/* MMC write 4 bytes function */
+static void
+exynos4210_sdhc_write_4(Exynos4210SDHCState *s, target_phys_addr_t offset,
+                                 uint32_t value)
+{
+    switch (offset) {
+    case SDHC_SYSAD:
+        s->sdmasysad = value;
+        if ((s->prnsts & (SDHC_DOING_READ | SDHC_DOING_WRITE))
+                && (s->blkcnt != 0) && (s->blksize != 0) &&
+                (s->hostctl & SDHC_CTRL_DMA_CHECK_MASK) == SDHC_CTRL_SDMA) {
+            sdhc_sdma_transfer_multi_blocks(s);
+        }
+        break;
+    case SDHC_ARGUMENT:
+        s->argument = value;
+        break;
+    case SDHC_BDATA:
+        sdhc_write_dataport(s, value);
+        break;
+    case SDHC_NORINTSTS:
+        if (s->norintstsen & SDHC_NISEN_CARDINT) {
+            value &= ~SDHC_NIS_CARDINT;
+        }
+        s->norintsts &= (s->norintsts & 0x8000) | (uint16_t)(~(value & 0xFFFF));
+
+        s->errintsts &= ~(((value >> 16) & 0x077F) | 0xF880);
+        s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) :
+                (s->norintsts &= ~SDHC_NIS_ERR);
+        break;
+    case SDHC_NORINTSTSEN:
+        s->norintstsen = (uint16_t)(value & 0x7FFF);
+        s->errintstsen = (uint16_t)((value >> 16) & 0x07FF);
+        break;
+    case SDHC_NORINTSIGEN:
+        s->norintsigen = (uint16_t)(value & 0x7FFF);
+        s->errintsigen = (uint16_t)((value >> 16) & 0x07FF);
+        break;
+    case SDHC_CAPAREG:
+        if (!(s->control2 & SDHC_HWINITFIN)) {
+            s->capareg = value & 0x07EB3FBF;
+        }
+        break;
+    case SDHC_MAXCURR:
+        if (!(s->control2 & SDHC_HWINITFIN)) {
+            s->maxcurr = value & 0x00FFFFFF;
+        }
+        break;
+    case SDHC_ADMAERR:
+        s->admaerr = ((s->admaerr & SDHC_ADMAERR_FINAL_BLOCK) |
+                (value & 0x00000007)) & 0x00000507;
+        s->admaerr &= ~(value & SDHC_ADMAERR_INTRRUPT_STATUS);
+        if (value & SDHC_ADMAERR_CONTINUE_REQUEST) {
+            s->stoped_state = not_stoped;
+            sdhc_run_adma(s);
+        }
+        break;
+    case SDHC_ADMASYSADDR:
+        s->admasysaddr = value;
+        break;
+    case SDHC_CONTROL2:
+        s->control2 = value & 0xDF00DFFB;
+        break;
+    case SDHC_CONTROL3:
+        s->control3 = value;
+        break;
+    case SDHC_RSPREG0: case SDHC_RSPREG1: case SDHC_RSPREG2:
+    case SDHC_RSPREG3: case SDHC_PRNSTS: case SDHC_CONTROL4:
+        /* Nothing for emulation */
+        break;
+    default:
+        DPRINTF("bad 4 byte write offset " TARGET_FMT_plx "\n", offset);
+        break;
+    }
+}
+
+static uint64_t
+exynos4210_sdhc_read(void *opaque, target_phys_addr_t offset, unsigned size)
+{
+    Exynos4210SDHCState *s = (Exynos4210SDHCState *)opaque;
+
+    switch (size) {
+    case (1):
+        return exynos4210_sdhc_read_1(s, offset);
+    case (2):
+        return exynos4210_sdhc_read_2(s, offset);
+    case (4):
+        return exynos4210_sdhc_read_4(s, offset);
+    }
+    return 0xBAADBAADull;
+}
+
+static void
+exynos4210_sdhc_write(void *opaque, target_phys_addr_t offset, uint64_t val,
+        unsigned size)
+{
+    Exynos4210SDHCState *s = (Exynos4210SDHCState *)opaque;
+
+    switch (size) {
+    case (1):
+        exynos4210_sdhc_write_1(s, offset, (uint32_t)val);
+        break;
+    case (2):
+        exynos4210_sdhc_write_2(s, offset, (uint32_t)val);
+        break;
+    case (4):
+        exynos4210_sdhc_write_4(s, offset, (uint32_t)val);
+        break;
+    }
+}
+
+static const MemoryRegionOps exynos4210_sdhc_mmio_ops = {
+    .read = exynos4210_sdhc_read,
+    .write = exynos4210_sdhc_write,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 4,
+        .unaligned = false
+    },
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription exynos4210_sdhc_vmstate = {
+    .name = "exynos4210.sdhc",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields      = (VMStateField[]) {
+        VMSTATE_UINT8(stoped_state, Exynos4210SDHCState),
+        VMSTATE_UINT32_ARRAY(fifo_buffer, Exynos4210SDHCState,
+                (SDHC_CAPAB_MAXBLOCKLENGTH / TARGET_WORD_SIZE)),
+        VMSTATE_UINT16(data_count, Exynos4210SDHCState),
+        VMSTATE_UINT32(sdmasysad, Exynos4210SDHCState),
+        VMSTATE_UINT16(blksize, Exynos4210SDHCState),
+        VMSTATE_UINT16(blkcnt, Exynos4210SDHCState),
+        VMSTATE_UINT32(argument, Exynos4210SDHCState),
+        VMSTATE_UINT16(trnmod, Exynos4210SDHCState),
+        VMSTATE_UINT16(cmdreg, Exynos4210SDHCState),
+        VMSTATE_UINT32_ARRAY(rspreg, Exynos4210SDHCState, 4),
+        VMSTATE_UINT32(prnsts, Exynos4210SDHCState),
+        VMSTATE_UINT8(hostctl, Exynos4210SDHCState),
+        VMSTATE_UINT8(pwrcon, Exynos4210SDHCState),
+        VMSTATE_UINT8(blkgap, Exynos4210SDHCState),
+        VMSTATE_UINT8(wakcon, Exynos4210SDHCState),
+        VMSTATE_UINT16(clkcon, Exynos4210SDHCState),
+        VMSTATE_UINT8(timeoutcon, Exynos4210SDHCState),
+        VMSTATE_UINT16(norintsts, Exynos4210SDHCState),
+        VMSTATE_UINT16(errintsts, Exynos4210SDHCState),
+        VMSTATE_UINT16(norintstsen, Exynos4210SDHCState),
+        VMSTATE_UINT16(errintstsen, Exynos4210SDHCState),
+        VMSTATE_UINT16(norintsigen, Exynos4210SDHCState),
+        VMSTATE_UINT16(errintsigen, Exynos4210SDHCState),
+        VMSTATE_UINT16(acmd12errsts, Exynos4210SDHCState),
+        VMSTATE_UINT32(capareg, Exynos4210SDHCState),
+        VMSTATE_UINT32(maxcurr, Exynos4210SDHCState),
+        VMSTATE_UINT32(admaerr, Exynos4210SDHCState),
+        VMSTATE_UINT32(admasysaddr, Exynos4210SDHCState),
+        VMSTATE_UINT32(control2, Exynos4210SDHCState),
+        VMSTATE_UINT32(control3, Exynos4210SDHCState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static int exynos4210_sdhc_init(SysBusDevice *dev)
+{
+    Exynos4210SDHCState *s = FROM_SYSBUS(Exynos4210SDHCState, dev);
+    DriveInfo *bd;
+
+    sysbus_init_irq(dev, &s->irq);
+    memory_region_init_io(&s->iomem, &exynos4210_sdhc_mmio_ops, s,
+            "exynos4210.sdhc", SDHC_REG_SIZE);
+    sysbus_init_mmio(dev, &s->iomem);
+    bd = drive_get_next(IF_SD);
+
+    if ((bd == NULL)) {
+        s->card = NULL;
+        DPRINTF("s->card = NULL\n");
+    } else {
+        s->eject = qemu_allocate_irqs(sdhc_insert_eject, s, 1)[0];
+        DPRINTF("name = %s, sectors = %ld\n",
+                bd->bdrv->device_name, bd->bdrv->total_sectors);
+        s->card = sd_init(bd->bdrv, 0);
+        sd_set_cb(s->card, NULL, s->eject);
+    }
+
+    s->insert_timer =
+        qemu_new_timer(vm_clock, SCALE_NS, sdhc_raise_insertion_irq, s);
+
+    s->read_buffer_timer =
+        qemu_new_timer(vm_clock, SCALE_NS, sdhc_read_block_from_card, s);
+
+    s->write_buffer_timer =
+        qemu_new_timer(vm_clock, SCALE_NS, sdhc_write_block_to_card, s);
+
+    s->transfer_complete_timer =
+        qemu_new_timer(vm_clock, SCALE_NS, sdhc_raise_transfer_complete_irq, s);
+
+    return 0;
+}
+
+static SysBusDeviceInfo exynos4210_sdhc_info = {
+    .init = exynos4210_sdhc_init,
+    .qdev.name  = "exynos4210.sdhc",
+    .qdev.size  = sizeof(Exynos4210SDHCState),
+    .qdev.vmsd  = &exynos4210_sdhc_vmstate,
+    .qdev.reset = exynos4210_sdhc_reset,
+};
+
+static void sdhc_register_devices(void)
+{
+    sysbus_register_withprop(&exynos4210_sdhc_info);
+}
+
+device_init(sdhc_register_devices)
-- 
1.7.4.1

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Qemu-devel] [PATCH 15/15] ARM: exynos4210: added display controller implementation
  2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
                   ` (13 preceding siblings ...)
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 14/15] ARM: exynos4210: added SD/MMC host controller Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
  14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: m.kozlov, Mitsyanko Igor, d.solodkiy, Evgeny Voevodin

From: Mitsyanko Igor <i.mitsyanko@samsung.com>

Exynos4210 display controller (FIMD) supports up to 5 hardware windows,
along with alpha and chromakey blending capabilities.

Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 Makefile.target      |    2 +-
 hw/exynos4210.c      |   11 +
 hw/exynos4210_fimd.c | 1737 ++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1749 insertions(+), 1 deletions(-)
 create mode 100644 hw/exynos4210_fimd.c

diff --git a/Makefile.target b/Makefile.target
index a5d67c9..b4cd185 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -346,7 +346,7 @@ obj-arm-y += versatile_pci.o
 obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
 obj-arm-y += exynos4210.o exynos4210_cmu.o exynos4210_uart.o exynos4210_gic.o \
              exynos4210_combiner.o exynos4210_pwm.o exynos4210_mct.o \
-             exynos4210_sdhc.o
+             exynos4210_sdhc.o exynos4210_fimd.o
 obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
 obj-arm-y += pl061.o
 obj-arm-y += arm-semi.o
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index 62c90e9..2f3a5be 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -101,6 +101,10 @@
 #define EXYNOS4210_SDHC2_BASE_ADDR          0x12530000
 #define EXYNOS4210_SDHC3_BASE_ADDR          0x12540000
 
+/* Display controllers (FIMD) */
+#define EXYNOS4210_FIMD0_BASE_ADDR          0x11C00000
+#define EXYNOS4210_FIMD1_BASE_ADDR          0x12000000
+
 /* Secondary CPU polling address to get loader start from */
 #define EXYNOS4210_SECOND_CPU_BOOTREG       0x10020814
 
@@ -483,6 +487,13 @@ static void exynos4210_init(ram_addr_t ram_size,
         }
     }
 
+    /*** Display controller (FIMD) ***/
+    sysbus_create_varargs("exynos4210.fimd", EXYNOS4210_FIMD0_BASE_ADDR,
+            irq_table[exynos4210_get_irq(11, 0)],
+            irq_table[exynos4210_get_irq(11, 1)],
+            irq_table[exynos4210_get_irq(11, 2)],
+            NULL);
+
     /*** Load kernel ***/
 
     exynos4210_binfo.ram_size = ram_size;
diff --git a/hw/exynos4210_fimd.c b/hw/exynos4210_fimd.c
new file mode 100644
index 0000000..dc26b8e
--- /dev/null
+++ b/hw/exynos4210_fimd.c
@@ -0,0 +1,1737 @@
+/*
+ * Samsung exynos4210 Display Controller (FIMD)
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ * Based on LCD controller for Samsung S5PC1xx-based board emulation
+ * by Kirill Batuzov <batuzovk@ispras.ru>
+ *
+ * Contributed by Mitsyanko Igor <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, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "qemu-common.h"
+#include "cpu-all.h"
+#include "sysbus.h"
+#include "console.h"
+#include "pixel_ops.h"
+
+/* Debug messages configuration */
+#define EXY_FIMD_DEBUG              0
+#define EXY_FIMD_MODE_TRACE         0
+
+#if EXY_FIMD_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 EXY_FIMD_DEBUG == 1
+    #define DPRINT_L1(fmt, args...) \
+        do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+    #define DPRINT_L2(fmt, args...)       do { } while (0)
+    #define DPRINT_ERROR(fmt, args...)  \
+        do {fprintf(stderr, "QEMU FIMD ERROR: "fmt, ## args); } while (0)
+#else
+    #define DPRINT_L1(fmt, args...) \
+        do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+    #define DPRINT_L2(fmt, args...) \
+        do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+    #define DPRINT_ERROR(fmt, args...)  \
+        do {fprintf(stderr, "QEMU FIMD ERROR: "fmt, ## args); } while (0)
+#endif
+
+#if EXY_FIMD_MODE_TRACE == 0
+    #define DPRINT_TRACE(fmt, args...)        do { } while (0)
+#else
+    #define DPRINT_TRACE(fmt, args...)        \
+        do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+#endif
+
+#define NUM_OF_WINDOWS              5
+#define FIMD_REGS_SIZE              0x4114
+
+/* Video main control registers */
+#define FIMD_VIDCON0                0x0000
+#define FIMD_VIDCON1                0x0004
+#define FIMD_VIDCON2                0x0008
+#define FIMD_VIDCON3                0x000C
+#define FIMD_VIDCON0_ENVID_F        (1 << 0)
+#define FIMD_VIDCON0_ENVID          (1 << 1)
+#define FIMD_VIDCON0_ENVID_MASK     ((1 << 0) | (1 << 1))
+#define FIMD_VIDCON1_ROMASK         0x07FFE000
+
+/* Video time control registers */
+#define FIMD_VIDTCON_START          0x10
+#define FIMD_VIDTCON_END            0x1C
+#define FIMD_VIDTCON2_SIZE_MASK     0x07FF
+#define FIMD_VIDTCON2_HOR_SHIFT     0
+#define FIMD_VIDTCON2_VER_SHIFT     11
+
+/* Window control registers */
+#define FIMD_WINCON_START           0x0020
+#define FIMD_WINCON_END             0x0030
+#define FIMD_WINCON_ROMASK          0x82200000
+#define FIMD_WINCON_ENWIN           (1 << 0)
+#define FIMD_WINCON_BLD_PIX         (1 << 6)
+#define FIMD_WINCON_ALPHA_MUL       (1 << 7)
+#define FIMD_WINCON_ALPHA_SEL       (1 << 1)
+#define FIMD_WINCON_SWAP            0x078000
+#define FIMD_WINCON_SWAP_SHIFT      15
+#define FIMD_WINCON_SWAP_WORD       0x1
+#define FIMD_WINCON_SWAP_HWORD      0x2
+#define FIMD_WINCON_SWAP_BYTE       0x4
+#define FIMD_WINCON_SWAP_BITS       0x8
+#define FIMD_WINCON_BUFSTATUS_L     (1 << 21)
+#define FIMD_WINCON_BUFSTATUS_H     (1 << 31)
+#define FIMD_WINCON_BUFSTATUS       ((1 << 21) | (1 << 31))
+#define FIMD_WINCON_BUF0_STAT       ((0 << 21) | (0 << 31))
+#define FIMD_WINCON_BUF1_STAT       ((1 << 21) | (0 << 31))
+#define FIMD_WINCON_BUF2_STAT       ((0 << 21) | (1 << 31))
+#define FIMD_WINCON_BUFSELECT       ((1 << 20) | (1 << 30))
+#define FIMD_WINCON_BUF0_SEL        ((0 << 20) | (0 << 30))
+#define FIMD_WINCON_BUF1_SEL        ((1 << 20) | (0 << 30))
+#define FIMD_WINCON_BUF2_SEL        ((0 << 20) | (1 << 30))
+#define FIMD_WINCON_BUFMODE         (1 << 14)
+#define IS_PALETTIZED_MODE(w)       (w->wincon & 0xC)
+#define WIN_BPP_MODE(w)             ((w->wincon >> 2) & 0xF)
+
+/* Shadow control register */
+#define FIMD_SHADOWCON              0x0034
+#define FIMD_WIN_NOT_PROTECTED(s, w)    (!(s->shadowcon & (1 << (10 + (w)))))
+/* Channel mapping control register */
+#define FIMD_WINCHMAP               0x003C
+
+/* Window position control registers */
+#define FIMD_VIDOSD_START           0x0040
+#define FIMD_VIDOSD_END             0x0088
+#define FIMD_VIDOSD_COORD_MASK      0x07FF
+#define FIMD_VIDOSD_HOR_SHIFT       11
+#define FIMD_VIDOSD_VER_SHIFT       0
+#define FIMD_VIDOSD_ALPHA_AEN0      0xFFF000
+#define FIMD_VIDOSD_AEN0_SHIFT      12
+#define FIMD_VIDOSD_ALPHA_AEN1      0x000FFF
+
+/* Frame buffer address registers */
+#define FIMD_VIDWADD0_START         0x0A0
+#define FIMD_VIDWADD0_END           0x0C4
+#define FIMD_VIDWADD1_START         0x0D0
+#define FIMD_VIDWADD1_END           0x0F4
+#define FIMD_VIDWADD2_START         0x100
+#define FIMD_VIDWADD2_END           0x110
+#define FIMD_VIDWADD2_PAGEWIDTH     0x1FFF
+#define FIMD_VIDWADD2_OFFSIZE       0x1FFF
+#define FIMD_VIDWADD2_OFFSIZE_SHIFT 13
+
+/* Video interrupt control registers */
+#define FIMD_VIDINTCON0             0x130
+#define FIMD_VIDINTCON1             0x134
+
+/* Window color key registers */
+#define FIMD_WKEYCON_START          0x140
+#define FIMD_WKEYCON_END            0x15C
+#define FIMD_WKEYCON0_COMPKEY       0x00FFFFFF
+#define FIMD_WKEYCON0_CTL_SHIFT     24
+#define FIMD_WKEYCON0_DIRCON        (1 << 24)
+#define FIMD_WKEYCON0_KEYEN         (1 << 25)
+#define FIMD_WKEYCON0_KEYBLEN       (1 << 26)
+/* Window color key alpha control register */
+#define FIMD_WKEYALPHA_START        0x160
+#define FIMD_WKEYALPHA_END          0x16C
+
+/* Dithering control register */
+#define FIMD_DITHMODE               0x170
+
+/* Window alpha control registers */
+#define FIMD_VIDALPHA_ALPHA_LOWER   0x000F0F0F
+#define FIMD_VIDALPHA_ALPHA_UPPER   0x00F0F0F0
+#define FIMD_VIDWALPHA_START        0x21C
+#define FIMD_VIDWALPHA_END          0x240
+
+/* Window color map registers */
+#define FIMD_WINMAP_START           0x180
+#define FIMD_WINMAP_END             0x190
+#define FIMD_WINMAP_EN              (1 << 24)
+#define FIMD_WINMAP_COLOR_MASK      0x00FFFFFF
+
+/* Window palette control registers */
+#define FIMD_WPALCON_HIGH           0x019C
+#define FIMD_WPALCON_LOW            0x01A0
+#define FIMD_WPALCON_UPDATEEN       (1 << 9)
+#define FIMD_WPAL_W0PAL_L           0x07
+#define FIMD_WPAL_W0PAL_L_SHT        0
+#define FIMD_WPAL_W1PAL_L           0x07
+#define FIMD_WPAL_W1PAL_L_SHT       3
+#define FIMD_WPAL_W2PAL_L           0x01
+#define FIMD_WPAL_W2PAL_L_SHT       6
+#define FIMD_WPAL_W2PAL_H           0x06
+#define FIMD_WPAL_W2PAL_H_SHT       8
+#define FIMD_WPAL_W3PAL_L           0x01
+#define FIMD_WPAL_W3PAL_L_SHT       7
+#define FIMD_WPAL_W3PAL_H           0x06
+#define FIMD_WPAL_W3PAL_H_SHT       12
+#define FIMD_WPAL_W4PAL_L           0x01
+#define FIMD_WPAL_W4PAL_L_SHT       8
+#define FIMD_WPAL_W4PAL_H           0x06
+#define FIMD_WPAL_W4PAL_H_SHT       16
+
+/* Trigger control registers */
+#define FIMD_TRIGCON                0x01A4
+#define FIMD_TRIGCON_ROMASK         0x00000004
+
+/* LCD I80 Interface Control */
+#define FIMD_I80IFCON_START         0x01B0
+#define FIMD_I80IFCON_END           0x01BC
+/* Color gain control register */
+#define FIMD_COLORGAINCON           0x01C0
+/* LCD i80 Interface Command Control */
+#define FIMD_LDI_CMDCON0            0x01D0
+#define FIMD_LDI_CMDCON1            0x01D4
+/* I80 System Interface Manual Command Control */
+#define FIMD_SIFCCON0               0x01E0
+#define FIMD_SIFCCON2               0x01E8
+
+/* Hue Control Registers */
+#define FIMD_HUECOEFCR_START       0x01EC
+#define FIMD_HUECOEFCR_END         0x01F4
+#define FIMD_HUECOEFCB_START       0x01FC
+#define FIMD_HUECOEFCB_END         0x0208
+#define FIMD_HUEOFFSET             0x020C
+
+/* Video interrupt control registers */
+#define FIMD_VIDINT_INTFIFOPEND     (1 << 0)
+#define FIMD_VIDINT_INTFRMPEND      (1 << 1)
+#define FIMD_VIDINT_INTI80PEND      (1 << 2)
+#define FIMD_VIDINT_INTEN           (1 << 0)
+#define FIMD_VIDINT_INTFIFOEN       (1 << 1)
+#define FIMD_VIDINT_INTFRMEN        (1 << 12)
+#define FIMD_VIDINT_I80IFDONE       (1 << 17)
+
+/* Window blend equation control registers */
+#define FIMD_BLENDEQ_START          0x0244
+#define FIMD_BLENDEQ_END            0x0250
+#define FIMD_BLENDCON               0x0260
+#define FIMD_ALPHA_8BIT             (1 << 0)
+#define FIMD_BLENDEQ_COEF_MASK      0xF
+
+/* Window RTQOS Control Registers */
+#define FIMD_WRTQOSCON_START       0x0264
+#define FIMD_WRTQOSCON_END         0x0274
+
+/* LCD I80 Interface Command */
+#define FIMD_I80IFCMD_START        0x0280
+#define FIMD_I80IFCMD_END          0x02AC
+
+/* Shadow windows control registers */
+#define FIMD_SHD_ADD0_START    0x40A0
+#define FIMD_SHD_ADD0_END      0x40C0
+#define FIMD_SHD_ADD1_START    0x40D0
+#define FIMD_SHD_ADD1_END      0x40F0
+#define FIMD_SHD_ADD2_START    0x4100
+#define FIMD_SHD_ADD2_END      0x4110
+
+/* Palette memory */
+#define FIMD_PAL_MEM_START     0x2400
+#define FIMD_PAL_MEM_END       0x37FC
+/* Palette memory aliases for windows 0 and 1 */
+#define FIMD_PALMEM_AL_START   0x0400
+#define FIMD_PALMEM_AL_END     0x0BFC
+
+typedef struct {
+    uint8_t r, g, b;
+    /* D[31..24]dummy, D[23..16]rAlpha, D[15..8]gAlpha, D[7..0]bAlpha */
+    uint32_t a;
+} rgba;
+#define RGBA_SIZE  7
+
+typedef void pixel_to_rgb_func(uint32_t pixel, rgba *p);
+typedef struct Exynos4210fimdWindow Exynos4210fimdWindow;
+
+struct Exynos4210fimdWindow {
+    uint32_t wincon;        /* Window control register */
+    uint32_t buf_start[3];  /* Start address for video frame buffer */
+    uint32_t buf_end[3];    /* End address for video frame buffer */
+    uint32_t keycon[2];     /* Window color key registers */
+    uint32_t keyalpha;      /* Color key alpha control register */
+    uint32_t winmap;        /* Window color map register */
+    uint32_t blendeq;       /* Window blending equation control register */
+    uint32_t rtqoscon;      /* Window RTQOS Control Registers */
+    uint32_t palette[256];  /* Palette RAM */
+    uint32_t shadow_buf_start;      /* Start address of shadow frame buffer */
+    uint32_t shadow_buf_end;        /* End address of shadow frame buffer */
+    uint32_t shadow_buf_size;       /* Virtual shadow screen width */
+
+    pixel_to_rgb_func *pixel_to_rgb;
+    void (*draw_line)(Exynos4210fimdWindow *w, uint8_t *src, uint8_t *dst,
+            Exynos4210fimdWindow *w_blend);
+    uint16_t lefttop_x, lefttop_y;   /* VIDOSD0 register */
+    uint16_t rightbot_x, rightbot_y; /* VIDOSD1 register */
+    uint32_t osdsize;                /* VIDOSD2&3 register */
+    uint32_t alpha_val[2];           /* VIDOSD2&3, VIDWALPHA registers */
+    uint16_t virtpage_width;         /* VIDWADD2 register */
+    uint16_t virtpage_offsize;       /* VIDWADD2 register */
+    bool alpha_8bit;                 /* BLENDCON register: alpha 4 or 8 bit */
+    uint8_t pal_format;              /* Window palette format index */
+};
+
+typedef struct {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    DisplayState *console;
+    qemu_irq irq[3];
+
+    uint32_t vidcon[4];     /* Video main control registers 0-3 */
+    uint32_t vidtcon[4];    /* Video time control registers 0-3 */
+    uint32_t shadowcon;     /* Window shadow control register */
+    uint32_t winchmap;      /* Channel mapping control register */
+    uint32_t vidintcon[2];  /* Video interrupt control registers */
+    uint32_t dithmode;      /* Dithering control register */
+    uint32_t wpalcon[2];    /* Window palette control registers */
+    uint32_t trigcon;       /* Trigger control register */
+    uint32_t i80ifcon[4];   /* I80 interface control registers */
+    uint32_t colorgaincon;  /* Color gain control register */
+    uint32_t ldi_cmdcon[2]; /* LCD I80 interface command control */
+    uint32_t sifccon[3];    /* I80 System Interface Manual Command Control */
+    uint32_t huecoef_cr[4]; /* Hue control registers */
+    uint32_t huecoef_cb[4]; /* Hue control registers */
+    uint32_t hueoffset;     /* Hue offset control register */
+/* blendcon Blend equation control register substituted by window.alpha_8bit */
+    uint32_t i80ifcmd[12];  /* LCD I80 Interface Command */
+
+    Exynos4210fimdWindow window[5];    /* Window-specific registers */
+    uint8_t *ifb;                   /* Internal frame buffer */
+    bool invalidate;                /* Image needs to be redrawn */
+    bool enabled;                   /* Display controller is enabled */
+} Exynos4210fimdState;
+
+/* Perform byte/halfword/word swap of data according to WINCON */
+static inline void fimd_swap_data(unsigned int swap_ctl, uint64_t *data)
+{
+    int i;
+    uint64_t res;
+    uint64_t x = *data;
+
+    if (swap_ctl & FIMD_WINCON_SWAP_BITS) {
+        res = 0;
+        for (i = 0; i < 64; i++) {
+            if (x & (1ULL << (64 - i))) {
+                res |= (1ULL << i);
+            }
+        }
+        x = res;
+    }
+    if (swap_ctl & FIMD_WINCON_SWAP_BYTE) {
+        x = ((x & 0x00000000000000FFULL) << 56) |
+            ((x & 0x000000000000FF00ULL) << 40) |
+            ((x & 0x0000000000FF0000ULL) << 24) |
+            ((x & 0x00000000FF000000ULL) <<  8) |
+            ((x & 0x000000FF00000000ULL) >>  8) |
+            ((x & 0x0000FF0000000000ULL) >> 24) |
+            ((x & 0x00FF000000000000ULL) >> 40) |
+            ((x & 0xFF00000000000000ULL) >> 56);
+    }
+    if (swap_ctl & FIMD_WINCON_SWAP_HWORD) {
+        x = ((x & 0x000000000000FFFFULL) << 48) |
+            ((x & 0x00000000FFFF0000ULL) << 16) |
+            ((x & 0x0000FFFF00000000ULL) >> 16) |
+            ((x & 0xFFFF000000000000ULL) >> 48);
+    }
+    if (swap_ctl & FIMD_WINCON_SWAP_WORD) {
+        x = ((x & 0x00000000FFFFFFFFULL) << 32) |
+            ((x & 0xFFFFFFFF00000000ULL) >> 32);
+    }
+
+    *data = x;
+}
+
+/* Conversion routines of Pixel data from frame buffer area to internal RGBA
+ * pixel representation.
+ * Every color component internally represented as 8-bit value. If original
+ * data has less than 8 bit for component, data is extended to 8 bit. For
+ * example, if blue component has only two possible values 0 and 1 it will be
+ * extended to 0 and 0xFF */
+
+/* One bit for alpha representation */
+#define DEF_PIXEL_TO_RGB_A1(N, R, G, B) \
+static void N(uint32_t pixel, rgba *p) \
+{ \
+    p->b = ((pixel & ((1 << (B)) - 1)) << (8 - (B))) | \
+           ((pixel >> (2 * (B) - 8)) & ((1 << (8 - (B))) - 1)); \
+    pixel >>= (B); \
+    p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)) | \
+           ((pixel >> (2 * (G) - 8)) & ((1 << (8 - (G))) - 1)); \
+    pixel >>= (G); \
+    p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)) | \
+           ((pixel >> (2 * (R) - 8)) & ((1 << (8 - (R))) - 1)); \
+    pixel >>= (R); \
+    p->a = (pixel & 0x1); \
+}
+
+DEF_PIXEL_TO_RGB_A1(pixel_a444_to_rgb, 4, 4, 4)
+DEF_PIXEL_TO_RGB_A1(pixel_a555_to_rgb, 5, 5, 5)
+DEF_PIXEL_TO_RGB_A1(pixel_a666_to_rgb, 6, 6, 6)
+DEF_PIXEL_TO_RGB_A1(pixel_a665_to_rgb, 6, 6, 5)
+DEF_PIXEL_TO_RGB_A1(pixel_a888_to_rgb, 8, 8, 8)
+DEF_PIXEL_TO_RGB_A1(pixel_a887_to_rgb, 8, 8, 7)
+
+/* Alpha component is always zero */
+#define DEF_PIXEL_TO_RGB_A0(N, R, G, B) \
+static void N(uint32_t pixel, rgba *p) \
+{ \
+    p->b = ((pixel & ((1 << (B)) - 1)) << (8 - (B))) | \
+           ((pixel >> (2 * (B) - 8)) & ((1 << (8 - (B))) - 1)); \
+    pixel >>= (B); \
+    p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)) | \
+           ((pixel >> (2 * (G) - 8)) & ((1 << (8 - (G))) - 1)); \
+    pixel >>= (G); \
+    p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)) | \
+           ((pixel >> (2 * (R) - 8)) & ((1 << (8 - (R))) - 1)); \
+    p->a = 0x0; \
+}
+
+DEF_PIXEL_TO_RGB_A0(pixel_565_to_rgb,  5, 6, 5)
+DEF_PIXEL_TO_RGB_A0(pixel_555_to_rgb,  5, 5, 5)
+DEF_PIXEL_TO_RGB_A0(pixel_666_to_rgb,  6, 6, 6)
+DEF_PIXEL_TO_RGB_A0(pixel_888_to_rgb,  8, 8, 8)
+
+/* Alpha component has some meaningful value */
+#define DEF_PIXEL_TO_RGB_A(N, R, G, B, A) \
+static void N(uint32_t pixel, rgba *p) \
+{ \
+    p->b = ((pixel & ((1 << (B)) - 1)) << (8 - (B))) | \
+           ((pixel >> (2 * (B) - 8)) & ((1 << (8 - (B))) - 1)); \
+    pixel >>= (B); \
+    p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)) | \
+           ((pixel >> (2 * (G) - 8)) & ((1 << (8 - (G))) - 1)); \
+    pixel >>= (G); \
+    p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)) | \
+           ((pixel >> (2 * (R) - 8)) & ((1 << (8 - (R))) - 1)); \
+    pixel >>= (R); \
+    p->a = (pixel & ((1 << (A)) - 1)) << (8 - (A)) | \
+           ((pixel >> (2 * (A) - 8)) & ((1 << (8 - (A))) - 1)); \
+    p->a = p->a | (p->a << 8) | (p->a << 16); \
+}
+
+DEF_PIXEL_TO_RGB_A(pixel_4444_to_rgb, 4, 4, 4, 4)
+DEF_PIXEL_TO_RGB_A(pixel_8888_to_rgb, 8, 8, 8, 8)
+
+/* Lookup table to extent 2-bit color component to 8 bit */
+static const uint8_t pixel_lutable_2b[4] = {
+     0x0, 0x55, 0xAA, 0xFF
+};
+/* Lookup table to extent 3-bit color component to 8 bit */
+static const uint8_t pixel_lutable_3b[8] = {
+     0x0, 0x24, 0x49, 0x6D, 0x92, 0xB6, 0xDB, 0xFF
+};
+/* Special case for a232 bpp mode */
+static void pixel_a232_to_rgb(uint32_t pixel, rgba *p)
+{
+    p->b = pixel_lutable_2b[(pixel & 0x3)];
+    pixel >>= 2;
+    p->g = pixel_lutable_3b[(pixel & 0x7)];
+    pixel >>= 3;
+    p->r = pixel_lutable_2b[(pixel & 0x3)];
+    pixel >>= 2;
+    p->a = (pixel & 0x1);
+}
+
+/* Special case for (5+1, 5+1, 5+1) mode. Data bit 15 is common LSB
+ * for all three color components */
+static void pixel_1555_to_rgb(uint32_t pixel, rgba *p)
+{
+    uint8_t comm = (pixel >> 15) & 1;
+    p->b = ((((pixel & 0x1F) << 1) | comm) << 2) | ((pixel >> 3) & 0x3);
+    pixel >>= 5;
+    p->g = ((((pixel & 0x1F) << 1) | comm) << 2) | ((pixel >> 3) & 0x3);
+    pixel >>= 5;
+    p->r = ((((pixel & 0x1F) << 1) | comm) << 2) | ((pixel >> 3) & 0x3);
+    p->a = 0x0;
+}
+
+/* Put/get pixel to/from internal LCD Controller framebuffer */
+
+static int put_pixel_ifb(const rgba p, uint8_t *d)
+{
+    *(uint8_t *)d++ = p.r;
+    *(uint8_t *)d++ = p.g;
+    *(uint8_t *)d++ = p.b;
+    *(uint32_t *)d = p.a;
+    return RGBA_SIZE;
+}
+
+static int get_pixel_ifb(const uint8_t *s, rgba *p)
+{
+    p->r = *(uint8_t *)s++;
+    p->g = *(uint8_t *)s++;
+    p->b = *(uint8_t *)s++;
+    p->a = (*(uint32_t *)s) & 0x00FFFFFF;
+    return RGBA_SIZE;
+}
+#define FIMD_1_MINUS_COLOR(x)    \
+            ((0xFF - ((x) & 0xFF)) | (0xFF00 - ((x) & 0xFF00)) | \
+                                  (0xFF0000 - ((x) & 0xFF0000)))
+#define EXTEND_LOWER_HALFBYTE(x) (((x) & 0xF0F0F) | (((x) << 4) & 0xF0F0F0))
+#define EXTEND_UPPER_HALFBYTE(x) (((x) & 0xF0F0F0) | (((x) >> 4) & 0xF0F0F))
+
+/* Multiply three lower bytes of two 32-bit words with each other.
+ * Each byte with values 0-255 is considered as a number with possible values
+ * in a range [0 - 1] */
+static inline uint32_t fimd_mult_each_byte(uint32_t a, uint32_t b)
+{
+    uint32_t tmp;
+    uint32_t ret;
+
+    ret = ((tmp = (((a & 0xFF) * (b & 0xFF)) / 0xFF)) > 0xFF) ? 0xFF : tmp;
+    ret |= ((tmp = ((((a >> 8) & 0xFF) * ((b >> 8) & 0xFF)) / 0xFF)) > 0xFF) ?
+            0xFF00 : tmp << 8;
+    ret |= ((tmp = ((((a >> 16) & 0xFF) * ((b >> 16) & 0xFF)) / 0xFF)) > 0xFF) ?
+            0xFF0000 : tmp << 16;
+    return ret;
+}
+
+/* For each corresponding bytes of two 32-bit words: (a*b + c*d)
+ * Byte values 0-255 are mapped to a range [0 .. 1] */
+static inline uint32_t
+fimd_mult_and_sum_each_byte(uint32_t a, uint32_t b, uint32_t c, uint32_t d)
+{
+    uint32_t tmp;
+    uint32_t ret;
+
+    ret = ((tmp = (((a & 0xFF) * (b & 0xFF) + (c & 0xFF) * (d & 0xFF)) / 0xFF))
+            > 0xFF) ? 0xFF : tmp;
+    ret |= ((tmp = ((((a >> 8) & 0xFF) * ((b >> 8) & 0xFF) + ((c >> 8) & 0xFF) *
+            ((d >> 8) & 0xFF)) / 0xFF)) > 0xFF) ? 0xFF00 : tmp << 8;
+    ret |= ((tmp = ((((a >> 16) & 0xFF) * ((b >> 16) & 0xFF) +
+            ((c >> 16) & 0xFF) * ((d >> 16) & 0xFF)) / 0xFF)) > 0xFF) ?
+                    0xFF0000 : tmp << 16;
+    return ret;
+}
+
+/* Updates currently active alpha value for 3 color components combined
+ * in 32-bit word:
+ * D[31..24]dummy, D[23..16]rAlpha, D[15..8]gAlpha, D[7..0]bAlpha */
+static inline uint32_t fimd_get_alpha(Exynos4210fimdWindow *w, rgba pix)
+{
+    uint32_t ret;
+
+    if (w->wincon & FIMD_WINCON_BLD_PIX) {
+        if ((w->wincon & FIMD_WINCON_ALPHA_SEL) &&
+                (WIN_BPP_MODE(w) == 0xD || WIN_BPP_MODE(w) == 0xE)) {
+            /* In this case, alpha component contains meaningful value */
+            ret = w->alpha_8bit ? pix.a : EXTEND_LOWER_HALFBYTE(pix.a);
+            if (w->wincon & FIMD_WINCON_ALPHA_MUL) {
+                uint32_t mult = w->alpha_val[0];
+                if (w->alpha_8bit == false) {
+                    mult = EXTEND_UPPER_HALFBYTE(mult);
+                }
+                ret = fimd_mult_each_byte(ret, mult);
+            }
+            return ret;
+        } else {
+            if (IS_PALETTIZED_MODE(w) && (w->pal_format == 7)) {
+                /* Alpha component has 8-bit numeric value */
+                ret = pix.a;
+            } else {
+                /* Alpha has only two possible values (AEN) */
+                ret = w->alpha_val[pix.a];
+            }
+        }
+    } else {
+        ret =  w->alpha_val[(w->wincon & FIMD_WINCON_ALPHA_SEL) ? 1 : 0];
+    }
+
+    if (w->alpha_8bit == false) {
+        ret = EXTEND_UPPER_HALFBYTE(ret);
+    }
+    return ret;
+}
+
+/* Blends current window's (w) pixel (foreground pixel *ret) with background
+ * window (w_blend) pixel p_bg according to formula:
+ * NEW_COLOR = a_coef x FG_PIXEL_COLOR + b_coef x BG_PIXEL_COLOR
+ * NEW_ALPHA = p_coef x FG_ALPHA + q_coef x BG_ALPHA
+ */
+static void exynos4210_fimd_blend_pixel(Exynos4210fimdWindow *w,
+        Exynos4210fimdWindow *w_blend, rgba p_bg, rgba *ret)
+{
+    rgba p_fg = *ret;
+    uint32_t bg_color = ((p_bg.r & 0xFF) << 16) | ((p_bg.g & 0xFF) << 8) |
+            (p_bg.b & 0xFF);
+    uint32_t fg_color = ((p_fg.r & 0xFF) << 16) | ((p_fg.g & 0xFF) << 8) |
+            (p_fg.b & 0xFF);
+    uint32_t alpha_fg, alpha_bg;
+    int i;
+    /* It is possible that blending equation parameters a and b do not
+     * depend on window BLENEQ register. Account for this with first_coef */
+    enum { A_COEF = 0, B_COEF = 1, P_COEF = 2, Q_COEF = 3, COEF_NUM = 4};
+    uint32_t first_coef = A_COEF;
+    uint32_t blend_param[COEF_NUM];
+
+    if (w->keycon[0] & FIMD_WKEYCON0_KEYEN) {
+        uint32_t colorkey = (w->keycon[1] &
+              ~(w->keycon[0] & FIMD_WKEYCON0_COMPKEY)) & FIMD_WKEYCON0_COMPKEY;
+
+        if ((w->keycon[0] & FIMD_WKEYCON0_DIRCON) &&
+            (bg_color & ~(w->keycon[0] & FIMD_WKEYCON0_COMPKEY)) == colorkey) {
+            /* Foreground pixel is displayed */
+            if (w->keycon[0] & FIMD_WKEYCON0_KEYBLEN) {
+                alpha_fg = w->keyalpha;
+                alpha_bg = w_blend->keyalpha;
+                blend_param[A_COEF] = alpha_fg;
+                blend_param[B_COEF] = FIMD_1_MINUS_COLOR(alpha_fg);
+            } else {
+                alpha_fg = 0;
+                alpha_bg = 0;
+                blend_param[A_COEF] = 0xFFFFFF;
+                blend_param[B_COEF] = 0x0;
+            }
+            first_coef = P_COEF;
+        } else if ((w->keycon[0] & FIMD_WKEYCON0_DIRCON) == 0 &&
+            (fg_color & ~(w->keycon[0] & FIMD_WKEYCON0_COMPKEY)) == colorkey) {
+            /* Background pixel is displayed */
+            if (w->keycon[0] & FIMD_WKEYCON0_KEYBLEN) {
+                alpha_fg = w->keyalpha;
+                alpha_bg = w_blend->keyalpha;
+                blend_param[A_COEF] = alpha_fg;
+                blend_param[B_COEF] = FIMD_1_MINUS_COLOR(alpha_fg);
+            } else {
+                alpha_fg = 0;
+                alpha_bg = 0;
+                blend_param[A_COEF] = 0x0;
+                blend_param[B_COEF] = 0xFFFFFF;
+            }
+            first_coef = P_COEF;
+        } else {
+            alpha_fg = fimd_get_alpha(w, p_fg);
+            alpha_bg = fimd_get_alpha(w_blend, p_bg);
+            /* Depend on window BLENDEQ register */
+        }
+    } else {
+        alpha_fg = fimd_get_alpha(w, p_fg);
+        alpha_bg = fimd_get_alpha(w_blend, p_bg);
+        /* Depend on window BLENDEQ register */
+    }
+
+    for (i = first_coef; i < COEF_NUM; i++) {
+        switch ((w->blendeq >> i * 6) & FIMD_BLENDEQ_COEF_MASK) {
+        case 0:
+            blend_param[i] = 0;
+            break;
+        case 1:
+            blend_param[i] = 0xFFFFFF;
+            break;
+        case 2:
+            blend_param[i] = alpha_fg;
+            break;
+        case 3:
+            blend_param[i] = FIMD_1_MINUS_COLOR(alpha_fg);
+            break;
+        case 4:
+            blend_param[i] = alpha_bg;
+            break;
+        case 5:
+            blend_param[i] = FIMD_1_MINUS_COLOR(alpha_bg);
+            break;
+        case 6:
+            blend_param[i] = w->alpha_val[0];
+            break;
+        case 10:
+            blend_param[i] = fg_color;
+            break;
+        case 11:
+            blend_param[i] = FIMD_1_MINUS_COLOR(fg_color);
+            break;
+        case 12:
+            blend_param[i] = bg_color;
+            break;
+        case 13:
+            blend_param[i] = FIMD_1_MINUS_COLOR(bg_color);
+            break;
+        default:
+            hw_error("exynos4210.fimd: blend equation coef illegal value\n");
+            break;
+        }
+    }
+
+    fg_color = fimd_mult_and_sum_each_byte(bg_color, blend_param[B_COEF],
+            fg_color, blend_param[A_COEF]);
+    ret->b = fg_color & 0xFF;
+    fg_color >>= 8;
+    ret->g = fg_color & 0xFF;
+    fg_color >>= 8;
+    ret->r = fg_color & 0xFF;
+    ret->a = fimd_mult_and_sum_each_byte(alpha_fg, blend_param[P_COEF],
+            alpha_bg, blend_param[Q_COEF]);
+}
+
+/* These routines read data from video frame buffer in system RAM, convert
+ * this data to display controller internal representation, if necessary,
+ * perform pixel blending with data, currently presented in internal buffer.
+ * Result is stored in display controller internal frame buffer. */
+
+/* Draw line with index in palette table in RAM frame buffer data */
+#define DEF_DRAW_LINE_PALLETE(N) \
+static void glue(draw_line_palette_, N)(Exynos4210fimdWindow *w, uint8_t *src, \
+               uint8_t *dst, Exynos4210fimdWindow *w_blend) \
+{ \
+    int width = w->rightbot_x - w->lefttop_x + 1; \
+    uint8_t *ifb = dst; \
+    uint8_t swap = (w->wincon & FIMD_WINCON_SWAP) >> FIMD_WINCON_SWAP_SHIFT; \
+    uint64_t data; \
+    rgba p, p_old; \
+    int i; \
+    do { \
+        data = ldq_raw((void *)src); \
+        src += 8; \
+        fimd_swap_data(swap, &data); \
+        for (i = (64 / (N) - 1); i >= 0; i--) { \
+            w->pixel_to_rgb(w->palette[(data >> ((N) * i)) & \
+                                   ((1ULL << (N)) - 1)], &p); \
+            if (w_blend != NULL) { \
+                ifb +=  get_pixel_ifb(ifb, &p_old); \
+                exynos4210_fimd_blend_pixel(w, w_blend, p_old, &p); \
+            } \
+            dst += put_pixel_ifb(p, dst); \
+        } \
+        width -= (64 / (N)); \
+    } while (width > 0); \
+}
+
+/* Draw line with direct color value in RAM frame buffer data */
+#define DEF_DRAW_LINE_NOPALLETE(N) \
+static void glue(draw_line_, N)(Exynos4210fimdWindow *w, uint8_t *src, \
+                    uint8_t *dst, Exynos4210fimdWindow *w_blend) \
+{ \
+    int width = w->rightbot_x - w->lefttop_x + 1; \
+    uint8_t *ifb = dst; \
+    uint8_t swap = (w->wincon & FIMD_WINCON_SWAP) >> FIMD_WINCON_SWAP_SHIFT; \
+    uint64_t data; \
+    rgba p, p_old; \
+    int i; \
+    do { \
+        data = ldq_raw((void *)src); \
+        src += 8; \
+        fimd_swap_data(swap, &data); \
+        for (i = (64 / (N) - 1); i >= 0; i--) { \
+            w->pixel_to_rgb((data >> ((N) * i)) & ((1ULL << (N)) - 1), &p); \
+            if (w_blend != NULL) { \
+                ifb += get_pixel_ifb(ifb, &p_old); \
+                exynos4210_fimd_blend_pixel(w, w_blend, p_old, &p); \
+            } \
+            dst += put_pixel_ifb(p, dst); \
+        } \
+        width -= (64 / (N)); \
+    } while (width > 0); \
+}
+
+DEF_DRAW_LINE_PALLETE(1)
+DEF_DRAW_LINE_PALLETE(2)
+DEF_DRAW_LINE_PALLETE(4)
+DEF_DRAW_LINE_PALLETE(8)
+DEF_DRAW_LINE_NOPALLETE(8)  /* 8bpp mode has palette and non-palette versions */
+DEF_DRAW_LINE_NOPALLETE(16)
+DEF_DRAW_LINE_NOPALLETE(32)
+
+/* Special draw line routine for window color map case */
+static void draw_line_mapcolor(Exynos4210fimdWindow *w, uint8_t *src,
+                       uint8_t *dst, Exynos4210fimdWindow *w_blend)
+{
+    rgba p, p_old;
+    uint8_t *ifb = dst;
+    int width = w->rightbot_x - w->lefttop_x + 1;
+    uint32_t map_color = w->winmap & FIMD_WINMAP_COLOR_MASK;
+
+    do {
+        pixel_888_to_rgb(map_color, &p);
+        if (w_blend != NULL) {
+            ifb += get_pixel_ifb(ifb, &p_old);
+            exynos4210_fimd_blend_pixel(w, w_blend, p_old, &p);
+        }
+        dst += put_pixel_ifb(p, dst);
+    } while (--width);
+}
+
+/* Write RGB to QEMU's GraphicConsole framebuffer */
+
+static int put_to_qemufb_pixel8(const rgba p, uint8_t *d)
+{
+    uint32_t pixel = rgb_to_pixel8(p.r, p.g, p.b);
+    *(uint8_t *)d = pixel;
+    return 1;
+}
+
+static int put_to_qemufb_pixel15(const rgba p, uint8_t *d)
+{
+    uint32_t pixel = rgb_to_pixel15(p.r, p.g, p.b);
+    *(uint16_t *)d = pixel;
+    return 2;
+}
+
+static int put_to_qemufb_pixel16(const rgba p, uint8_t *d)
+{
+    uint32_t pixel = rgb_to_pixel16(p.r, p.g, p.b);
+    *(uint16_t *)d = pixel;
+    return 2;
+}
+
+static int put_to_qemufb_pixel24(const rgba p, uint8_t *d)
+{
+    uint32_t pixel = rgb_to_pixel24(p.r, p.g, p.b);
+    *(uint8_t *)d++ = (pixel >>  0) & 0xFF;
+    *(uint8_t *)d++ = (pixel >>  8) & 0xFF;
+    *(uint8_t *)d++ = (pixel >> 16) & 0xFF;
+    return 3;
+}
+
+static int put_to_qemufb_pixel32(const rgba p, uint8_t *d)
+{
+    uint32_t pixel = rgb_to_pixel24(p.r, p.g, p.b);
+    *(uint32_t *)d = pixel;
+    return 4;
+}
+
+/* Routine to copy pixel from internal buffer to QEMU buffer */
+static int (*put_pixel_toqemu)(const rgba p, uint8_t *pixel);
+static inline void fimd_update_putpix_qemu(int bpp)
+{
+    switch (bpp) {
+    case 8:
+        put_pixel_toqemu = put_to_qemufb_pixel8;
+        break;
+    case 15:
+        put_pixel_toqemu = put_to_qemufb_pixel15;
+        break;
+    case 16:
+        put_pixel_toqemu = put_to_qemufb_pixel16;
+        break;
+    case 24:
+        put_pixel_toqemu = put_to_qemufb_pixel24;
+        break;
+    case 32:
+        put_pixel_toqemu = put_to_qemufb_pixel32;
+        break;
+    default:
+        hw_error("exynos4210.fimd: unsupported BPP (%d)", bpp);
+        break;
+    }
+}
+
+/* Routine to copy a line from internal frame buffer to QEMU display */
+static void fimd_copy_line_toqemu(int width, uint8_t *src, uint8_t *dst)
+{
+    rgba p;
+
+    do {
+        src += get_pixel_ifb(src, &p);
+        dst += put_pixel_toqemu(p, dst);
+    } while (--width);
+}
+
+static pixel_to_rgb_func *palette_data_format[8] = {
+    [0] = pixel_565_to_rgb,
+    [1] = pixel_a555_to_rgb,
+    [2] = pixel_666_to_rgb,
+    [3] = pixel_a665_to_rgb,
+    [4] = pixel_a666_to_rgb,
+    [5] = pixel_888_to_rgb,
+    [6] = pixel_a888_to_rgb,
+    [7] = pixel_8888_to_rgb
+};
+
+/* Returns Index in palette data formats table for given window number WINDOW */
+static uint32_t
+exynos4210_fimd_palette_format(Exynos4210fimdState *s, int window)
+{
+    uint32_t ret;
+
+    switch (window) {
+    case 0:
+        ret = (s->wpalcon[1] >> FIMD_WPAL_W0PAL_L_SHT) & FIMD_WPAL_W0PAL_L;
+        if (ret != 7) {
+            ret = 6 - ret;
+        }
+        break;
+    case 1:
+        ret = (s->wpalcon[1] >> FIMD_WPAL_W1PAL_L_SHT) & FIMD_WPAL_W1PAL_L;
+        if (ret != 7) {
+            ret = 6 - ret;
+        }
+        break;
+    case 2:
+        ret = ((s->wpalcon[0] >> FIMD_WPAL_W2PAL_H_SHT) & FIMD_WPAL_W2PAL_H) |
+            ((s->wpalcon[1] >> FIMD_WPAL_W2PAL_L_SHT) & FIMD_WPAL_W2PAL_L);
+        break;
+    case 3:
+        ret = ((s->wpalcon[0] >> FIMD_WPAL_W3PAL_H_SHT) & FIMD_WPAL_W3PAL_H) |
+            ((s->wpalcon[1] >> FIMD_WPAL_W3PAL_L_SHT) & FIMD_WPAL_W3PAL_L);
+        break;
+    case 4:
+        ret = ((s->wpalcon[0] >> FIMD_WPAL_W4PAL_H_SHT) & FIMD_WPAL_W4PAL_H) |
+            ((s->wpalcon[1] >> FIMD_WPAL_W4PAL_L_SHT) & FIMD_WPAL_W4PAL_L);
+        break;
+    default:
+        hw_error("exynos4210.fimd: incorrect window number %d\n", window);
+        ret = 0;
+        break;
+    }
+    return ret;
+}
+
+/* Parse BPPMODE_F = WINCON1[5:2] bits */
+static void exynos4210_fimd_update_win_bppmode(Exynos4210fimdState *s, int win)
+{
+    Exynos4210fimdWindow *w = &s->window[win];
+    uint32_t palette_format_idx = exynos4210_fimd_palette_format(s, win);
+    w->pal_format = palette_format_idx;
+
+    if (w->winmap & FIMD_WINMAP_EN) {
+        w->draw_line = draw_line_mapcolor;
+        return;
+    }
+
+    switch (WIN_BPP_MODE(w)) {
+    case 0:
+        w->draw_line = draw_line_palette_1;
+        w->pixel_to_rgb = palette_data_format[palette_format_idx];
+        break;
+    case 1:
+        w->draw_line = draw_line_palette_2;
+        w->pixel_to_rgb = palette_data_format[palette_format_idx];
+        break;
+    case 2:
+        w->draw_line = draw_line_palette_4;
+        w->pixel_to_rgb = palette_data_format[palette_format_idx];
+        break;
+    case 3:
+        w->draw_line = draw_line_palette_8;
+        w->pixel_to_rgb = palette_data_format[palette_format_idx];
+        break;
+    case 4:
+        w->draw_line = draw_line_8;
+        w->pixel_to_rgb = pixel_a232_to_rgb;
+        break;
+    case 5:
+        w->draw_line = draw_line_16;
+        w->pixel_to_rgb = pixel_565_to_rgb;
+        break;
+    case 6:
+        w->draw_line = draw_line_16;
+        w->pixel_to_rgb = pixel_a555_to_rgb;
+        break;
+    case 7:
+        w->draw_line = draw_line_16;
+        w->pixel_to_rgb = pixel_1555_to_rgb;
+        break;
+    case 8:
+        w->draw_line = draw_line_32;
+        w->pixel_to_rgb = pixel_666_to_rgb;
+        break;
+    case 9:
+        w->draw_line = draw_line_32;
+        w->pixel_to_rgb = pixel_a665_to_rgb;
+        break;
+    case 10:
+        w->draw_line = draw_line_32;
+        w->pixel_to_rgb = pixel_a666_to_rgb;
+        break;
+    case 11:
+        w->draw_line = draw_line_32;
+        w->pixel_to_rgb = pixel_888_to_rgb;
+        break;
+    case 12:
+        w->draw_line = draw_line_32;
+        w->pixel_to_rgb = pixel_a887_to_rgb;
+        break;
+    case 13:
+        w->draw_line = draw_line_32;
+        if ((w->wincon & FIMD_WINCON_BLD_PIX) && (w->wincon &
+                FIMD_WINCON_ALPHA_SEL)) {
+            w->pixel_to_rgb = pixel_8888_to_rgb;
+        } else {
+            w->pixel_to_rgb = pixel_a888_to_rgb;
+        }
+        break;
+    case 14:
+        w->draw_line = draw_line_16;
+        if ((w->wincon & FIMD_WINCON_BLD_PIX) && (w->wincon &
+                FIMD_WINCON_ALPHA_SEL)) {
+            w->pixel_to_rgb = pixel_4444_to_rgb;
+        } else {
+            w->pixel_to_rgb = pixel_a444_to_rgb;
+        }
+        break;
+    case 15:
+        w->draw_line = draw_line_16;
+        w->pixel_to_rgb = pixel_555_to_rgb;
+        break;
+    }
+}
+
+#if EXY_FIMD_MODE_TRACE > 0
+static const char *exynos4210_fimd_get_bppmode(int mode_code)
+{
+    switch (mode_code) {
+    case 0:
+        return "1 bpp";
+    case 1:
+        return "2 bpp";
+    case 2:
+        return "4 bpp";
+    case 3:
+        return "8 bpp (palletized)";
+    case 4:
+        return "8 bpp (non-palletized, A: 1-R:2-G:3-B:2)";
+    case 5:
+        return "16 bpp (non-palletized, R:5-G:6-B:5)";
+    case 6:
+        return "16 bpp (non-palletized, A:1-R:5-G:5-B:5)";
+    case 7:
+        return "16 bpp (non-palletized, I :1-R:5-G:5-B:5)";
+    case 8:
+        return "Unpacked 18 bpp (non-palletized, R:6-G:6-B:6)";
+    case 9:
+        return "Unpacked 18bpp (non-palletized,A:1-R:6-G:6-B:5)";
+    case 10:
+        return "Unpacked 19bpp (non-palletized,A:1-R:6-G:6-B:6)";
+    case 11:
+        return "Unpacked 24 bpp (non-palletized R:8-G:8-B:8)";
+    case 12:
+        return "Unpacked 24 bpp (non-palletized A:1-R:8-G:8-B:7)";
+    case 13:
+        return "Unpacked 25 bpp (non-palletized A:1-R:8-G:8-B:8)";
+    case 14:
+        return "Unpacked 13 bpp (non-palletized A:1-R:4-G:4-B:4)";
+    case 15:
+        return "Unpacked 15 bpp (non-palletized R:5-G:5-B:5)";
+    default:
+        return "Non-existing bpp mode";
+    }
+}
+
+static inline void exynos4210_fimd_trace_bppmode(Exynos4210fimdWindow *w,
+                int win_num, uint32_t val)
+{
+    if (((w->wincon >> FIMD_WINCON_BPPMODE_SHIFT) & FIMD_WINCON_BPPMODE) ==
+            ((val >> FIMD_WINCON_BPPMODE_SHIFT) & FIMD_WINCON_BPPMODE)) {
+        return;
+    }
+    printf("QEMU FIMD: Window %d BPP mode changed from %s to %s\n", win_num,
+        exynos4210_fimd_get_bppmode((w->wincon >> FIMD_WINCON_BPPMODE_SHIFT) &
+                FIMD_WINCON_BPPMODE),
+        exynos4210_fimd_get_bppmode((val >> FIMD_WINCON_BPPMODE_SHIFT) &
+                FIMD_WINCON_BPPMODE));
+}
+#else
+static inline void exynos4210_fimd_trace_bppmode(Exynos4210fimdWindow *w,
+                int win_num, uint32_t val)
+{
+
+}
+#endif
+
+static inline void exynos4210_fimd_enable(Exynos4210fimdState *s, bool enabled)
+{
+    s->enabled = enabled ? true : false;
+    DPRINT_TRACE("QEMU FIMD: display controller %s\n",
+            enabled ? "enabled" : "disabled");
+}
+
+static inline uint32_t unpack_upper_4(uint32_t x)
+{
+    return ((x & 0xF00) << 12) | ((x & 0xF0) << 8) | ((x & 0xF) << 4);
+}
+
+static inline uint32_t pack_upper_4(uint32_t x)
+{
+    return (((x & 0xF00000) >> 12) | ((x & 0xF000) >> 8) |
+            ((x & 0xF0) >> 4)) & 0xFFF;
+}
+
+static void exynos4210_fimd_update_irq(Exynos4210fimdState *s)
+{
+    if (!(s->vidintcon[0] & FIMD_VIDINT_INTEN)) {
+        qemu_irq_lower(s->irq[0]);
+        qemu_irq_lower(s->irq[1]);
+        qemu_irq_lower(s->irq[2]);
+        return;
+    }
+    if ((s->vidintcon[0] & FIMD_VIDINT_INTFIFOEN) &&
+            (s->vidintcon[1] & FIMD_VIDINT_INTFIFOPEND)) {
+        qemu_irq_raise(s->irq[0]);
+    } else {
+        qemu_irq_lower(s->irq[0]);
+    }
+    if ((s->vidintcon[0] & FIMD_VIDINT_INTFRMEN) &&
+            (s->vidintcon[1] & FIMD_VIDINT_INTFRMPEND)) {
+        qemu_irq_raise(s->irq[1]);
+    } else {
+        qemu_irq_lower(s->irq[1]);
+    }
+    if ((s->vidintcon[0] & FIMD_VIDINT_I80IFDONE) &&
+            (s->vidintcon[1] & FIMD_VIDINT_INTI80PEND)) {
+        qemu_irq_raise(s->irq[2]);
+    } else {
+        qemu_irq_lower(s->irq[2]);
+    }
+}
+
+static void exynos4210_update_resolution(Exynos4210fimdState *s)
+{
+    /* LCD resolution is stored in VIDEO TIME CONTROL REGISTER 2 */
+    uint32_t width = ((s->vidtcon[2] >> FIMD_VIDTCON2_HOR_SHIFT) &
+            FIMD_VIDTCON2_SIZE_MASK) + 1;
+    uint32_t height = ((s->vidtcon[2] >> FIMD_VIDTCON2_VER_SHIFT) &
+            FIMD_VIDTCON2_SIZE_MASK) + 1;
+
+    if (s->ifb == NULL || ds_get_width(s->console) != width ||
+            ds_get_height(s->console) != height) {
+        DPRINT_L1("Resolution changed from %ux%u to %ux%u\n",
+           ds_get_width(s->console), ds_get_height(s->console), width, height);
+        qemu_console_resize(s->console, width, height);
+        s->ifb = g_realloc(s->ifb, width * height * RGBA_SIZE + 1);
+        memset(s->ifb, 0, width * height * RGBA_SIZE + 1);
+        s->invalidate = true;
+    }
+}
+
+static inline int fimd_get_buffer_id(Exynos4210fimdWindow *w)
+{
+    switch (w->wincon & FIMD_WINCON_BUFSTATUS) {
+    case FIMD_WINCON_BUF0_STAT:
+        return 0;
+    case FIMD_WINCON_BUF1_STAT:
+        return 1;
+    case FIMD_WINCON_BUF2_STAT:
+        return 2;
+    default:
+        DPRINT_ERROR("Non-existent buffer index\n");
+        return 0;
+    }
+}
+
+static void exynos4210_fimd_update(void *opaque)
+{
+    Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+    int i, line;
+    target_phys_addr_t fb_start_addr, fb_line_end_addr, inc_size, fb_len, x;
+    int scrn_height;
+    int first_line = -1, last_line = -1, scrn_width;
+    Exynos4210fimdWindow *window_to_blend = NULL;
+    uint8_t *host_fb_addr, *host_fb_start_addr;
+    bool is_dirty = false;
+    ram_addr_t pd;
+    const int global_width = (s->vidtcon[2] & FIMD_VIDTCON2_SIZE_MASK) + 1;
+    const int global_height = ((s->vidtcon[2] >> FIMD_VIDTCON2_VER_SHIFT) &
+            FIMD_VIDTCON2_SIZE_MASK) + 1;
+
+    if (!s || !s->console || !ds_get_bits_per_pixel(s->console)) {
+        return;
+    }
+
+    if (s->enabled == false) {
+        return;
+    }
+
+    exynos4210_update_resolution(s);
+
+    for (i = 0; i < NUM_OF_WINDOWS; i++) {
+        if ((s->window[i].wincon & FIMD_WINCON_ENWIN) &&
+                FIMD_WIN_NOT_PROTECTED(s, i)) {
+            scrn_height = s->window[i].rightbot_y - s->window[i].lefttop_y + 1;
+            scrn_width = s->window[i].virtpage_width;
+            fb_start_addr =
+                    s->window[i].buf_start[fimd_get_buffer_id(&s->window[i])];
+            /* Total width of virtual screen page in bytes */
+            inc_size = scrn_width + s->window[i].virtpage_offsize;
+            /* Total number of bytes of virtual screen used by current window */
+            fb_len = inc_size * scrn_height;
+            cpu_physical_sync_dirty_bitmap(fb_start_addr,
+                                              fb_start_addr + fb_len);
+            host_fb_addr = cpu_physical_memory_map(fb_start_addr, &fb_len, 0);
+            if (!host_fb_addr) {
+                return;
+            }
+            if (fb_len != inc_size * scrn_height) {
+                cpu_physical_memory_unmap(host_fb_addr, fb_len, 0, 0);
+                return;
+            }
+
+            host_fb_start_addr = host_fb_addr;
+            for (line = 0; line < scrn_height; line++) {
+                fb_line_end_addr = fb_start_addr + scrn_width;
+                for (x = fb_start_addr; x < fb_line_end_addr;
+                    x += TARGET_PAGE_SIZE) {
+                    pd = (cpu_get_physical_page_desc(x) & TARGET_PAGE_MASK) +
+                            (x & ~TARGET_PAGE_MASK);
+                    is_dirty = is_dirty ||
+                            cpu_physical_memory_get_dirty(pd, VGA_DIRTY_FLAG);
+                }
+
+                if (s->invalidate || is_dirty) {
+                    if (first_line == -1) {
+                        first_line = line;
+                    }
+                    last_line = line;
+                    s->window[i].draw_line(&s->window[i], host_fb_addr,
+                          s->ifb + s->window[i].lefttop_x * RGBA_SIZE +
+                              (s->window[i].lefttop_y + line) * global_width
+                              * RGBA_SIZE, window_to_blend);
+                }
+                host_fb_addr += inc_size;
+                is_dirty = false;
+                fb_start_addr += inc_size;
+            }
+            cpu_physical_memory_unmap(host_fb_start_addr, fb_len, 0, 0);
+            fb_start_addr =
+                    s->window[i].buf_start[fimd_get_buffer_id(&s->window[i])];
+            pd = (cpu_get_physical_page_desc(fb_start_addr) & TARGET_PAGE_MASK)+
+                             (fb_start_addr & ~TARGET_PAGE_MASK);
+            cpu_physical_memory_reset_dirty(pd, pd + inc_size * scrn_height,
+                                             VGA_DIRTY_FLAG);
+            window_to_blend = &s->window[i];
+        }
+    }
+
+    /* Copy resulting image to QEMU_CONSOLE. */
+    if (first_line >= 0) {
+        uint8_t *d;
+        int bpp;
+
+        bpp = ds_get_bits_per_pixel(s->console);
+        fimd_update_putpix_qemu(bpp);
+        bpp = (bpp + 1) >> 3;
+        d = ds_get_data(s->console);
+        for (line = first_line; line < last_line; line++) {
+            fimd_copy_line_toqemu(global_width, s->ifb + global_width * line *
+                    RGBA_SIZE, d + global_width * line * bpp);
+        }
+        dpy_update(s->console, 0, 0, global_width, global_height);
+    }
+    s->invalidate = false;
+    s->vidintcon[1] |= FIMD_VIDINT_INTFRMPEND;
+    if ((s->vidcon[0] & FIMD_VIDCON0_ENVID_F) == 0) {
+        exynos4210_fimd_enable(s, false);
+    }
+    exynos4210_fimd_update_irq(s);
+}
+
+static void exynos4210_fimd_invalidate(void *opaque)
+{
+    Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+    s->invalidate = true;
+}
+
+static void exynos4210_fimd_reset(DeviceState *d)
+{
+    Exynos4210fimdState *s = container_of(d, Exynos4210fimdState, busdev.qdev);
+    int i;
+    unsigned long begin =
+            (unsigned long)s + offsetof(Exynos4210fimdState, vidcon);
+    unsigned long len =
+            ((unsigned long)s + offsetof(Exynos4210fimdState, window)) - begin;
+    DPRINT_TRACE("Display controller reset\n");
+
+    /* Set all display controller registers to 0 */
+    memset((void *)begin, 0, len);
+    for (i = 0; i < NUM_OF_WINDOWS; i++) {
+        memset(&s->window[i], 0, sizeof(Exynos4210fimdWindow));
+        s->window[i].blendeq = 0xC2;
+        s->window[i].pixel_to_rgb = NULL;
+        s->window[i].draw_line = NULL;
+    }
+
+    if (s->ifb != NULL) {
+        g_free(s->ifb);
+    }
+    s->ifb = NULL;
+
+    s->invalidate = true;
+    exynos4210_fimd_enable(s, false);
+    /* Some registers have non-zero initial values */
+    s->winchmap = 0x7D517D51;
+    s->colorgaincon = 0x10040100;
+    s->huecoef_cr[0] = s->huecoef_cr[3] = 0x01000100;
+    s->huecoef_cb[0] = s->huecoef_cb[3] = 0x01000100;
+    s->hueoffset = 0x01800080;
+}
+
+static void exynos4210_fimd_write(void *opaque, target_phys_addr_t offset,
+                              uint64_t val, unsigned size)
+{
+    Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+    int w, i;
+
+    DPRINT_L2("write offset 0x%08x, value=%ld(0x%08lx)\n", offset, val, val);
+
+    if (offset == FIMD_VIDCON0) {
+        if ((val & FIMD_VIDCON0_ENVID_MASK) == FIMD_VIDCON0_ENVID_MASK) {
+            exynos4210_fimd_enable(s, true);
+        } else {
+            if ((val & FIMD_VIDCON0_ENVID) == 0) {
+                exynos4210_fimd_enable(s, false);
+            }
+        }
+        s->vidcon[0] = val;
+    } else if (offset == FIMD_VIDCON1) {
+        /* Leave read-only bits as is */
+        val = (val & (~FIMD_VIDCON1_ROMASK)) |
+                (s->vidcon[1] & FIMD_VIDCON1_ROMASK);
+        s->vidcon[1] = val;
+    } else if (offset >= FIMD_VIDCON2 && offset <= FIMD_VIDCON3) {
+        s->vidcon[(offset) >> 2] = val;
+    } else if (offset >= FIMD_VIDTCON_START && offset <= FIMD_VIDTCON_END) {
+        s->vidtcon[(offset - FIMD_VIDTCON_START) >> 2] = val;
+    } else if (offset >= FIMD_WINCON_START && offset <= FIMD_WINCON_END) {
+        w = (offset - FIMD_WINCON_START) >> 2;
+        val = (val & ~FIMD_WINCON_ROMASK) |
+                (s->window[w].wincon & FIMD_WINCON_ROMASK);
+        if (w == 0) {
+            /* Window 0 wincon ALPHA_MUL bit must always be 0 */
+            val &= ~FIMD_WINCON_ALPHA_MUL;
+        }
+        exynos4210_fimd_trace_bppmode(&s->window[w], w, val);
+        switch (val & FIMD_WINCON_BUFSELECT) {
+        case FIMD_WINCON_BUF0_SEL:
+            val &= ~FIMD_WINCON_BUFSTATUS;
+            break;
+        case FIMD_WINCON_BUF1_SEL:
+            val = (val & ~FIMD_WINCON_BUFSTATUS_H) |
+                FIMD_WINCON_BUFSTATUS_L;
+            break;
+        case FIMD_WINCON_BUF2_SEL:
+            if (val & FIMD_WINCON_BUFMODE) {
+                val = (val & ~FIMD_WINCON_BUFSTATUS_L) |
+                    FIMD_WINCON_BUFSTATUS_H;
+            }
+            break;
+        default:
+            break;
+        }
+        s->window[w].wincon = val;
+        exynos4210_fimd_update_win_bppmode(s, w);
+    } else if (offset == FIMD_SHADOWCON) {
+        s->shadowcon = val;
+    } else if (offset == FIMD_WINCHMAP) {
+        s->winchmap = val;
+    } else if (offset >= FIMD_VIDOSD_START && offset <= FIMD_VIDOSD_END) {
+        w = (offset - FIMD_VIDOSD_START) >> 4;
+        i = ((offset - FIMD_VIDOSD_START) & 0xF) >> 2;
+        switch (i) {
+        case 0:
+            s->window[w].lefttop_x = (val >> FIMD_VIDOSD_HOR_SHIFT) &
+                                      FIMD_VIDOSD_COORD_MASK;
+            s->window[w].lefttop_y = (val >> FIMD_VIDOSD_VER_SHIFT) &
+                                      FIMD_VIDOSD_COORD_MASK;
+            break;
+        case 1:
+            s->window[w].rightbot_x = (val >> FIMD_VIDOSD_HOR_SHIFT) &
+                                       FIMD_VIDOSD_COORD_MASK;
+            s->window[w].rightbot_y = (val >> FIMD_VIDOSD_VER_SHIFT) &
+                                       FIMD_VIDOSD_COORD_MASK;
+            break;
+        case 2:
+            if (w == 0) {
+                s->window[w].osdsize = val;
+            } else {
+                s->window[w].alpha_val[0] =
+                    unpack_upper_4((val & FIMD_VIDOSD_ALPHA_AEN0) >>
+                    FIMD_VIDOSD_AEN0_SHIFT) |
+                    (s->window[w].alpha_val[0] & FIMD_VIDALPHA_ALPHA_LOWER);
+                s->window[w].alpha_val[1] =
+                    unpack_upper_4(val & FIMD_VIDOSD_ALPHA_AEN1) |
+                    (s->window[w].alpha_val[1] & FIMD_VIDALPHA_ALPHA_LOWER);
+            }
+            break;
+        case 3:
+            if (w != 1 && w != 2) {
+                DPRINT_ERROR("Bad write offset 0x%08x\n", offset);
+                return;
+            }
+            s->window[w].osdsize = val;
+            break;
+        }
+    } else if (offset >= FIMD_VIDWADD0_START && offset <= FIMD_VIDWADD0_END) {
+        w = (offset - FIMD_VIDWADD0_START) >> 3;
+        i = ((offset - FIMD_VIDWADD0_START) >> 2) & 1;
+        s->window[w].buf_start[i] = val;
+    } else if (offset >= FIMD_VIDWADD1_START && offset <= FIMD_VIDWADD1_END) {
+        w = (offset - FIMD_VIDWADD1_START) >> 3;
+        i = ((offset - FIMD_VIDWADD1_START) >> 2) & 1;
+        s->window[w].buf_end[i] = val;
+    } else if (offset >= FIMD_VIDWADD2_START && offset <= FIMD_VIDWADD2_END) {
+        w = (offset - FIMD_VIDWADD2_START) >> 2;
+        s->window[w].virtpage_width = val & FIMD_VIDWADD2_PAGEWIDTH;
+        s->window[w].virtpage_offsize = (val >> FIMD_VIDWADD2_OFFSIZE_SHIFT) &
+                FIMD_VIDWADD2_OFFSIZE;
+    } else if (offset == FIMD_VIDINTCON0) {
+        s->vidintcon[0] = val;
+    } else if (offset == FIMD_VIDINTCON1) {
+        s->vidintcon[1] &= ~(val & 7);
+        exynos4210_fimd_update_irq(s);
+    } else if (offset >= FIMD_WKEYCON_START && offset <= FIMD_WKEYCON_END) {
+        w = ((offset - FIMD_WKEYCON_START) >> 3) + 1;
+        i = ((offset - FIMD_WKEYCON_START) >> 2) & 1;
+        s->window[w].keycon[i] = val;
+    } else if (offset >= FIMD_WKEYALPHA_START && offset <= FIMD_WKEYALPHA_END) {
+        w = ((offset - FIMD_WKEYALPHA_START) >> 2) + 1;
+        s->window[w].keyalpha = val;
+    } else if (offset == FIMD_DITHMODE) {
+        s->dithmode = val;
+    } else if (offset >= FIMD_WINMAP_START && offset <= FIMD_WINMAP_END) {
+        w = (offset - FIMD_WINMAP_START) >> 2;
+        s->window[w].winmap = val;
+        if (val & FIMD_WINMAP_EN) {
+            s->invalidate = true;
+            exynos4210_fimd_update_win_bppmode(s, w);
+            exynos4210_fimd_update(s);
+        }
+    } else if (offset >= FIMD_WPALCON_HIGH && offset <= FIMD_WPALCON_LOW) {
+        i = (offset - FIMD_WPALCON_HIGH) >> 2;
+        s->wpalcon[i] = val;
+        if (s->wpalcon[1] & FIMD_WPALCON_UPDATEEN) {
+            for (w = 0; w < NUM_OF_WINDOWS; w++) {
+                exynos4210_fimd_update_win_bppmode(s, w);
+            }
+        }
+    } else if (offset == FIMD_TRIGCON) {
+        val = (val & ~FIMD_TRIGCON_ROMASK) | (s->trigcon & FIMD_TRIGCON_ROMASK);
+        s->trigcon = val;
+    } else if (offset >= FIMD_I80IFCON_START && offset <= FIMD_I80IFCON_END) {
+        s->i80ifcon[(offset - FIMD_I80IFCON_START) >> 2] = val;
+    } else if (offset == FIMD_COLORGAINCON) {
+        s->colorgaincon = val;
+    } else if (offset >= FIMD_LDI_CMDCON0 && offset <= FIMD_LDI_CMDCON1) {
+        s->ldi_cmdcon[(offset - FIMD_LDI_CMDCON0) >> 2] = val;
+    } else if (offset >= FIMD_SIFCCON0 && offset <= FIMD_SIFCCON2) {
+        i = (offset - FIMD_SIFCCON0) >> 2;
+        if (i != 2) {
+            s->sifccon[i] = val;
+        }
+    } else if (offset >= FIMD_HUECOEFCR_START && offset <= FIMD_HUECOEFCR_END) {
+        i = (offset - FIMD_HUECOEFCR_START) >> 2;
+        s->huecoef_cr[i] = val;
+    } else if (offset >= FIMD_HUECOEFCB_START && offset <= FIMD_HUECOEFCB_END) {
+        i = (offset - FIMD_HUECOEFCB_START) >> 2;
+        s->huecoef_cb[i] = val;
+    } else if (offset == FIMD_HUEOFFSET) {
+        s->hueoffset = val;
+    } else if (offset >= FIMD_VIDWALPHA_START && offset <= FIMD_VIDWALPHA_END) {
+        w = ((offset - FIMD_VIDWALPHA_START) >> 3);
+        i = ((offset - FIMD_VIDWALPHA_START) >> 2) & 1;
+        if (w == 0) {
+            s->window[w].alpha_val[i] = val;
+        } else {
+            s->window[w].alpha_val[i] = (val & FIMD_VIDALPHA_ALPHA_LOWER) |
+                (s->window[w].alpha_val[i] & FIMD_VIDALPHA_ALPHA_UPPER);
+        }
+    } else if (offset >= FIMD_BLENDEQ_START && offset <= FIMD_BLENDEQ_END) {
+        s->window[(offset - FIMD_BLENDEQ_START) >> 2].blendeq = val;
+    } else if (offset == FIMD_BLENDCON) {
+        /* We need information in BLENDCON register to be accessible from
+         * within every Window substructure */
+        for (i = 0; i < NUM_OF_WINDOWS; i++) {
+            s->window[i].alpha_8bit = (val & FIMD_ALPHA_8BIT) ? true : false;
+        }
+    } else if (offset >= FIMD_WRTQOSCON_START && offset <= FIMD_WRTQOSCON_END) {
+        s->window[(offset - FIMD_WRTQOSCON_START) >> 2].rtqoscon = val;
+    } else if (offset >= FIMD_I80IFCMD_START && offset <= FIMD_I80IFCMD_END) {
+        s->i80ifcmd[(offset - FIMD_I80IFCMD_START) >> 2] = val;
+    } else if (offset >= FIMD_SHD_ADD0_START && offset <= FIMD_SHD_ADD0_END) {
+        if (offset & 0x0004) {
+            DPRINT_ERROR("bad write offset 0x%08x\n", offset);
+        }
+        s->window[(offset - FIMD_SHD_ADD0_START) >> 3].shadow_buf_start = val;
+    } else if (offset >= FIMD_SHD_ADD1_START && offset <= FIMD_SHD_ADD1_END) {
+        if (offset & 0x0004) {
+            DPRINT_ERROR("bad write offset 0x%08x\n", offset);
+        }
+        s->window[(offset - FIMD_SHD_ADD1_START) >> 3].shadow_buf_end = val;
+    } else if (offset >= FIMD_SHD_ADD2_START && offset <= FIMD_SHD_ADD2_END) {
+        s->window[(offset - FIMD_SHD_ADD2_START) >> 2].shadow_buf_size = val;
+    } else if (offset >= FIMD_PAL_MEM_START && offset <= FIMD_PAL_MEM_END) {
+        w = (offset - FIMD_PAL_MEM_START) >> 10;
+        i = ((offset - FIMD_PAL_MEM_START) >> 2) & 0xFF;
+        s->window[w].palette[i] = val;
+    } else if (offset >= FIMD_PALMEM_AL_START && offset <= FIMD_PALMEM_AL_END) {
+        /* Palette memory aliases for windows 0 and 1 */
+        w = (offset - FIMD_PALMEM_AL_START) >> 10;
+        i = ((offset - FIMD_PALMEM_AL_START) >> 2) & 0xFF;
+        s->window[w].palette[i] = val;
+    } else {
+        DPRINT_ERROR("bad write offset 0x%08x\n", offset);
+    }
+}
+
+static uint64_t exynos4210_fimd_read(void *opaque, target_phys_addr_t offset,
+                                  unsigned size)
+{
+    Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+    int w, i;
+    uint32_t ret = 0;
+
+    DPRINT_L2("read offset 0x%08x\n", offset);
+
+    if (offset <= FIMD_VIDCON3) {
+        return s->vidcon[(offset) >> 2];
+    } else if (offset >= FIMD_VIDTCON_START && offset <= FIMD_VIDTCON_END) {
+        return s->vidtcon[(offset - FIMD_VIDTCON_START) >> 2];
+    } else if (offset >= FIMD_WINCON_START && offset <= FIMD_WINCON_END) {
+        return s->window[(offset - FIMD_WINCON_START) >> 2].wincon;
+    } else if (offset == FIMD_SHADOWCON) {
+        return s->shadowcon;
+    } else if (offset == FIMD_WINCHMAP) {
+        return s->winchmap;
+    } else if (offset >= FIMD_VIDOSD_START && offset <= FIMD_VIDOSD_END) {
+        w = (offset - FIMD_VIDOSD_START) >> 4;
+        i = ((offset - FIMD_VIDOSD_START) & 0xF) >> 2;
+        switch (i) {
+        case 0:
+            ret = ((s->window[w].lefttop_x & FIMD_VIDOSD_COORD_MASK) <<
+            FIMD_VIDOSD_HOR_SHIFT) |
+            (s->window[w].lefttop_y & FIMD_VIDOSD_COORD_MASK);
+            break;
+        case 1:
+            ret = ((s->window[w].rightbot_x & FIMD_VIDOSD_COORD_MASK) <<
+                FIMD_VIDOSD_HOR_SHIFT) |
+                (s->window[w].rightbot_y & FIMD_VIDOSD_COORD_MASK);
+            break;
+        case 2:
+            if (w == 0) {
+                ret = s->window[w].osdsize;
+            } else {
+                ret = (pack_upper_4(s->window[w].alpha_val[0]) <<
+                    FIMD_VIDOSD_AEN0_SHIFT) |
+                    pack_upper_4(s->window[w].alpha_val[1]);
+            }
+            break;
+        case 3:
+            if (w != 1 && w != 2) {
+                goto return_error;
+            }
+            ret = s->window[w].osdsize;
+            break;
+        }
+        return ret;
+    } else if (offset >= FIMD_VIDWADD0_START && offset <= FIMD_VIDWADD0_END) {
+        w = (offset - FIMD_VIDWADD0_START) >> 3;
+        i = ((offset - FIMD_VIDWADD0_START) >> 2) & 1;
+        return s->window[w].buf_start[i];
+    } else if (offset >= FIMD_VIDWADD1_START && offset <= FIMD_VIDWADD1_END) {
+        w = (offset - FIMD_VIDWADD1_START) >> 3;
+        i = ((offset - FIMD_VIDWADD1_START) >> 2) & 1;
+        return s->window[w].buf_end[i];
+    } else if (offset >= FIMD_VIDWADD2_START && offset <= FIMD_VIDWADD2_END) {
+        w = (offset - FIMD_VIDWADD2_START) >> 2;
+        return s->window[w].virtpage_width | (s->window[w].virtpage_offsize <<
+            FIMD_VIDWADD2_OFFSIZE_SHIFT);
+    } else if (offset >= FIMD_VIDINTCON0 && offset <= FIMD_VIDINTCON1) {
+        return s->vidintcon[(offset - FIMD_VIDINTCON0) >> 2];
+    } else if (offset >= FIMD_WKEYCON_START && offset <= FIMD_WKEYCON_END) {
+        w = ((offset - FIMD_WKEYCON_START) >> 3) + 1;
+        i = ((offset - FIMD_WKEYCON_START) >> 2) & 1;
+        return s->window[w].keycon[i];
+    } else if (offset >= FIMD_WKEYALPHA_START && offset <= FIMD_WKEYALPHA_END) {
+        w = ((offset - FIMD_WKEYALPHA_START) >> 2) + 1;
+        return s->window[w].keyalpha;
+    } else if (offset == FIMD_DITHMODE) {
+        return s->dithmode;
+    } else if (offset >= FIMD_WINMAP_START && offset <= FIMD_WINMAP_END) {
+        return s->window[(offset - FIMD_WINMAP_START) >> 2].winmap;
+    } else if (offset >= FIMD_WPALCON_HIGH && offset <= FIMD_WPALCON_LOW) {
+        return s->wpalcon[(offset - FIMD_WPALCON_HIGH) >> 2];
+    } else if (offset == FIMD_TRIGCON) {
+        return s->trigcon;
+    } else if (offset >= FIMD_I80IFCON_START && offset <= FIMD_I80IFCON_END) {
+        return s->i80ifcon[(offset - FIMD_I80IFCON_START) >> 2];
+    } else if (offset == FIMD_COLORGAINCON) {
+        return s->colorgaincon;
+    } else if (offset >= FIMD_LDI_CMDCON0 && offset <= FIMD_LDI_CMDCON1) {
+        return s->ldi_cmdcon[(offset - FIMD_LDI_CMDCON0) >> 2];
+    } else if (offset >= FIMD_SIFCCON0 && offset <= FIMD_SIFCCON2) {
+        i = (offset - FIMD_SIFCCON0) >> 2;
+        return s->sifccon[i];
+    } else if (offset >= FIMD_HUECOEFCR_START && offset <= FIMD_HUECOEFCR_END) {
+        i = (offset - FIMD_HUECOEFCR_START) >> 2;
+        return s->huecoef_cr[i];
+    } else if (offset >= FIMD_HUECOEFCB_START && offset <= FIMD_HUECOEFCB_END) {
+        i = (offset - FIMD_HUECOEFCB_START) >> 2;
+        return s->huecoef_cb[i];
+    } else if (offset == FIMD_HUEOFFSET) {
+        return s->hueoffset;
+    } else if (offset >= FIMD_VIDWALPHA_START && offset <= FIMD_VIDWALPHA_END) {
+        w = ((offset - FIMD_VIDWALPHA_START) >> 3);
+        i = ((offset - FIMD_VIDWALPHA_START) >> 2) & 1;
+        return s->window[w].alpha_val[i] &
+                (w == 0 ? 0xFFFFFF : FIMD_VIDALPHA_ALPHA_LOWER);
+    } else if (offset >= FIMD_BLENDEQ_START && offset <= FIMD_BLENDEQ_END) {
+        return s->window[(offset - FIMD_BLENDEQ_START) >> 2].blendeq;
+    } else if (offset == FIMD_BLENDCON) {
+        return (s->window[0].alpha_8bit == true) ? 0x1 : 0x0;
+    } else if (offset >= FIMD_WRTQOSCON_START && offset <= FIMD_WRTQOSCON_END) {
+        return s->window[(offset - FIMD_WRTQOSCON_START) >> 2].rtqoscon;
+    } else if (offset >= FIMD_I80IFCMD_START && offset <= FIMD_I80IFCMD_END) {
+        return s->i80ifcmd[(offset - FIMD_I80IFCMD_START) >> 2];
+    } else if (offset >= FIMD_SHD_ADD0_START && offset <= FIMD_SHD_ADD0_END) {
+        if (offset & 0x0004) {
+            goto return_error;
+        }
+        return s->window[(offset - FIMD_SHD_ADD0_START) >> 3].shadow_buf_start;
+    } else if (offset >= FIMD_SHD_ADD1_START && offset <= FIMD_SHD_ADD1_END) {
+        if (offset & 0x0004) {
+            goto return_error;
+        }
+        return s->window[(offset - FIMD_SHD_ADD1_START) >> 3].shadow_buf_end;
+    } else if (offset >= FIMD_SHD_ADD2_START && offset <= FIMD_SHD_ADD2_END) {
+        return s->window[(offset - FIMD_SHD_ADD2_START) >> 2].shadow_buf_size;
+    } else if (offset >= FIMD_PAL_MEM_START && offset <= FIMD_PAL_MEM_END) {
+        w = (offset - FIMD_PAL_MEM_START) >> 10;
+        i = ((offset - FIMD_PAL_MEM_START) >> 2) & 0xFF;
+        return s->window[w].palette[i];
+    } else if (offset >= FIMD_PALMEM_AL_START && offset <= FIMD_PALMEM_AL_END) {
+        /* Palette aliases for win 0,1 */
+        w = (offset - FIMD_PALMEM_AL_START) >> 10;
+        i = ((offset - FIMD_PALMEM_AL_START) >> 2) & 0xFF;
+        return s->window[w].palette[i];
+    }
+return_error:
+    DPRINT_ERROR("bad read offset 0x%08x\n", offset);
+    return 0xBAADBAAD;
+}
+
+static const MemoryRegionOps exynos4210_fimd_mmio_ops = {
+    .read = exynos4210_fimd_read,
+    .write = exynos4210_fimd_write,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false
+    },
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int exynos4210_fimd_load(void *opaque, int version_id)
+{
+    Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+    int w;
+
+    if (version_id != 1) {
+        return -EINVAL;
+    }
+
+    for (w = 0; w < NUM_OF_WINDOWS; w++) {
+        exynos4210_fimd_update_win_bppmode(s, w);
+    }
+
+    /* Redraw the whole screen */
+    exynos4210_update_resolution(s);
+    exynos4210_fimd_invalidate(s);
+    exynos4210_fimd_enable(s, (s->vidcon[0] & FIMD_VIDCON0_ENVID_MASK) ==
+            FIMD_VIDCON0_ENVID_MASK);
+    return 0;
+}
+
+static const VMStateDescription exynos4210_fimd_window_vmstate = {
+    .name = "exynos4210.fimd_window",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields      = (VMStateField[]) {
+        VMSTATE_UINT32(wincon, Exynos4210fimdWindow),
+        VMSTATE_UINT32_ARRAY(buf_start, Exynos4210fimdWindow, 3),
+        VMSTATE_UINT32_ARRAY(buf_end, Exynos4210fimdWindow, 3),
+        VMSTATE_UINT32_ARRAY(keycon, Exynos4210fimdWindow, 2),
+        VMSTATE_UINT32(keyalpha, Exynos4210fimdWindow),
+        VMSTATE_UINT32(winmap, Exynos4210fimdWindow),
+        VMSTATE_UINT32(blendeq, Exynos4210fimdWindow),
+        VMSTATE_UINT32(rtqoscon, Exynos4210fimdWindow),
+        VMSTATE_UINT32_ARRAY(palette, Exynos4210fimdWindow, 256),
+        VMSTATE_UINT32(shadow_buf_start, Exynos4210fimdWindow),
+        VMSTATE_UINT32(shadow_buf_end, Exynos4210fimdWindow),
+        VMSTATE_UINT32(shadow_buf_size, Exynos4210fimdWindow),
+        VMSTATE_UINT16(lefttop_x, Exynos4210fimdWindow),
+        VMSTATE_UINT16(lefttop_y, Exynos4210fimdWindow),
+        VMSTATE_UINT16(rightbot_x, Exynos4210fimdWindow),
+        VMSTATE_UINT16(rightbot_y, Exynos4210fimdWindow),
+        VMSTATE_UINT32(osdsize, Exynos4210fimdWindow),
+        VMSTATE_UINT32_ARRAY(alpha_val, Exynos4210fimdWindow, 2),
+        VMSTATE_UINT16(virtpage_width, Exynos4210fimdWindow),
+        VMSTATE_UINT16(virtpage_offsize, Exynos4210fimdWindow),
+        VMSTATE_BOOL(alpha_8bit, Exynos4210fimdWindow),
+        VMSTATE_UINT8(pal_format, Exynos4210fimdWindow),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static const VMStateDescription exynos4210_fimd_vmstate = {
+    .name = "exynos4210.fimd",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .post_load = exynos4210_fimd_load,
+    .fields      = (VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(vidcon, Exynos4210fimdState, 4),
+        VMSTATE_UINT32_ARRAY(vidtcon, Exynos4210fimdState, 4),
+        VMSTATE_UINT32(shadowcon, Exynos4210fimdState),
+        VMSTATE_UINT32(winchmap, Exynos4210fimdState),
+        VMSTATE_UINT32_ARRAY(vidintcon, Exynos4210fimdState, 2),
+        VMSTATE_UINT32(dithmode, Exynos4210fimdState),
+        VMSTATE_UINT32_ARRAY(wpalcon, Exynos4210fimdState, 2),
+        VMSTATE_UINT32(trigcon, Exynos4210fimdState),
+        VMSTATE_UINT32_ARRAY(i80ifcon, Exynos4210fimdState, 4),
+        VMSTATE_UINT32(colorgaincon, Exynos4210fimdState),
+        VMSTATE_UINT32_ARRAY(ldi_cmdcon, Exynos4210fimdState, 2),
+        VMSTATE_UINT32_ARRAY(sifccon, Exynos4210fimdState, 3),
+        VMSTATE_UINT32_ARRAY(huecoef_cr, Exynos4210fimdState, 4),
+        VMSTATE_UINT32_ARRAY(huecoef_cb, Exynos4210fimdState, 4),
+        VMSTATE_UINT32(hueoffset, Exynos4210fimdState),
+        VMSTATE_UINT32_ARRAY(i80ifcmd, Exynos4210fimdState, 12),
+        VMSTATE_STRUCT_ARRAY(window, Exynos4210fimdState, 5, 1,
+                exynos4210_fimd_window_vmstate, Exynos4210fimdWindow),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static int exynos4210_fimd_init(SysBusDevice *dev)
+{
+    Exynos4210fimdState *s = FROM_SYSBUS(Exynos4210fimdState, dev);
+
+    s->ifb = NULL;
+
+    sysbus_init_irq(dev, &s->irq[0]);
+    sysbus_init_irq(dev, &s->irq[1]);
+    sysbus_init_irq(dev, &s->irq[2]);
+
+    memory_region_init_io(&s->iomem, &exynos4210_fimd_mmio_ops, s,
+            "exynos4210.fimd", FIMD_REGS_SIZE);
+    sysbus_init_mmio(dev, &s->iomem);
+    s->console = graphic_console_init(exynos4210_fimd_update,
+                                  exynos4210_fimd_invalidate, NULL, NULL, s);
+
+    return 0;
+}
+
+static SysBusDeviceInfo exynos4210_fimd_info = {
+    .init = exynos4210_fimd_init,
+    .qdev.name  = "exynos4210.fimd",
+    .qdev.size  = sizeof(Exynos4210fimdState),
+    .qdev.vmsd  = &exynos4210_fimd_vmstate,
+    .qdev.reset = exynos4210_fimd_reset,
+};
+
+static void exynos4210_fimd_register_devices(void)
+{
+    sysbus_register_withprop(&exynos4210_fimd_info);
+}
+
+device_init(exynos4210_fimd_register_devices)
-- 
1.7.4.1

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* Re: [Qemu-devel] [PATCH 08/15] hw/arm_gic.c: lower IRQ only on changing of enable bit.
  2011-12-09 13:34 ` [Qemu-devel] [PATCH 08/15] hw/arm_gic.c: lower IRQ only on changing of enable bit Evgeny Voevodin
@ 2011-12-09 15:55   ` Paul Brook
  0 siblings, 0 replies; 17+ messages in thread
From: Paul Brook @ 2011-12-09 15:55 UTC (permalink / raw)
  To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin

> In previous version IRQ was lowered every time if enable bits were
> not set. If platform has splitted IRQ source to pass IRQ to two
> identical GICs simultaneously in first of which IRQ passing is
> enabled but in second is disabled, handling IRQ by second GIC would
> lower IRQ previously raised by first GIC.
> Linux kernel v3.0 faces this problem.
> The problem is avoided if IRQ is only lowered as result of
> transitioning enable bits to zeroes.

This is almost certainly wrong.  IRQ lines are state based, not event based. 
Re-asserting an existing level should always be a no-op.

My guess is your real bug it that your IRQ wiring is broken, and you have 
multiple outputs connected to a single input. Don't do that. Ever.  If you 
need to connent multiple output pins to a single input pin then you need an 
explicit device (e.g. an or gate) in between.

Paul

^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2011-12-09 15:55 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 01/15] ARM: Samsung exynos4210-based boards emulation Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 02/15] ARM: exynos4210: CMU support Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 03/15] ARM: exynos4210: UART support Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 04/15] hw/sysbus.h: Increase maximum number of device IRQs Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 05/15] ARM: exynos4210: IRQ subsystem support Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 06/15] ARM: exynos4210: PWM support Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 07/15] hw/arm_boot.c: Add new secondary CPU bootloader Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 08/15] hw/arm_gic.c: lower IRQ only on changing of enable bit Evgeny Voevodin
2011-12-09 15:55   ` Paul Brook
2011-12-09 13:34 ` [Qemu-devel] [PATCH 09/15] ARM: exynos4210: MCT support Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 10/15] hw/exynos4210.c: Boot secondary CPU Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 11/15] hw/lan9118: Add basic 16-bit mode support Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 12/15] hw/exynos4210.c: Add LAN support for SMDKC210 Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 13/15] hw/sd.c, hw/sd.h: add receive ready query routine to SD/MMC API Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 14/15] ARM: exynos4210: added SD/MMC host controller Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 15/15] ARM: exynos4210: added display controller implementation Evgeny Voevodin

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.