diff mbox

[01/14] ARM: s5pc210: Basic support of s5pc210 boards

Message ID 1323251225-1072-2-git-send-email-e.voevodin@samsung.com
State New
Headers show

Commit Message

Evgeny Voevodin Dec. 7, 2011, 9:46 a.m. UTC
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

Comments

Peter Maydell Dec. 7, 2011, 11:01 a.m. UTC | #1
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
diff mbox

Patch

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)