qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support.
@ 2011-12-07  9:46 Evgeny Voevodin
  2011-12-07  9:46 ` [Qemu-devel] [PATCH 01/14] ARM: s5pc210: Basic support of s5pc210 boards Evgeny Voevodin
                   ` (14 more replies)
  0 siblings, 15 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07  9:46 UTC (permalink / raw)
  To: qemu-devel; +Cc: d.solodkiy, Evgeny Voevodin

This set of patches adds support for Samsung S5PC210-based boards NURI and SMDKC210.
Tested on Linux kernel v3.x series. Usage of "-smp 2" option is reuired for now.

Evgeny Voevodin (10):
  hw/sysbus.h: Increase maximum number of device IRQs.
  ARM: s5pc210: IRQ subsystem support.
  ARM: s5pc210: PWM support.
  hw/arm_boot.c: Add new secondary CPU bootloader.
  hw/arm_gic.c: lower IRQ only on changing of enable bit.
  ARM: s5pc210: MCT support.
  ARM: s5pc210: Boot secondary CPU.
  hw/lan9118.c: Basic byte/word/long access support.
  hw/s5pc210.c: Add lan9118 support to SMDK board.
  s5pc210: Switch to sysbus_init_mmio.

Maksim Kozlov (1):
  ARM: s5pc210: Basic support of s5pc210 boards

Mitsyanko Igor (3):
  ARM: s5pc210: added s5pc210 display controller device (FIMD)
  SD card: add query function to check wether SD card currently ready
    to recieve data     Before executing data transfer to card, we must
    check that previously issued command wasn't a simple query command
    (for ex. CMD13), which doesn't require data transfer. Currently, we
    only can aquire information about whether SD card is in sending
    data state or not. This patch allows us to query wether previous
    command was data write command and it was successfully accepted by
    card (meaning that SD card in recieving data state).
  ARM: s5pc210: added SD/MMC host controller (ver. 2.0 compliant)
    implementation

 Makefile.target       |    3 +
 hw/arm-misc.h         |    1 +
 hw/arm_boot.c         |   22 +-
 hw/arm_gic.c          |   20 +-
 hw/lan9118.c          |   96 +++-
 hw/s5pc210.c          |  550 ++++++++++++++++
 hw/s5pc210.h          |  105 +++
 hw/s5pc210_cmu.c      | 1144 +++++++++++++++++++++++++++++++++
 hw/s5pc210_combiner.c |  381 +++++++++++
 hw/s5pc210_fimd.c     | 1698 +++++++++++++++++++++++++++++++++++++++++++++++++
 hw/s5pc210_gic.c      |  411 ++++++++++++
 hw/s5pc210_mct.c      | 1483 ++++++++++++++++++++++++++++++++++++++++++
 hw/s5pc210_pwm.c      |  433 +++++++++++++
 hw/s5pc210_sdhc.c     | 1693 ++++++++++++++++++++++++++++++++++++++++++++++++
 hw/s5pc210_uart.c     |  677 ++++++++++++++++++++
 hw/sd.c               |    5 +
 hw/sd.h               |    1 +
 hw/sysbus.h           |    2 +-
 18 files changed, 8710 insertions(+), 15 deletions(-)
 create mode 100644 hw/s5pc210.c
 create mode 100644 hw/s5pc210.h
 create mode 100644 hw/s5pc210_cmu.c
 create mode 100644 hw/s5pc210_combiner.c
 create mode 100644 hw/s5pc210_fimd.c
 create mode 100644 hw/s5pc210_gic.c
 create mode 100644 hw/s5pc210_mct.c
 create mode 100644 hw/s5pc210_pwm.c
 create mode 100644 hw/s5pc210_sdhc.c
 create mode 100644 hw/s5pc210_uart.c

-- 
1.7.4.1

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

* [Qemu-devel] [PATCH 01/14] ARM: s5pc210: Basic support of s5pc210 boards
  2011-12-07  9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
@ 2011-12-07  9:46 ` Evgeny Voevodin
  2011-12-07 11:01   ` Peter Maydell
  2011-12-07  9:46 ` [Qemu-devel] [PATCH 02/14] hw/sysbus.h: Increase maximum number of device IRQs Evgeny Voevodin
                   ` (13 subsequent siblings)
  14 siblings, 1 reply; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07  9:46 UTC (permalink / raw)
  To: qemu-devel; +Cc: Maksim Kozlov, d.solodkiy, Evgeny Voevodin

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

Added support of S5PC210 based boards - SMDKC210 and NURI.
CMU and UART basic implementation.

Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 Makefile.target   |    1 +
 hw/s5pc210.c      |  284 +++++++++++++
 hw/s5pc210.h      |   66 +++
 hw/s5pc210_cmu.c  | 1144 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/s5pc210_uart.c |  677 +++++++++++++++++++++++++++++++
 5 files changed, 2172 insertions(+), 0 deletions(-)
 create mode 100644 hw/s5pc210.c
 create mode 100644 hw/s5pc210.h
 create mode 100644 hw/s5pc210_cmu.c
 create mode 100644 hw/s5pc210_uart.c

diff --git a/Makefile.target b/Makefile.target
index a111521..38fc364 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 += s5pc210.o s5pc210_cmu.o s5pc210_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/s5pc210.c b/hw/s5pc210.c
new file mode 100644
index 0000000..d20ac95
--- /dev/null
+++ b/hw/s5pc210.c
@@ -0,0 +1,284 @@
+/*
+ *  Samsung s5pc210 (aka Exynos4210) board 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>
+ *
+ *  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 "boards.h"
+#include "sysemu.h"
+#include "sysbus.h"
+#include "arm-misc.h"
+#include "exec-memory.h"
+#include "s5pc210.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 S5PC210_DRAM0_BASE_ADDR          0x40000000
+#define S5PC210_DRAM1_BASE_ADDR          0xa0000000
+#define S5PC210_DRAM_MAX_SIZE            0x60000000  /* 1.5 GB */
+
+#define S5PC210_IROM_BASE_ADDR           0x00000000
+#define S5PC210_IROM_SIZE                0x00010000  /* 64 KB */
+#define S5PC210_IROM_MIRROR_BASE_ADDR    0x02000000
+#define S5PC210_IROM_MIRROR_SIZE         0x00010000  /* 64 KB */
+
+#define S5PC210_IRAM_BASE_ADDR           0x02020000
+#define S5PC210_IRAM_SIZE                0x00020000  /* 128 KB */
+
+#define S5PC210_SFR_BASE_ADDR            0x10000000
+
+/* SFR Base Address for CMUs */
+#define S5PC210_CMU_BASE_ADDR            0x10030000
+
+/* UART's definitions */
+#define S5PC210_UART_BASE_ADDR      0x13800000
+#define S5PC210_UART_SHIFT          0x00010000
+
+#define S5PC210_UARTS_NUMBER        4
+
+#define S5PC210_UART_CHANNEL(addr)  ((addr >> 16) & 0x7)
+#define S5PC210_UART0_FIFO_SIZE     256
+#define S5PC210_UART1_FIFO_SIZE     64
+#define S5PC210_UART2_FIFO_SIZE     16
+#define S5PC210_UART3_FIFO_SIZE     16
+#define S5PC210_UART4_FIFO_SIZE     64
+
+#define S5PC210_BASE_BOOT_ADDR           S5PC210_DRAM0_BASE_ADDR
+
+static struct arm_boot_info s5pc210_binfo = {
+        .loader_start     = S5PC210_BASE_BOOT_ADDR,
+};
+
+static uint8_t chipid_and_omr[] = { 0x11, 0x02, 0x21, 0x43,
+                                    0x09, 0x00, 0x00, 0x00 };
+
+enum s5pc210_board_type {
+    BOARD_S5PC210_NURI,
+    BOARD_S5PC210_SMDKC210,
+};
+
+enum s5pc210_mach_id {
+    MACH_NURI_ID     = 0xD33,
+    MACH_SMDKC210_ID = 0xB16,
+};
+
+
+static void s5pc210_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 s5pc210_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_S5PC210_NURI:
+        s5pc210_binfo.board_id      = MACH_NURI_ID;
+        break;
+    case BOARD_S5PC210_SMDKC210:
+        s5pc210_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, "s5pc210.chipid",
+            sizeof(chipid_and_omr), chipid_and_omr);
+    memory_region_set_readonly(chipid_mem, true);
+    memory_region_add_subregion(system_mem, S5PC210_SFR_BASE_ADDR, chipid_mem);
+
+    /* Internal ROM */
+    memory_region_init_ram(irom_mem, NULL, "s5pc210.irom", S5PC210_IROM_SIZE);
+    memory_region_set_readonly(irom_mem, true);
+    memory_region_add_subregion(system_mem, S5PC210_IROM_BASE_ADDR, irom_mem);
+    /* mirror of 0x0 – 0x10000 */
+    memory_region_init_alias(irom_alias_mem, "s5pc210.irom_alias",
+            irom_mem, 0, S5PC210_IROM_SIZE);
+    memory_region_set_readonly(irom_alias_mem, true);
+    memory_region_add_subregion(system_mem, S5PC210_IROM_MIRROR_BASE_ADDR,
+            irom_alias_mem);
+
+    /* Internal RAM */
+    memory_region_init_ram(iram_mem, NULL, "s5pc210.iram", S5PC210_IRAM_SIZE);
+    memory_region_set_readonly(iram_mem, false);
+    memory_region_add_subregion(system_mem, S5PC210_IRAM_BASE_ADDR, iram_mem);
+
+    /* DRAM */
+    mem_size = ram_size;
+    if (mem_size > S5PC210_DRAM_MAX_SIZE) {
+        dram1_mem = g_new(MemoryRegion, 1);
+        memory_region_init_ram(dram1_mem, NULL, "s5pc210.dram1",
+                mem_size - S5PC210_DRAM_MAX_SIZE);
+        memory_region_add_subregion(system_mem, S5PC210_DRAM1_BASE_ADDR,
+                dram1_mem);
+        mem_size = S5PC210_DRAM_MAX_SIZE;
+    }
+    memory_region_init_ram(dram0_mem, NULL, "s5pc210.dram0", mem_size);
+    memory_region_add_subregion(system_mem, S5PC210_DRAM0_BASE_ADDR,
+            dram0_mem);
+
+    /* CMU */
+    sysbus_create_simple("s5pc210.cmu", S5PC210_CMU_BASE_ADDR, NULL);
+
+    /*** UARTs ***/
+    for (n = 0; n < S5PC210_UARTS_NUMBER; n++) {
+
+        uint32_t addr = S5PC210_UART_BASE_ADDR + S5PC210_UART_SHIFT * n;
+        int channel = S5PC210_UART_CHANNEL(addr);
+        qemu_irq uart_irq;
+        int fifo_size = 0;
+
+        switch (channel) {
+        case 0:
+            fifo_size = S5PC210_UART0_FIFO_SIZE;
+        break;
+        case 1:
+            fifo_size = S5PC210_UART1_FIFO_SIZE;
+        break;
+        case 2:
+            fifo_size = S5PC210_UART2_FIFO_SIZE;
+        break;
+        case 3:
+            fifo_size = S5PC210_UART3_FIFO_SIZE;
+        break;
+        case 4:
+            fifo_size = S5PC210_UART4_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;
+
+        s5pc210_uart_create(addr, fifo_size, channel, NULL, uart_irq);
+    }
+
+    /*** Load kernel ***/
+
+    s5pc210_binfo.ram_size = ram_size;
+    s5pc210_binfo.nb_cpus = smp_cpus;
+    s5pc210_binfo.kernel_filename = kernel_filename;
+    s5pc210_binfo.initrd_filename = initrd_filename;
+    s5pc210_binfo.kernel_cmdline = kernel_cmdline;
+
+
+    arm_load_kernel(first_cpu, &s5pc210_binfo);
+}
+
+static void s5pc210_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)
+{
+    s5pc210_init(ram_size, boot_device, kernel_filename, kernel_cmdline,
+            initrd_filename, cpu_model, BOARD_S5PC210_NURI);
+}
+
+static void s5pc210_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)
+{
+    s5pc210_init(ram_size, boot_device, kernel_filename, kernel_cmdline,
+            initrd_filename, cpu_model, BOARD_S5PC210_SMDKC210);
+}
+
+
+static QEMUMachine s5pc210_nuri_machine = {
+        .name = "s5pc210-nuri",
+        .desc = "Samsung Exynos4210 NURI board",
+        .init = s5pc210_nuri_init,
+        .max_cpus = S5PC210_MAX_CPUS,
+};
+
+static QEMUMachine s5pc210_smdkc210_machine = {
+        .name = "s5pc210-smdkc210",
+        .desc = "Samsung Exynos4210 SMDKC210 board",
+        .init = s5pc210_smdkc210_init,
+        .max_cpus = S5PC210_MAX_CPUS,
+};
+
+static void s5pc210_machine_init(void)
+{
+    qemu_register_machine(&s5pc210_nuri_machine);
+    qemu_register_machine(&s5pc210_smdkc210_machine);
+}
+
+machine_init(s5pc210_machine_init);
diff --git a/hw/s5pc210.h b/hw/s5pc210.h
new file mode 100644
index 0000000..bbf927c
--- /dev/null
+++ b/hw/s5pc210.h
@@ -0,0 +1,66 @@
+/*
+ *  Samsung s5pc210 (aka Exynos4210) board 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 S5PC210_H_
+#define S5PC210_H_
+
+#include "qemu-common.h"
+
+#define S5PC210_MAX_CPUS             2
+
+/*
+ * Interface for s5pc210 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
+} S5pc210CmuClock;
+
+uint64_t s5pc210_cmu_get_rate(S5pc210CmuClock clock);
+
+/*
+ * s5pc210 UART
+ */
+
+DeviceState *s5pc210_uart_create(target_phys_addr_t addr,
+                                 int fifo_size,
+                                 int channel,
+                                 CharDriverState *chr,
+                                 qemu_irq irq);
+
+#endif /* S5PC210_H_ */
diff --git a/hw/s5pc210_cmu.c b/hw/s5pc210_cmu.c
new file mode 100644
index 0000000..12fba47
--- /dev/null
+++ b/hw/s5pc210_cmu.c
@@ -0,0 +1,1144 @@
+/*
+ *  s5pc210 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 "s5pc210.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 S5pc210CmuReg {
+        const char *name; /* for debugging */
+        uint32_t    offset;
+        uint32_t    reset_value;
+} S5pc210CmuReg;
+
+
+static S5pc210CmuReg s5pc210_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 S5PC210_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[S5PC210_CMU_REGS_MEM_SIZE];
+
+} S5pc210CmuState;
+
+#define SOURCES_NUMBER    9
+#define RECIPIENTS_NUMBER 9
+
+typedef struct S5pc210CmuClockState {
+
+        const char  *name;
+        uint64_t     rate;
+
+        /* Current source clock */
+        struct S5pc210CmuClockState *source;
+        /*
+         * Available sources. Their order must correspond to CLK_SRC_ register
+         */
+        struct S5pc210CmuClockState *sources[SOURCES_NUMBER];
+        /* Who uses this clock? */
+        struct S5pc210CmuClockState *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;
+
+} S5pc210CmuClockState;
+
+
+/* Clocks from Clock Pads */
+
+/* It should be used only for testing purposes. XOM_0 is 0 */
+static S5pc210CmuClockState xxti = {
+        .name       = "XXTI",
+        .rate       = 24000000,
+};
+
+/* Main source. XOM_0 is 1 */
+static S5pc210CmuClockState xusbxti = {
+        .name       = "XUSBXTI",
+        .rate       = 24000000,
+};
+
+/* PLLs */
+
+static S5pc210CmuClockState mpll = {
+        .name       = "MPLL",
+        .source     = (XOM_0 ? &xusbxti : &xxti),
+};
+
+static S5pc210CmuClockState apll = {
+        .name       = "APLL",
+        .source     = (XOM_0 ? &xusbxti : &xxti),
+};
+
+
+/**/
+static S5pc210CmuClockState sclk_mpll = {
+        .name       = "SCLK_MPLL",
+        .sources    = {XOM_0 ? &xusbxti : &xxti, &mpll},
+        .src_reg    = CLK_SRC_CPU,
+        .mux_shift  = MUX_MPLL_SEL_SHIFT,
+};
+
+static S5pc210CmuClockState 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 S5pc210CmuClockState 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 S5pc210CmuClockState 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 S5pc210CmuClockState 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 S5pc210CmuClockState 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 S5pc210CmuClockState 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 S5pc210CmuClockState 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 S5pc210CmuClock enumerator
+ *  which is defined in s5pc210.h file
+ *
+ */
+static S5pc210CmuClockState *s5pc210_clock[] = {
+        &xxti,
+        &xusbxti,
+        &apll,
+        &mpll,
+        &sclk_apll,
+        &sclk_mpll,
+        &aclk_100,
+        &sclk_uart0,
+        &sclk_uart1,
+        &sclk_uart2,
+        &sclk_uart3,
+        &sclk_uart4,
+        NULL
+};
+
+
+uint64_t s5pc210_cmu_get_rate(S5pc210CmuClock clock)
+{
+    return s5pc210_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 *s5pc210_cmu_regname(target_phys_addr_t  offset)
+{
+
+    int regs_number = sizeof(s5pc210_cmu_regs)/sizeof(S5pc210CmuReg);
+    int i;
+
+    for (i = 0; i < regs_number; i++) {
+        if (offset == s5pc210_cmu_regs[i].offset) {
+            return s5pc210_cmu_regs[i].name;
+        }
+    }
+
+    return NULL;
+}
+#endif
+
+static void s5pc210_cmu_set_pll(void *opaque, target_phys_addr_t offset)
+{
+    S5pc210CmuState *s = (S5pc210CmuState *)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("s5pc210_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("s5pc210_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("s5pc210_cmu_set_pll: Bad offset 0x%x\n", (int)offset);
+    }
+
+    s->reg[I_(offset)] |= PLL_LOCKED_MASK;
+}
+
+
+static void s5pc210_cmu_set_rate(void *opaque, S5pc210CmuClockState *clock)
+{
+    S5pc210CmuState *s = (S5pc210CmuState *)opaque;
+    int i;
+
+    /* Rates of PLLs are calculated differently than ordinary clocks rates */
+    if (clock == &mpll) {
+        s5pc210_cmu_set_pll(s, MPLL_CON0);
+    } else if (clock == &apll) {
+        s5pc210_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,
+                           s5pc210_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 {
+
+        S5pc210CmuClockState *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;
+        }
+
+        s5pc210_cmu_set_rate(s, recipient_clock);
+
+        i++;
+    } while (i < RECIPIENTS_NUMBER);
+}
+
+
+static uint64_t s5pc210_cmu_read(void *opaque, target_phys_addr_t offset,
+                                  unsigned size)
+{
+    S5pc210CmuState *s = (S5pc210CmuState *)opaque;
+
+    if (offset > (S5PC210_CMU_REGS_MEM_SIZE - sizeof(uint32_t))) {
+        hw_error("s5pc210_cmu_read: Bad offset 0x%x\n", (int)offset);
+    }
+
+    PRINT_DEBUG_EXTEND("<0x%05x> %s -> %08x\n",
+                       offset, s5pc210_cmu_regname(offset), s->reg[I_(offset)]);
+
+    return s->reg[I_(offset)];
+}
+
+
+static void s5pc210_cmu_write(void *opaque, target_phys_addr_t offset,
+                               uint64_t val, unsigned size)
+{
+    S5pc210CmuState *s = (S5pc210CmuState *)opaque;
+    uint32_t pre_val;
+
+    if (offset > (S5PC210_CMU_REGS_MEM_SIZE - sizeof(uint32_t))) {
+        hw_error("s5pc210_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, s5pc210_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;
+        s5pc210_cmu_set_rate(s, &apll);
+        break;
+    case MPLL_CON0:
+        val = (val & ~PLL_LOCKED_MASK) | (pre_val & PLL_LOCKED_MASK);
+        s->reg[I_(offset)] = val;
+        s5pc210_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)) {
+                s5pc210_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)) {
+                s5pc210_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)) {
+                s5pc210_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)) {
+                s5pc210_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)) {
+                s5pc210_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)) {
+                s5pc210_cmu_set_rate(s, XOM_0 ? &xusbxti : &xxti);
+            }
+        }
+
+        if (val & MUX_HPM_SEL) {
+            s5pc210_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)) {
+                s5pc210_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)) {
+                s5pc210_cmu_set_rate(s, XOM_0 ? &xusbxti : &xxti);
+            }
+        }
+    }
+    break;
+    case CLK_DIV_CPU0:
+        s5pc210_cmu_set_rate(s, &sclk_apll);
+        s5pc210_cmu_set_rate(s, &sclk_mpll);
+        break;
+    case CLK_SRC_TOP0:
+    case CLK_DIV_TOP:
+        s5pc210_cmu_set_rate(s, &aclk_100);
+        break;
+    default:
+        break;
+    }
+}
+
+
+static const MemoryRegionOps s5pc210_cmu_ops = {
+        .read = s5pc210_cmu_read,
+        .write = s5pc210_cmu_write,
+        .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+
+static void s5pc210_cmu_reset(void *opaque)
+{
+    S5pc210CmuState *s = (S5pc210CmuState *)opaque;
+    int i = 0, j = 0, n = 0;
+    int regs_number = sizeof(s5pc210_cmu_regs)/sizeof(S5pc210CmuReg);
+    uint32_t index = 0;
+
+    /* Set default values for registers */
+    for (i = 0; i < regs_number; i++) {
+        index = (s5pc210_cmu_regs[i].offset) / sizeof(uint32_t);
+        s->reg[index] = s5pc210_cmu_regs[i].reset_value;
+    }
+
+    /* clear recipients array from previous reset */
+    for (i = 0; i < CLOCKS_NUMBER; i++) {
+        bzero(s5pc210_clock[i]->recipients,
+              RECIPIENTS_NUMBER * sizeof(S5pc210CmuClockState *));
+    }
+
+    /*
+     * 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 s5pc210_clock */
+
+        PRINT_DEBUG("[SOURCES] %s: ", s5pc210_clock[i]->name);
+
+        j = 0;
+        do { /* visit all sources for current clock (s5pc210_clock[i]) */
+
+            if ((s5pc210_clock[i]->sources[j] == NULL)) {
+
+                if (j == 0) { /* check if we have empty '.sources[]' array */
+                    if (s5pc210_clock[i]->source != NULL) {
+                        s5pc210_clock[i]->sources[j] = s5pc210_clock[i]->source;
+                    } else {
+                        /*
+                         * We haven't any defined sources for this clock. Error
+                         * during definition of appropriate clock structure
+                         *
+                         */
+                        if ((s5pc210_clock[i] != &xusbxti) &&
+                            (s5pc210_clock[i] != &xxti)) {
+
+                            hw_error("s5pc210_cmu_reset:"
+                                    "There aren't any sources for %s clock!\n",
+                                     s5pc210_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", s5pc210_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 ((s5pc210_clock[i]->sources[j]->recipients[n]) == NULL) {
+                    s5pc210_clock[i]->sources[j]->recipients[n] =
+                            s5pc210_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: ", s5pc210_clock[i]->name);
+        for (j = 0;
+             (j < RECIPIENTS_NUMBER) &&
+             ((s5pc210_clock[i]->recipients[j]) != NULL);
+             j++) {
+            PRINT_DEBUG_SIMPLE("%s ", s5pc210_clock[i]->recipients[j]->name);
+        }
+        PRINT_DEBUG_SIMPLE("\n");
+    }
+#endif
+
+    s5pc210_cmu_set_rate(s, XOM_0 ? &xusbxti : &xxti);
+}
+
+static const VMStateDescription vmstate_s5pc210_cmu = {
+        .name = "s5pc210.cmu",
+        .version_id = 1,
+        .minimum_version_id = 1,
+        .minimum_version_id_old = 1,
+        .fields = (VMStateField[]) {
+            /*
+             * TODO: Maybe we should save S5pc210CmuClockState structs as well
+             */
+            VMSTATE_UINT32_ARRAY(reg, S5pc210CmuState,
+                                 S5PC210_CMU_REGS_MEM_SIZE),
+            VMSTATE_END_OF_LIST()
+        }
+};
+
+static int s5pc210_cmu_init(SysBusDevice *dev)
+{
+    S5pc210CmuState *s = FROM_SYSBUS(S5pc210CmuState, dev);
+
+    /* memory mapping */
+    memory_region_init_io(&s->iomem, &s5pc210_cmu_ops, s, "s5pc210.cmu",
+                          S5PC210_CMU_REGS_MEM_SIZE);
+    sysbus_init_mmio_region(dev, &s->iomem);
+
+    qemu_register_reset(s5pc210_cmu_reset, s);
+
+    vmstate_register(&dev->qdev, -1, &vmstate_s5pc210_cmu, s);
+
+    s5pc210_cmu_reset(s);
+
+    return 0;
+}
+
+
+static void s5pc210_cmu_register(void)
+{
+    sysbus_register_dev("s5pc210.cmu",
+                        sizeof(S5pc210CmuState),
+                        s5pc210_cmu_init);
+}
+
+
+device_init(s5pc210_cmu_register)
diff --git a/hw/s5pc210_uart.c b/hw/s5pc210_uart.c
new file mode 100644
index 0000000..d1fbddc
--- /dev/null
+++ b/hw/s5pc210_uart.c
@@ -0,0 +1,677 @@
+/*
+ *  s5pc210 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 "s5pc210.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 S5pc210UartReg {
+    const char         *name; /* the only reason is the debug output */
+    target_phys_addr_t  offset;
+    uint32_t            reset_value;
+} S5pc210UartReg;
+
+static S5pc210UartReg s5pc210_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 S5PC210_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;
+} S5pc210UartFIFO;
+
+typedef struct {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+
+    uint32_t          reg[S5PC210_UART_REGS_MEM_SIZE];
+    S5pc210UartFIFO   rx;
+    S5pc210UartFIFO   tx;
+
+    CharDriverState  *chr;
+    qemu_irq          irq;
+
+    uint32_t channel;
+
+} S5pc210UartState;
+
+
+#ifdef DEBUG_UART
+/* The only meaning of life - debugging. This functions should be only used
+ * inside PRINT_DEBUG_... macroses
+ */
+static const char *s5pc210_uart_regname(target_phys_addr_t  offset)
+{
+
+    int regs_number = sizeof(s5pc210_uart_regs)/sizeof(S5pc210UartReg);
+    int i;
+
+    for (i = 0; i < regs_number; i++) {
+        if (offset == s5pc210_uart_regs[i].offset) {
+            return s5pc210_uart_regs[i].name;
+        }
+    }
+
+    return NULL;
+}
+#endif
+
+
+static void fifo_store(S5pc210UartFIFO *q, uint8_t ch)
+{
+    q->data[q->sp] = ch;
+    q->sp = (q->sp + 1) % q->size;
+}
+
+static uint8_t fifo_retrieve(S5pc210UartFIFO *q)
+{
+    uint8_t ret = q->data[q->rp];
+    q->rp = (q->rp + 1) % q->size;
+    return  ret;
+}
+
+static int fifo_elements_number(S5pc210UartFIFO *q)
+{
+    if (q->sp < q->rp) {
+        return q->size - q->rp + q->sp;
+    }
+
+    return q->sp - q->rp;
+}
+
+static int fifo_empty_elements_number(S5pc210UartFIFO *q)
+{
+    return q->size - fifo_elements_number(q);
+}
+
+static void fifo_reset(S5pc210UartFIFO *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 s5pc210_uart_Tx_FIFO_trigger_level(S5pc210UartState *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 s5pc210_uart_update_irq(S5pc210UartState *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 <= s5pc210_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 s5pc210_uart_update_parameters(S5pc210UartState *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 = s5pc210_cmu_get_rate(SCLK_UART0); break;
+    case 1:
+        uclk_rate = s5pc210_cmu_get_rate(SCLK_UART1); break;
+    case 2:
+        uclk_rate = s5pc210_cmu_get_rate(SCLK_UART2); break;
+    case 3:
+        uclk_rate = s5pc210_cmu_get_rate(SCLK_UART3); break;
+    case 4:
+        uclk_rate = s5pc210_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 s5pc210_uart_write(void *opaque, target_phys_addr_t offset,
+                               uint64_t val, unsigned size)
+{
+    S5pc210UartState *s = (S5pc210UartState *)opaque;
+    uint8_t ch;
+
+    if (offset > (S5PC210_UART_REGS_MEM_SIZE - sizeof(uint32_t))) {
+        hw_error("s5pc210_cmu_write: Bad offset 0x%x\n", (int)offset);
+    }
+
+    PRINT_DEBUG_EXTEND("UART%d: <0x%04x> %s <- 0x%08llx\n",
+                       s->channel, offset, s5pc210_uart_regname(offset), val);
+
+    switch (offset) {
+    case ULCON:
+    case UBRDIV:
+    case UFRACVAL:
+        s->reg[I_(offset)] = val;
+        s5pc210_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;
+            s5pc210_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)]);
+        s5pc210_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, s5pc210_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;
+        s5pc210_uart_update_irq(s);
+        break;
+    case UCON:
+    case UMCON:
+    default:
+        s->reg[I_(offset)] = val;
+        break;
+    }
+}
+static uint64_t s5pc210_uart_read(void *opaque, target_phys_addr_t offset,
+                                  unsigned size)
+{
+    S5pc210UartState *s = (S5pc210UartState *)opaque;
+    uint32_t res;
+
+    if (offset > (S5PC210_UART_REGS_MEM_SIZE - sizeof(uint32_t))) {
+        hw_error("s5pc210_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;
+                s5pc210_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, s5pc210_uart_regname(offset), offset);
+        break;
+    default:
+        return s->reg[I_(offset)];
+        break;
+    }
+
+    return 0;
+}
+
+
+static const MemoryRegionOps s5pc210_uart_ops = {
+        .read = s5pc210_uart_read,
+        .write = s5pc210_uart_write,
+        .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int s5pc210_uart_can_receive(void *opaque)
+{
+    S5pc210UartState *s = (S5pc210UartState *)opaque;
+
+    return fifo_empty_elements_number(&s->rx);
+}
+
+
+static void s5pc210_uart_receive(void *opaque, const uint8_t *buf, int size)
+{
+    S5pc210UartState *s = (S5pc210UartState *)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;
+    }
+
+    s5pc210_uart_update_irq(s);
+}
+
+
+static void s5pc210_uart_event(void *opaque, int event)
+{
+    /*
+     * TODO: Implement this.
+     *
+     */
+}
+
+
+static void s5pc210_uart_reset(void *opaque)
+{
+    S5pc210UartState *s = (S5pc210UartState *)opaque;
+    int regs_number = sizeof(s5pc210_uart_regs)/sizeof(S5pc210UartReg);
+    int i;
+
+    for (i = 0; i < regs_number; i++) {
+        s->reg[I_(s5pc210_uart_regs[i].offset)] =
+                s5pc210_uart_regs[i].reset_value;
+    }
+
+    fifo_reset(&s->rx);
+    fifo_reset(&s->tx);
+
+    PRINT_DEBUG_EXTEND("UART%d: Rx FIFO size: %d\n", s->channel, s->rx.size);
+}
+
+
+static const VMStateDescription vmstate_s5pc210_uart = {
+        .name = "s5pc210.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, S5pc210UartState,
+                                 S5PC210_UART_REGS_MEM_SIZE),
+            VMSTATE_END_OF_LIST()
+        }
+};
+
+DeviceState *s5pc210_uart_create(target_phys_addr_t addr,
+                                 int fifo_size,
+                                 int channel,
+                                 CharDriverState *chr,
+                                 qemu_irq irq)
+{
+    DeviceState  *dev;
+    SysBusDevice *bus;
+    S5pc210UartState *state;
+
+    dev = qdev_create(NULL, "s5pc210.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("s5pc210.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);
+
+    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);
+
+    state = FROM_SYSBUS(S5pc210UartState, bus);
+
+    state->rx.size = fifo_size;
+    state->tx.size = fifo_size;
+
+    return dev;
+}
+
+static int s5pc210_uart_init(SysBusDevice *dev)
+{
+    S5pc210UartState *s = FROM_SYSBUS(S5pc210UartState, dev);
+
+    /* memory mapping */
+    memory_region_init_io(&s->iomem, &s5pc210_uart_ops, s, "s5pc210.uart",
+                          S5PC210_UART_REGS_MEM_SIZE);
+    sysbus_init_mmio_region(dev, &s->iomem);
+
+    sysbus_init_irq(dev, &s->irq);
+
+    qemu_chr_add_handlers(s->chr, s5pc210_uart_can_receive,
+                          s5pc210_uart_receive, s5pc210_uart_event, s);
+
+    vmstate_register(&dev->qdev, -1, &vmstate_s5pc210_uart, s);
+
+    qemu_register_reset(s5pc210_uart_reset, s);
+
+    return 0;
+}
+
+static SysBusDeviceInfo s5pc210_uart_info = {
+        .init       = s5pc210_uart_init,
+        .qdev.name  = "s5pc210.uart",
+        .qdev.size  = sizeof(S5pc210UartState),
+        .qdev.props = (Property[]) {
+            DEFINE_PROP_CHR("chardev", S5pc210UartState, chr),
+            DEFINE_PROP_UINT32("channel", S5pc210UartState, channel, 0),
+            DEFINE_PROP_END_OF_LIST(),
+        }
+};
+
+static void s5pc210_uart_register(void)
+{
+    sysbus_register_withprop(&s5pc210_uart_info);
+}
+
+device_init(s5pc210_uart_register)
-- 
1.7.4.1

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

* [Qemu-devel] [PATCH 02/14] hw/sysbus.h: Increase maximum number of device IRQs.
  2011-12-07  9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
  2011-12-07  9:46 ` [Qemu-devel] [PATCH 01/14] ARM: s5pc210: Basic support of s5pc210 boards Evgeny Voevodin
@ 2011-12-07  9:46 ` Evgeny Voevodin
  2011-12-07  9:46 ` [Qemu-devel] [PATCH 03/14] ARM: s5pc210: IRQ subsystem support Evgeny Voevodin
                   ` (12 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07  9:46 UTC (permalink / raw)
  To: qemu-devel; +Cc: d.solodkiy, Evgeny Voevodin

Samsung s5pc210 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] 25+ messages in thread

* [Qemu-devel] [PATCH 03/14] ARM: s5pc210: IRQ subsystem support.
  2011-12-07  9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
  2011-12-07  9:46 ` [Qemu-devel] [PATCH 01/14] ARM: s5pc210: Basic support of s5pc210 boards Evgeny Voevodin
  2011-12-07  9:46 ` [Qemu-devel] [PATCH 02/14] hw/sysbus.h: Increase maximum number of device IRQs Evgeny Voevodin
@ 2011-12-07  9:46 ` Evgeny Voevodin
  2011-12-07  9:46 ` [Qemu-devel] [PATCH 04/14] ARM: s5pc210: PWM support Evgeny Voevodin
                   ` (11 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07  9:46 UTC (permalink / raw)
  To: qemu-devel; +Cc: d.solodkiy, Evgeny Voevodin


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

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

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

* [Qemu-devel] [PATCH 04/14] ARM: s5pc210: PWM support.
  2011-12-07  9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
                   ` (2 preceding siblings ...)
  2011-12-07  9:46 ` [Qemu-devel] [PATCH 03/14] ARM: s5pc210: IRQ subsystem support Evgeny Voevodin
@ 2011-12-07  9:46 ` Evgeny Voevodin
  2011-12-07  9:46 ` [Qemu-devel] [PATCH 05/14] hw/arm_boot.c: Add new secondary CPU bootloader Evgeny Voevodin
                   ` (10 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07  9:46 UTC (permalink / raw)
  To: qemu-devel; +Cc: d.solodkiy, Evgeny Voevodin


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

diff --git a/Makefile.target b/Makefile.target
index ed338a8..b9830d3 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 += s5pc210.o s5pc210_cmu.o s5pc210_uart.o s5pc210_gic.o \
-             s5pc210_combiner.o
+             s5pc210_combiner.o s5pc210_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/s5pc210.c b/hw/s5pc210.c
index 99e9b1b..9110228 100644
--- a/hw/s5pc210.c
+++ b/hw/s5pc210.c
@@ -89,6 +89,9 @@
 #define S5PC210_EXT_COMBINER_BASE_ADDR   0x10440000
 #define S5PC210_INT_COMBINER_BASE_ADDR   0x10448000
 
+/* PWM */
+#define S5PC210_PWM_BASE_ADDR            0x139D0000
+
 #define S5PC210_BASE_BOOT_ADDR           S5PC210_DRAM0_BASE_ADDR
 
 static struct arm_boot_info s5pc210_binfo = {
@@ -350,6 +353,15 @@ static void s5pc210_init(ram_addr_t ram_size,
     /* CMU */
     sysbus_create_simple("s5pc210.cmu", S5PC210_CMU_BASE_ADDR, NULL);
 
+    /* PWM */
+    sysbus_create_varargs("s5pc210.pwm", S5PC210_PWM_BASE_ADDR,
+            irq_table[s5pc210_get_irq(22, 0)],
+            irq_table[s5pc210_get_irq(22, 1)],
+            irq_table[s5pc210_get_irq(22, 2)],
+            irq_table[s5pc210_get_irq(22, 3)],
+            irq_table[s5pc210_get_irq(22, 4)],
+            NULL);
+
     /*** UARTs ***/
     for (n = 0; n < S5PC210_UARTS_NUMBER; n++) {
 
diff --git a/hw/s5pc210_pwm.c b/hw/s5pc210_pwm.c
new file mode 100644
index 0000000..5dfaa41
--- /dev/null
+++ b/hw/s5pc210_pwm.c
@@ -0,0 +1,433 @@
+/*
+ * Samsung s5pc210 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 "s5pc210.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     S5PC210_PWM_TIMERS_NUM      5
+#define     S5PC210_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 */
+
+} S5pc210_pwm;
+
+
+typedef struct S5pc210PWMState {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+
+    uint32_t    reg_tcfg[2];
+    uint32_t    reg_tcon;
+    uint32_t    reg_tint_cstat;
+
+    S5pc210CmuClock clk;         /* clock source for timer */
+    S5pc210_pwm timer[S5PC210_PWM_TIMERS_NUM];
+
+} S5pc210PWMState;
+
+/*** VMState ***/
+static const VMStateDescription VMState_S5pc210_pwm = {
+        .name = "s5pc210.pwm.pwm",
+        .version_id = 1,
+        .minimum_version_id = 1,
+        .minimum_version_id_old = 1,
+        .fields = (VMStateField[]) {
+            VMSTATE_UINT32(id, S5pc210_pwm),
+            VMSTATE_UINT32(freq, S5pc210_pwm),
+            VMSTATE_UINT32(reg_tcntb, S5pc210_pwm),
+            VMSTATE_UINT32(reg_tcmpb, S5pc210_pwm),
+            VMSTATE_END_OF_LIST()
+        }
+};
+
+static const VMStateDescription VMState_S5pc210PWMState = {
+        .name = "s5pc210.pwm",
+        .version_id = 1,
+        .minimum_version_id = 1,
+        .minimum_version_id_old = 1,
+        .fields = (VMStateField[]) {
+            VMSTATE_UINT32_ARRAY(reg_tcfg, S5pc210PWMState, 2),
+            VMSTATE_UINT32(reg_tcon, S5pc210PWMState),
+            VMSTATE_UINT32(reg_tint_cstat, S5pc210PWMState),
+            VMSTATE_STRUCT_ARRAY(timer, S5pc210PWMState,
+                    S5PC210_PWM_TIMERS_NUM, 0,
+            VMState_S5pc210_pwm, S5pc210_pwm),
+            VMSTATE_END_OF_LIST()
+        }
+};
+
+/*
+ * PWM update frequency
+ */
+static void s5pc210_pwm_update_freq(S5pc210PWMState *s, uint32_t id)
+{
+    uint32_t freq;
+    freq = s->timer[id].freq;
+    if (id > 1) {
+        s->timer[id].freq = s5pc210_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 = s5pc210_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 s5pc210_pwm_tick(void *opaque, uint32_t id)
+{
+    S5pc210PWMState *s = (S5pc210PWMState *)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 s5pc210_pwm_tick0(void *opaque)
+{
+    s5pc210_pwm_tick(opaque, 0);
+}
+static void s5pc210_pwm_tick1(void *opaque)
+{
+    s5pc210_pwm_tick(opaque, 1);
+}
+static void s5pc210_pwm_tick2(void *opaque)
+{
+    s5pc210_pwm_tick(opaque, 2);
+}
+static void s5pc210_pwm_tick3(void *opaque)
+{
+    s5pc210_pwm_tick(opaque, 3);
+}
+static void s5pc210_pwm_tick4(void *opaque)
+{
+    s5pc210_pwm_tick(opaque, 4);
+}
+
+/*
+ * PWM Read
+ */
+static uint64_t s5pc210_pwm_read(void *opaque, target_phys_addr_t offset,
+        unsigned size)
+{
+    S5pc210PWMState *s = (S5pc210PWMState *)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,
+                "[s5pc210.pwm: bad read offset " TARGET_FMT_plx "]\n",
+                offset);
+        break;
+    }
+    return value;
+}
+
+/*
+ * PWM Write
+ */
+static void s5pc210_pwm_write(void *opaque, target_phys_addr_t offset,
+        uint64_t value, unsigned size)
+{
+    S5pc210PWMState *s = (S5pc210PWMState *)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 < S5PC210_PWM_TIMERS_NUM; i++) {
+            s5pc210_pwm_update_freq(s, s->timer[i].id);
+        }
+        break;
+
+    case TCON:
+        for (i = 0; i < S5PC210_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 < S5PC210_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,
+                "[s5pc210.pwm: bad write offset " TARGET_FMT_plx "]\n",
+                offset);
+        break;
+
+    }
+}
+
+/*
+ * Set default values to timer fields and registers
+ */
+static void s5pc210_pwm_reset(S5pc210PWMState *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 < S5PC210_PWM_TIMERS_NUM; i++) {
+        s->timer[i].reg_tcmpb = 0;
+        s->timer[i].reg_tcntb = 0;
+
+        s5pc210_pwm_update_freq(s, s->timer[i].id);
+        ptimer_stop(s->timer[i].ptimer);
+    }
+}
+
+static const MemoryRegionOps s5pc210_pwm_ops = {
+        .read = s5pc210_pwm_read,
+        .write = s5pc210_pwm_write,
+        .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/*
+ * PWM timer initialization
+ */
+static int s5pc210_pwm_init(SysBusDevice *dev)
+{
+    S5pc210PWMState *s = FROM_SYSBUS(S5pc210PWMState, dev);
+    int i;
+    QEMUBH * bh[S5PC210_PWM_TIMERS_NUM];
+
+    s->clk = ACLK_100;
+
+    bh[0] = qemu_bh_new(s5pc210_pwm_tick0, s);
+    bh[1] = qemu_bh_new(s5pc210_pwm_tick1, s);
+    bh[2] = qemu_bh_new(s5pc210_pwm_tick2, s);
+    bh[3] = qemu_bh_new(s5pc210_pwm_tick3, s);
+    bh[4] = qemu_bh_new(s5pc210_pwm_tick4, s);
+
+    for (i = 0; i < S5PC210_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, &s5pc210_pwm_ops, s, "s5pc210-pwm",
+            S5PC210_PWM_REG_MEM_SIZE);
+    sysbus_init_mmio_region(dev, &s->iomem);
+
+    s5pc210_pwm_reset(s);
+
+    qemu_register_reset((QEMUResetHandler *)s5pc210_pwm_reset, s);
+    vmstate_register(NULL, -1, &VMState_S5pc210PWMState, s);
+    return 0;
+}
+
+static void s5pc210_pwm_register_devices(void)
+{
+    sysbus_register_dev("s5pc210.pwm", sizeof(S5pc210PWMState),
+            s5pc210_pwm_init);
+}
+
+device_init(s5pc210_pwm_register_devices)
-- 
1.7.4.1

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

* [Qemu-devel] [PATCH 05/14] hw/arm_boot.c: Add new secondary CPU bootloader.
  2011-12-07  9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
                   ` (3 preceding siblings ...)
  2011-12-07  9:46 ` [Qemu-devel] [PATCH 04/14] ARM: s5pc210: PWM support Evgeny Voevodin
@ 2011-12-07  9:46 ` Evgeny Voevodin
  2011-12-07  9:46 ` [Qemu-devel] [PATCH 06/14] hw/arm_gic.c: lower IRQ only on changing of enable bit Evgeny Voevodin
                   ` (9 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07  9:46 UTC (permalink / raw)
  To: qemu-devel; +Cc: 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] 25+ messages in thread

* [Qemu-devel] [PATCH 06/14] hw/arm_gic.c: lower IRQ only on changing of enable bit.
  2011-12-07  9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
                   ` (4 preceding siblings ...)
  2011-12-07  9:46 ` [Qemu-devel] [PATCH 05/14] hw/arm_boot.c: Add new secondary CPU bootloader Evgeny Voevodin
@ 2011-12-07  9:46 ` Evgeny Voevodin
  2011-12-07  9:46 ` [Qemu-devel] [PATCH 07/14] ARM: s5pc210: MCT support Evgeny Voevodin
                   ` (8 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07  9:46 UTC (permalink / raw)
  To: qemu-devel; +Cc: 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] 25+ messages in thread

* [Qemu-devel] [PATCH 07/14] ARM: s5pc210: MCT support.
  2011-12-07  9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
                   ` (5 preceding siblings ...)
  2011-12-07  9:46 ` [Qemu-devel] [PATCH 06/14] hw/arm_gic.c: lower IRQ only on changing of enable bit Evgeny Voevodin
@ 2011-12-07  9:46 ` Evgeny Voevodin
  2011-12-07  9:46 ` [Qemu-devel] [PATCH 08/14] ARM: s5pc210: Boot secondary CPU Evgeny Voevodin
                   ` (7 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07  9:46 UTC (permalink / raw)
  To: qemu-devel; +Cc: d.solodkiy, Evgeny Voevodin


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

diff --git a/Makefile.target b/Makefile.target
index b9830d3..805993b 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 += s5pc210.o s5pc210_cmu.o s5pc210_uart.o s5pc210_gic.o \
-             s5pc210_combiner.o s5pc210_pwm.o
+             s5pc210_combiner.o s5pc210_pwm.o s5pc210_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/s5pc210.c b/hw/s5pc210.c
index 9110228..eabe3b1 100644
--- a/hw/s5pc210.c
+++ b/hw/s5pc210.c
@@ -92,6 +92,9 @@
 /* PWM */
 #define S5PC210_PWM_BASE_ADDR            0x139D0000
 
+/* MCT */
+#define S5PC210_MCT_BASE_ADDR            0x10050000
+
 #define S5PC210_BASE_BOOT_ADDR           S5PC210_DRAM0_BASE_ADDR
 
 static struct arm_boot_info s5pc210_binfo = {
@@ -362,6 +365,22 @@ static void s5pc210_init(ram_addr_t ram_size,
             irq_table[s5pc210_get_irq(22, 4)],
             NULL);
 
+    /* Multi Core Timer */
+    dev = qdev_create(NULL, "s5pc210.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[s5pc210_get_irq(1, 4 + n)]);
+    }
+    /* Connect local timer interrupts to Combiner gpio_in */
+    sysbus_connect_irq(busdev, 4,
+            irq_table[s5pc210_get_irq(51, 0)]);
+    sysbus_connect_irq(busdev, 5,
+            irq_table[s5pc210_get_irq(35, 3)]);
+    sysbus_mmio_map(busdev, 0, S5PC210_MCT_BASE_ADDR);
+
     /*** UARTs ***/
     for (n = 0; n < S5PC210_UARTS_NUMBER; n++) {
 
diff --git a/hw/s5pc210_mct.c b/hw/s5pc210_mct.c
new file mode 100644
index 0000000..7a7dd84
--- /dev/null
+++ b/hw/s5pc210_mct.c
@@ -0,0 +1,1483 @@
+/*
+ * Samsung s5pc210 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 "s5pc210.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 */
+
+} s5pc210_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;
+
+} s5pc210_mct_lt;
+
+
+
+typedef struct S5pc210MCTState {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+
+    /* Registers */
+    uint32_t    reg_mct_cfg;
+
+    s5pc210_mct_lt l_timer[2];
+    s5pc210_mct_gt g_timer;
+
+    uint32_t    freq;                   /* all timers tick frequency, TCLK */
+} S5pc210MCTState;
+
+/*** VMState ***/
+static const VMStateDescription VMState_tick_timer = {
+        .name = "s5pc210.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 = "s5pc210.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_S5pc210_mct_lt = {
+        .name = "s5pc210.mct.lt",
+        .version_id = 1,
+        .minimum_version_id = 1,
+        .minimum_version_id_old = 1,
+        .fields = (VMStateField[]) {
+            VMSTATE_INT32(id, s5pc210_mct_lt),
+            VMSTATE_STRUCT(tick_timer, s5pc210_mct_lt, 0,
+                    VMState_tick_timer,
+                    struct tick_timer),
+            VMSTATE_STRUCT(reg, s5pc210_mct_lt, 0,
+                    VMState_lregs,
+                    struct lregs),
+            VMSTATE_END_OF_LIST()
+        }
+};
+
+static const VMStateDescription VMState_gregs = {
+        .name = "s5pc210.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_S5pc210_mct_gt = {
+        .name = "s5pc210.mct.lt",
+        .version_id = 1,
+        .minimum_version_id = 1,
+        .minimum_version_id_old = 1,
+        .fields = (VMStateField[]) {
+            VMSTATE_STRUCT(reg, s5pc210_mct_gt, 0, VMState_gregs, struct gregs),
+            VMSTATE_UINT64(count, s5pc210_mct_gt),
+            VMSTATE_INT32(curr_comp, s5pc210_mct_gt),
+            VMSTATE_END_OF_LIST()
+        }
+};
+
+static const VMStateDescription VMState_S5pc210MCTState = {
+        .name = "s5pc210.mct",
+        .version_id = 1,
+        .minimum_version_id = 1,
+        .minimum_version_id_old = 1,
+        .fields = (VMStateField[]) {
+            VMSTATE_UINT32(reg_mct_cfg, S5pc210MCTState),
+            VMSTATE_STRUCT_ARRAY(l_timer, S5pc210MCTState, 2, 0,
+                VMState_S5pc210_mct_lt, s5pc210_mct_lt),
+            VMSTATE_STRUCT(g_timer, S5pc210MCTState, 0,
+                VMState_S5pc210_mct_gt, s5pc210_mct_gt),
+            VMSTATE_UINT32(freq, S5pc210MCTState),
+                VMSTATE_END_OF_LIST()
+        }
+};
+
+static void s5pc210_mct_update_freq(S5pc210MCTState *s);
+
+/*
+ * Set counter of FRC global timer.
+ */
+static void s5pc210_gfrc_set_count(s5pc210_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 s5pc210_gfrc_get_count(s5pc210_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 s5pc210_gfrc_stop(s5pc210_mct_gt *s)
+{
+    DPRINTF("global timer frc stop\n");
+
+    ptimer_stop(s->ptimer_frc);
+}
+
+/*
+ * Start global FRC timer
+ */
+static void s5pc210_gfrc_start(s5pc210_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 s5pc210_gcomp_find(S5pc210MCTState *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 = s5pc210_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 s5pc210_gcomp_get_distance(S5pc210MCTState *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 s5pc210_gfrc_restart(S5pc210MCTState *s)
+{
+    uint64_t distance;
+
+    s5pc210_gfrc_stop(&s->g_timer);
+
+    s->g_timer.curr_comp = s5pc210_gcomp_find(s);
+
+    distance = s5pc210_gcomp_get_distance(s, s->g_timer.curr_comp);
+
+    if (distance > MCT_GT_COUNTER_STEP || !distance) {
+        distance = MCT_GT_COUNTER_STEP;
+    }
+
+    s5pc210_gfrc_set_count(&s->g_timer, distance);
+    s5pc210_gfrc_start(&s->g_timer);
+}
+
+/*
+ * Raise global timer CMP IRQ
+ */
+static void s5pc210_gcomp_raise_irq(void *opaque, uint32_t id)
+{
+    s5pc210_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 s5pc210_gcomp_lower_irq(void *opaque, uint32_t id)
+{
+    s5pc210_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 s5pc210_gfrc_event(void *opaque)
+{
+    S5pc210MCTState *s = (S5pc210MCTState *)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 */
+            s5pc210_gcomp_raise_irq(&s->g_timer, i);
+        }
+    }
+
+    /* Reload FRC to reach nearest comparator */
+    s->g_timer.curr_comp = s5pc210_gcomp_find(s);
+    distance = s5pc210_gcomp_get_distance(s, s->g_timer.curr_comp);
+    if (distance > MCT_GT_COUNTER_STEP) {
+        distance = MCT_GT_COUNTER_STEP;
+    }
+    s5pc210_gfrc_set_count(&s->g_timer, distance);
+
+    s5pc210_gfrc_start(&s->g_timer);
+
+    return;
+}
+
+/*
+ * Get counter of FRC local timer.
+ */
+static uint64_t s5pc210_lfrc_get_count(s5pc210_mct_lt *s)
+{
+    return ptimer_get_count(s->ptimer_frc);
+}
+
+/*
+ * Set counter of FRC local timer.
+ */
+static void s5pc210_lfrc_update_count(s5pc210_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 s5pc210_lfrc_start(s5pc210_mct_lt *s)
+{
+    ptimer_run(s->ptimer_frc, 1);
+}
+
+/*
+ * Stop local FRC timer
+ */
+static void s5pc210_lfrc_stop(s5pc210_mct_lt *s)
+{
+    ptimer_stop(s->ptimer_frc);
+}
+
+/*
+ * Local timer free running counter tick handler
+ */
+static void s5pc210_lfrc_event(void *opaque)
+{
+    s5pc210_mct_lt * s = (s5pc210_mct_lt *)opaque;
+
+    /* local frc expired */
+
+    DPRINTF("\n");
+
+    s->reg.int_cstat |= L_INT_CSTAT_FRCCNT;
+
+    /* update frc counter */
+    s5pc210_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 */
+    s5pc210_lfrc_start(s);
+}
+
+
+static uint32_t s5pc210_ltick_int_get_cnto(struct tick_timer *s);
+static void s5pc210_ltick_cnt_start(struct tick_timer *s);
+static void s5pc210_ltick_cnt_stop(struct tick_timer *s);
+static uint32_t s5pc210_ltick_cnt_get_cnto(struct tick_timer *s);
+static void s5pc210_ltick_recalc_count(struct tick_timer *s);
+
+
+
+/*
+ * Action on enabling local tick int timer
+ */
+static void s5pc210_ltick_int_start(struct tick_timer *s)
+{
+    if (!s->int_run) {
+        s->int_run = 1;
+    }
+}
+
+/*
+ * Action on disabling local tick int timer
+ */
+static void s5pc210_ltick_int_stop(struct tick_timer *s)
+{
+    if (s->int_run) {
+        s->last_icnto = s5pc210_ltick_int_get_cnto(s);
+        s->int_run = 0;
+    }
+}
+
+/*
+ * Get count for INT timer
+ */
+static uint32_t s5pc210_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 s5pc210_ltick_cnt_start(struct tick_timer *s)
+{
+    if (!s->cnt_run) {
+
+        s5pc210_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 s5pc210_ltick_cnt_stop(struct tick_timer *s)
+{
+    if (s->cnt_run) {
+
+        s->last_tcnto = s5pc210_ltick_cnt_get_cnto(s);
+
+        if (s->int_run) {
+            s5pc210_ltick_int_stop(s);
+        }
+
+        ptimer_stop(s->ptimer_tick);
+
+        s->cnt_run = 0;
+    }
+}
+
+/*
+ * Get counter for CNT timer
+ */
+static uint32_t s5pc210_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 s5pc210_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) {
+        s5pc210_ltick_cnt_stop(s);
+        cnt_stopped = 1;
+    }
+
+    if (s->int_run) {
+        s5pc210_ltick_int_stop(s);
+        int_stopped = 1;
+    }
+
+    s->tcntb = new_cnt + 1;
+    s->icntb = new_int + 1;
+
+    if (cnt_stopped) {
+        s5pc210_ltick_cnt_start(s);
+    }
+    if (int_stopped) {
+        s5pc210_ltick_int_start(s);
+    }
+
+}
+
+/*
+ * Calculate new counter value for tick timer
+ */
+static void s5pc210_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 s5pc210_ltick_timer_init(struct tick_timer *s)
+{
+    s5pc210_ltick_int_stop(s);
+    s5pc210_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 s5pc210_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 s5pc210_ltick_event(void *opaque)
+{
+    s5pc210_mct_lt * s = (s5pc210_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 */
+    s5pc210_ltick_timer_event(&s->tick_timer);
+
+    /* get tick_timer cnt */
+    tcnto = s5pc210_ltick_cnt_get_cnto(&s->tick_timer);
+
+    /* get tick_timer int */
+    icnto = s5pc210_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) {
+            s5pc210_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) {
+            s5pc210_ltick_set_cntb(&s->tick_timer,
+                    s->reg.cnt[L_REG_CNT_TCNTB],
+                    icnto);
+        }
+    }
+
+    /* start tick_timer cnt */
+    s5pc210_ltick_cnt_start(&s->tick_timer);
+
+    /* start tick_timer int */
+    s5pc210_ltick_int_start(&s->tick_timer);
+}
+
+/* update timer frequency */
+static void s5pc210_mct_update_freq(S5pc210MCTState *s)
+{
+    uint32_t freq = s->freq;
+    s->freq = s5pc210_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 s5pc210_mct_reset(void *opaque)
+{
+    S5pc210MCTState *s = (S5pc210MCTState *) opaque;
+    uint32_t i;
+
+    s->reg_mct_cfg = 0;
+
+    /* global timer */
+    memset(&s->g_timer.reg, 0, sizeof(s->g_timer.reg));
+    s5pc210_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);
+
+        s5pc210_ltick_timer_init(&s->l_timer[i].tick_timer);
+    }
+
+    s5pc210_mct_update_freq(s);
+
+}
+
+/* Multi Core Timer read */
+static uint64_t s5pc210_mct_read(void *opaque, target_phys_addr_t offset,
+        unsigned size)
+{
+    S5pc210MCTState *s = (S5pc210MCTState *)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 = s5pc210_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 = s5pc210_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 = s5pc210_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 = s5pc210_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("s5pc210.mct: bad read offset " TARGET_FMT_plx "\n", offset);
+        break;
+    }
+    return value;
+}
+
+/* MCT write */
+static void s5pc210_mct_write(void *opaque, target_phys_addr_t offset,
+        uint64_t value, unsigned size)
+{
+    S5pc210MCTState *s = (S5pc210MCTState *)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;
+        s5pc210_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;
+        s5pc210_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);
+    }
+
+    s5pc210_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)) {
+            s5pc210_gfrc_start(&s->g_timer);
+        }
+        if ((value & G_TCON_TIMER_ENABLE) < (old_val &
+                G_TCON_TIMER_ENABLE)) {
+            s5pc210_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))) {
+                s5pc210_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)) {
+                s5pc210_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)) {
+                    s5pc210_gcomp_raise_irq(&s->g_timer, i);
+                }
+            }
+
+            if ((value & G_INT_ENABLE(i)) < (s->g_timer.reg.tcon &
+                    G_INT_ENABLE(i))) {
+                s5pc210_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);
+            s5pc210_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);
+            s5pc210_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);
+            s5pc210_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);
+            s5pc210_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);
+            s5pc210_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);
+            s5pc210_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.
+         */
+        s5pc210_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) {
+            s5pc210_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[s5pc210.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("s5pc210.mct: bad write offset " TARGET_FMT_plx "\n", offset);
+        break;
+    }
+}
+
+static const MemoryRegionOps s5pc210_mct_ops = {
+        .read = s5pc210_mct_read,
+        .write = s5pc210_mct_write,
+        .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/* MCT init */
+static int s5pc210_mct_init(SysBusDevice *dev)
+{
+    int i;
+    S5pc210MCTState *s = FROM_SYSBUS(S5pc210MCTState, dev);
+    QEMUBH * bh[2];
+
+    /* Global timer */
+    bh[0] = qemu_bh_new(s5pc210_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(s5pc210_ltick_event, &s->l_timer[i]);
+        bh[1] = qemu_bh_new(s5pc210_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, &s5pc210_mct_ops, s, "s5pc210-mct",
+            MCT_SFR_SIZE);
+    sysbus_init_mmio_region(dev, &s->iomem);
+
+    s5pc210_mct_reset(s);
+
+    qemu_register_reset(s5pc210_mct_reset, s);
+    vmstate_register(NULL, -1, &VMState_S5pc210MCTState, s);
+    return 0;
+}
+
+static void s5pc210_mct_register_devices(void)
+{
+    sysbus_register_dev("s5pc210.mct", sizeof(S5pc210MCTState),
+            s5pc210_mct_init);
+}
+
+device_init(s5pc210_mct_register_devices)
-- 
1.7.4.1

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

* [Qemu-devel] [PATCH 08/14] ARM: s5pc210: Boot secondary CPU.
  2011-12-07  9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
                   ` (6 preceding siblings ...)
  2011-12-07  9:46 ` [Qemu-devel] [PATCH 07/14] ARM: s5pc210: MCT support Evgeny Voevodin
@ 2011-12-07  9:46 ` Evgeny Voevodin
  2011-12-07  9:47 ` [Qemu-devel] [PATCH 09/14] hw/lan9118.c: Basic byte/word/long access support Evgeny Voevodin
                   ` (6 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07  9:46 UTC (permalink / raw)
  To: qemu-devel; +Cc: d.solodkiy, Evgeny Voevodin


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

diff --git a/hw/s5pc210.c b/hw/s5pc210.c
index eabe3b1..90858e9 100644
--- a/hw/s5pc210.c
+++ b/hw/s5pc210.c
@@ -97,6 +97,12 @@
 
 #define S5PC210_BASE_BOOT_ADDR           S5PC210_DRAM0_BASE_ADDR
 
+/* Secondary CPU startup code is in IROM memory */
+#define S5PC210_SMP_BOOT_ADDR            S5PC210_IROM_BASE_ADDR
+
+/* Secondary CPU polling address to get loader start from */
+#define S5PC210_SECOND_CPU_BOOTREG       0x10020814
+
 static struct arm_boot_info s5pc210_binfo = {
         .loader_start     = S5PC210_BASE_BOOT_ADDR,
 };
@@ -213,6 +219,8 @@ static void s5pc210_init(ram_addr_t ram_size,
     MemoryRegion *irom_alias_mem = g_new(MemoryRegion, 1);
     MemoryRegion *dram0_mem = g_new(MemoryRegion, 1);
     MemoryRegion *dram1_mem = NULL;
+    MemoryRegion *hack_mem = g_new(MemoryRegion, 1);
+    MemoryRegion *bootreg_mem = g_new(MemoryRegion, 1);
     S5pc210Irq *irqs;
     qemu_irq *irq_table;
     qemu_irq *irqp;
@@ -225,9 +233,11 @@ static void s5pc210_init(ram_addr_t ram_size,
     switch (board_type) {
     case BOARD_S5PC210_NURI:
         s5pc210_binfo.board_id      = MACH_NURI_ID;
+        s5pc210_binfo.smp_bootreg_addr = S5PC210_SECOND_CPU_BOOTREG;
         break;
     case BOARD_S5PC210_SMDKC210:
         s5pc210_binfo.board_id = MACH_SMDKC210_ID;
+        s5pc210_binfo.smp_bootreg_addr = S5PC210_SECOND_CPU_BOOTREG;
         break;
     default:
         break;
@@ -353,6 +363,20 @@ static void s5pc210_init(ram_addr_t ram_size,
     memory_region_add_subregion(system_mem, S5PC210_DRAM0_BASE_ADDR,
             dram0_mem);
 
+    /*
+     * Secondary CPU startup code will be placed here.
+     */
+    memory_region_init_ram(hack_mem, NULL, "s5pc210.hack", 0x1000);
+    memory_region_add_subregion(system_mem, S5PC210_SMP_BOOT_ADDR,
+            hack_mem);
+
+    /*
+     * Hack: Map SECOND_CPU_BOOTREG, because it is in PMU USER5 register.
+     */
+    memory_region_init_ram(bootreg_mem, NULL, "s5pc210.bootreg", 0x4);
+    memory_region_add_subregion(system_mem, S5PC210_SECOND_CPU_BOOTREG,
+            bootreg_mem);
+
     /* CMU */
     sysbus_create_simple("s5pc210.cmu", S5PC210_CMU_BASE_ADDR, NULL);
 
@@ -429,7 +453,7 @@ static void s5pc210_init(ram_addr_t ram_size,
     s5pc210_binfo.kernel_filename = kernel_filename;
     s5pc210_binfo.initrd_filename = initrd_filename;
     s5pc210_binfo.kernel_cmdline = kernel_cmdline;
-
+    s5pc210_binfo.smp_priv_base = S5PC210_SMP_PRIVATE_BASE_ADDR;
 
     arm_load_kernel(first_cpu, &s5pc210_binfo);
 }
-- 
1.7.4.1

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

* [Qemu-devel] [PATCH 09/14] hw/lan9118.c: Basic byte/word/long access support.
  2011-12-07  9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
                   ` (7 preceding siblings ...)
  2011-12-07  9:46 ` [Qemu-devel] [PATCH 08/14] ARM: s5pc210: Boot secondary CPU Evgeny Voevodin
@ 2011-12-07  9:47 ` Evgeny Voevodin
  2011-12-07 10:09   ` Peter Maydell
  2011-12-07  9:47 ` [Qemu-devel] [PATCH 10/14] hw/s5pc210.c: Add lan9118 support to SMDK board Evgeny Voevodin
                   ` (5 subsequent siblings)
  14 siblings, 1 reply; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07  9:47 UTC (permalink / raw)
  To: qemu-devel; +Cc: d.solodkiy, Evgeny Voevodin

We included this chip into s5pc210 platform because SMDK board holds
lan9215 chip. Difference is that 9215 access is 16-bit wide and some
registers differ. By addition basic 16-bit access to 9118 emulation we
achieved ethernet controller support by Linux lernel on SMDK boards.

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

diff --git a/hw/lan9118.c b/hw/lan9118.c
index ee8b2ea..7aebabd 100644
--- a/hw/lan9118.c
+++ b/hw/lan9118.c
@@ -212,6 +212,14 @@ 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;
 } lan9118_state;
 
 static void lan9118_update(lan9118_state *s)
@@ -345,6 +353,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 +907,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 +1040,43 @@ 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_writeb(void *opaque, target_phys_addr_t offset,
+                           uint32_t val)
+{
+    hw_error("lan9118_writeb: Bad reg 0x%x = %x\n", (int)offset, val);
+}
+
+static void lan9118_writew(void *opaque, target_phys_addr_t offset,
+                           uint32_t val)
+{
+    lan9118_state *s = (lan9118_state *)opaque;
+    offset &= 0xff;
+
+    /* TODO: Implement fair word operation, because this implementation
+     * assumes that any register is accessed as two 16-bit operations. */
+
+    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 uint32_t lan9118_readl(void *opaque, target_phys_addr_t offset)
 {
     lan9118_state *s = (lan9118_state *)opaque;
 
@@ -1103,9 +1149,47 @@ static uint64_t lan9118_readl(void *opaque, target_phys_addr_t offset,
     return 0;
 }
 
+static uint32_t lan9118_readb(void *opaque, target_phys_addr_t offset)
+{
+    hw_error("lan9118_readb: Bad reg 0x%x\n", (int)offset);
+}
+
+static uint32_t lan9118_readw(void *opaque, target_phys_addr_t offset)
+{
+    lan9118_state *s = (lan9118_state *)opaque;
+    uint32_t val;
+
+    /* TODO: Implement fair word operation, because this implementation
+     * assumes that any register is accessed as two 16-bit operations. */
+
+    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 const MemoryRegionOps lan9118_mem_ops = {
-    .read = lan9118_readl,
-    .write = lan9118_writel,
+    .old_mmio = {
+        .read = { lan9118_readb, lan9118_readw, lan9118_readl, },
+        .write = { lan9118_writeb, lan9118_writew, lan9118_writel, },
+    },
     .endianness = DEVICE_NATIVE_ENDIAN,
 };
 
-- 
1.7.4.1

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

* [Qemu-devel] [PATCH 10/14] hw/s5pc210.c: Add lan9118 support to SMDK board.
  2011-12-07  9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
                   ` (8 preceding siblings ...)
  2011-12-07  9:47 ` [Qemu-devel] [PATCH 09/14] hw/lan9118.c: Basic byte/word/long access support Evgeny Voevodin
@ 2011-12-07  9:47 ` Evgeny Voevodin
  2011-12-07  9:47 ` [Qemu-devel] [PATCH 11/14] ARM: s5pc210: added s5pc210 display controller device (FIMD) Evgeny Voevodin
                   ` (4 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07  9:47 UTC (permalink / raw)
  To: qemu-devel; +Cc: d.solodkiy, Evgeny Voevodin


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

diff --git a/hw/s5pc210.c b/hw/s5pc210.c
index 90858e9..8678b97 100644
--- a/hw/s5pc210.c
+++ b/hw/s5pc210.c
@@ -29,6 +29,8 @@
 #include "sysemu.h"
 #include "sysbus.h"
 #include "arm-misc.h"
+#include "net.h"
+#include "devices.h"
 #include "exec-memory.h"
 #include "s5pc210.h"
 
@@ -229,6 +231,8 @@ static void s5pc210_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_S5PC210_NURI:
@@ -446,6 +450,21 @@ static void s5pc210_init(ram_addr_t ram_size,
         s5pc210_uart_create(addr, fifo_size, channel, NULL, uart_irq);
     }
 
+    /*** LAN adapter ***/
+    if (board_type == BOARD_S5PC210_SMDKC210) {
+
+        for (n = 0; n < nb_nics; n++) {
+            nd = &nd_table[n];
+
+            if (!done_nic && (!nd->model ||
+                    strcmp(nd->model, "lan9118") == 0)) {
+                lan9118_init(nd, 0x05000000,
+                        qemu_irq_invert(irq_table[s5pc210_get_irq(37, 1)]));
+                done_nic = 1;
+            }
+        }
+    }
+
     /*** Load kernel ***/
 
     s5pc210_binfo.ram_size = ram_size;
-- 
1.7.4.1

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

* [Qemu-devel] [PATCH 11/14] ARM: s5pc210: added s5pc210 display controller device (FIMD)
  2011-12-07  9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
                   ` (9 preceding siblings ...)
  2011-12-07  9:47 ` [Qemu-devel] [PATCH 10/14] hw/s5pc210.c: Add lan9118 support to SMDK board Evgeny Voevodin
@ 2011-12-07  9:47 ` Evgeny Voevodin
  2011-12-07  9:47 ` [Qemu-devel] [PATCH 12/14] SD card: add query function to check wether SD card currently ready to recieve data Before executing data transfer to card, we must check that previously issued command wasn't a simple query command (for ex. CMD13), which doesn't require data transfer. Currently, we only can aquire information about whether SD card is in sending data state or not. This patch allows us to query wether previous command was data write command and it was successfully accepted by card (meaning that SD card in recieving data state) Evgeny Voevodin
                   ` (3 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07  9:47 UTC (permalink / raw)
  To: qemu-devel; +Cc: Mitsyanko Igor, d.solodkiy, Evgeny Voevodin

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


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

diff --git a/Makefile.target b/Makefile.target
index 805993b..84c4a05 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 += s5pc210.o s5pc210_cmu.o s5pc210_uart.o s5pc210_gic.o \
-             s5pc210_combiner.o s5pc210_pwm.o s5pc210_mct.o
+             s5pc210_combiner.o s5pc210_pwm.o s5pc210_mct.o s5pc210_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/s5pc210.c b/hw/s5pc210.c
index 8678b97..c7cba3e 100644
--- a/hw/s5pc210.c
+++ b/hw/s5pc210.c
@@ -97,6 +97,10 @@
 /* MCT */
 #define S5PC210_MCT_BASE_ADDR            0x10050000
 
+/* Display controllers (FIMD) */
+#define S5PC210_FIMD0_BASE_ADDR          0x11C00000
+#define S5PC210_FIMD1_BASE_ADDR          0x12000000
+
 #define S5PC210_BASE_BOOT_ADDR           S5PC210_DRAM0_BASE_ADDR
 
 /* Secondary CPU startup code is in IROM memory */
@@ -465,6 +469,13 @@ static void s5pc210_init(ram_addr_t ram_size,
         }
     }
 
+    /*** Display controller (FIMD) ***/
+    sysbus_create_varargs("s5pc210.fimd", S5PC210_FIMD0_BASE_ADDR,
+            irq_table[s5pc210_get_irq(11, 0)],
+            irq_table[s5pc210_get_irq(11, 1)],
+            irq_table[s5pc210_get_irq(11, 2)],
+            NULL);
+
     /*** Load kernel ***/
 
     s5pc210_binfo.ram_size = ram_size;
diff --git a/hw/s5pc210_fimd.c b/hw/s5pc210_fimd.c
new file mode 100644
index 0000000..b3b01f2
--- /dev/null
+++ b/hw/s5pc210_fimd.c
@@ -0,0 +1,1698 @@
+/*
+ * Samsung s5pc210 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 S5P_FIMD_DEBUG              0
+#define S5P_FIMD_MODE_TRACE         0
+
+#if S5P_FIMD_DEBUG == 0
+    #define print_debug1(fmt, args...)   do { } while (0)
+    #define print_debug2(fmt, args...)   do { } while (0)
+    #define print_error(fmt, args...)  \
+    do {fprintf(stderr, "QEMU FIMD ERROR: "fmt, ## args); } while (0)
+#elif S5P_FIMD_DEBUG == 1
+    #define print_debug1(fmt, args...) \
+    do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+    #define print_debug2(fmt, args...)   do { } while (0)
+    #define print_error(fmt, args...)  \
+    do {fprintf(stderr, "QEMU FIMD ERROR: "fmt, ## args); } while (0)
+#else
+    #define print_debug1(fmt, args...) \
+    do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+    #define print_debug2(fmt, args...) \
+    do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+    #define print_error(fmt, args...)  \
+    do {fprintf(stderr, "QEMU FIMD ERROR: "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_VIDTCON2_SIZE_MASK     0x07FF
+#define FIMD_VIDTCON2_HOR_SHIFT     0
+#define FIMD_VIDTCON2_VER_SHIFT     11
+
+/* Window control registers */
+#define FIMD_WINCON0                0x0020
+#define FIMD_WINCON_ROMASK          0x82200000
+#define FIMD_WINCON_ENWIN           (1 << 0)
+#define FIMD_WINCON_BLD_PIX         (1 << 6)
+#define FIMD_WINCON_ALPHA_SEL       (1 << 1)
+#define FIMD_WINCON_ALSEL_SHIFT     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_BPPMODE_SHIFT   2
+#define FIMD_WINCON_BPPMODE         0x0F
+#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)
+
+/* Window position control registers */
+#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_VIDWADD2_PAGEWIDTH     0x1FFF
+#define FIMD_VIDWADD2_OFFSIZE       0x1FFF
+#define FIMD_VIDWADD2_OFFSIZE_SHIFT 13
+
+/* Window color key registers */
+#define FIMD_WKEYCON0_COMPKEY       0x00FFFFFF
+#define FIMD_WKEYCON0_CTL_SHIFT     24
+#define FIMD_WKEYCON0_CTL_DIRCON    0x1
+#define FIMD_WKEYCON0_CTL_KEYEN     0x2
+#define FIMD_WKEYCON0_CTL_KEYBLEN   0x4
+
+/* Window alpha control registers */
+#define FIMD_VIDALPHA_ALPHA_MASK    0x000F0F0F
+
+/* Window color map registers */
+#define FIMD_WINMAP_EN              (1 << 24)
+#define FIMD_WINMAP_COLOR_MASK      0x00FFFFFF
+#define FIMD_WINMAP_NOCOLOR         (~FIMD_WINMAP_COLOR_MASK)
+
+/* Window palette control registers */
+#define FIMD_WPAL_W0PAL_L           0x07
+#define FIMD_WPAL_W0PAL_L_SH     0
+#define FIMD_WPAL_W1PAL_L           0x07
+#define FIMD_WPAL_W1PAL_L_SH     3
+#define FIMD_WPAL_W2PAL_L           0x01
+#define FIMD_WPAL_W2PAL_L_SH     6
+#define FIMD_WPAL_W2PAL_H           0x06
+#define FIMD_WPAL_W2PAL_H_SH     8
+#define FIMD_WPAL_W3PAL_L           0x01
+#define FIMD_WPAL_W3PAL_L_SH     7
+#define FIMD_WPAL_W3PAL_H           0x06
+#define FIMD_WPAL_W3PAL_H_SH     12
+#define FIMD_WPAL_W4PAL_L           0x01
+#define FIMD_WPAL_W4PAL_L_SH     8
+#define FIMD_WPAL_W4PAL_H           0x06
+#define FIMD_WPAL_W4PAL_H_SH     16
+
+/* Trigger control registers */
+#define FIMD_TRIGCON                0x01A4
+#define FIMD_TRIGCON_ROMASK         0x00000004
+
+/* 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_COEF_MASK      0xF
+#define FIMD_BLENDEQ_COEF_Q         18
+#define FIMD_BLENDEQ_COEF_P         12
+#define FIMD_BLENDEQ_COEF_B         6
+#define FIMD_BLENDEQ_COEF_A         0
+
+typedef struct {
+    uint8_t r, g, b;
+    uint32_t a;
+} rgba;
+#define RGBA_SIZE  7
+
+typedef struct DrawConfig DrawConfig;
+
+typedef void pixel_to_rgb_func(uint32_t pixel, rgba *p);
+typedef void draw_line_func(struct DrawConfig *cfg, uint8_t *src,
+                            uint8_t *dst, uint8_t *ifb);
+typedef uint32_t coef_func(const struct DrawConfig *cfg, rgba pa, rgba pb);
+
+typedef struct {
+    uint32_t wincon;        /* Window control register */
+    uint32_t vidosd[4];     /* Window position control registers A-D */
+    uint32_t buf_start[3];  /* Start address for video frame buffer */
+    uint32_t buf_end[3];    /* End address for video frame buffer */
+    uint32_t buf_size;      /* Virtual screen width */
+    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 vidw_alpha[2]; /* Window alpha control registers */
+    uint32_t blendeq;       /* Window blending equation control register */
+    uint32_t rtqoscon;      /* Window RTQOS Control Registers */
+    uint32_t palette[256];  /* Pallete 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 */
+} S5pc210fimdWindow;
+
+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 maping control register */
+    uint32_t vidintcon[2];  /* Video interrupt control registers */
+    uint32_t dithmode;      /* Dithering control register */
+    uint32_t wpalcon[2];    /* Window pallete 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 */
+    uint32_t blendcon;      /* Blending equation control register */
+    uint32_t dualrgb;       /* Undocumented register */
+    uint32_t i80ifcmd[12];  /* LCD I80 Interface Command */
+
+    S5pc210fimdWindow window[5];    /* Window-specific registers */
+    uint8_t *ifb;                   /* Internal frame buffer */
+    bool invalidate;                /* Image needs to be redrawn */
+    bool enabled;                   /* Display controller is enabled */
+} S5pc210fimdState;
+
+struct DrawConfig {
+    pixel_to_rgb_func *pixel_to_rgb;
+    draw_line_func *draw_line;
+    int (*put_pixel)(const rgba p, uint8_t *pixel);
+    int (*get_pixel)(const uint8_t *src, rgba *p);
+    void (*blend)(struct DrawConfig *cfg, rgba p_old, rgba p_new, rgba *p);
+    coef_func *coef_p, *coef_q, *coef_a, *coef_b;
+    uint8_t is_palletized;
+    uint32_t bg_alpha[2], fg_alpha[2];
+    uint32_t color_key, color_mask, color_ctl;
+    uint8_t fg_alpha_pix, bg_alpha_pix;
+    int width;
+    int bpp;
+    uint32_t *palette;
+    uint8_t swap;
+    uint8_t fg_pixel_blending, bg_pixel_blending;
+    uint8_t fg_alpha_sel, bg_alpha_sel;
+    uint32_t map_color;
+};
+
+static inline int s5pc210_buffer_status(S5pc210fimdWindow *w)
+{
+    switch (w->wincon & FIMD_WINCON_BUFSTATUS) {
+    case FIMD_WINCON_BUF0_STAT: default:
+        return 0;
+    case FIMD_WINCON_BUF1_STAT:
+        return 1;
+    case FIMD_WINCON_BUF2_STAT:
+        return 2;
+    }
+}
+
+/* Perform byte/halfword/word swap of data according to config */
+static inline uint64_t swap_data(const DrawConfig *cfg, uint64_t x)
+{
+    int i;
+    uint64_t res;
+
+    if (cfg->swap & FIMD_WINCON_SWAP_BITS) {
+        res = 0;
+        for (i = 0; i < 64; i++) {
+            if (x & (1ULL << (64 - i))) {
+                res |= (1ULL << i);
+            }
+        }
+        x = res;
+    }
+    if (cfg->swap & 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 (cfg->swap & FIMD_WINCON_SWAP_HWORD) {
+        x = ((x & 0x000000000000FFFFULL) << 48) |
+            ((x & 0x00000000FFFF0000ULL) << 16) |
+            ((x & 0x0000FFFF00000000ULL) >> 16) |
+            ((x & 0xFFFF000000000000ULL) >> 48);
+    }
+    if (cfg->swap & FIMD_WINCON_SWAP_WORD) {
+        x = ((x & 0x00000000FFFFFFFFULL) << 32) |
+            ((x & 0xFFFFFFFF00000000ULL) >> 32);
+    }
+    return x;
+}
+
+/* Palette/pixel to RGB conversion */
+
+#define DEF_PIXEL_TO_RGB(N, R, G, B, A) \
+static void N(uint32_t pixel, rgba *p) \
+{ \
+    p->b = (pixel & ((1 << (B)) - 1)) << (8 - (B)); \
+    pixel >>= (B); \
+    p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)); \
+    pixel >>= (G); \
+    p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)); \
+    pixel >>= (R); \
+    if (1 == (A)) { \
+        p->a = pixel & 1; \
+    } else if (8 == (A)) { \
+        p->a = pixel & 0xFF; \
+        p->a = (p->a << 16) | (p->a << 8) | p->a; \
+    } else { \
+        p->a = (pixel & ((1 << (A)) - 1)) << (8 - (A)); \
+    } \
+}
+
+DEF_PIXEL_TO_RGB(pixel_a232_to_rgb, 2, 3, 2, 1)
+DEF_PIXEL_TO_RGB(pixel_a444_to_rgb, 4, 4, 4, 1)
+DEF_PIXEL_TO_RGB(pixel_4444_to_rgb, 4, 4, 4, 4)
+DEF_PIXEL_TO_RGB(pixel_565_to_rgb,  5, 6, 5, 0)
+DEF_PIXEL_TO_RGB(pixel_a555_to_rgb, 5, 5, 5, 1)
+DEF_PIXEL_TO_RGB(pixel_555_to_rgb,  5, 5, 5, 0)
+DEF_PIXEL_TO_RGB(pixel_666_to_rgb,  6, 6, 6, 0)
+DEF_PIXEL_TO_RGB(pixel_a666_to_rgb, 6, 6, 6, 1)
+DEF_PIXEL_TO_RGB(pixel_a665_to_rgb, 6, 6, 5, 1)
+DEF_PIXEL_TO_RGB(pixel_888_to_rgb,  8, 8, 8, 0)
+DEF_PIXEL_TO_RGB(pixel_a888_to_rgb, 8, 8, 8, 1)
+DEF_PIXEL_TO_RGB(pixel_a887_to_rgb, 8, 8, 7, 1)
+DEF_PIXEL_TO_RGB(pixel_8888_to_rgb, 8, 8, 8, 8)
+
+/* Special case for (5+1,5+1,5+1) mode */
+static void pixel_1555_to_rgb(uint32_t pixel, rgba *p)
+{
+    uint8_t u = (pixel >> 15) & 1;
+    p->b = (((pixel & 0x1F) << 1) | u) << 2;
+    pixel >>= 5;
+    p->g = (((pixel & 0x3F) << 1) | u) << 2;
+    pixel >>= 6;
+    p->r = (((pixel & 0x1F) << 1) | u) << 2;
+}
+
+/* Draw line with pallete index in frame buffer data */
+#define DEF_DRAW_LINE_PALLETE(N) \
+static void glue(draw_line_pallete_, N)(DrawConfig *cfg, uint8_t *src, \
+                               uint8_t *dst, uint8_t *ifb) \
+{ \
+    int width = cfg->width; \
+    uint64_t data; \
+    rgba p, p_old; \
+    int i; \
+    do { \
+        data = ldq_raw((void *)src); \
+        src += 8; \
+        data = swap_data(cfg, data); \
+        for (i = (64 / (N) - 1); i >= 0; i--) { \
+            cfg->pixel_to_rgb(cfg->palette[(data >> ((N) * i)) & \
+                                   ((1ULL << (N)) - 1)], &p); \
+            if (cfg->blend) { \
+                ifb += cfg->get_pixel(ifb, &p_old); \
+                cfg->blend(cfg, p_old, p, &p); \
+            } \
+            dst += cfg->put_pixel(p, dst); \
+        } \
+        width -= (64 / (N)); \
+    } while (width > 0); \
+}
+
+/* Draw line with direct color value in frame buffer data */
+#define DEF_DRAW_LINE_NOPALLETE(N) \
+static void glue(draw_line_, N)(DrawConfig *cfg, uint8_t *src, \
+                               uint8_t *dst, uint8_t *ifb) \
+{ \
+    int width = cfg->width; \
+    uint64_t data; \
+    rgba p, p_old; \
+    int i; \
+    do { \
+        data = ldq_raw((void *)src); \
+        src += 8; \
+        data = swap_data(cfg, data); \
+        for (i = (64 / (N) - 1); i >= 0; i--) { \
+            cfg->pixel_to_rgb((data >> ((N) * i)) & ((1ULL << (N)) - 1), &p); \
+            if (cfg->blend) { \
+                ifb += cfg->get_pixel(ifb, &p_old); \
+                cfg->blend(cfg, p_old, p, &p); \
+            } \
+            dst += cfg->put_pixel(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 pallete and non-pallete 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(DrawConfig *cfg, uint8_t *src,
+                               uint8_t *dst, uint8_t *ifb)
+{
+    rgba p, p_old;
+    int width = cfg->width;
+    uint32_t map_color = cfg->map_color;
+
+    do {
+        pixel_888_to_rgb(map_color, &p);
+        if (cfg->blend) {
+            ifb += cfg->get_pixel(ifb, &p_old);
+            cfg->blend(cfg, p_old, p, &p);
+        }
+        dst += cfg->put_pixel(p, dst);
+    } while (--width);
+}
+
+/* Routine to copy line from internal frame buffer to QEMU display */
+static void draw_line_copy(DrawConfig *cfg, uint8_t *src, uint8_t *dst)
+{
+    rgba p;
+    int width = cfg->width;
+
+    do {
+        src += cfg->get_pixel(src, &p);
+        dst += cfg->put_pixel(p, dst);
+    } while (--width);
+}
+
+/* Parse BPPMODE_F bits and setup known DRAW_CONFIG fields accordingly.
+   BPPMODE_F = WINCON1[5:2] */
+static void s5pc210_parse_win_bppmode(S5pc210fimdWindow *w, DrawConfig *cfg)
+{
+    switch ((w->wincon >> FIMD_WINCON_BPPMODE_SHIFT) & FIMD_WINCON_BPPMODE) {
+    case 0:
+        cfg->draw_line = draw_line_pallete_1;
+        cfg->is_palletized = 1;
+        cfg->bpp = 1;
+        break;
+    case 1:
+        cfg->draw_line = draw_line_pallete_2;
+        cfg->is_palletized = 1;
+        cfg->bpp = 2;
+        break;
+    case 2:
+        cfg->draw_line = draw_line_pallete_4;
+        cfg->is_palletized = 1;
+        cfg->bpp = 4;
+        break;
+    case 3:
+        cfg->draw_line = draw_line_pallete_8;
+        cfg->is_palletized = 1;
+        cfg->bpp = 8;
+        break;
+    case 4:
+        cfg->draw_line = draw_line_8;
+        cfg->is_palletized = 0;
+        cfg->pixel_to_rgb = pixel_a232_to_rgb;
+        cfg->bpp = 8;
+        break;
+    case 5:
+        cfg->draw_line = draw_line_16;
+        cfg->is_palletized = 0;
+        cfg->pixel_to_rgb = pixel_565_to_rgb;
+        cfg->bpp = 16;
+        break;
+    case 6:
+        cfg->draw_line = draw_line_16;
+        cfg->is_palletized = 0;
+        cfg->pixel_to_rgb = pixel_a555_to_rgb;
+        cfg->bpp = 16;
+        break;
+    case 7:
+        cfg->draw_line = draw_line_16;
+        cfg->is_palletized = 0;
+        cfg->pixel_to_rgb = pixel_1555_to_rgb;
+        cfg->bpp = 16;
+        break;
+    case 8:
+        cfg->draw_line = draw_line_32;
+        cfg->is_palletized = 0;
+        cfg->pixel_to_rgb = pixel_666_to_rgb;
+        cfg->bpp = 32;
+        break;
+    case 9:
+        cfg->draw_line = draw_line_32;
+        cfg->is_palletized = 0;
+        cfg->pixel_to_rgb = pixel_a665_to_rgb;
+        cfg->bpp = 32;
+        break;
+    case 10:
+        cfg->draw_line = draw_line_32;
+        cfg->is_palletized = 0;
+        cfg->pixel_to_rgb = pixel_a666_to_rgb;
+        cfg->bpp = 32;
+        break;
+    case 11:
+        cfg->draw_line = draw_line_32;
+        cfg->is_palletized = 0;
+        cfg->pixel_to_rgb = pixel_888_to_rgb;
+        cfg->bpp = 32;
+        break;
+    case 12:
+        cfg->draw_line = draw_line_32;
+        cfg->is_palletized = 0;
+        cfg->pixel_to_rgb = pixel_a887_to_rgb;
+        cfg->bpp = 32;
+        break;
+    case 13:
+        cfg->draw_line = draw_line_32;
+        cfg->is_palletized = 0;
+        if ((w->wincon & FIMD_WINCON_BLD_PIX) && (w->wincon &
+                FIMD_WINCON_ALPHA_SEL)) {
+            cfg->pixel_to_rgb = pixel_8888_to_rgb;
+            cfg->fg_alpha_pix = 1;
+        } else {
+            cfg->pixel_to_rgb = pixel_a888_to_rgb;
+        }
+        cfg->bpp = 32;
+        break;
+    case 14:
+        cfg->draw_line = draw_line_16;
+        cfg->is_palletized = 0;
+        if ((w->wincon & FIMD_WINCON_BLD_PIX) && (w->wincon &
+                FIMD_WINCON_ALPHA_SEL)) {
+            cfg->pixel_to_rgb = pixel_4444_to_rgb;
+            cfg->fg_alpha_pix = 1;
+        } else {
+            cfg->pixel_to_rgb = pixel_a444_to_rgb;
+        }
+        cfg->bpp = 16;
+        break;
+    case 15:
+        cfg->draw_line = draw_line_16;
+        cfg->is_palletized = 0;
+        cfg->pixel_to_rgb = pixel_555_to_rgb;
+        cfg->bpp = 16;
+        break;
+    }
+}
+
+#if S5P_FIMD_MODE_TRACE > 0
+static const char *s5pc210_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";
+    }
+}
+#endif
+
+static inline void s5pc210_fimd_trace_bppmode(S5pc210fimdWindow *w,
+                int win_num, uint32_t val)
+{
+#if S5P_FIMD_MODE_TRACE > 0
+    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,
+        s5pc210_fimd_get_bppmode((w->wincon >> FIMD_WINCON_BPPMODE_SHIFT) &
+                FIMD_WINCON_BPPMODE),
+        s5pc210_fimd_get_bppmode((val >> FIMD_WINCON_BPPMODE_SHIFT) &
+                FIMD_WINCON_BPPMODE));
+#endif
+}
+
+static inline void s5pc210_fimd_trace_reset(void)
+{
+#if S5P_FIMD_MODE_TRACE > 0
+    fprintf(stderr, "QEMU FIMD: Display controller reset\n");
+#endif
+}
+
+static inline void s5pc210_fimd_enable(S5pc210fimdState *s, bool enabled)
+{
+    s->enabled = enabled ? true : false;
+#if S5P_FIMD_MODE_TRACE > 0
+    fprintf(stderr, "QEMU FIMD: display controller %s\n",
+            (enabled ? "enabled" : "disabled"));
+#endif
+}
+
+/* Returns WxPAL for given window number WINDOW */
+static uint32_t s5pc210_wxpal(S5pc210fimdState *s, int window)
+{
+    switch (window) {
+    case 0:
+        return (s->wpalcon[1] >> FIMD_WPAL_W0PAL_L_SH) & FIMD_WPAL_W0PAL_L;
+    case 1:
+        return (s->wpalcon[1] >> FIMD_WPAL_W1PAL_L_SH) & FIMD_WPAL_W1PAL_L;
+    case 2:
+        return ((s->wpalcon[0] >> FIMD_WPAL_W2PAL_H_SH) & FIMD_WPAL_W2PAL_H) |
+            ((s->wpalcon[1] >> FIMD_WPAL_W2PAL_L_SH) & FIMD_WPAL_W2PAL_L);
+    case 3:
+        return ((s->wpalcon[0] >> FIMD_WPAL_W3PAL_H_SH) & FIMD_WPAL_W3PAL_H) |
+            ((s->wpalcon[1] >> FIMD_WPAL_W3PAL_L_SH) & FIMD_WPAL_W3PAL_L);
+    case 4:
+        return ((s->wpalcon[0] >> FIMD_WPAL_W4PAL_H_SH) & FIMD_WPAL_W4PAL_H) |
+            ((s->wpalcon[1] >> FIMD_WPAL_W4PAL_L_SH) & FIMD_WPAL_W4PAL_L);
+    }
+    hw_error("s5pc210.fimd: incorrect window number %d\n", window);
+    return 0;
+}
+
+pixel_to_rgb_func *wxpal_to_rgb[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
+};
+
+/* Put/get pixel to/from internal LCD Controller framebuffer */
+
+static int put_rgba(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_rgba(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;
+    return RGBA_SIZE;
+}
+
+/* Write RGB to QEMU's GraphicConsole framebuffer */
+
+static int put_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_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_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_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_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;
+}
+
+static inline uint32_t unpack_by_4(uint32_t x)
+{
+    return ((x & 0xF00) << 12) | ((x & 0xF0) << 8) | ((x & 0xF) << 4);
+}
+
+
+/* Coefficient extraction functions */
+
+static uint32_t coef_zero(const DrawConfig *cfg,
+                          rgba pa, rgba pb)
+{
+    return 0;
+}
+
+static uint32_t coef_one(const DrawConfig *cfg,
+                         rgba pa, rgba pb)
+{
+    return 0xFFFFFF;
+}
+
+static uint32_t coef_alphaa(const DrawConfig *cfg,
+                            rgba pa, rgba pb)
+{
+    if (!cfg->fg_pixel_blending) {
+        pa.a = cfg->fg_alpha_sel;
+    }
+    if (cfg->fg_alpha_pix) {
+        return pa.a;
+    } else {
+        return cfg->fg_alpha[pa.a];
+    }
+}
+
+static uint32_t coef_one_minus_alphaa(const DrawConfig *cfg,
+                                      rgba pa, rgba pb)
+{
+    if (!cfg->fg_pixel_blending) {
+        pa.a = cfg->fg_alpha_sel;
+    }
+    if (cfg->fg_alpha_pix) {
+        return 0xFFFFFF - pa.a;
+    } else {
+        return 0xFFFFFF - cfg->fg_alpha[pa.a];
+    }
+}
+
+static uint32_t coef_alphab(const DrawConfig *cfg,
+                            rgba pa, rgba pb)
+{
+    if (!cfg->bg_pixel_blending) {
+        pb.a = cfg->bg_alpha_sel;
+    }
+    if (cfg->bg_alpha_pix) {
+        return pb.a;
+    } else {
+        return cfg->bg_alpha[pb.a];
+    }
+}
+
+static uint32_t coef_one_minus_alphab(const DrawConfig *cfg,
+                                      rgba pa, rgba pb)
+{
+    if (!cfg->bg_pixel_blending) {
+        pb.a = cfg->bg_alpha_sel;
+    }
+    if (cfg->bg_alpha_pix) {
+        return 0xFFFFFF - pb.a;
+    } else {
+        return 0xFFFFFF - cfg->bg_alpha[pb.a];
+    }
+}
+
+static uint32_t coef_a(const DrawConfig *cfg,
+                       rgba pa, rgba pb)
+{
+    return (pa.r << 16) | (pa.g << 8) | pa.b;
+}
+
+static uint32_t coef_one_minus_a(const DrawConfig *cfg,
+                                 rgba pa, rgba pb)
+{
+    return 0xFFFFFF - ((pa.r << 16) | (pa.g << 8) | pa.b);
+}
+
+static uint32_t coef_b(const DrawConfig *cfg,
+                       rgba pa, rgba pb)
+{
+    return (pb.r << 16) | (pb.g << 8) | pb.b;
+}
+
+static uint32_t coef_one_minus_b(const DrawConfig *cfg,
+                                 rgba pa, rgba pb)
+{
+    return 0xFFFFFF - ((pb.r << 16) | (pb.g << 8) | pb.b);
+}
+
+
+static coef_func *coef_decode(uint32_t x)
+{
+    switch (x) {
+    case 0:
+        return coef_zero;
+    case 1:
+        return coef_one;
+    case 2:
+        return coef_alphaa;
+    case 3:
+        return coef_one_minus_alphaa;
+    case 4:
+        return coef_alphab;
+    case 5:
+        return coef_one_minus_alphab;
+    case 10:
+        return coef_a;
+    case 11:
+        return coef_one_minus_a;
+    case 12:
+        return coef_b;
+    case 13:
+        return coef_one_minus_b;
+    default:
+        hw_error("s5pc210.fimd: blend equation coef illegal value\n");
+        return 0;
+    }
+}
+
+static void blend_alpha(const DrawConfig *cfg, rgba p_bg, rgba p_fg, rgba *res)
+{
+    uint32_t pl, ql, al, bl;
+    uint32_t p, q, a, b, fg, bg, fga, bga;
+
+    pl = cfg->coef_p(cfg, p_fg, p_bg);
+    ql = cfg->coef_q(cfg, p_fg, p_bg);
+    al = cfg->coef_a(cfg, p_fg, p_bg);
+    bl = cfg->coef_b(cfg, p_fg, p_bg);
+    res->a = 0;
+    /* B */
+    p = pl & 0xFF;
+    pl >>= 8;
+    q = ql & 0xFF;
+    ql >>= 8;
+    a = al & 0xFF;
+    al >>= 8;
+    b = bl & 0xFF;
+    bl >>= 8;
+    fg = p_fg.b;
+    bg = p_bg.b;
+    if (cfg->fg_pixel_blending) {
+        if (cfg->fg_alpha_pix) {
+            fga = p_fg.a & 0xFF;
+        } else {
+            fga = cfg->fg_alpha[p_fg.a] & 0xFF;
+        }
+    } else {
+        fga = cfg->fg_alpha[cfg->fg_alpha_sel] & 0xFF;
+    }
+    if (cfg->bg_pixel_blending) {
+        if (cfg->bg_alpha_pix) {
+            bga = p_bg.a & 0xFF;
+        } else {
+            bga = cfg->bg_alpha[p_bg.a] & 0xFF;
+        }
+    } else {
+        bga = cfg->bg_alpha[cfg->bg_alpha_sel] & 0xFF;
+    }
+    bg = (bg * b + fg * a) / 0xFF;
+    if (bg > 0xFF) {
+        res->b = 0xFF;
+    } else {
+        res->b = bg;
+    }
+    bga = (bga * p + fga * q) / 0xFF;
+    if (bga > 0xFF) {
+        res->a |= 0xFF;
+    } else {
+        res->a |= bga;
+    }
+    /* G */
+    p = pl & 0xFF;
+    pl >>= 8;
+    q = ql & 0xFF;
+    ql >>= 8;
+    a = al & 0xFF;
+    al >>= 8;
+    b = bl & 0xFF;
+    bl >>= 8;
+    fg = p_fg.g;
+    bg = p_bg.g;
+    if (cfg->fg_pixel_blending) {
+        if (cfg->fg_alpha_pix) {
+            fga = (p_fg.a >> 8) & 0xFF;
+        } else {
+            fga = (cfg->fg_alpha[p_fg.a] >> 8) & 0xFF;
+        }
+    } else {
+        fga = (cfg->fg_alpha[cfg->fg_alpha_sel] >> 8) & 0xFF;
+    }
+    if (cfg->bg_pixel_blending) {
+        if (cfg->bg_alpha_pix) {
+            bga = (p_bg.a >> 8) & 0xFF;
+        } else {
+            bga = (cfg->bg_alpha[p_bg.a] >> 8) & 0xFF;
+        }
+    } else {
+        bga = (cfg->bg_alpha[cfg->bg_alpha_sel] >> 8) & 0xFF;
+    }
+    bg = (bg * b + fg * a) / 0xFF;
+    if (bg > 0xFF) {
+        res->g = 0xFF;
+    } else {
+        res->g = bg;
+    }
+    bga = (bga * p + fga * q) / 0xFF;
+    if (bga > 0xFF) {
+        res->a |= 0xFF << 8;
+    } else {
+        res->a |= bga << 8;
+    }
+    /* R */
+    p = pl & 0xFF;
+    pl >>= 8;
+    q = ql & 0xFF;
+    ql >>= 8;
+    a = al & 0xFF;
+    al >>= 8;
+    b = bl & 0xFF;
+    bl >>= 8;
+    fg = p_fg.r;
+    bg = p_bg.r;
+    if (cfg->fg_pixel_blending) {
+        if (cfg->fg_alpha_pix) {
+            fga = (p_fg.a >> 16) & 0xFF;
+        } else {
+            fga = (cfg->fg_alpha[p_fg.a] >> 16) & 0xFF;
+        }
+    } else {
+        fga = (cfg->fg_alpha[cfg->fg_alpha_sel] >> 16) & 0xFF;
+    }
+    if (cfg->bg_pixel_blending) {
+        if (cfg->bg_alpha_pix) {
+            bga = (p_bg.a >> 16) & 0xFF;
+        } else {
+            bga = (cfg->bg_alpha[p_bg.a] >> 16) & 0xFF;
+        }
+    } else {
+        bga = (cfg->bg_alpha[cfg->bg_alpha_sel] >> 16) & 0xFF;
+    }
+    bg = (bg * b + fg * a) / 0xFF;
+    if (bg > 0xFF) {
+        res->r = 0xFF;
+    } else {
+        res->r = bg;
+    }
+    bga = (bga * p + fga * q) / 0xFF;
+    if (bga > 0xFF) {
+        res->a |= 0xFF << 16;
+    } else {
+        res->a |= bga << 16;
+    }
+}
+
+static void blend_colorkey(DrawConfig *cfg, rgba p_bg, rgba p_fg, rgba *p)
+{
+    uint8_t r, g, b;
+
+    if (cfg->color_ctl & FIMD_WKEYCON0_CTL_KEYEN) {
+        blend_alpha(cfg, p_bg, p_fg, p);
+        return ;
+    }
+    r = ((cfg->color_key & ~cfg->color_mask) >> 16) & 0xFF;
+    g = ((cfg->color_key & ~cfg->color_mask) >>  8) & 0xFF;
+    b = ((cfg->color_key & ~cfg->color_mask) >>  0) & 0xFF;
+    if (cfg->color_ctl & FIMD_WKEYCON0_CTL_DIRCON) {
+        if ((p_fg.r & ~((cfg->color_mask >> 16) & 0xFF)) == r &&
+                (p_fg.g & ~((cfg->color_mask >>  8) & 0xFF)) == g &&
+                (p_fg.b & ~((cfg->color_mask >>  0) & 0xFF)) == b) {
+            if (cfg->color_ctl & FIMD_WKEYCON0_CTL_KEYBLEN) {
+                p_fg.a = 1;
+                cfg->fg_pixel_blending = 0;
+                blend_alpha(cfg, p_bg, p_fg, p);
+            } else {
+                *p = p_bg;
+            }
+        } else {
+            if (cfg->color_ctl & FIMD_WKEYCON0_CTL_KEYBLEN) {
+                p_fg.a = 0;
+                cfg->fg_pixel_blending = 0;
+                blend_alpha(cfg, p_bg, p_fg, p);
+            } else {
+                *p = p_fg;
+            }
+        }
+    } else {
+        if ((p_bg.r & ~((cfg->color_mask >> 16) & 0xFF)) == r &&
+                (p_bg.g & ~((cfg->color_mask >>  8) & 0xFF)) == g &&
+                (p_bg.b & ~((cfg->color_mask >>  0) & 0xFF)) == b) {
+            if (cfg->color_ctl & FIMD_WKEYCON0_CTL_KEYBLEN) {
+                p_fg.a = 1;
+                cfg->fg_pixel_blending = 0;
+                blend_alpha(cfg, p_bg, p_fg, p);
+            } else {
+                *p = p_fg;
+            }
+        } else {
+            if (cfg->color_ctl & FIMD_WKEYCON0_CTL_KEYBLEN) {
+                p_fg.a = 0;
+                cfg->fg_pixel_blending = 0;
+                blend_alpha(cfg, p_bg, p_fg, p);
+            } else {
+                *p = p_bg;
+            }
+        }
+    }
+}
+
+static inline void putpixel_by_bpp(DrawConfig *cfg, int bpp)
+{
+    switch (bpp) {
+    case 8:
+        cfg->put_pixel = put_pixel8;
+        break;
+    case 15:
+        cfg->put_pixel = put_pixel15;
+        break;
+    case 16:
+        cfg->put_pixel = put_pixel16;
+        break;
+    case 24:
+        cfg->put_pixel = put_pixel24;
+        break;
+    case 32:
+        cfg->put_pixel = put_pixel32;
+        break;
+    default:
+        hw_error("s5pc210.fimd: unsupported BPP (%d)", bpp);
+        break;
+    }
+}
+
+static void s5pc210_fimd_update_irq(S5pc210fimdState *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 s5pc210_update_resolution(S5pc210fimdState *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) {
+        print_debug1("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);
+        memset(s->ifb, 0, width * height * RGBA_SIZE);
+        s->invalidate = true;
+    }
+}
+
+static void s5pc210_fimd_update(void *opaque)
+{
+    S5pc210fimdState *s = (S5pc210fimdState *)opaque;
+    DrawConfig cfg;
+    int i, line, buf_id;
+    target_phys_addr_t fb_start_addr, fb_next_line_addr, inc_size, fb_len, x;
+    int lefttop_x, lefttop_y, rightbottom_x, rightbottom_y;
+    int width, height;
+    int first_line = -1, last_line = -1, fb_line_size;
+    uint8_t is_first_window = 1;
+    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;
+    }
+
+    memset(&cfg, 0, sizeof(cfg));
+    s5pc210_update_resolution(s);
+
+    for (i = 0; i < NUM_OF_WINDOWS; i++) {
+        if (s->window[i].wincon & FIMD_WINCON_ENWIN) {
+            cfg.fg_alpha_pix = 0;
+            s5pc210_parse_win_bppmode(&s->window[i], &cfg);
+            /* If we have mode with palletized color then we need to parse
+               palette color mode and set pixel-to-rgb conversion function
+               accordingly. */
+            if (cfg.is_palletized) {
+                uint32_t tmp = s5pc210_wxpal(s, i);
+                /* Different windows have different mapping WxPAL to palette
+                   pixel format. This transform happens to unify them all. */
+                if (i < 2 && tmp < 7) {
+                    tmp = 6 - tmp;
+                }
+                cfg.pixel_to_rgb = wxpal_to_rgb[tmp];
+                if (tmp == 7) {
+                    cfg.fg_alpha_pix = 1;
+                }
+            }
+            cfg.put_pixel = put_rgba;
+            cfg.get_pixel = get_rgba;
+            cfg.bg_alpha_pix = 1;
+            cfg.color_mask = s->window[i].keycon[0] & FIMD_WKEYCON0_COMPKEY;
+            cfg.color_key = s->window[i].keycon[1];
+            cfg.color_ctl =
+                    (s->window[i].keycon[0] >> FIMD_WKEYCON0_CTL_SHIFT) & 7;
+            if (i == 0) {
+                cfg.fg_alpha[0] = s->window[i].vidw_alpha[0];
+                cfg.fg_alpha[1] = s->window[i].vidw_alpha[1];
+            } else {
+                cfg.fg_alpha[0] =
+                    unpack_by_4((s->window[i].vidosd[2] &
+                    FIMD_VIDOSD_ALPHA_AEN0) >> FIMD_VIDOSD_AEN0_SHIFT) |
+                    (s->window[i].vidw_alpha[0] & FIMD_VIDALPHA_ALPHA_MASK);
+                cfg.fg_alpha[1] =
+                    unpack_by_4(s->window[i].vidosd[2] &
+                    FIMD_VIDOSD_ALPHA_AEN1) | (s->window[i].vidw_alpha[0]
+                    & FIMD_VIDALPHA_ALPHA_MASK);
+            }
+            cfg.bg_pixel_blending = 1;
+            cfg.fg_pixel_blending = s->window[i].wincon & FIMD_WINCON_BLD_PIX;
+            cfg.fg_alpha_sel = (s->window[i].wincon & FIMD_WINCON_ALPHA_SEL) >>
+                    FIMD_WINCON_ALSEL_SHIFT;
+            cfg.palette = s->window[i].palette;
+            cfg.swap = (s->window[i].wincon & FIMD_WINCON_SWAP) >>
+                    FIMD_WINCON_SWAP_SHIFT;
+            cfg.coef_q = coef_decode((s->window[i].blendeq >>
+                    FIMD_BLENDEQ_COEF_Q) & FIMD_BLENDEQ_COEF_MASK);
+            cfg.coef_p = coef_decode((s->window[i].blendeq >>
+                    FIMD_BLENDEQ_COEF_P) & FIMD_BLENDEQ_COEF_MASK);
+            cfg.coef_b = coef_decode((s->window[i].blendeq >>
+                    FIMD_BLENDEQ_COEF_B) & FIMD_BLENDEQ_COEF_MASK);
+            cfg.coef_a = coef_decode((s->window[i].blendeq >>
+                    FIMD_BLENDEQ_COEF_A) & FIMD_BLENDEQ_COEF_MASK);
+            if (is_first_window) {
+                cfg.blend = NULL;
+            } else {
+                cfg.blend = blend_colorkey;
+            }
+            is_first_window = 0;
+            if (s->window[i].winmap & FIMD_WINMAP_EN) {
+                cfg.map_color = s->window[i].winmap & FIMD_WINMAP_COLOR_MASK;
+                cfg.draw_line = draw_line_mapcolor;
+            }
+
+            /* At this point CFG is fully set up except WIDTH. We can proceed
+               with drawing. */
+            lefttop_x = (s->window[i].vidosd[0] >> FIMD_VIDOSD_HOR_SHIFT)
+                    & FIMD_VIDOSD_COORD_MASK;
+            lefttop_y = (s->window[i].vidosd[0] >> FIMD_VIDOSD_VER_SHIFT)
+                    & FIMD_VIDOSD_COORD_MASK;
+            rightbottom_x = (s->window[i].vidosd[1] >> FIMD_VIDOSD_HOR_SHIFT)
+                    & FIMD_VIDOSD_COORD_MASK;
+            rightbottom_y = (s->window[i].vidosd[1] >> FIMD_VIDOSD_VER_SHIFT)
+                    & FIMD_VIDOSD_COORD_MASK;
+            height = rightbottom_y - lefttop_y + 1;
+            width = rightbottom_x - lefttop_x + 1;
+            cfg.width = width;
+            buf_id = s5pc210_buffer_status(&s->window[i]);
+
+            fb_line_size = s->window[i].buf_size & FIMD_VIDWADD2_PAGEWIDTH;
+            fb_start_addr = s->window[i].buf_start[buf_id];
+            inc_size = (s->window[i].buf_size & FIMD_VIDWADD2_PAGEWIDTH) +
+                    ((s->window[i].buf_size >> FIMD_VIDWADD2_OFFSIZE_SHIFT) &
+                            FIMD_VIDWADD2_OFFSIZE);
+            fb_len = inc_size * 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 * height) {
+                cpu_physical_memory_unmap(host_fb_addr, fb_len, 0, 0);
+                return;
+            }
+
+            host_fb_start_addr = host_fb_addr;
+            for (line = 0; line < height; line++) {
+                fb_next_line_addr = fb_start_addr + fb_line_size;
+                for (x = fb_start_addr; x < fb_next_line_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;
+                    cfg.draw_line(&cfg, host_fb_addr,
+                          s->ifb + lefttop_x * RGBA_SIZE +
+                              (lefttop_y + line) * global_width * RGBA_SIZE,
+                          s->ifb + lefttop_x * RGBA_SIZE +
+                              (lefttop_y + line) * global_width * RGBA_SIZE);
+                }
+                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[buf_id];
+            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 * height,
+                                             VGA_DIRTY_FLAG);
+        }
+    }
+    /* Last pass: copy resulting image to QEMU_CONSOLE. */
+    if (first_line >= 0) {
+        uint8_t *d;
+        int bpp;
+
+        cfg.get_pixel = get_rgba;
+        bpp = ds_get_bits_per_pixel(s->console);
+        putpixel_by_bpp(&cfg, bpp);
+        bpp = (bpp + 1) >> 3;
+        d = ds_get_data(s->console);
+        for (line = first_line; line < last_line; line++) {
+            draw_line_copy(&cfg, 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) {
+        s5pc210_fimd_enable(s, false);
+    }
+    s5pc210_fimd_update_irq(s);
+}
+
+static void s5pc210_fimd_invalidate(void *opaque)
+{
+    S5pc210fimdState *s = (S5pc210fimdState *)opaque;
+    s->invalidate = true;
+}
+
+static void s5pc210_fimd_reset(DeviceState *d)
+{
+    S5pc210fimdState *s = container_of(d, S5pc210fimdState, busdev.qdev);
+    int i;
+    unsigned long begin = (unsigned long)s + offsetof(S5pc210fimdState, vidcon);
+    unsigned long len = ((unsigned long)s + offsetof(S5pc210fimdState, window))
+            - begin;
+    s5pc210_fimd_trace_reset();
+
+    /* 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(S5pc210fimdWindow));
+        s->window[i].blendeq = 0xC2;
+    }
+
+    if (s->ifb != NULL) {
+        g_free(s->ifb);
+    }
+    s->ifb = NULL;
+
+    s->invalidate = true;
+    s5pc210_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 s5pc210_fimd_write(void *opaque, target_phys_addr_t offset,
+                              uint64_t val, unsigned size)
+{
+    S5pc210fimdState *s = (S5pc210fimdState *)opaque;
+    int w, i;
+
+    print_debug2("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) {
+            s5pc210_fimd_enable(s, true);
+        } else {
+            if ((val & FIMD_VIDCON0_ENVID) == 0) {
+                s5pc210_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 >= 0x010 && offset <= 0x01C) {
+        s->vidtcon[(offset - 0x010) >> 2] = val;
+    } else if (offset >= 0x020 && offset <= 0x030) {
+        w = (offset - 0x020) >> 2;
+        val = (val & ~FIMD_WINCON_ROMASK) |
+                (s->window[w].wincon & FIMD_WINCON_ROMASK);
+        s5pc210_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;
+    } else if (offset == 0x034) {
+        s->shadowcon = val;
+    } else if (offset == 0x03C) {
+        s->winchmap = val;
+    } else if (offset >= 0x040 && offset <= 0x088) {
+        w = (offset - 0x040) >> 4;
+        i = ((offset - 0x040) & 0xF) >> 2;
+        if (i == 3 && w != 1 && w != 2) {
+            print_error("Bad write offset 0x%08x\n", offset);
+        }
+        s->window[w].vidosd[i] = val;
+    } else if (offset >= 0x0A0 && offset <= 0x0C4) {
+        w = (offset - 0x0A0) >> 3;
+        i = ((offset - 0x0A0) >> 2) & 1;
+        s->window[w].buf_start[i] = val;
+    } else if (offset >= 0x0D0 && offset <= 0x0F4) {
+        w = (offset - 0x0D0) >> 3;
+        i = ((offset - 0x0D0) >> 2) & 1;
+        s->window[w].buf_end[i] = val;
+    } else if (offset >= 0x100 && offset <= 0x110) {
+        s->window[(offset - 0x100) >> 2].buf_size = val;
+    } else if (offset == 0x130) {
+        s->vidintcon[0] = val;
+    } else if (offset == 0x134) {
+        s->vidintcon[1] &= ~(val & 7);
+        s5pc210_fimd_update_irq(s);
+    } else if (offset >= 0x140 && offset <= 0x15C) {
+        w = ((offset - 0x140) >> 3) + 1;
+        i = ((offset - 0x140) >> 2) & 1;
+        s->window[w].keycon[i] = val;
+    } else if (offset >= 0x160 && offset <= 0x16C) {
+        w = ((offset - 0x160) >> 2) + 1;
+        s->window[w].keyalpha = val;
+    } else if (offset == 0x170) {
+        s->dithmode = val;
+    } else if (offset >= 0x180 && offset <= 0x190) {
+        if (val & FIMD_WINMAP_EN) {
+            s->invalidate = true;
+            s5pc210_fimd_update(s);
+        }
+        s->window[(offset - 0x180) >> 2].winmap = val;
+    } else if (offset >= 0x19C && offset <= 0x1A0) {
+        s->wpalcon[(offset - 0x19C) >> 2] = val;
+    } else if (offset == 0x1A4) {
+        val = (val & ~FIMD_TRIGCON_ROMASK) | (s->trigcon & FIMD_TRIGCON_ROMASK);
+        s->trigcon = val;
+    } else if (offset >= 0x1B0 && offset <= 0x1BC) {
+        s->i80ifcon[(offset - 0x1B0) >> 2] = val;
+    } else if (offset == 0x1C0) {
+        s->colorgaincon = val;
+    } else if (offset >= 0x1D0 && offset <= 0x1D4) {
+        s->ldi_cmdcon[(offset - 0x1D0) >> 2] = val;
+    } else if (offset >= 0x1E0 && offset <= 0x1E8) {
+        i = (offset - 0x1E0) >> 2;
+        if (i != 2) {
+            s->sifccon[i] = val;
+        }
+    } else if (offset >= 0x1EC && offset <= 0x1F8) {
+        i = (offset - 0x1EC) >> 2;
+        s->huecoef_cr[i] = val;
+    } else if (offset >= 0x1FC && offset <= 0x208) {
+        i = (offset - 0x1FC) >> 2;
+        s->huecoef_cb[i] = val;
+    } else if (offset == 0x20C) {
+        s->hueoffset = val;
+    } else if (offset >= 0x21C && offset <= 0x240) {
+        w = ((offset - 0x21C) >> 3);
+        i = ((offset - 0x21C) >> 2) & 1;
+        s->window[w].vidw_alpha[i] = val;
+    } else if (offset >= 0x244 && offset <= 0x250) {
+        s->window[(offset - 0x244) >> 2].blendeq = val;
+    } else if (offset == 0x260) {
+        s->blendcon = val;
+    } else if (offset >= 0x264 && offset <= 0x274) {
+        s->window[(offset - 0x264) >> 2].rtqoscon = val;
+    } else if (offset == 0x27C) {
+        s->dualrgb = val;
+    } else if (offset >= 0x280 && offset <= 0x2AC) {
+        s->i80ifcmd[(offset - 0x280) >> 2] = val;
+    } else if (offset >= 0x40A0 && offset <= 0x40C0) {
+        if (offset & 0x0004) {
+            print_error("bad write offset 0x%08x\n", offset);
+        }
+        s->window[(offset - 0x40A0) >> 3].shadow_buf_start = val;
+    } else if (offset >= 0x40D0 && offset <= 0x40F0) {
+        if (offset & 0x0004) {
+            print_error("bad write offset 0x%08x\n", offset);
+        }
+        s->window[(offset - 0x40D0) >> 3].shadow_buf_end = val;
+    } else if (offset >= 0x4100 && offset <= 0x4110) {
+        s->window[(offset - 0x4100) >> 2].shadow_buf_size = val;
+    } else if (offset >= 0x2400 && offset <= 0x37FC) {
+        w = (offset - 0x2400) >> 10;
+        i = ((offset - 0x2400) >> 2) & 0xFF;
+        s->window[w].palette[i] = val;
+    } else if (offset >= 0x0400 && offset <= 0x0BFC) {
+        w = (offset - 0x0400) >> 10;
+        i = ((offset - 0x0400) >> 2) & 0xFF;
+        s->window[w].palette[i] = val;
+    } else {
+        print_error("bad write offset 0x%08x\n", offset);
+    }
+}
+
+static uint64_t s5pc210_fimd_read(void *opaque, target_phys_addr_t offset,
+                                  unsigned size)
+{
+    S5pc210fimdState *s = (S5pc210fimdState *)opaque;
+    int w, i;
+
+    print_debug2("read offset 0x%08x\n", offset);
+
+    if (offset <= 0x00C) {
+        return s->vidcon[(offset) >> 2];
+    } else if (offset >= 0x010 && offset <= 0x01C) {
+        return s->vidtcon[(offset - 0x010) >> 2];
+    } else if (offset >= 0x020 && offset <= 0x030) {
+        return s->window[(offset - 0x020) >> 2].wincon;
+    } else if (offset == 0x034) {
+        return s->shadowcon;
+    } else if (offset == 0x03C) {
+        return s->winchmap;
+    } else if (offset >= 0x040 && offset <= 0x088) {
+        w = (offset - 0x040) >> 4;
+        i = ((offset - 0x040) & 0xF) >> 2;
+        if (i == 3 && w != 1 && w != 2) {
+            goto return_error;
+        }
+        return s->window[w].vidosd[i];
+    } else if (offset >= 0x0A0 && offset <= 0x0C4) {
+        w = (offset - 0x0A0) >> 3;
+        i = ((offset - 0x0A0) >> 2) & 1;
+        return s->window[w].buf_start[i];
+    } else if (offset >= 0x0D0 && offset <= 0x0F4) {
+        w = (offset - 0x0D0) >> 3;
+        i = ((offset - 0x0D0) >> 2) & 1;
+        return s->window[w].buf_end[i];
+    } else if (offset >= 0x100 && offset <= 0x110) {
+        return s->window[(offset - 0x100) >> 2].buf_size;
+    } else if (offset >= 0x130 && offset <= 0x134) {
+        return s->vidintcon[(offset - 0x130) >> 2];
+    } else if (offset >= 0x140 && offset <= 0x15C) {
+        w = ((offset - 0x140) >> 3) + 1;
+        i = ((offset - 0x140) >> 2) & 1;
+        return s->window[w].keycon[i];
+    } else if (offset >= 0x160 && offset <= 0x16C) {
+        w = ((offset - 0x160) >> 2) + 1;
+        return s->window[w].keyalpha;
+    } else if (offset == 0x170) {
+        return s->dithmode;
+    } else if (offset >= 0x180 && offset <= 0x190) {
+        return s->window[(offset - 0x180) >> 2].winmap;
+    } else if (offset >= 0x19C && offset <= 0x1A0) {
+        return s->wpalcon[(offset - 0x19C) >> 2];
+    } else if (offset == 0x1A4) {
+        return s->trigcon;
+    } else if (offset >= 0x1B0 && offset <= 0x1BC) {
+        return s->i80ifcon[(offset - 0x1B0) >> 2];
+    } else if (offset == 0x1C0) {
+        return s->colorgaincon;
+    } else if (offset >= 0x1D0 && offset <= 0x1D4) {
+        return s->ldi_cmdcon[(offset - 0x1D0) >> 2];
+    } else if (offset >= 0x1E0 && offset <= 0x1E8) {
+        i = (offset - 0x1E0) >> 2;
+        return s->sifccon[i];
+    } else if (offset >= 0x1EC && offset <= 0x1F8) {
+        i = (offset - 0x1EC) >> 2;
+        return s->huecoef_cr[i];
+    } else if (offset >= 0x1FC && offset <= 0x208) {
+        i = (offset - 0x1FC) >> 2;
+        return s->huecoef_cb[i];
+    } else if (offset == 0x20C) {
+        return s->hueoffset;
+    } else if (offset >= 0x21C && offset <= 0x240) {
+        w = ((offset - 0x21C) >> 3);
+        i = ((offset - 0x21C) >> 2) & 1;
+        return s->window[w].vidw_alpha[i];
+    } else if (offset >= 0x244 && offset <= 0x250) {
+        return s->window[(offset - 0x244) >> 2].blendeq;
+    } else if (offset == 0x260) {
+        return s->blendcon;
+    } else if (offset >= 0x264 && offset <= 0x274) {
+        return s->window[(offset - 0x264) >> 2].rtqoscon;
+    } else if (offset == 0x27C) {
+        return s->dualrgb;
+    } else if (offset >= 0x280 && offset <= 0x2AC) {
+        return s->i80ifcmd[(offset - 0x280) >> 2];
+    } else if (offset >= 0x40A0 && offset <= 0x40C0) {
+        if (offset & 0x0004) {
+            goto return_error;
+        }
+        return s->window[(offset - 0x40A0) >> 3].shadow_buf_start;
+    } else if (offset >= 0x40D0 && offset <= 0x40F0) {
+        if (offset & 0x0004) {
+            goto return_error;
+        }
+        return s->window[(offset - 0x40D0) >> 3].shadow_buf_end;
+    } else if (offset >= 0x4100 && offset <= 0x4110) {
+        return s->window[(offset - 0x4100) >> 2].shadow_buf_size;
+    } else if (offset >= 0x2400 && offset <= 0x37FC) {
+        w = (offset - 0x2400) >> 10;
+        i = ((offset - 0x2400) >> 2) & 0xFF;
+        return s->window[w].palette[i];
+    } else if (offset >= 0x0400 && offset <= 0x0BFC) {
+        /* Palete aliases for win 0,1 */
+        w = (offset - 0x0400) >> 10;
+        i = ((offset - 0x0400) >> 2) & 0xFF;
+        return s->window[w].palette[i];
+    }
+return_error:
+    print_error("bad read offset 0x%08x\n", offset);
+    return 0xBAADBAAD;
+}
+
+static const MemoryRegionOps s5pc210_fimd_mmio_ops = {
+    .read = s5pc210_fimd_read,
+    .write = s5pc210_fimd_write,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false
+    },
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int s5pc210_fimd_load(void *opaque, int version_id)
+{
+    S5pc210fimdState *s = (S5pc210fimdState *)opaque;
+
+    if (version_id != 1) {
+        return -EINVAL;
+    }
+
+    /* Redraw the whole screen */
+    s5pc210_update_resolution(s);
+    s5pc210_fimd_invalidate(s);
+    s5pc210_fimd_enable(s, (s->vidcon[0] & FIMD_VIDCON0_ENVID_MASK) ==
+            FIMD_VIDCON0_ENVID_MASK);
+    return 0;
+}
+
+static const VMStateDescription s5pc210_fimd_window_vmstate = {
+    .name = "s5pc210.fimd_window",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields      = (VMStateField[]) {
+        VMSTATE_UINT32(wincon, S5pc210fimdWindow),
+        VMSTATE_UINT32_ARRAY(vidosd, S5pc210fimdWindow, 4),
+        VMSTATE_UINT32_ARRAY(buf_start, S5pc210fimdWindow, 3),
+        VMSTATE_UINT32_ARRAY(buf_end, S5pc210fimdWindow, 3),
+        VMSTATE_UINT32(buf_size, S5pc210fimdWindow),
+        VMSTATE_UINT32_ARRAY(keycon, S5pc210fimdWindow, 2),
+        VMSTATE_UINT32(keyalpha, S5pc210fimdWindow),
+        VMSTATE_UINT32(winmap, S5pc210fimdWindow),
+        VMSTATE_UINT32_ARRAY(vidw_alpha, S5pc210fimdWindow, 2),
+        VMSTATE_UINT32(blendeq, S5pc210fimdWindow),
+        VMSTATE_UINT32(rtqoscon, S5pc210fimdWindow),
+        VMSTATE_UINT32_ARRAY(palette, S5pc210fimdWindow, 256),
+        VMSTATE_UINT32(shadow_buf_start, S5pc210fimdWindow),
+        VMSTATE_UINT32(shadow_buf_end, S5pc210fimdWindow),
+        VMSTATE_UINT32(shadow_buf_size, S5pc210fimdWindow),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static const VMStateDescription s5pc210_fimd_vmstate = {
+    .name = "s5pc210.fimd",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .post_load = s5pc210_fimd_load,
+    .fields      = (VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(vidcon, S5pc210fimdState, 4),
+        VMSTATE_UINT32_ARRAY(vidtcon, S5pc210fimdState, 4),
+        VMSTATE_UINT32(shadowcon, S5pc210fimdState),
+        VMSTATE_UINT32(winchmap, S5pc210fimdState),
+        VMSTATE_UINT32_ARRAY(vidintcon, S5pc210fimdState, 2),
+        VMSTATE_UINT32(dithmode, S5pc210fimdState),
+        VMSTATE_UINT32_ARRAY(wpalcon, S5pc210fimdState, 2),
+        VMSTATE_UINT32(trigcon, S5pc210fimdState),
+        VMSTATE_UINT32_ARRAY(i80ifcon, S5pc210fimdState, 4),
+        VMSTATE_UINT32(colorgaincon, S5pc210fimdState),
+        VMSTATE_UINT32_ARRAY(ldi_cmdcon, S5pc210fimdState, 2),
+        VMSTATE_UINT32_ARRAY(sifccon, S5pc210fimdState, 3),
+        VMSTATE_UINT32_ARRAY(huecoef_cr, S5pc210fimdState, 4),
+        VMSTATE_UINT32_ARRAY(huecoef_cb, S5pc210fimdState, 4),
+        VMSTATE_UINT32(hueoffset, S5pc210fimdState),
+        VMSTATE_UINT32(blendcon, S5pc210fimdState),
+        VMSTATE_UINT32(dualrgb, S5pc210fimdState),
+        VMSTATE_UINT32_ARRAY(i80ifcmd, S5pc210fimdState, 12),
+        VMSTATE_STRUCT_ARRAY(window, S5pc210fimdState, 5, 1,
+                s5pc210_fimd_window_vmstate, S5pc210fimdWindow),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static int s5pc210_fimd_init(SysBusDevice *dev)
+{
+    S5pc210fimdState *s = FROM_SYSBUS(S5pc210fimdState, 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, &s5pc210_fimd_mmio_ops, s, "s5pc210.fimd",
+            FIMD_REGS_SIZE);
+    sysbus_init_mmio_region(dev, &s->iomem);
+    s->console = graphic_console_init(s5pc210_fimd_update,
+                                      s5pc210_fimd_invalidate, NULL, NULL, s);
+
+    return 0;
+}
+
+static SysBusDeviceInfo s5pc210_fimd_info = {
+    .init = s5pc210_fimd_init,
+    .qdev.name  = "s5pc210.fimd",
+    .qdev.size  = sizeof(S5pc210fimdState),
+    .qdev.vmsd  = &s5pc210_fimd_vmstate,
+    .qdev.reset = s5pc210_fimd_reset,
+};
+
+static void s5pc210_fimd_register_devices(void)
+{
+    sysbus_register_withprop(&s5pc210_fimd_info);
+}
+
+device_init(s5pc210_fimd_register_devices)
-- 
1.7.4.1

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

* [Qemu-devel] [PATCH 12/14] SD card: add query function to check wether SD card currently ready to recieve data Before executing data transfer to card, we must check that previously issued command wasn't a simple query command (for ex. CMD13), which doesn't require data transfer. Currently, we only can aquire information about whether SD card is in sending data state or not. This patch allows us to query wether previous command was data write command and it was successfully accepted by card (meaning that SD card in recieving data state).
  2011-12-07  9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
                   ` (10 preceding siblings ...)
  2011-12-07  9:47 ` [Qemu-devel] [PATCH 11/14] ARM: s5pc210: added s5pc210 display controller device (FIMD) Evgeny Voevodin
@ 2011-12-07  9:47 ` Evgeny Voevodin
  2011-12-07  9:47 ` [Qemu-devel] [PATCH 13/14] ARM: s5pc210: added SD/MMC host controller (ver. 2.0 compliant) implementation Evgeny Voevodin
                   ` (2 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07  9:47 UTC (permalink / raw)
  To: qemu-devel; +Cc: Mitsyanko Igor, d.solodkiy, Evgeny Voevodin

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


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..ddb9d39 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_recieve_ready(SDState *sd)
+{
+    return sd->state == sd_receivingdata_state;
+}
diff --git a/hw/sd.h b/hw/sd.h
index ac4b7c4..782c8d6 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_recieve_ready(SDState *sd);
 
 #endif	/* __hw_sd_h */
-- 
1.7.4.1

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

* [Qemu-devel] [PATCH 13/14] ARM: s5pc210: added SD/MMC host controller (ver. 2.0 compliant) implementation
  2011-12-07  9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
                   ` (11 preceding siblings ...)
  2011-12-07  9:47 ` [Qemu-devel] [PATCH 12/14] SD card: add query function to check wether SD card currently ready to recieve data Before executing data transfer to card, we must check that previously issued command wasn't a simple query command (for ex. CMD13), which doesn't require data transfer. Currently, we only can aquire information about whether SD card is in sending data state or not. This patch allows us to query wether previous command was data write command and it was successfully accepted by card (meaning that SD card in recieving data state) Evgeny Voevodin
@ 2011-12-07  9:47 ` Evgeny Voevodin
  2011-12-07  9:47 ` [Qemu-devel] [PATCH 14/14] s5pc210: Switch to sysbus_init_mmio Evgeny Voevodin
  2011-12-07 10:33 ` [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Peter Maydell
  14 siblings, 0 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07  9:47 UTC (permalink / raw)
  To: qemu-devel; +Cc: 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/s5pc210.c      |   20 +
 hw/s5pc210_sdhc.c | 1693 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1715 insertions(+), 1 deletions(-)
 create mode 100644 hw/s5pc210_sdhc.c

diff --git a/Makefile.target b/Makefile.target
index 84c4a05..be51dbf 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 += s5pc210.o s5pc210_cmu.o s5pc210_uart.o s5pc210_gic.o \
-             s5pc210_combiner.o s5pc210_pwm.o s5pc210_mct.o s5pc210_fimd.o
+             s5pc210_combiner.o s5pc210_pwm.o s5pc210_mct.o s5pc210_fimd.o \
+             s5pc210_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/s5pc210.c b/hw/s5pc210.c
index c7cba3e..8892f0a 100644
--- a/hw/s5pc210.c
+++ b/hw/s5pc210.c
@@ -97,6 +97,12 @@
 /* MCT */
 #define S5PC210_MCT_BASE_ADDR            0x10050000
 
+/* SD/MMC host controllers SFR base addresses */
+#define S5PC210_SDHC0_BASE_ADDR          0x12510000
+#define S5PC210_SDHC1_BASE_ADDR          0x12520000
+#define S5PC210_SDHC2_BASE_ADDR          0x12530000
+#define S5PC210_SDHC3_BASE_ADDR          0x12540000
+
 /* Display controllers (FIMD) */
 #define S5PC210_FIMD0_BASE_ADDR          0x11C00000
 #define S5PC210_FIMD1_BASE_ADDR          0x12000000
@@ -454,6 +460,20 @@ static void s5pc210_init(ram_addr_t ram_size,
         s5pc210_uart_create(addr, fifo_size, channel, NULL, uart_irq);
     }
 
+    /*** SD/MMC host controllers ***/
+
+    sysbus_create_simple("s5pc210.sdhc", S5PC210_SDHC0_BASE_ADDR,
+            irq_table[s5pc210_get_irq(29, 0)]);
+
+    sysbus_create_simple("s5pc210.sdhc", S5PC210_SDHC1_BASE_ADDR,
+            irq_table[s5pc210_get_irq(29, 1)]);
+
+    sysbus_create_simple("s5pc210.sdhc", S5PC210_SDHC2_BASE_ADDR,
+            irq_table[s5pc210_get_irq(29, 2)]);
+
+    sysbus_create_simple("s5pc210.sdhc", S5PC210_SDHC3_BASE_ADDR,
+            irq_table[s5pc210_get_irq(29, 3)]);
+
     /*** LAN adapter ***/
     if (board_type == BOARD_S5PC210_SMDKC210) {
 
diff --git a/hw/s5pc210_sdhc.c b/hw/s5pc210_sdhc.c
new file mode 100644
index 0000000..dbbb885
--- /dev/null
+++ b/hw/s5pc210_sdhc.c
@@ -0,0 +1,1693 @@
+/*
+ * Samsung s5pc210 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 Suport */
+#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 offset
+ ***************************************************/
+
+/* 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 divied 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 stoped at bloch gap during read operation */
+    gap_write = 2,         /* sdhc stoped at bloch gap during write operation */
+    adma_intr = 3          /* sdhc stoped after ADMA interrupt occured */
+};
+
+/* SD/MMC host controller state */
+typedef struct S5pc210SDHCState {
+    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;
+    uint8_t stoped_state;
+
+    /* sd host i/o fifo buffer */
+    uint32_t fifo_buffer[SDHC_CAPAB_MAXBLOCKLENGTH / TARGET_WORD_SIZE];
+    uint16_t data_count; /* curent element in fifo buffer */
+
+    uint32_t sdmasysad;   /* SDMA System Address register */
+    uint16_t blksize;   /* Host DMA Buff Boundary and Transfer BlSize 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 */
+/*  uint32_t 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 swrst Software Reset Register - must 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 */
+/*  uint16_t feaer Force Event Auto CMD12 Error Interrupt Reg - write only */
+/*  uint16_t 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) */
+/*  uint32_t control4 makes no sense in emulation so always return 0 */
+/*  uint16_t hcver Host Controller Ver Reg 0x2401 -sd specification v2 */
+} S5pc210SDHCState;
+
+/****************************************************
+ * Interrupt raising
+ * functions
+ ***************************************************/
+
+static void sdhc_raise_transfer_complete_irq(void *opaque)
+{
+    S5pc210SDHCState *s = (S5pc210SDHCState *)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 comand response recieved interrupt */
+static inline void sdhc_raise_response_recieved_irq(S5pc210SDHCState *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)
+{
+    S5pc210SDHCState *s = (S5pc210SDHCState *)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)
+{
+    S5pc210SDHCState *s = (S5pc210SDHCState *)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));
+    }
+}
+
+/****************************************************
+ * Misc control
+ * functions
+ ***************************************************/
+
+/* Reset ALL */
+static void s5pc210_sdhc_reset(DeviceState *d)
+{
+    S5pc210SDHCState *s = container_of(d, S5pc210SDHCState, busdev.qdev);
+    unsigned long begin = (unsigned long)s + offsetof(S5pc210SDHCState,
+            sdmasysad);
+    unsigned long len = ((unsigned long)s + offsetof(S5pc210SDHCState,
+            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(S5pc210SDHCState *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;
+    }
+}
+
+/* Do transfer complete routine */
+static void sdhc_do_transfer_complete(S5pc210SDHCState *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
+ * functions
+ ***************************************************/
+
+/* Fill host controlerr's read buffer with BLKSIZE bytes of data from card */
+static void sdhc_read_block_from_card(void *opaque)
+{
+    S5pc210SDHCState *s = (S5pc210SDHCState *)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 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 controler BUFFER DATA PORT register */
+static inline uint32_t sdhc_read_dataport(S5pc210SDHCState *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) 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)
+{
+    S5pc210SDHCState *s = (S5pc210SDHCState *)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 writen 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 requsted and if not the last block of data */
+    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 controler BUFFER DATA PORT register */
+static inline void sdhc_write_dataport(S5pc210SDHCState *s, uint32_t value)
+{
+    if ((s->prnsts & SDHC_SPACE_AVAILABLE) == 0) {
+        DPRINTF("s5pc210.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
+ * functions
+ ***************************************************/
+
+/* multi block SDMA transfer */
+static void sdhc_sdma_transfer_multi_blocks(S5pc210SDHCState *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 loop when it stoped at DMA buffer boundary.
+     * Will not execute if all SDMA transfer are aligned to DMA buff 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(S5pc210SDHCState *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);
+}
+
+/****************************************************
+ * Advansed DMA data transfer
+ * function
+ ***************************************************/
+
+static void sdhc_run_adma(S5pc210SDHCState *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 occured 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;
+        }
+    }
+}
+
+/****************************************************
+ * Do nessesary transfers, specified by previosly
+ * issued command
+ ***************************************************/
+static void sdhc_process_command(S5pc210SDHCState *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_recieve_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_recieve_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 from and write to SD/MMC host controller
+ * registers functions
+ ***************************************************/
+
+/* Read byte from SD/MMC host controller registers */
+static uint32_t s5pc210_sdhc_read_1(S5pc210SDHCState *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("sdhc: bad 1 byte read offset " TARGET_FMT_plx "\n", offset);
+        return 0xBAADBAAD;
+    }
+}
+
+/* Read two bytes from SD/MMC host controller registers */
+static uint32_t s5pc210_sdhc_read_2(S5pc210SDHCState *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("sdhc: bad 2 byte read offset " TARGET_FMT_plx "\n", offset);
+        return 0xBAADBAAD;
+    }
+}
+
+/* MMC read 4 bytes function */
+static uint32_t s5pc210_sdhc_read_4(S5pc210SDHCState *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 s5pc210_sdhc_write_1(S5pc210SDHCState *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:
+            s5pc210_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 s5pc210_sdhc_write_2(S5pc210SDHCState *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 s5pc210_sdhc_write_4(S5pc210SDHCState *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("sdhc: bad 4 byte write offset " TARGET_FMT_plx "\n", offset);
+        break;
+    }
+}
+
+static uint64_t s5pc210_sdhc_read(void *opaque, target_phys_addr_t offset,
+                                  unsigned size)
+{
+    S5pc210SDHCState *s = (S5pc210SDHCState *)opaque;
+
+    switch (size) {
+    case (1):
+        return s5pc210_sdhc_read_1(s, offset);
+    case (2):
+        return s5pc210_sdhc_read_2(s, offset);
+    case (4):
+        return s5pc210_sdhc_read_4(s, offset);
+    }
+    return 0xBAADBAADull;
+}
+
+static void s5pc210_sdhc_write(void *opaque, target_phys_addr_t offset,
+                              uint64_t val, unsigned size)
+{
+    S5pc210SDHCState *s = (S5pc210SDHCState *)opaque;
+
+    switch (size) {
+    case (1):
+        s5pc210_sdhc_write_1(s, offset, (uint32_t)val);
+        break;
+    case (2):
+        s5pc210_sdhc_write_2(s, offset, (uint32_t)val);
+        break;
+    case (4):
+        s5pc210_sdhc_write_4(s, offset, (uint32_t)val);
+        break;
+    }
+}
+
+static const MemoryRegionOps s5pc210_sdhc_mmio_ops = {
+    .read = s5pc210_sdhc_read,
+    .write = s5pc210_sdhc_write,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 4,
+        .unaligned = false
+    },
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription s5pc210_sdhc_vmstate = {
+    .name = "s5pc210.sdhc",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields      = (VMStateField[]) {
+        VMSTATE_UINT8(stoped_state, S5pc210SDHCState),
+        VMSTATE_UINT32_ARRAY(fifo_buffer, S5pc210SDHCState,
+                (SDHC_CAPAB_MAXBLOCKLENGTH / TARGET_WORD_SIZE)),
+        VMSTATE_UINT16(data_count, S5pc210SDHCState),
+        VMSTATE_UINT32(sdmasysad, S5pc210SDHCState),
+        VMSTATE_UINT16(blksize, S5pc210SDHCState),
+        VMSTATE_UINT16(blkcnt, S5pc210SDHCState),
+        VMSTATE_UINT32(argument, S5pc210SDHCState),
+        VMSTATE_UINT16(trnmod, S5pc210SDHCState),
+        VMSTATE_UINT16(cmdreg, S5pc210SDHCState),
+        VMSTATE_UINT32_ARRAY(rspreg, S5pc210SDHCState, 4),
+        VMSTATE_UINT32(prnsts, S5pc210SDHCState),
+        VMSTATE_UINT8(hostctl, S5pc210SDHCState),
+        VMSTATE_UINT8(pwrcon, S5pc210SDHCState),
+        VMSTATE_UINT8(blkgap, S5pc210SDHCState),
+        VMSTATE_UINT8(wakcon, S5pc210SDHCState),
+        VMSTATE_UINT16(clkcon, S5pc210SDHCState),
+        VMSTATE_UINT8(timeoutcon, S5pc210SDHCState),
+        VMSTATE_UINT16(norintsts, S5pc210SDHCState),
+        VMSTATE_UINT16(errintsts, S5pc210SDHCState),
+        VMSTATE_UINT16(norintstsen, S5pc210SDHCState),
+        VMSTATE_UINT16(errintstsen, S5pc210SDHCState),
+        VMSTATE_UINT16(norintsigen, S5pc210SDHCState),
+        VMSTATE_UINT16(errintsigen, S5pc210SDHCState),
+        VMSTATE_UINT16(acmd12errsts, S5pc210SDHCState),
+        VMSTATE_UINT32(capareg, S5pc210SDHCState),
+        VMSTATE_UINT32(maxcurr, S5pc210SDHCState),
+        VMSTATE_UINT32(admaerr, S5pc210SDHCState),
+        VMSTATE_UINT32(admasysaddr, S5pc210SDHCState),
+        VMSTATE_UINT32(control2, S5pc210SDHCState),
+        VMSTATE_UINT32(control3, S5pc210SDHCState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+/****************************************************
+ * Initialization and registration
+ * functions
+ ***************************************************/
+
+static int s5pc210_sdhc_init(SysBusDevice *dev)
+{
+    S5pc210SDHCState *s = FROM_SYSBUS(S5pc210SDHCState, dev);
+    DriveInfo *bd;
+
+    sysbus_init_irq(dev, &s->irq);
+    memory_region_init_io(&s->iomem, &s5pc210_sdhc_mmio_ops, s, "s5pc210.sdhc",
+            SDHC_REG_SIZE);
+    sysbus_init_mmio_region(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 s5pc210_sdhc_info = {
+    .init = s5pc210_sdhc_init,
+    .qdev.name  = "s5pc210.sdhc",
+    .qdev.size  = sizeof(S5pc210SDHCState),
+    .qdev.vmsd  = &s5pc210_sdhc_vmstate,
+    .qdev.reset = s5pc210_sdhc_reset,
+};
+
+static void sdhc_register_devices(void)
+{
+    sysbus_register_withprop(&s5pc210_sdhc_info);
+}
+
+device_init(sdhc_register_devices)
-- 
1.7.4.1

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

* [Qemu-devel] [PATCH 14/14] s5pc210: Switch to sysbus_init_mmio.
  2011-12-07  9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
                   ` (12 preceding siblings ...)
  2011-12-07  9:47 ` [Qemu-devel] [PATCH 13/14] ARM: s5pc210: added SD/MMC host controller (ver. 2.0 compliant) implementation Evgeny Voevodin
@ 2011-12-07  9:47 ` Evgeny Voevodin
  2011-12-07 10:41   ` Peter Maydell
  2011-12-07 10:33 ` [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Peter Maydell
  14 siblings, 1 reply; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07  9:47 UTC (permalink / raw)
  To: qemu-devel; +Cc: d.solodkiy, Evgeny Voevodin


Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 hw/s5pc210_cmu.c      |    2 +-
 hw/s5pc210_combiner.c |    2 +-
 hw/s5pc210_fimd.c     |    2 +-
 hw/s5pc210_gic.c      |    4 ++--
 hw/s5pc210_mct.c      |    2 +-
 hw/s5pc210_pwm.c      |    2 +-
 hw/s5pc210_sdhc.c     |    2 +-
 hw/s5pc210_uart.c     |    2 +-
 8 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/hw/s5pc210_cmu.c b/hw/s5pc210_cmu.c
index 12fba47..b8f0e5d 100644
--- a/hw/s5pc210_cmu.c
+++ b/hw/s5pc210_cmu.c
@@ -1121,7 +1121,7 @@ static int s5pc210_cmu_init(SysBusDevice *dev)
     /* memory mapping */
     memory_region_init_io(&s->iomem, &s5pc210_cmu_ops, s, "s5pc210.cmu",
                           S5PC210_CMU_REGS_MEM_SIZE);
-    sysbus_init_mmio_region(dev, &s->iomem);
+    sysbus_init_mmio(dev, &s->iomem);
 
     qemu_register_reset(s5pc210_cmu_reset, s);
 
diff --git a/hw/s5pc210_combiner.c b/hw/s5pc210_combiner.c
index 04ae024..680e761 100644
--- a/hw/s5pc210_combiner.c
+++ b/hw/s5pc210_combiner.c
@@ -352,7 +352,7 @@ static int s5pc210_combiner_init(SysBusDevice *dev)
 
     memory_region_init_io(&s->iomem, &s5pc210_combiner_ops, s,
             "s5pc210-combiner", IIC_REGION_SIZE);
-    sysbus_init_mmio_region(dev, &s->iomem);
+    sysbus_init_mmio(dev, &s->iomem);
 
     s5pc210_combiner_reset(s);
     vmstate_register(NULL, -1, &VMState_S5pc210Combiner, s);
diff --git a/hw/s5pc210_fimd.c b/hw/s5pc210_fimd.c
index b3b01f2..77174ee 100644
--- a/hw/s5pc210_fimd.c
+++ b/hw/s5pc210_fimd.c
@@ -1675,7 +1675,7 @@ static int s5pc210_fimd_init(SysBusDevice *dev)
 
     memory_region_init_io(&s->iomem, &s5pc210_fimd_mmio_ops, s, "s5pc210.fimd",
             FIMD_REGS_SIZE);
-    sysbus_init_mmio_region(dev, &s->iomem);
+    sysbus_init_mmio(dev, &s->iomem);
     s->console = graphic_console_init(s5pc210_fimd_update,
                                       s5pc210_fimd_invalidate, NULL, NULL, s);
 
diff --git a/hw/s5pc210_gic.c b/hw/s5pc210_gic.c
index c6f18d4..4eeeae1 100644
--- a/hw/s5pc210_gic.c
+++ b/hw/s5pc210_gic.c
@@ -386,8 +386,8 @@ static int s5pc210_gic_init(SysBusDevice *dev)
     memory_region_init_io(&s->dist_container, &s5pc210_gic_dist_ops, &s->gic,
             "s5pc210-gic-dist", S5PC210_GIC_DIST_REGION_SIZE);
 
-    sysbus_init_mmio_region(dev, &s->cpu_container);
-    sysbus_init_mmio_region(dev, &s->dist_container);
+    sysbus_init_mmio(dev, &s->cpu_container);
+    sysbus_init_mmio(dev, &s->dist_container);
 
     gic_cpu_write(&s->gic, 1, 0, 1);
     return 0;
diff --git a/hw/s5pc210_mct.c b/hw/s5pc210_mct.c
index 7a7dd84..adb0a2c 100644
--- a/hw/s5pc210_mct.c
+++ b/hw/s5pc210_mct.c
@@ -1465,7 +1465,7 @@ static int s5pc210_mct_init(SysBusDevice *dev)
 
     memory_region_init_io(&s->iomem, &s5pc210_mct_ops, s, "s5pc210-mct",
             MCT_SFR_SIZE);
-    sysbus_init_mmio_region(dev, &s->iomem);
+    sysbus_init_mmio(dev, &s->iomem);
 
     s5pc210_mct_reset(s);
 
diff --git a/hw/s5pc210_pwm.c b/hw/s5pc210_pwm.c
index 5dfaa41..8b280d1 100644
--- a/hw/s5pc210_pwm.c
+++ b/hw/s5pc210_pwm.c
@@ -415,7 +415,7 @@ static int s5pc210_pwm_init(SysBusDevice *dev)
 
     memory_region_init_io(&s->iomem, &s5pc210_pwm_ops, s, "s5pc210-pwm",
             S5PC210_PWM_REG_MEM_SIZE);
-    sysbus_init_mmio_region(dev, &s->iomem);
+    sysbus_init_mmio(dev, &s->iomem);
 
     s5pc210_pwm_reset(s);
 
diff --git a/hw/s5pc210_sdhc.c b/hw/s5pc210_sdhc.c
index dbbb885..8aecf51 100644
--- a/hw/s5pc210_sdhc.c
+++ b/hw/s5pc210_sdhc.c
@@ -1648,7 +1648,7 @@ static int s5pc210_sdhc_init(SysBusDevice *dev)
     sysbus_init_irq(dev, &s->irq);
     memory_region_init_io(&s->iomem, &s5pc210_sdhc_mmio_ops, s, "s5pc210.sdhc",
             SDHC_REG_SIZE);
-    sysbus_init_mmio_region(dev, &s->iomem);
+    sysbus_init_mmio(dev, &s->iomem);
     bd = drive_get_next(IF_SD);
 
     if ((bd == NULL)) {
diff --git a/hw/s5pc210_uart.c b/hw/s5pc210_uart.c
index d1fbddc..3870740 100644
--- a/hw/s5pc210_uart.c
+++ b/hw/s5pc210_uart.c
@@ -644,7 +644,7 @@ static int s5pc210_uart_init(SysBusDevice *dev)
     /* memory mapping */
     memory_region_init_io(&s->iomem, &s5pc210_uart_ops, s, "s5pc210.uart",
                           S5PC210_UART_REGS_MEM_SIZE);
-    sysbus_init_mmio_region(dev, &s->iomem);
+    sysbus_init_mmio(dev, &s->iomem);
 
     sysbus_init_irq(dev, &s->irq);
 
-- 
1.7.4.1

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

* Re: [Qemu-devel] [PATCH 09/14] hw/lan9118.c: Basic byte/word/long access support.
  2011-12-07  9:47 ` [Qemu-devel] [PATCH 09/14] hw/lan9118.c: Basic byte/word/long access support Evgeny Voevodin
@ 2011-12-07 10:09   ` Peter Maydell
  2011-12-07 10:58     ` Evgeny Voevodin
  0 siblings, 1 reply; 25+ messages in thread
From: Peter Maydell @ 2011-12-07 10:09 UTC (permalink / raw)
  To: Evgeny Voevodin; +Cc: qemu-devel, d.solodkiy

On 7 December 2011 09:47, Evgeny Voevodin <e.voevodin@samsung.com> wrote:
> We included this chip into s5pc210 platform because SMDK board holds
> lan9215 chip. Difference is that 9215 access is 16-bit wide and some
> registers differ. By addition basic 16-bit access to 9118 emulation we
> achieved ethernet controller support by Linux lernel on SMDK boards.

If it differs then shouldn't we add a new qdev device for 9215 ?
(sharing most of the implementation code, obviously)

>  static const MemoryRegionOps lan9118_mem_ops = {
> -    .read = lan9118_readl,
> -    .write = lan9118_writel,
> +    .old_mmio = {
> +        .read = { lan9118_readb, lan9118_readw, lan9118_readl, },
> +        .write = { lan9118_writeb, lan9118_writew, lan9118_writel, },
> +    },
>     .endianness = DEVICE_NATIVE_ENDIAN,
>  };

This is going backwards -- the .old_mmio hooks are for backwards
compatibility when converting old devices to MemoryRegions -- they
shouldn't be added in new code.

You need to make the lan9118_read/write functions look at their
'size' argument instead.

-- PMM

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

* Re: [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support.
  2011-12-07  9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
                   ` (13 preceding siblings ...)
  2011-12-07  9:47 ` [Qemu-devel] [PATCH 14/14] s5pc210: Switch to sysbus_init_mmio Evgeny Voevodin
@ 2011-12-07 10:33 ` Peter Maydell
  2011-12-07 10:45   ` Dmitry Solodkiy
  14 siblings, 1 reply; 25+ messages in thread
From: Peter Maydell @ 2011-12-07 10:33 UTC (permalink / raw)
  To: Evgeny Voevodin; +Cc: qemu-devel, d.solodkiy

On 7 December 2011 09:46, Evgeny Voevodin <e.voevodin@samsung.com> wrote:
> This set of patches adds support for Samsung S5PC210-based boards NURI and SMDKC210.
> Tested on Linux kernel v3.x series.

I'm a bit confused by the naming here -- which Linux kernel mach-*
directory does this correspond to? I can see a mach-s5pc100 and
a mach-s5pv210 but no mach-s5pc210. The NURE and SMDKC210 board
source files seem to be in the mach-exynos4 directory.

-- PMM

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

* Re: [Qemu-devel] [PATCH 14/14] s5pc210: Switch to sysbus_init_mmio.
  2011-12-07  9:47 ` [Qemu-devel] [PATCH 14/14] s5pc210: Switch to sysbus_init_mmio Evgeny Voevodin
@ 2011-12-07 10:41   ` Peter Maydell
  0 siblings, 0 replies; 25+ messages in thread
From: Peter Maydell @ 2011-12-07 10:41 UTC (permalink / raw)
  To: Evgeny Voevodin; +Cc: qemu-devel, d.solodkiy

On 7 December 2011 09:47, Evgeny Voevodin <e.voevodin@samsung.com> wrote:
>
> Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
> ---
>  hw/s5pc210_cmu.c      |    2 +-
>  hw/s5pc210_combiner.c |    2 +-
>  hw/s5pc210_fimd.c     |    2 +-
>  hw/s5pc210_gic.c      |    4 ++--
>  hw/s5pc210_mct.c      |    2 +-
>  hw/s5pc210_pwm.c      |    2 +-
>  hw/s5pc210_sdhc.c     |    2 +-
>  hw/s5pc210_uart.c     |    2 +-
>  8 files changed, 9 insertions(+), 9 deletions(-)
>
> diff --git a/hw/s5pc210_cmu.c b/hw/s5pc210_cmu.c
> index 12fba47..b8f0e5d 100644
> --- a/hw/s5pc210_cmu.c
> +++ b/hw/s5pc210_cmu.c
> @@ -1121,7 +1121,7 @@ static int s5pc210_cmu_init(SysBusDevice *dev)
>     /* memory mapping */
>     memory_region_init_io(&s->iomem, &s5pc210_cmu_ops, s, "s5pc210.cmu",
>                           S5PC210_CMU_REGS_MEM_SIZE);
> -    sysbus_init_mmio_region(dev, &s->iomem);
> +    sysbus_init_mmio(dev, &s->iomem);
>
>     qemu_register_reset(s5pc210_cmu_reset, s);

These changes need to be folded in with the earlier patches, otherwise
they will not compile. Each patch in the series should be correct as it
stands, not broken and fixed up by a later patch.

-- PMM

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

* Re: [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support.
  2011-12-07 10:33 ` [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Peter Maydell
@ 2011-12-07 10:45   ` Dmitry Solodkiy
  2011-12-08 10:58     ` Peter Maydell
  0 siblings, 1 reply; 25+ messages in thread
From: Dmitry Solodkiy @ 2011-12-07 10:45 UTC (permalink / raw)
  To: 'Peter Maydell'; +Cc: qemu-devel, 'Evgeny Voevodin'

Dear Peter,

  Orion, s5pc210 and Exynos4210 are aliases for the same hardware chip.
  We decided that s5pc210 is more informative than exynos4 (there are many hardware models started with Exynos4...) or Orion(obsolete one).

Thanks,
     Dmitry Solodkiy,
     Mobile SW PL, Advanced Software Group,
     Moscow R&D center, Samsung Electronics

-----Original Message-----
From: Peter Maydell [mailto:peter.maydell@linaro.org] 
Sent: Wednesday, December 07, 2011 2:34 PM
To: Evgeny Voevodin
Cc: qemu-devel@nongnu.org; d.solodkiy@samsung.com
Subject: Re: [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support.

On 7 December 2011 09:46, Evgeny Voevodin <e.voevodin@samsung.com> wrote:
> This set of patches adds support for Samsung S5PC210-based boards NURI and SMDKC210.
> Tested on Linux kernel v3.x series.

I'm a bit confused by the naming here -- which Linux kernel mach-* directory does this correspond to? I can see a mach-s5pc100 and a mach-s5pv210 but no mach-s5pc210. The NURE and SMDKC210 board source files seem to be in the mach-exynos4 directory.

-- PMM

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

* Re: [Qemu-devel] [PATCH 09/14] hw/lan9118.c: Basic byte/word/long access support.
  2011-12-07 10:09   ` Peter Maydell
@ 2011-12-07 10:58     ` Evgeny Voevodin
  2011-12-07 11:17       ` Peter Maydell
  0 siblings, 1 reply; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07 10:58 UTC (permalink / raw)
  To: Peter Maydell; +Cc: qemu-devel, d.solodkiy

On 12/07/2011 02:09 PM, Peter Maydell wrote:
> On 7 December 2011 09:47, Evgeny Voevodin<e.voevodin@samsung.com>  wrote:
>> We included this chip into s5pc210 platform because SMDK board holds
>> lan9215 chip. Difference is that 9215 access is 16-bit wide and some
>> registers differ. By addition basic 16-bit access to 9118 emulation we
>> achieved ethernet controller support by Linux lernel on SMDK boards.
>
> If it differs then shouldn't we add a new qdev device for 9215 ?
> (sharing most of the implementation code, obviously)
>

This patch could be interpreted as lan9118 emulation expansion since 
this chip supports 16-bit access too. These changes don't cover all the 
difference between 9118 and 9215, but it's enough to provide network 
support to Samsung boards. When 9215 support will be added we can easily 
switch to this chip.

>>   static const MemoryRegionOps lan9118_mem_ops = {
>> -    .read = lan9118_readl,
>> -    .write = lan9118_writel,
>> +    .old_mmio = {
>> +        .read = { lan9118_readb, lan9118_readw, lan9118_readl, },
>> +        .write = { lan9118_writeb, lan9118_writew, lan9118_writel, },
>> +    },
>>      .endianness = DEVICE_NATIVE_ENDIAN,
>>   };
>
> This is going backwards -- the .old_mmio hooks are for backwards
> compatibility when converting old devices to MemoryRegions -- they
> shouldn't be added in new code.
>
> You need to make the lan9118_read/write functions look at their
> 'size' argument instead.
>
> -- PMM
>
>

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

* Re: [Qemu-devel] [PATCH 01/14] ARM: s5pc210: Basic support of s5pc210 boards
  2011-12-07  9:46 ` [Qemu-devel] [PATCH 01/14] ARM: s5pc210: Basic support of s5pc210 boards Evgeny Voevodin
@ 2011-12-07 11:01   ` Peter Maydell
  0 siblings, 0 replies; 25+ messages in thread
From: Peter Maydell @ 2011-12-07 11:01 UTC (permalink / raw)
  To: Evgeny Voevodin; +Cc: Maksim Kozlov, qemu-devel, d.solodkiy

If you split this patch up so it is:
 1 cmu
 2 uart
 3 initial basic board

this will be easier to review than a 2000 line patch.

> +DeviceState *s5pc210_uart_create(target_phys_addr_t addr,
> +                                 int fifo_size,
> +                                 int channel,
> +                                 CharDriverState *chr,
> +                                 qemu_irq irq)
> +{
> +    DeviceState  *dev;
> +    SysBusDevice *bus;
> +    S5pc210UartState *state;
> +
> +    dev = qdev_create(NULL, "s5pc210.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("s5pc210.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);
> +
> +    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);
> +
> +    state = FROM_SYSBUS(S5pc210UartState, bus);
> +
> +    state->rx.size = fifo_size;
> +    state->tx.size = fifo_size;

This is wrong. Code at the "qdev_create(something)" level should
not then reach in and mess with the underlying state structure
of the device it's just created. fifo_size should probably be passed
to the device as a qdev property.

> +static int s5pc210_uart_init(SysBusDevice *dev)
> +{
> +    S5pc210UartState *s = FROM_SYSBUS(S5pc210UartState, dev);
> +
> +    /* memory mapping */
> +    memory_region_init_io(&s->iomem, &s5pc210_uart_ops, s, "s5pc210.uart",
> +                          S5PC210_UART_REGS_MEM_SIZE);
> +    sysbus_init_mmio_region(dev, &s->iomem);
> +
> +    sysbus_init_irq(dev, &s->irq);
> +
> +    qemu_chr_add_handlers(s->chr, s5pc210_uart_can_receive,
> +                          s5pc210_uart_receive, s5pc210_uart_event, s);
> +
> +    vmstate_register(&dev->qdev, -1, &vmstate_s5pc210_uart, s);
> +
> +    qemu_register_reset(s5pc210_uart_reset, s);

You can set these up using .qdev.reset and .qdev.vmsd fields
in your SysBusDeviceInfo struct, which is cleaner than calling
functions to register them.

-- PMM

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

* Re: [Qemu-devel] [PATCH 09/14] hw/lan9118.c: Basic byte/word/long access support.
  2011-12-07 10:58     ` Evgeny Voevodin
@ 2011-12-07 11:17       ` Peter Maydell
  0 siblings, 0 replies; 25+ messages in thread
From: Peter Maydell @ 2011-12-07 11:17 UTC (permalink / raw)
  To: Evgeny Voevodin; +Cc: qemu-devel, d.solodkiy

On 7 December 2011 10:58, Evgeny Voevodin <e.voevodin@samsung.com> wrote:
> On 12/07/2011 02:09 PM, Peter Maydell wrote:
>>
>> On 7 December 2011 09:47, Evgeny Voevodin<e.voevodin@samsung.com>  wrote:
>>>
>>> We included this chip into s5pc210 platform because SMDK board holds
>>> lan9215 chip. Difference is that 9215 access is 16-bit wide and some
>>> registers differ. By addition basic 16-bit access to 9118 emulation we
>>> achieved ethernet controller support by Linux lernel on SMDK boards.
>>
>>
>> If it differs then shouldn't we add a new qdev device for 9215 ?
>> (sharing most of the implementation code, obviously)
>>
>
> This patch could be interpreted as lan9118 emulation expansion since this
> chip supports 16-bit access too. These changes don't cover all the
> difference between 9118 and 9215, but it's enough to provide network support
> to Samsung boards. When 9215 support will be added we can easily switch to
> this chip.

If you're adding a legitimate missing feature (16 bit access support) to
lan9118 then your commit message should say so, not talk about 9215.
The right place to say "this should be a 9215 but the 9118 is close enough"
is in the patch adding the network chip to the board model, not in the
commit adding a feature to the network chip model.

We should probably make 16 vs 32 bit be a qdev property of lan9118
(corresponding to the way the hardware is configured by an input pin
on startup) and reflect this properly in the HW_CFG register.

Also, this todo comment is too obscure:
+    /* TODO: Implement fair word operation, because this implementation
+     * assumes that any register is accessed as two 16-bit operations. */

I have no idea what it means.

-- PMM

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

* Re: [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support.
  2011-12-07 10:45   ` Dmitry Solodkiy
@ 2011-12-08 10:58     ` Peter Maydell
  2011-12-09  1:54       ` Chih-Min Chao
  2011-12-09  9:21       ` Dmitry Solodkiy
  0 siblings, 2 replies; 25+ messages in thread
From: Peter Maydell @ 2011-12-08 10:58 UTC (permalink / raw)
  To: Dmitry Solodkiy; +Cc: qemu-devel, Evgeny Voevodin

On 7 December 2011 10:45, Dmitry Solodkiy <d.solodkiy@samsung.com> wrote:
> Dear Peter,
>
>  Orion, s5pc210 and Exynos4210 are aliases for the same hardware chip.
>  We decided that s5pc210 is more informative than exynos4 (there are
> many hardware models started with Exynos4...) or Orion(obsolete one).

Hmm. You know the SoC better than I do, but I'm a bit dubious
about using a different naming scheme than the kernel does.
u-boot is currently working on a renaming too (eg patches being
submitted: http://patchwork.ozlabs.org/patch/129922/)

-- PMM

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

* Re: [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support.
  2011-12-08 10:58     ` Peter Maydell
@ 2011-12-09  1:54       ` Chih-Min Chao
  2011-12-09  9:21       ` Dmitry Solodkiy
  1 sibling, 0 replies; 25+ messages in thread
From: Chih-Min Chao @ 2011-12-09  1:54 UTC (permalink / raw)
  To: Peter Maydell; +Cc: Dmitry Solodkiy, Evgeny Voevodin, qemu-devel

On Thu, Dec 8, 2011 at 6:58 PM, Peter Maydell <peter.maydell@linaro.org> wrote:
> On 7 December 2011 10:45, Dmitry Solodkiy <d.solodkiy@samsung.com> wrote:
>> Dear Peter,
>>
>>  Orion, s5pc210 and Exynos4210 are aliases for the same hardware chip.
>>  We decided that s5pc210 is more informative than exynos4 (there are
>> many hardware models started with Exynos4...) or Orion(obsolete one).
>
> Hmm. You know the SoC better than I do, but I'm a bit dubious
> about using a different naming scheme than the kernel does.
> u-boot is currently working on a renaming too (eg patches being
> submitted: http://patchwork.ozlabs.org/patch/129922/)
>
> -- PMM
>
>

In u-boot

To replace arch-s5pc2xx by arch-exynos  is also proposed
http://www.mail-archive.com/u-boot@lists.denx.de/msg72104.html

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

* Re: [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support.
  2011-12-08 10:58     ` Peter Maydell
  2011-12-09  1:54       ` Chih-Min Chao
@ 2011-12-09  9:21       ` Dmitry Solodkiy
  1 sibling, 0 replies; 25+ messages in thread
From: Dmitry Solodkiy @ 2011-12-09  9:21 UTC (permalink / raw)
  To: 'Peter Maydell'; +Cc: qemu-devel, 'Evgeny Voevodin'

Dear Peter,

  Well, for a long-term Exynos keyword would be better so we're going to use it in the next patch proposal (estimated today (09.12) or next Monday).

Thanks,
     Dmitry Solodkiy,
     Mobile SW PL, Advanced Software Group,
     Moscow R&D center, Samsung Electronics


-----Original Message-----
From: Peter Maydell [mailto:peter.maydell@linaro.org] 
Sent: Thursday, December 08, 2011 2:58 PM
To: Dmitry Solodkiy
Cc: qemu-devel@nongnu.org; Evgeny Voevodin
Subject: Re: [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support.

On 7 December 2011 10:45, Dmitry Solodkiy <d.solodkiy@samsung.com> wrote:
> Dear Peter,
>
>  Orion, s5pc210 and Exynos4210 are aliases for the same hardware chip.
>  We decided that s5pc210 is more informative than exynos4 (there are 
> many hardware models started with Exynos4...) or Orion(obsolete one).

Hmm. You know the SoC better than I do, but I'm a bit dubious about using a different naming scheme than the kernel does.
u-boot is currently working on a renaming too (eg patches being
submitted: http://patchwork.ozlabs.org/patch/129922/)

-- PMM

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

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

Thread overview: 25+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-12-07  9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
2011-12-07  9:46 ` [Qemu-devel] [PATCH 01/14] ARM: s5pc210: Basic support of s5pc210 boards Evgeny Voevodin
2011-12-07 11:01   ` Peter Maydell
2011-12-07  9:46 ` [Qemu-devel] [PATCH 02/14] hw/sysbus.h: Increase maximum number of device IRQs Evgeny Voevodin
2011-12-07  9:46 ` [Qemu-devel] [PATCH 03/14] ARM: s5pc210: IRQ subsystem support Evgeny Voevodin
2011-12-07  9:46 ` [Qemu-devel] [PATCH 04/14] ARM: s5pc210: PWM support Evgeny Voevodin
2011-12-07  9:46 ` [Qemu-devel] [PATCH 05/14] hw/arm_boot.c: Add new secondary CPU bootloader Evgeny Voevodin
2011-12-07  9:46 ` [Qemu-devel] [PATCH 06/14] hw/arm_gic.c: lower IRQ only on changing of enable bit Evgeny Voevodin
2011-12-07  9:46 ` [Qemu-devel] [PATCH 07/14] ARM: s5pc210: MCT support Evgeny Voevodin
2011-12-07  9:46 ` [Qemu-devel] [PATCH 08/14] ARM: s5pc210: Boot secondary CPU Evgeny Voevodin
2011-12-07  9:47 ` [Qemu-devel] [PATCH 09/14] hw/lan9118.c: Basic byte/word/long access support Evgeny Voevodin
2011-12-07 10:09   ` Peter Maydell
2011-12-07 10:58     ` Evgeny Voevodin
2011-12-07 11:17       ` Peter Maydell
2011-12-07  9:47 ` [Qemu-devel] [PATCH 10/14] hw/s5pc210.c: Add lan9118 support to SMDK board Evgeny Voevodin
2011-12-07  9:47 ` [Qemu-devel] [PATCH 11/14] ARM: s5pc210: added s5pc210 display controller device (FIMD) Evgeny Voevodin
2011-12-07  9:47 ` [Qemu-devel] [PATCH 12/14] SD card: add query function to check wether SD card currently ready to recieve data Before executing data transfer to card, we must check that previously issued command wasn't a simple query command (for ex. CMD13), which doesn't require data transfer. Currently, we only can aquire information about whether SD card is in sending data state or not. This patch allows us to query wether previous command was data write command and it was successfully accepted by card (meaning that SD card in recieving data state) Evgeny Voevodin
2011-12-07  9:47 ` [Qemu-devel] [PATCH 13/14] ARM: s5pc210: added SD/MMC host controller (ver. 2.0 compliant) implementation Evgeny Voevodin
2011-12-07  9:47 ` [Qemu-devel] [PATCH 14/14] s5pc210: Switch to sysbus_init_mmio Evgeny Voevodin
2011-12-07 10:41   ` Peter Maydell
2011-12-07 10:33 ` [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Peter Maydell
2011-12-07 10:45   ` Dmitry Solodkiy
2011-12-08 10:58     ` Peter Maydell
2011-12-09  1:54       ` Chih-Min Chao
2011-12-09  9:21       ` Dmitry Solodkiy

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).