Tizen ARM: Add and improve ARM SOC specific files.
authorEvgeny Voevodin <e.voevodin@samsung.com>
Thu, 14 Jun 2012 06:29:42 +0000 (10:29 +0400)
committerEvgeny Voevodin <e.voevodin@samsung.com>
Fri, 15 Jun 2012 07:23:19 +0000 (11:23 +0400)
This commit adds and improves files related to Exynos4210 SOC
emulation. This SOC will be used in maru ARM board.

Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
15 files changed:
Makefile.objs
Makefile.target
default-configs/arm-softmmu.mak
hw/exynos4210.h
hw/exynos4210_audio.c [new file with mode: 0644]
hw/exynos4210_cmu.c [new file with mode: 0644]
hw/exynos4210_g3d.c [new file with mode: 0644]
hw/exynos4210_gic.c
hw/exynos4210_i2c.c [new file with mode: 0644]
hw/exynos4210_i2s.c [new file with mode: 0644]
hw/exynos4210_i2s.h [new file with mode: 0644]
hw/exynos4210_rtc.c [new file with mode: 0644]
hw/exynos4210_uart.c
hw/wm8994.c [new file with mode: 0644]
hw/wm8994.h [new file with mode: 0644]

index 6c9efd3..b001d21 100644 (file)
@@ -106,6 +106,7 @@ common-obj-y += irq.o input.o
 common-obj-$(CONFIG_PTIMER) += ptimer.o
 common-obj-$(CONFIG_MAX7310) += max7310.o
 common-obj-$(CONFIG_WM8750) += wm8750.o
+common-obj-$(CONFIG_WM8994) += wm8994.o
 common-obj-$(CONFIG_TWL92230) += twl92230.o
 common-obj-$(CONFIG_TSC2005) += tsc2005.o
 common-obj-$(CONFIG_LM832X) += lm832x.o
index 03598f5..547c5ce 100755 (executable)
@@ -349,6 +349,8 @@ obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
 obj-arm-y += exynos4210_gic.o exynos4210_combiner.o exynos4210.o
 obj-arm-y += exynos4_boards.o exynos4210_uart.o exynos4210_pwm.o
 obj-arm-y += exynos4210_pmu.o exynos4210_mct.o exynos4210_fimd.o
+obj-arm-y += exynos4210_rtc.o exynos4210_cmu.o exynos4210_g3d.o
+obj-arm-y += exynos4210_i2c.o exynos4210_i2s.o exynos4210_audio.o
 obj-arm-y += arm_l2x0.o
 obj-arm-y += arm_mptimer.o a15mpcore.o
 obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
index e542b4f..dd64484 100644 (file)
@@ -11,6 +11,7 @@ CONFIG_PTIMER=y
 CONFIG_SD=y
 CONFIG_MAX7310=y
 CONFIG_WM8750=y
+CONFIG_WM8994=y
 CONFIG_TWL92230=y
 CONFIG_TSC2005=y
 CONFIG_LM832X=y
index f7c7027..1a37565 100644 (file)
 #define EXYNOS4210_IRAM_BASE_ADDR           0x02020000
 #define EXYNOS4210_IRAM_SIZE                0x00020000  /* 128 KB */
 
+#define EXYNOS4210_AUDSS_INTMEM_BASE_ADDR   0x03000000
+#define EXYNOS4210_AUDSS_INTMEM_SIZE        0x00039000  /* 228 KB */
+
+#define EXYNOS4210_VPCI_CFG_BASE_ADDR       0xC2000000
+
 /* Secondary CPU startup code is in IROM memory */
 #define EXYNOS4210_SMP_BOOT_ADDR            EXYNOS4210_IROM_BASE_ADDR
 #define EXYNOS4210_SMP_BOOT_SIZE            0x1000
@@ -53,6 +58,8 @@
 #define EXYNOS4210_SMP_PRIVATE_BASE_ADDR    0x10500000
 #define EXYNOS4210_L2X0_BASE_ADDR           0x10502000
 
+#define EXYNOS4210_I2C_NUMBER               9
+
 /*
  * exynos4210 IRQ subsystem stub definitions.
  */
@@ -95,6 +102,10 @@ typedef struct Exynos4210State {
     MemoryRegion dram1_mem;
     MemoryRegion boot_secondary;
     MemoryRegion bootreg_mem;
+    MemoryRegion audss_intmem;
+    i2c_bus *i2c_if[EXYNOS4210_I2C_NUMBER];
+    BusState *i2s_bus[3];
+    SysBusDevice *vpci_bus;
 } Exynos4210State;
 
 void exynos4210_write_secondary(CPUARMState *env,
@@ -123,8 +134,54 @@ void exynos4210_combiner_get_gpioin(Exynos4210Irq *irqs, DeviceState *dev,
         int ext);
 
 /*
+ * Interface for exynos4210 Clock Management Units (CMUs)
+ */
+typedef enum {
+    UNSPECIFIED_CMU = -1,
+    EXYNOS4210_CMU_LEFTBUS,
+    EXYNOS4210_CMU_RIGHTBUS,
+    EXYNOS4210_CMU_TOP,
+    EXYNOS4210_CMU_DMC,
+    EXYNOS4210_CMU_CPU,
+    EXYNOS4210_CMU_NUMBER
+} Exynos4210Cmu;
+
+typedef enum {
+    UNSPECIFIED_CLOCK,
+    EXYNOS4210_XXTI,
+    EXYNOS4210_XUSBXTI,
+//    EXYNOS4210_USB_PHY,
+//    EXYNOS4210_USB_HOST_PHY,
+//    EXYNOS4210_HDMI_PHY,
+    EXYNOS4210_APLL,
+    EXYNOS4210_MPLL,
+    EXYNOS4210_SCLK_HDMI24M,
+    EXYNOS4210_SCLK_USBPHY0,
+    EXYNOS4210_SCLK_USBPHY1,
+    EXYNOS4210_SCLK_HDMIPHY,
+    EXYNOS4210_SCLK_APLL,
+    EXYNOS4210_SCLK_MPLL,
+    EXYNOS4210_ACLK_100,
+    EXYNOS4210_SCLK_UART0,
+    EXYNOS4210_SCLK_UART1,
+    EXYNOS4210_SCLK_UART2,
+    EXYNOS4210_SCLK_UART3,
+    EXYNOS4210_SCLK_UART4,
+    EXYNOS4210_CLOCKS_NUMBER
+} Exynos4210Clock;
+
+typedef void ClockChangeHandler(void *opaque);
+
+DeviceState *exynos4210_cmu_create(target_phys_addr_t addr, Exynos4210Cmu cmu);
+uint64_t exynos4210_cmu_get_rate(Exynos4210Clock clock_id);
+void exynos4210_register_clock_handler(ClockChangeHandler *func,
+                                       Exynos4210Clock clock_id,
+                                       void *opaque);
+
+/*
  * exynos4210 UART
  */
+
 DeviceState *exynos4210_uart_create(target_phys_addr_t addr,
                                     int fifo_size,
                                     int channel,
diff --git a/hw/exynos4210_audio.c b/hw/exynos4210_audio.c
new file mode 100644 (file)
index 0000000..c5004ab
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * Samsung exynos4210 Audio driver
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ *  Vorobiov Stanislav <s.vorobiov@samsung.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License as published by the
+ *  Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ *  for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "exynos4210_i2s.h"
+#include "wm8994.h"
+
+/* #define DEBUG_EXYNOS4210_AUDIO */
+
+#ifdef DEBUG_EXYNOS4210_AUDIO
+#define DPRINTF(fmt, ...) \
+    do { \
+        fprintf(stdout, "AUDIO: [%s:%d] " fmt, __func__, __LINE__, \
+            ## __VA_ARGS__); \
+    } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+#define AUDIO_MAX_WORDS (4096 / EXYNOS4210_I2S_WORD_LEN)
+
+typedef struct {
+    Exynos4210I2SSlave i2s;
+
+    DeviceState *wm8994;
+} Exynos4210AudioState;
+
+static void exynos4210_audio_callback(void *opaque, int free_out_bytes)
+{
+    Exynos4210AudioState *s = (Exynos4210AudioState *)opaque;
+    uint8_t buff[AUDIO_MAX_WORDS * EXYNOS4210_I2S_WORD_LEN];
+    int free_out_words = free_out_bytes / EXYNOS4210_I2S_WORD_LEN;
+    uint32_t num_words;
+
+    if (free_out_words <= 0) {
+        return;
+    }
+
+    if (free_out_words > AUDIO_MAX_WORDS) {
+        free_out_words = AUDIO_MAX_WORDS;
+    }
+
+    if (!exynos4210_i2s_dma_enabled(qdev_get_parent_bus(&s->i2s.qdev))) {
+        return;
+    }
+
+    num_words = exynos4210_i2s_dma_get_words_available(
+                    qdev_get_parent_bus(&s->i2s.qdev));
+
+    num_words = MIN(num_words, free_out_words);
+
+    exynos4210_i2s_dma_read(qdev_get_parent_bus(&s->i2s.qdev),
+                            &buff[0],
+                            num_words);
+
+    num_words = wm8994_dac_write(s->wm8994,
+        &buff[0],
+        num_words * EXYNOS4210_I2S_WORD_LEN) / EXYNOS4210_I2S_WORD_LEN;
+
+    exynos4210_i2s_dma_advance(qdev_get_parent_bus(&s->i2s.qdev), num_words);
+}
+
+static void exynos4210_audio_dma_enable(Exynos4210I2SSlave *i2s, bool enable)
+{
+    Exynos4210AudioState *s =
+        FROM_EXYNOS4210_I2S_SLAVE(Exynos4210AudioState, i2s);
+
+    DPRINTF("enter %d\n", enable);
+
+    wm8994_set_active(s->wm8994, enable);
+}
+
+static void exynos4210_audio_reset(DeviceState *dev)
+{
+    DPRINTF("enter\n");
+}
+
+static int exynos4210_audio_init(Exynos4210I2SSlave *i2s)
+{
+    Exynos4210AudioState *s =
+        FROM_EXYNOS4210_I2S_SLAVE(Exynos4210AudioState, i2s);
+
+    wm8994_data_req_set(s->wm8994, exynos4210_audio_callback, s);
+
+    return 0;
+}
+
+static const VMStateDescription vmstate_exynos4210_audio = {
+    .name = "exynos4210.audio",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_EXYNOS4210_I2S_SLAVE(i2s, Exynos4210AudioState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static Property exynos4210_audio_properties[] = {
+    {
+        .name   = "wm8994",
+        .info   = &qdev_prop_ptr,
+        .offset = offsetof(Exynos4210AudioState, wm8994),
+    },
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void exynos4210_audio_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    Exynos4210I2SSlaveClass *sc = EXYNOS4210_I2S_SLAVE_CLASS(klass);
+
+    sc->init = exynos4210_audio_init;
+    sc->dma_enable = exynos4210_audio_dma_enable;
+    dc->reset = exynos4210_audio_reset;
+    dc->props = exynos4210_audio_properties;
+    dc->vmsd = &vmstate_exynos4210_audio;
+}
+
+static TypeInfo exynos4210_audio_info = {
+    .name          = "exynos4210.audio",
+    .parent        = TYPE_EXYNOS4210_I2S_SLAVE,
+    .instance_size = sizeof(Exynos4210AudioState),
+    .class_init    = exynos4210_audio_class_init,
+};
+
+static void exynos4210_audio_register_types(void)
+{
+    type_register_static(&exynos4210_audio_info);
+}
+
+type_init(exynos4210_audio_register_types)
diff --git a/hw/exynos4210_cmu.c b/hw/exynos4210_cmu.c
new file mode 100644 (file)
index 0000000..1ea9d7a
--- /dev/null
@@ -0,0 +1,1482 @@
+/*
+ *  exynos4210 Clock Management Units (CMUs) Emulation
+ *
+ *  Copyright (C) 2011 Samsung Electronics Co Ltd.
+ *    Maksim Kozlov, <m.kozlov@samsung.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License as published by the
+ *  Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ *  for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "sysbus.h"
+
+#include "exynos4210.h"
+
+
+
+#define DEBUG_CMU         0
+#define DEBUG_CMU_EXTEND  0
+
+#if DEBUG_CMU || DEBUG_CMU_EXTEND
+
+    #define  PRINT_DEBUG(fmt, args...)  \
+        do { \
+            fprintf(stderr, "  [%s:%d]   "fmt, __func__, __LINE__, ##args); \
+        } while (0)
+
+    #define  PRINT_DEBUG_SIMPLE(fmt, args...)  \
+        do { \
+            fprintf(stderr, fmt, ## args); \
+        } while (0)
+
+#if DEBUG_CMU_EXTEND
+
+    #define  PRINT_DEBUG_EXTEND(fmt, args...) \
+        do { \
+            fprintf(stderr, "  [%s:%d]   "fmt, __func__, __LINE__, ##args); \
+        } while (0)
+#else
+    #define  PRINT_DEBUG_EXTEND(fmt, args...) \
+        do {} while (0)
+#endif /* EXTEND */
+
+#else
+    #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)
+#endif
+
+#define  PRINT_ERROR(fmt, args...)                                          \
+        do {                                                                \
+            fprintf(stderr, "  [%s:%d]   "fmt, __func__, __LINE__, ##args); \
+        } while (0)
+
+
+
+/* function blocks */
+#define LEFTBUS_BLK   0x0
+#define RIGHTBUS_BLK  0x0
+#define TOP_BLK       0x10
+#define TOP0_BLK      0x10
+#define TOP1_BLK      0x14
+#define DMC_BLK       0x0
+#define DMC0_BLK      0x0
+#define DMC1_BLK      0x4
+#define CPU_BLK       0x0
+#define CPU0_BLK      0x0
+#define CPU1_BLK      0x4
+
+#define CAM_BLK       0x20
+#define TV_BLK        0x24
+#define MFC_BLK       0x28
+#define G3D_BLK       0x2C
+#define IMAGE_BLK     0x30
+#define LCD0_BLK      0x34
+#define LCD1_BLK      0x38
+#define MAUDIO_BLK    0x3C
+#define FSYS_BLK      0x40
+#define FSYS0_BLK     0x40
+#define FSYS1_BLK     0x44
+#define FSYS2_BLK     0x48
+#define FSYS3_BLK     0x4C
+#define GPS_BLK       0x4C /*  CLK_GATE_IP_GPS in CMU_TOP */
+#define PERIL_BLK     0x50
+#define PERIL0_BLK    0x50
+#define PERIL1_BLK    0x54
+#define PERIL2_BLK    0x58
+#define PERIL3_BLK    0x5C
+#define PERIL4_BLK    0x60
+#define PERIR_BLK     0x60 /* CLK_GATE_IP_PERIR in CMU_TOP */
+#define PERIL5_BLK    0x64
+
+#define BLOCK_MASK    0xFF
+
+/* PLLs */
+/* located in CMU_CPU block */
+#define APLL  0x00
+#define MPLL  0x08
+/* located in CMU_TOP block */
+#define EPLL  0x10
+#define VPLL  0x20
+
+/* groups of registers */
+#define PLL_LOCK       0x000
+#define PLL_CON        0x100
+#define CLK_SRC        0x200
+#define CLK_SRC_MASK   0x300
+#define CLK_MUX_STAT   0x400
+#define CLK_DIV        0x500
+#define CLK_DIV_STAT   0x600
+#define CLK_GATE_SCLK  0x800
+#define CLK_GATE_IP    0x900
+
+#define GROUP_MASK     0xF00
+
+#define PLL_LOCK_(pll)  (PLL_LOCK + pll)
+#define PLL_CON0_(pll)  (PLL_CON  + pll)
+#define PLL_CON1_(pll)  (PLL_CON  + pll + 4)
+
+#define CLK_SRC_(block)         (CLK_SRC       + block)
+#define CLK_SRC_MASK_(block)    (CLK_SRC_MASK  + block)
+#define CLK_MUX_STAT_(block)    (CLK_MUX_STAT  + block)
+#define CLK_DIV_(block)         (CLK_DIV       + block)
+#define CLKDIV2_RATIO           0x580 /* described for CMU_TOP only */
+#define CLK_DIV_STAT_(block)    (CLK_DIV_STAT  + block)
+#define CLKDIV2_STAT            0x680 /* described for CMU_TOP only */
+#define CLK_GATE_SCLK_(block)   (CLK_GATE_SCLK + block)
+/* For CMU_LEFTBUS and CMU_RIGHTBUS, CLK_GATE_IP_XXX
+   registers are located at 0x800. */
+#define CLK_GATE_IP_LR_BUS      0x800
+#define CLK_GATE_IP_(block)     (CLK_GATE_IP + block)
+#define CLK_GATE_BLOCK          0x970 /* described for CMU_TOP only */
+#define CLKOUT_CMU              0xA00
+#define CLKOUT_CMU_DIV_STAT     0xA04
+
+/*
+ * registers which are located outside of 0xAFF region
+ */
+/* CMU_DMC */
+#define DCGIDX_MAP0        0x01000
+#define DCGIDX_MAP1        0x01004
+#define DCGIDX_MAP2        0x01008
+#define DCGPERF_MAP0       0x01020
+#define DCGPERF_MAP1       0x01024
+#define DVCIDX_MAP         0x01040
+#define FREQ_CPU           0x01060
+#define FREQ_DPM           0x01064
+#define DVSEMCLK_EN        0x01080
+#define MAXPERF            0x01084
+/* CMU_CPU */
+#define ARMCLK_STOPCTRL    0x01000
+#define ATCLK_STOPCTRL     0x01004
+#define PARITYFAIL_STATUS  0x01010
+#define PARITYFAIL_CLEAR   0x01014
+#define PWR_CTRL           0x01020
+#define APLL_CON0_L8       0x01100
+#define APLL_CON0_L7       0x01104
+#define APLL_CON0_L6       0x01108
+#define APLL_CON0_L5       0x0110C
+#define APLL_CON0_L4       0x01110
+#define APLL_CON0_L3       0x01114
+#define APLL_CON0_L2       0x01118
+#define APLL_CON0_L1       0x0111C
+#define IEM_CONTROL        0x01120
+#define APLL_CON1_L8       0x01200
+#define APLL_CON1_L7       0x01204
+#define APLL_CON1_L6       0x01208
+#define APLL_CON1_L5       0x0120C
+#define APLL_CON1_L4       0x01210
+#define APLL_CON1_L3       0x01214
+#define APLL_CON1_L2       0x01218
+#define APLL_CON1_L1       0x0121C
+#define CLKDIV_IEM_L8      0x01300
+#define CLKDIV_IEM_L7      0x01304
+#define CLKDIV_IEM_L6      0x01308
+#define CLKDIV_IEM_L5      0x0130C
+#define CLKDIV_IEM_L4      0x01310
+#define CLKDIV_IEM_L3      0x01314
+#define CLKDIV_IEM_L2      0x01318
+#define CLKDIV_IEM_L1      0x0131C
+
+#define EXTENDED_REGION_MASK    0x1000
+#define EXTENDED_REGISTER_MASK  0xFFF
+
+typedef struct {
+        const char *name; /* for debugging */
+        uint32_t    offset;
+        uint32_t    reset_value;
+} Exynos4210CmuReg;
+
+
+static Exynos4210CmuReg exynos4210_cmu_leftbus_regs[] = {
+    /* CMU_LEFTBUS registers */
+    {"CLK_SRC_LEFTBUS",             CLK_SRC_(LEFTBUS_BLK),         0x00000000},
+    {"CLK_MUX_STAT_LEFTBUS",        CLK_MUX_STAT_(LEFTBUS_BLK),    0x00000001},
+    {"CLK_DIV_LEFTBUS",             CLK_DIV_(LEFTBUS_BLK),         0x00000000},
+    {"CLK_DIV_STAT_LEFTBUS",        CLK_DIV_STAT_(LEFTBUS_BLK),    0x00000000},
+    {"CLK_GATE_IP_LEFTBUS",         CLK_GATE_IP_LR_BUS,            0xFFFFFFFF},
+    {"CLKOUT_CMU_LEFTBUS",          CLKOUT_CMU,                    0x00010000},
+    {"CLKOUT_CMU_LEFTBUS_DIV_STAT", CLKOUT_CMU_DIV_STAT,           0x00000000},
+};
+
+static Exynos4210CmuReg exynos4210_cmu_rightbus_regs[] = {
+    /* CMU_RIGHTBUS registers */
+    {"CLK_SRC_RIGHTBUS",             CLK_SRC_(RIGHTBUS_BLK),       0x00000000},
+    {"CLK_MUX_STAT_RIGHTBUS",        CLK_MUX_STAT_(RIGHTBUS_BLK),  0x00000001},
+    {"CLK_DIV_RIGHTBUS",             CLK_DIV_(RIGHTBUS_BLK),       0x00000000},
+    {"CLK_DIV_STAT_RIGHTBUS",        CLK_DIV_STAT_(RIGHTBUS_BLK),  0x00000000},
+    {"CLK_GATE_IP_RIGHTBUS",         CLK_GATE_IP_LR_BUS,           0xFFFFFFFF},
+    {"CLKOUT_CMU_RIGHTBUS",          CLKOUT_CMU,                   0x00010000},
+    {"CLKOUT_CMU_RIGHTBUS_DIV_STAT", CLKOUT_CMU_DIV_STAT,          0x00000000},
+};
+
+static Exynos4210CmuReg exynos4210_cmu_top_regs[] = {
+    /* CMU_TOP registers */
+    {"EPLL_LOCK",               PLL_LOCK_(EPLL),              0x00000FFF},
+    {"VPLL_LOCK",               PLL_LOCK_(VPLL),              0x00000FFF},
+    {"EPLL_CON0",               PLL_CON0_(EPLL),              0x00300301},
+    {"EPLL_CON1",               PLL_CON1_(EPLL),              0x00000000},
+    {"VPLL_CON0",               PLL_CON0_(VPLL),              0x00240201},
+    {"VPLL_CON1",               PLL_CON1_(VPLL),              0x66010464},
+    {"CLK_SRC_TOP0",            CLK_SRC_(TOP0_BLK),           0x00000000},
+    {"CLK_SRC_TOP1",            CLK_SRC_(TOP1_BLK),           0x00000000},
+    {"CLK_SRC_CAM",             CLK_SRC_(CAM_BLK),            0x11111111},
+    {"CLK_SRC_TV",              CLK_SRC_(TV_BLK),             0x00000000},
+    {"CLK_SRC_MFC",             CLK_SRC_(MFC_BLK),            0x00000000},
+    {"CLK_SRC_G3D",             CLK_SRC_(G3D_BLK),            0x00000000},
+    {"CLK_SRC_IMAGE",           CLK_SRC_(IMAGE_BLK),          0x00000000},
+    {"CLK_SRC_LCD0",            CLK_SRC_(LCD0_BLK),           0x00001111},
+    {"CLK_SRC_LCD1",            CLK_SRC_(LCD1_BLK),           0x00001111},
+    {"CLK_SRC_MAUDIO",          CLK_SRC_(MAUDIO_BLK),         0x00000005},
+    {"CLK_SRC_FSYS",            CLK_SRC_(FSYS_BLK),           0x00011111},
+    {"CLK_SRC_PERIL0",          CLK_SRC_(PERIL0_BLK),         0x00011111},
+    {"CLK_SRC_PERIL1",          CLK_SRC_(PERIL1_BLK),         0x01110055},
+    {"CLK_SRC_MASK_TOP",        CLK_SRC_MASK_(TOP_BLK),       0x00000001},
+    {"CLK_SRC_MASK_CAM",        CLK_SRC_MASK_(CAM_BLK),       0x11111111},
+    {"CLK_SRC_MASK_TV",         CLK_SRC_MASK_(TV_BLK),        0x00000111},
+    {"CLK_SRC_MASK_LCD0",       CLK_SRC_MASK_(LCD0_BLK),      0x00001111},
+    {"CLK_SRC_MASK_LCD1",       CLK_SRC_MASK_(LCD1_BLK),      0x00001111},
+    {"CLK_SRC_MASK_MAUDIO",     CLK_SRC_MASK_(MAUDIO_BLK),    0x00000001},
+    {"CLK_SRC_MASK_FSYS",       CLK_SRC_MASK_(FSYS_BLK),      0x01011111},
+    {"CLK_SRC_MASK_PERIL0",     CLK_SRC_MASK_(PERIL0_BLK),    0x00011111},
+    {"CLK_SRC_MASK_PERIL1",     CLK_SRC_MASK_(PERIL1_BLK),    0x01110111},
+    {"CLK_MUX_STAT_TOP",        CLK_MUX_STAT_(TOP_BLK),       0x11111111},
+    {"CLK_MUX_STAT_MFC",        CLK_MUX_STAT_(MFC_BLK),       0x00000111},
+    {"CLK_MUX_STAT_G3D",        CLK_MUX_STAT_(G3D_BLK),       0x00000111},
+    {"CLK_MUX_STAT_IMAGE",      CLK_MUX_STAT_(IMAGE_BLK),     0x00000111},
+    {"CLK_DIV_TOP",             CLK_DIV_(TOP_BLK),            0x00000000},
+    {"CLK_DIV_CAM",             CLK_DIV_(CAM_BLK),            0x00000000},
+    {"CLK_DIV_TV",              CLK_DIV_(TV_BLK),             0x00000000},
+    {"CLK_DIV_MFC",             CLK_DIV_(MFC_BLK),            0x00000000},
+    {"CLK_DIV_G3D",             CLK_DIV_(G3D_BLK),            0x00000000},
+    {"CLK_DIV_IMAGE",           CLK_DIV_(IMAGE_BLK),          0x00000000},
+    {"CLK_DIV_LCD0",            CLK_DIV_(LCD0_BLK),           0x00700000},
+    {"CLK_DIV_LCD1",            CLK_DIV_(LCD1_BLK),           0x00700000},
+    {"CLK_DIV_MAUDIO",          CLK_DIV_(MAUDIO_BLK),         0x00000000},
+    {"CLK_DIV_FSYS0",           CLK_DIV_(FSYS0_BLK),          0x00B00000},
+    {"CLK_DIV_FSYS1",           CLK_DIV_(FSYS1_BLK),          0x00000000},
+    {"CLK_DIV_FSYS2",           CLK_DIV_(FSYS2_BLK),          0x00000000},
+    {"CLK_DIV_FSYS3",           CLK_DIV_(FSYS3_BLK),          0x00000000},
+    {"CLK_DIV_PERIL0",          CLK_DIV_(PERIL0_BLK),         0x00000000},
+    {"CLK_DIV_PERIL1",          CLK_DIV_(PERIL1_BLK),         0x00000000},
+    {"CLK_DIV_PERIL2",          CLK_DIV_(PERIL2_BLK),         0x00000000},
+    {"CLK_DIV_PERIL3",          CLK_DIV_(PERIL3_BLK),         0x00000000},
+    {"CLK_DIV_PERIL4",          CLK_DIV_(PERIL4_BLK),         0x00000000},
+    {"CLK_DIV_PERIL5",          CLK_DIV_(PERIL5_BLK),         0x00000000},
+    {"CLKDIV2_RATIO",           CLKDIV2_RATIO,                0x11111111},
+    {"CLK_DIV_STAT_TOP",        CLK_DIV_STAT_(TOP_BLK),       0x00000000},
+    {"CLK_DIV_STAT_CAM",        CLK_DIV_STAT_(CAM_BLK),       0x00000000},
+    {"CLK_DIV_STAT_TV",         CLK_DIV_STAT_(TV_BLK),        0x00000000},
+    {"CLK_DIV_STAT_MFC",        CLK_DIV_STAT_(MFC_BLK),       0x00000000},
+    {"CLK_DIV_STAT_G3D",        CLK_DIV_STAT_(G3D_BLK),       0x00000000},
+    {"CLK_DIV_STAT_IMAGE",      CLK_DIV_STAT_(IMAGE_BLK),     0x00000000},
+    {"CLK_DIV_STAT_LCD0",       CLK_DIV_STAT_(LCD0_BLK),      0x00000000},
+    {"CLK_DIV_STAT_LCD1",       CLK_DIV_STAT_(LCD1_BLK),      0x00000000},
+    {"CLK_DIV_STAT_MAUDIO",     CLK_DIV_STAT_(MAUDIO_BLK),    0x00000000},
+    {"CLK_DIV_STAT_FSYS0",      CLK_DIV_STAT_(FSYS0_BLK),     0x00000000},
+    {"CLK_DIV_STAT_FSYS1",      CLK_DIV_STAT_(FSYS1_BLK),     0x00000000},
+    {"CLK_DIV_STAT_FSYS2",      CLK_DIV_STAT_(FSYS2_BLK),     0x00000000},
+    {"CLK_DIV_STAT_FSYS3",      CLK_DIV_STAT_(FSYS3_BLK),     0x00000000},
+    {"CLK_DIV_STAT_PERIL0",     CLK_DIV_STAT_(PERIL0_BLK),    0x00000000},
+    {"CLK_DIV_STAT_PERIL1",     CLK_DIV_STAT_(PERIL1_BLK),    0x00000000},
+    {"CLK_DIV_STAT_PERIL2",     CLK_DIV_STAT_(PERIL2_BLK),    0x00000000},
+    {"CLK_DIV_STAT_PERIL3",     CLK_DIV_STAT_(PERIL3_BLK),    0x00000000},
+    {"CLK_DIV_STAT_PERIL4",     CLK_DIV_STAT_(PERIL4_BLK),    0x00000000},
+    {"CLK_DIV_STAT_PERIL5",     CLK_DIV_STAT_(PERIL5_BLK),    0x00000000},
+    {"CLKDIV2_STAT",            CLKDIV2_STAT,                 0x00000000},
+    {"CLK_GATE_SCLK_CAM",       CLK_GATE_SCLK_(CAM_BLK),      0xFFFFFFFF},
+    {"CLK_GATE_IP_CAM",         CLK_GATE_IP_(CAM_BLK),        0xFFFFFFFF},
+    {"CLK_GATE_IP_TV",          CLK_GATE_IP_(TV_BLK),         0xFFFFFFFF},
+    {"CLK_GATE_IP_MFC",         CLK_GATE_IP_(MFC_BLK),        0xFFFFFFFF},
+    {"CLK_GATE_IP_G3D",         CLK_GATE_IP_(G3D_BLK),        0xFFFFFFFF},
+    {"CLK_GATE_IP_IMAGE",       CLK_GATE_IP_(IMAGE_BLK),      0xFFFFFFFF},
+    {"CLK_GATE_IP_LCD0",        CLK_GATE_IP_(LCD0_BLK),       0xFFFFFFFF},
+    {"CLK_GATE_IP_LCD1",        CLK_GATE_IP_(LCD1_BLK),       0xFFFFFFFF},
+    {"CLK_GATE_IP_FSYS",        CLK_GATE_IP_(FSYS_BLK),       0xFFFFFFFF},
+    {"CLK_GATE_IP_GPS",         CLK_GATE_IP_(GPS_BLK),        0xFFFFFFFF},
+    {"CLK_GATE_IP_PERIL",       CLK_GATE_IP_(PERIL_BLK),      0xFFFFFFFF},
+    {"CLK_GATE_IP_PERIR",       CLK_GATE_IP_(PERIR_BLK),      0xFFFFFFFF},
+    {"CLK_GATE_BLOCK",          CLK_GATE_BLOCK,               0xFFFFFFFF},
+    {"CLKOUT_CMU_TOP",          CLKOUT_CMU,                   0x00010000},
+    {"CLKOUT_CMU_TOP_DIV_STAT", CLKOUT_CMU_DIV_STAT,          0x00000000},
+};
+
+static Exynos4210CmuReg exynos4210_cmu_dmc_regs[] = {
+    /* CMU_DMC registers */
+    {"CLK_SRC_DMC",             CLK_SRC_(DMC_BLK),            0x00010000},
+    {"CLK_SRC_MASK_DMC",        CLK_SRC_MASK_(DMC_BLK),       0x00010000},
+    {"CLK_MUX_STAT_DMC",        CLK_MUX_STAT_(DMC_BLK),       0x11100110},
+    {"CLK_DIV_DMC0",            CLK_DIV_(DMC0_BLK),           0x00000000},
+    {"CLK_DIV_DMC1",            CLK_DIV_(DMC1_BLK),           0x00000000},
+    {"CLK_DIV_STAT_DMC0",       CLK_DIV_STAT_(DMC0_BLK),      0x00000000},
+    {"CLK_DIV_STAT_DMC1",       CLK_DIV_STAT_(DMC1_BLK),      0x00000000},
+    {"CLK_GATE_IP_DMC",         CLK_GATE_IP_(DMC_BLK),        0xFFFFFFFF},
+    {"CLKOUT_CMU_DMC",          CLKOUT_CMU,                   0x00010000},
+    {"CLKOUT_CMU_DMC_DIV_STAT", CLKOUT_CMU_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},
+};
+
+static Exynos4210CmuReg exynos4210_cmu_cpu_regs[] = {
+    /* CMU_CPU registers */
+    {"APLL_LOCK",               PLL_LOCK_(APLL),              0x00000FFF},
+    {"MPLL_LOCK",               PLL_LOCK_(MPLL),              0x00000FFF},
+    {"APLL_CON0",               PLL_CON0_(APLL),              0x00C80601},
+    {"APLL_CON1",               PLL_CON1_(APLL),              0x0000001C},
+    {"MPLL_CON0",               PLL_CON0_(MPLL),              0x00C80601},
+    {"MPLL_CON1",               PLL_CON1_(MPLL),              0x0000001C},
+    {"CLK_SRC_CPU",             CLK_SRC_(CPU_BLK),            0x00000000},
+    {"CLK_MUX_STAT_CPU",        CLK_MUX_STAT_(CPU_BLK),       0x00110101},
+    {"CLK_DIV_CPU0",            CLK_DIV_(CPU0_BLK),           0x00000000},
+    {"CLK_DIV_CPU1",            CLK_DIV_(CPU1_BLK),           0x00000000},
+    {"CLK_DIV_STAT_CPU0",       CLK_DIV_STAT_(CPU0_BLK),      0x00000000},
+    {"CLK_DIV_STAT_CPU1",       CLK_DIV_STAT_(CPU1_BLK),      0x00000000},
+    {"CLK_GATE_SCLK_CPU",       CLK_GATE_SCLK_(CPU_BLK),      0xFFFFFFFF},
+    {"CLK_GATE_IP_CPU",         CLK_GATE_IP_(CPU_BLK),        0xFFFFFFFF},
+    {"CLKOUT_CMU_CPU",          CLKOUT_CMU,                  0x00010000},
+    {"CLKOUT_CMU_CPU_DIV_STAT", CLKOUT_CMU_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},
+};
+
+#define EXYNOS4210_CMU_REGS_MEM_SIZE     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
+
+#define SOURCES_NUMBER   9
+
+typedef struct ClockChangeEntry {
+    QTAILQ_ENTRY(ClockChangeEntry) entry;
+    ClockChangeHandler *func;
+    void *opaque;
+} ClockChangeEntry;
+
+#define TYPE_EXYNOS4210_CMU "exynos4210.cmu"
+
+typedef struct {
+
+        const char      *name;
+        Exynos4210Clock  id;
+        uint64_t         rate;
+
+        /* Current source clock */
+        Exynos4210Clock  src_id;
+        /*
+         * Available sources. Their order must correspond to CLK_SRC_ register
+         */
+        Exynos4210Clock  src_ids[SOURCES_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;
+
+        /* Which CMU controls this clock */
+        Exynos4210Cmu cmu_id;
+
+        QTAILQ_HEAD(, ClockChangeEntry) clock_change_handler;
+
+} Exynos4210ClockState;
+
+
+typedef struct {
+
+        SysBusDevice busdev;
+        MemoryRegion iomem;
+
+        /* registers values */
+        uint32_t reg[EXYNOS4210_CMU_REGS_MEM_SIZE];
+
+        /* which CMU it is */
+        Exynos4210Cmu cmu_id;
+
+        /* registers information for debugging and resetting */
+        Exynos4210CmuReg *regs;
+        int regs_number;
+
+        Exynos4210ClockState *clock;
+        int clock_number; /* how many clocks are controlled by given CMU */
+
+} Exynos4210CmuState;
+
+
+/* Clocks from Clock Pads */
+/*
+ *  Two following clocks aren't controlled by any CMUs. These structures are
+ *  used directly from global space and their fields  shouldn't be modified.
+ */
+
+/* It should be used only for testing purposes. XOM_0 is 0 */
+static Exynos4210ClockState xxti = {
+        .name       = "XXTI",
+        .id         = EXYNOS4210_XXTI,
+        .rate       = 24000000,
+        .cmu_id     = UNSPECIFIED_CMU,
+};
+
+/* Main source. XOM_0 is 1 */
+static Exynos4210ClockState xusbxti = {
+        .name       = "XUSBXTI",
+        .id         = EXYNOS4210_XUSBXTI,
+        .rate       = 24000000,
+        .cmu_id     = UNSPECIFIED_CMU,
+};
+
+//static Exynos4210ClockState usb_phy = {
+//        .name       = "USB_PHY",
+//        .id         = EXYNOS4210_USB_PHY,
+//        .src_id     = EXYNOS4210_XUSBXTI,
+//        .cmu_id     = UNSPECIFIED_CMU,
+//};
+//
+//static Exynos4210ClockState usb_host_phy = {
+//        .name       = "USB_HOST_PHY",
+//        .id         = EXYNOS4210_USB_HOST_PHY,
+//        .src_id     = EXYNOS4210_XUSBXTI,
+//        .cmu_id     = UNSPECIFIED_CMU,
+//};
+//
+//static Exynos4210ClockState hdmi_phy = {
+//        .name       = "HDMI_PHY",
+//        .id         = EXYNOS4210_HDMI_PHY,
+//        .src_id     = EXYNOS4210_XUSBXTI,
+//        .cmu_id     = UNSPECIFIED_CMU,
+//};
+
+
+/* PLLs */
+
+static Exynos4210ClockState mpll = {
+        .name       = "MPLL",
+        .id         = EXYNOS4210_MPLL,
+        .src_id     = (XOM_0 ? EXYNOS4210_XUSBXTI : EXYNOS4210_XXTI),
+        .div_reg    = PLL_CON0_(MPLL),
+        .cmu_id     = EXYNOS4210_CMU_CPU,
+};
+
+static Exynos4210ClockState apll = {
+        .name       = "APLL",
+        .id         = EXYNOS4210_APLL,
+        .src_id     = (XOM_0 ? EXYNOS4210_XUSBXTI : EXYNOS4210_XXTI),
+        .div_reg    = PLL_CON0_(APLL),
+        .cmu_id     = EXYNOS4210_CMU_CPU,
+};
+
+
+/**/
+
+static Exynos4210ClockState sclk_hdmi24m = {
+        .name    = "SCLK_HDMI24M",
+        .id      = EXYNOS4210_SCLK_HDMI24M,
+        .rate    = 24000000,
+//        .src_id  = EXYNOS4210_HDMI_PHY,
+        .cmu_id  = UNSPECIFIED_CMU,
+};
+
+static Exynos4210ClockState sclk_usbphy0 = {
+        .name    = "SCLK_USBPHY0",
+        .id      = EXYNOS4210_SCLK_USBPHY0,
+        .rate    = 24000000,
+//        .src_id  = EXYNOS4210_USB_PHY,
+        .cmu_id  = UNSPECIFIED_CMU,
+};
+
+static Exynos4210ClockState sclk_usbphy1 = {
+        .name    = "SCLK_USBPHY1",
+        .id      = EXYNOS4210_SCLK_USBPHY1,
+        .rate    = 24000000,
+//        .src_id  = EXYNOS4210_USB_HOST_PHY,
+        .cmu_id  = UNSPECIFIED_CMU,
+};
+
+static Exynos4210ClockState sclk_hdmiphy = {
+        .name    = "SCLK_HDMIPHY",
+        .id      = EXYNOS4210_SCLK_HDMIPHY,
+        .rate    = 24000000,
+//        .src_id  = EXYNOS4210_HDMI_PHY,
+        .cmu_id  = UNSPECIFIED_CMU,
+};
+
+static Exynos4210ClockState sclk_mpll = {
+        .name       = "SCLK_MPLL",
+        .id         = EXYNOS4210_SCLK_MPLL,
+        .src_ids    = {XOM_0 ? EXYNOS4210_XUSBXTI : EXYNOS4210_XXTI,
+                       EXYNOS4210_MPLL},
+        .src_reg    = CLK_SRC_(CPU_BLK),
+        .mux_shift  = MUX_MPLL_SEL_SHIFT,
+        .cmu_id     = EXYNOS4210_CMU_CPU,
+};
+
+static Exynos4210ClockState sclk_apll = {
+        .name       = "SCLK_APLL",
+        .id         = EXYNOS4210_SCLK_APLL,
+        .src_ids    = {XOM_0 ? EXYNOS4210_XUSBXTI : EXYNOS4210_XXTI,
+                       EXYNOS4210_APLL},
+        .src_reg    = CLK_SRC_(CPU_BLK),
+        .div_reg    = CLK_DIV_(CPU0_BLK),
+        .mux_shift  = MUX_APLL_SEL_SHIFT,
+        .div_shift  = APLL_RATIO_SHIFT,
+        .cmu_id     = EXYNOS4210_CMU_CPU,
+};
+
+static Exynos4210ClockState aclk_100 = {
+        .name      = "ACLK_100",
+        .id        = EXYNOS4210_ACLK_100,
+        .src_ids   = {EXYNOS4210_SCLK_MPLL, EXYNOS4210_SCLK_APLL},
+        .src_reg   = CLK_SRC_(TOP0_BLK),
+        .div_reg   = CLK_DIV_(TOP_BLK),
+        .mux_shift = MUX_ACLK_100_SEL_SHIFT,
+        .div_shift = ACLK_100_RATIO_SHIFT,
+        .cmu_id    = EXYNOS4210_CMU_TOP,
+};
+
+static Exynos4210ClockState sclk_uart0 = {
+        .name      = "SCLK_UART0",
+        .id        = EXYNOS4210_SCLK_UART0,
+        .src_ids   = {EXYNOS4210_XXTI,
+                      EXYNOS4210_XUSBXTI,
+                      EXYNOS4210_SCLK_HDMI24M,
+                      EXYNOS4210_SCLK_USBPHY0,
+                      EXYNOS4210_SCLK_USBPHY1,
+                      EXYNOS4210_SCLK_HDMIPHY,
+                      EXYNOS4210_SCLK_MPLL},
+        .src_reg   = CLK_SRC_(PERIL0_BLK),
+        .div_reg   = CLK_DIV_(PERIL0_BLK),
+        .mux_shift = UART0_SEL_SHIFT,
+        .div_shift = UART0_DIV_SHIFT,
+        .cmu_id    = EXYNOS4210_CMU_TOP,
+};
+
+static Exynos4210ClockState sclk_uart1 = {
+        .name      = "SCLK_UART1",
+        .id        = EXYNOS4210_SCLK_UART1,
+        .src_ids   = {EXYNOS4210_XXTI,
+                      EXYNOS4210_XUSBXTI,
+                      EXYNOS4210_SCLK_HDMI24M,
+                      EXYNOS4210_SCLK_USBPHY0,
+                      EXYNOS4210_SCLK_USBPHY1,
+                      EXYNOS4210_SCLK_HDMIPHY,
+                      EXYNOS4210_SCLK_MPLL},
+        .src_reg   = CLK_SRC_(PERIL0_BLK),
+        .div_reg   = CLK_DIV_(PERIL0_BLK),
+        .mux_shift = UART1_SEL_SHIFT,
+        .div_shift = UART1_DIV_SHIFT,
+        .cmu_id    = EXYNOS4210_CMU_TOP,
+};
+
+static Exynos4210ClockState sclk_uart2 = {
+        .name      = "SCLK_UART2",
+        .id        = EXYNOS4210_SCLK_UART2,
+        .src_ids   = {EXYNOS4210_XXTI,
+                      EXYNOS4210_XUSBXTI,
+                      EXYNOS4210_SCLK_HDMI24M,
+                      EXYNOS4210_SCLK_USBPHY0,
+                      EXYNOS4210_SCLK_USBPHY1,
+                      EXYNOS4210_SCLK_HDMIPHY,
+                      EXYNOS4210_SCLK_MPLL},
+        .src_reg   = CLK_SRC_(PERIL0_BLK),
+        .div_reg   = CLK_DIV_(PERIL0_BLK),
+        .mux_shift = UART2_SEL_SHIFT,
+        .div_shift = UART2_DIV_SHIFT,
+        .cmu_id    = EXYNOS4210_CMU_TOP,
+};
+
+static Exynos4210ClockState sclk_uart3 = {
+        .name      = "SCLK_UART3",
+        .id        = EXYNOS4210_SCLK_UART3,
+        .src_ids   = {EXYNOS4210_XXTI,
+                      EXYNOS4210_XUSBXTI,
+                      EXYNOS4210_SCLK_HDMI24M,
+                      EXYNOS4210_SCLK_USBPHY0,
+                      EXYNOS4210_SCLK_USBPHY1,
+                      EXYNOS4210_SCLK_HDMIPHY,
+                      EXYNOS4210_SCLK_MPLL},
+        .src_reg   = CLK_SRC_(PERIL0_BLK),
+        .div_reg   = CLK_DIV_(PERIL0_BLK),
+        .mux_shift = UART3_SEL_SHIFT,
+        .div_shift = UART3_DIV_SHIFT,
+        .cmu_id    = EXYNOS4210_CMU_TOP,
+};
+
+static Exynos4210ClockState sclk_uart4 = {
+        .name      = "SCLK_UART4",
+        .id        = EXYNOS4210_SCLK_UART4,
+        .src_ids   = {EXYNOS4210_XXTI,
+                      EXYNOS4210_XUSBXTI,
+                      EXYNOS4210_SCLK_HDMI24M,
+                      EXYNOS4210_SCLK_USBPHY0,
+                      EXYNOS4210_SCLK_USBPHY1,
+                      EXYNOS4210_SCLK_HDMIPHY,
+                      EXYNOS4210_SCLK_MPLL},
+        .src_reg   = CLK_SRC_(PERIL0_BLK),
+        .div_reg   = CLK_DIV_(PERIL0_BLK),
+        .mux_shift = UART4_SEL_SHIFT,
+        .div_shift = UART4_DIV_SHIFT,
+        .cmu_id    = EXYNOS4210_CMU_TOP,
+};
+
+/*
+ *  This array must correspond to Exynos4210Clock enumerator
+ *  which is defined in exynos4210.h file
+ *
+ */
+static Exynos4210ClockState *exynos4210_clock[] = {
+        NULL,
+        &xxti,
+        &xusbxti,
+//        &usb_phy,
+//        &usb_host_phy,
+//        &hdmi_phy,
+        &apll,
+        &mpll,
+        &sclk_hdmi24m,
+        &sclk_usbphy0,
+        &sclk_usbphy1,
+        &sclk_hdmiphy,
+        &sclk_apll,
+        &sclk_mpll,
+        &aclk_100,
+        &sclk_uart0,
+        &sclk_uart1,
+        &sclk_uart2,
+        &sclk_uart3,
+        &sclk_uart4,
+        NULL,
+};
+
+/*
+ * This array must correspond to Exynos4210Cmu enumerator
+ * which is defined in exynos4210.h file
+ *
+ */
+static char exynos4210_cmu_path[][13] = {
+        "cmu_leftbus",
+        "cmu_rightbus",
+        "cmu_top",
+        "cmu_dmc",
+        "cmu_cpu",
+};
+
+#if DEBUG_CMU_EXTEND
+/* The only meaning of life - debugging. This function should be only used
+ * inside PRINT_DEBUG_EXTEND macros
+ */
+static const char *exynos4210_cmu_regname(Exynos4210CmuState *s,
+                                          target_phys_addr_t  offset)
+{
+    int i;
+
+    for (i = 0; i < s->regs_number; i++) {
+        if (offset == s->regs[i].offset) {
+            return s->regs[i].name;
+        }
+    }
+
+    return NULL;
+}
+#endif
+
+
+static Exynos4210ClockState *exynos4210_clock_find(Exynos4210Clock clock_id)
+{
+    int i;
+    int cmu_id;
+    Object *cmu;
+    Exynos4210CmuState *s;
+
+    cmu_id = exynos4210_clock[clock_id]->cmu_id;
+
+    if (cmu_id == UNSPECIFIED_CMU) {
+        for (i = 1; i < EXYNOS4210_CLOCKS_NUMBER; i++) {
+            if (exynos4210_clock[i]->id == clock_id) {
+
+                PRINT_DEBUG("Clock %s [%p] in CMU %d have been found\n",
+                            exynos4210_clock[i]->name,
+                            exynos4210_clock[i],
+                            cmu_id);
+
+                return  exynos4210_clock[i];
+            }
+        }
+    }
+
+    cmu = object_resolve_path(exynos4210_cmu_path[cmu_id], NULL);
+    s = OBJECT_CHECK(Exynos4210CmuState, cmu, TYPE_EXYNOS4210_CMU);
+
+    for (i = 0; i < s->clock_number; i++) {
+        if (s->clock[i].id == clock_id) {
+
+            PRINT_DEBUG("Clock %s [%p] in CMU %d have been found\n",
+                                        s->clock[i].name,
+                                        &s->clock[i],
+                                        s->clock[i].cmu_id);
+            return  &s->clock[i];
+        }
+    }
+
+    PRINT_ERROR("Clock %d not found\n", clock_id);
+
+    return NULL;
+}
+
+
+void exynos4210_register_clock_handler(ClockChangeHandler *func,
+                                       Exynos4210Clock clock_id, void *opaque)
+{
+    ClockChangeEntry *cce = g_malloc0(sizeof(ClockChangeEntry));
+    Exynos4210ClockState *clock = exynos4210_clock_find(clock_id);
+
+    if (clock == NULL) {
+        hw_error("We aren't be able to find clock %d\n", clock_id);
+    } else if (clock->cmu_id == UNSPECIFIED_CMU) {
+
+        PRINT_DEBUG("Clock %s never are changed. Handler won't be set.",
+                    exynos4210_clock[clock_id]->name);
+
+        return;
+    }
+
+    cce->func = func;
+    cce->opaque = opaque;
+
+    QTAILQ_INSERT_TAIL(&clock->clock_change_handler, cce, entry);
+
+    PRINT_DEBUG("For %s have been set handler [%p]\n", clock->name, cce->func);
+
+    return;
+}
+
+uint64_t exynos4210_cmu_get_rate(Exynos4210Clock clock_id)
+{
+    Exynos4210ClockState *clock = exynos4210_clock_find(clock_id);
+
+    if (clock == NULL) {
+        hw_error("We aren't be able to find clock %d\n", clock_id);
+    }
+
+    return clock->rate;
+}
+
+static void exynos4210_cmu_set_pll(void *opaque, Exynos4210ClockState *pll)
+{
+    Exynos4210CmuState *s = (Exynos4210CmuState *)opaque;
+    Exynos4210ClockState *source;
+    target_phys_addr_t offset = pll->div_reg;
+    ClockChangeEntry *cce;
+    uint32_t pdiv, mdiv, sdiv, enable;
+
+    source = exynos4210_clock_find(pll->src_id);
+
+    if (source == NULL) {
+        hw_error("We haven't find source clock %d (requested for %s)\n",
+                 pll->src_id, pll->name);
+    }
+
+    /*
+     * 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;
+
+    if (source) {
+        if (enable) {
+            pll->rate = mdiv * source->rate / (pdiv * (1 << (sdiv-1)));
+        } else {
+            pll->rate = 0;
+        }
+    } else {
+        hw_error("%s: Source undefined for %s\n", __FUNCTION__, pll->name);
+    }
+
+    QTAILQ_FOREACH(cce, &pll->clock_change_handler, entry) {
+        cce->func(cce->opaque);
+    }
+
+    PRINT_DEBUG("%s rate: %llu\n", pll->name, pll->rate);
+
+    s->reg[I_(offset)] |= PLL_LOCKED_MASK;
+}
+
+
+static void exynos4210_cmu_set_rate(void *opaque, Exynos4210Clock clock_id)
+{
+    Exynos4210CmuState *s = (Exynos4210CmuState *)opaque;
+    Exynos4210ClockState *clock = exynos4210_clock_find(clock_id);
+    ClockChangeEntry *cce;
+
+    if (clock == NULL) {
+        hw_error("We haven't find source clock %d ", clock_id);
+    }
+
+    if ((clock->id == EXYNOS4210_MPLL) || (clock->id == EXYNOS4210_APLL)) {
+
+        exynos4210_cmu_set_pll(s, clock);
+
+    } else if ((clock->cmu_id != UNSPECIFIED_CMU)) {
+
+        Exynos4210ClockState *source;
+
+        uint32_t src_index = I_(clock->src_reg);
+        uint32_t div_index = I_(clock->div_reg);
+
+        clock->src_id = clock->src_ids[(s->reg[src_index] >>
+                                                      clock->mux_shift) & 0xf];
+
+        source = exynos4210_clock_find(clock->src_id);
+        if (source == NULL) {
+            hw_error("We haven't find source clock %d (requested for %s)\n",
+                     clock->src_id, clock->name);
+        }
+
+        clock->rate = muldiv64(source->rate, 1,
+                                 ((((clock->div_reg ? s->reg[div_index] : 0) >>
+                                                clock->div_shift) & 0xf) + 1));
+
+        QTAILQ_FOREACH(cce, &clock->clock_change_handler, entry) {
+            cce->func(cce->opaque);
+        }
+
+        PRINT_DEBUG_EXTEND("SRC: <0x%05x> %s, SHIFT: %d\n",
+                           clock->src_reg,
+                           exynos4210_cmu_regname(s, clock->src_reg),
+                           clock->mux_shift);
+
+        PRINT_DEBUG("%s [%s:%llu]: %llu\n",
+                    clock->name,
+                    source->name,
+                    (long long unsigned int)source->rate,
+                    (long long unsigned int)clock->rate);
+    }
+}
+
+
+static uint64_t exynos4210_cmu_read(void *opaque, target_phys_addr_t offset,
+                                  unsigned size)
+{
+    Exynos4210CmuState *s = (Exynos4210CmuState *)opaque;
+
+    if (offset > (EXYNOS4210_CMU_REGS_MEM_SIZE - sizeof(uint32_t))) {
+        PRINT_ERROR("Bad offset: 0x%x\n", (int)offset);
+        return 0;
+    }
+
+    if (offset & EXTENDED_REGION_MASK) {
+        if (s->cmu_id == EXYNOS4210_CMU_DMC) {
+            switch (offset & 0xFFF) {
+            case DCGIDX_MAP0:
+            case DCGIDX_MAP1:
+            case DCGIDX_MAP2:
+            case DCGPERF_MAP0:
+            case DCGPERF_MAP1:
+            case DVCIDX_MAP:
+            case FREQ_CPU:
+            case FREQ_DPM:
+            case DVSEMCLK_EN:
+            case MAXPERF:
+                return s->reg[I_(offset)];
+            default:
+                PRINT_ERROR("Bad offset: 0x%x\n", (int)offset);
+                return 0;
+            }
+        }
+
+        if (s->cmu_id == EXYNOS4210_CMU_CPU) {
+            switch (offset & 0xFFF) {
+            case ARMCLK_STOPCTRL:
+            case ATCLK_STOPCTRL:
+            case PARITYFAIL_STATUS:
+            case PARITYFAIL_CLEAR:
+            case PWR_CTRL:
+            case APLL_CON0_L8:
+            case APLL_CON0_L7:
+            case APLL_CON0_L6:
+            case APLL_CON0_L5:
+            case APLL_CON0_L4:
+            case APLL_CON0_L3:
+            case APLL_CON0_L2:
+            case APLL_CON0_L1:
+            case IEM_CONTROL:
+            case APLL_CON1_L8:
+            case APLL_CON1_L7:
+            case APLL_CON1_L6:
+            case APLL_CON1_L5:
+            case APLL_CON1_L4:
+            case APLL_CON1_L3:
+            case APLL_CON1_L2:
+            case APLL_CON1_L1:
+            case CLKDIV_IEM_L8:
+            case CLKDIV_IEM_L7:
+            case CLKDIV_IEM_L6:
+            case CLKDIV_IEM_L5:
+            case CLKDIV_IEM_L4:
+            case CLKDIV_IEM_L3:
+            case CLKDIV_IEM_L2:
+            case CLKDIV_IEM_L1:
+                return s->reg[I_(offset)];
+            default:
+                PRINT_ERROR("Bad offset: 0x%x\n", (int)offset);
+                return 0;
+            }
+        }
+    }
+
+    switch (offset & GROUP_MASK) {
+    case PLL_LOCK:
+    case PLL_CON:
+    case CLK_SRC:
+    case CLK_SRC_MASK:
+    case CLK_MUX_STAT:
+    case CLK_DIV:
+    case CLK_DIV_STAT:
+    case 0x700: /* Reserved */
+    case CLK_GATE_SCLK: /* reserved? */
+    case CLK_GATE_IP:
+    case CLKOUT_CMU:
+        return s->reg[I_(offset)];
+    default:
+        PRINT_ERROR("Bad offset: 0x%x\n", (int)offset);
+        return 0;
+    }
+
+    PRINT_DEBUG_EXTEND("<0x%05x> %s -> %08x\n", offset,
+                       exynos4210_cmu_regname(s, offset), s->reg[I_(offset)]);
+}
+
+
+static void exynos4210_cmu_write(void *opaque, target_phys_addr_t offset,
+                               uint64_t val, unsigned size)
+{
+    Exynos4210CmuState *s = (Exynos4210CmuState *)opaque;
+    uint32_t group, block;
+
+    group = offset & GROUP_MASK;
+    block = offset & BLOCK_MASK;
+
+    switch (group) {
+    case PLL_LOCK:
+        /* it's not necessary at this moment
+         * TODO: do it
+         */
+        break;
+    case PLL_CON:
+        switch (block) {
+        case APLL:
+        {
+            uint32_t pre_val = s->reg[I_(offset)];
+            s->reg[I_(offset)] = val;
+            val = (val & ~PLL_LOCKED_MASK) | (pre_val & PLL_LOCKED_MASK);
+            s->reg[I_(offset)] = val;
+            exynos4210_cmu_set_rate(s, EXYNOS4210_APLL);
+        }
+            break;
+        case MPLL:
+        {
+            uint32_t pre_val = s->reg[I_(offset)];
+            s->reg[I_(offset)] = val;
+            val = (val & ~PLL_LOCKED_MASK) | (pre_val & PLL_LOCKED_MASK);
+            s->reg[I_(offset)] = val;
+            exynos4210_cmu_set_rate(s, EXYNOS4210_MPLL);
+        }
+            break;
+        }
+        break;
+    case CLK_SRC:
+        switch (block) {
+        case CPU_BLK:
+        {
+            uint32_t pre_val = s->reg[I_(offset)];
+            s->reg[I_(offset)] = val;
+
+            if (val & MUX_APLL_SEL) {
+                s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] =
+                       (s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] & ~(APLL_SEL_MASK)) |
+                       (2 << APLL_SEL_SHIFT);
+
+                if ((pre_val & MUX_APLL_SEL) !=
+                        (s->reg[I_(offset)] & MUX_APLL_SEL)) {
+                    exynos4210_cmu_set_rate(s, EXYNOS4210_APLL);
+                }
+
+            } else {
+                s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] =
+                       (s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] & ~(APLL_SEL_MASK)) |
+                       (1 << APLL_SEL_SHIFT);
+
+                if ((pre_val & MUX_APLL_SEL) !=
+                        (s->reg[I_(offset)] & MUX_APLL_SEL)) {
+                    exynos4210_cmu_set_rate(s, XOM_0 ? EXYNOS4210_XUSBXTI :
+                                                              EXYNOS4210_XXTI);
+                }
+            }
+
+
+            if (val & MUX_MPLL_SEL) {
+                s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] =
+                       (s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] & ~(MPLL_SEL_MASK)) |
+                       (2 << MPLL_SEL_SHIFT);
+
+                if ((pre_val & MUX_MPLL_SEL) !=
+                        (s->reg[I_(offset)] & MUX_MPLL_SEL)) {
+                    exynos4210_cmu_set_rate(s, EXYNOS4210_MPLL);
+                }
+
+            } else {
+                s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] =
+                       (s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] & ~(MPLL_SEL_MASK)) |
+                       (1 << MPLL_SEL_SHIFT);
+
+                if ((pre_val & MUX_MPLL_SEL) !=
+                        (s->reg[I_(offset)] & MUX_MPLL_SEL)) {
+                    exynos4210_cmu_set_rate(s, XOM_0 ? EXYNOS4210_XUSBXTI :
+                                                              EXYNOS4210_XXTI);
+                }
+            }
+
+            if (val & MUX_CORE_SEL) {
+                s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] =
+                       (s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] & ~(CORE_SEL_MASK)) |
+                       (2 << CORE_SEL_SHIFT);
+
+                if ((pre_val & MUX_CORE_SEL) !=
+                        (s->reg[I_(offset)] & MUX_CORE_SEL)) {
+                    exynos4210_cmu_set_rate(s, EXYNOS4210_SCLK_MPLL);
+                }
+
+            } else {
+                s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] =
+                       (s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] & ~(CORE_SEL_MASK)) |
+                        (1 << CORE_SEL_SHIFT);
+
+                if ((pre_val & MUX_CORE_SEL) !=
+                        (s->reg[I_(offset)] & MUX_CORE_SEL)) {
+                    exynos4210_cmu_set_rate(s, XOM_0 ? EXYNOS4210_XUSBXTI :
+                                                              EXYNOS4210_XXTI);
+                }
+            }
+
+            if (val & MUX_HPM_SEL) {
+                exynos4210_cmu_set_rate(s, EXYNOS4210_SCLK_MPLL);
+                s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] =
+                       (s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] & ~(HPM_SEL_MASK)) |
+                       (2 << HPM_SEL_SHIFT);
+
+                if ((pre_val & MUX_HPM_SEL) !=
+                                          (s->reg[I_(offset)] & MUX_HPM_SEL)) {
+                    exynos4210_cmu_set_rate(s, EXYNOS4210_SCLK_MPLL);
+                }
+
+            } else {
+                s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] =
+                        (s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] & ~(HPM_SEL_MASK)) |
+                        (1 << HPM_SEL_SHIFT);
+
+                if ((pre_val & MUX_HPM_SEL) !=
+                                          (s->reg[I_(offset)] & MUX_HPM_SEL)) {
+                    exynos4210_cmu_set_rate(s, XOM_0 ? EXYNOS4210_XUSBXTI :
+                                                              EXYNOS4210_XXTI);
+                }
+            }
+        }
+            break;
+        case TOP0_BLK:
+            s->reg[I_(offset)] = val;
+            exynos4210_cmu_set_rate(s, EXYNOS4210_ACLK_100);
+            break;
+        default:
+            PRINT_ERROR("Unknown functional block: 0x%x\n", (int)block);
+        }
+        break;
+    case CLK_SRC_MASK:
+        break;
+    case CLK_MUX_STAT:
+        break;
+    case CLK_DIV:
+        switch (block) {
+        case TOP_BLK:
+            s->reg[I_(offset)] = val;
+            exynos4210_cmu_set_rate(s, EXYNOS4210_ACLK_100);
+            break;
+        case CPU0_BLK:
+            s->reg[I_(offset)] = val;
+            exynos4210_cmu_set_rate(s, EXYNOS4210_SCLK_APLL);
+            exynos4210_cmu_set_rate(s, EXYNOS4210_SCLK_MPLL);
+            break;
+        }
+    case CLK_DIV_STAT: /* CLK_DIV_STAT */
+    case 0x700: /* Reserved */
+    case CLK_GATE_SCLK: /* reserved? */
+    case CLK_GATE_IP:
+    case CLKOUT_CMU:
+        break;
+    default:
+        PRINT_ERROR("Bad offset: 0x%x\n", (int)offset);
+    }
+}
+
+static const MemoryRegionOps exynos4210_cmu_ops = {
+        .read = exynos4210_cmu_read,
+        .write = exynos4210_cmu_write,
+        .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void clock_rate_changed(void *opaque)
+{
+    Exynos4210ClockState *cs = (Exynos4210ClockState *)opaque;
+    Object *cmu = object_resolve_path(exynos4210_cmu_path[cs->cmu_id], NULL);
+    Exynos4210CmuState *s = OBJECT_CHECK(Exynos4210CmuState, cmu,
+                                                          TYPE_EXYNOS4210_CMU);
+
+    PRINT_DEBUG("Clock %s was changed\n", cs->name);
+
+    exynos4210_cmu_set_rate(s, cs->id);
+
+}
+
+static void exynos4210_cmu_reset(DeviceState *dev)
+{
+    Exynos4210CmuState *s = OBJECT_CHECK(Exynos4210CmuState, OBJECT(dev),
+                                                          TYPE_EXYNOS4210_CMU);
+    int i, j;
+    uint32_t index = 0;
+
+    for (i = 0; i < s->regs_number; i++) {
+        index = (s->regs[i].offset) / sizeof(uint32_t);
+        s->reg[index] = s->regs[i].reset_value;
+    }
+
+    for (i = 0; i < s->clock_number; i++) {
+
+        for (j = 0; j < SOURCES_NUMBER; j++) {
+
+            if (s->clock[i].src_ids[j] == UNSPECIFIED_CLOCK) {
+
+                if (j == 0) {
+                    /*
+                     * we have empty '.sources[]' array
+                     */
+                    if (s->clock[i].src_id != UNSPECIFIED_CLOCK) {
+
+                        s->clock[i].src_ids[j] = s->clock[i].src_id;
+
+                    } else {
+
+                        if (s->clock[i].cmu_id != UNSPECIFIED_CMU) {
+                            /*
+                             * We haven't any defined sources for this clock.
+                             * Error during definition of appropriate clock
+                             * structure
+                             */
+                            hw_error("exynos4210_cmu_reset:"
+                                     "There aren't any sources for %s clock!\n",
+                                     s->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 */
+                }
+            } /* src_ids[j] == UNSPECIFIED_CLOCK */
+
+            Exynos4210ClockState *source =
+                                 exynos4210_clock_find(s->clock[i].src_ids[j]);
+
+            if (source == NULL) {
+                hw_error("We aren't be able to find source clock %d "
+                         "(requested for %s)\n",
+                         s->clock[i].src_ids[j], s->clock[i].name);
+            }
+
+            if (source->cmu_id != UNSPECIFIED_CMU) {
+
+                exynos4210_register_clock_handler(clock_rate_changed,
+                                         s->clock[i].src_ids[j], &s->clock[i]);
+            }
+        } /* SOURCES_NUMBER */
+
+        exynos4210_cmu_set_rate(s, s->clock[i].id);
+    }
+
+    PRINT_DEBUG("CMU %d reset completed\n", s->cmu_id);
+}
+
+
+static const VMStateDescription vmstate_exynos4210_cmu = {
+        .name = TYPE_EXYNOS4210_CMU,
+        .version_id = 1,
+        .minimum_version_id = 1,
+        .minimum_version_id_old = 1,
+        .fields = (VMStateField[]) {
+        /*
+         * TODO: Maybe we should save Exynos4210ClockState structs as well
+         */
+            VMSTATE_UINT32_ARRAY(reg, Exynos4210CmuState,
+                                 EXYNOS4210_CMU_REGS_MEM_SIZE),
+            VMSTATE_END_OF_LIST()
+        }
+};
+
+DeviceState *exynos4210_cmu_create(target_phys_addr_t addr,
+                                                          Exynos4210Cmu cmu_id)
+{
+    DeviceState  *dev;
+    SysBusDevice *bus;
+
+    dev = qdev_create(NULL, TYPE_EXYNOS4210_CMU);
+
+    qdev_prop_set_int32(dev, "cmu_id", cmu_id);
+
+    bus = sysbus_from_qdev(dev);
+    qdev_init_nofail(dev);
+    if (addr != (target_phys_addr_t)-1) {
+        sysbus_mmio_map(bus, 0, addr);
+    }
+
+    return dev;
+}
+
+static int exynos4210_cmu_init(SysBusDevice *dev)
+{
+    Exynos4210CmuState *s = FROM_SYSBUS(Exynos4210CmuState, dev);
+    int i, n;
+
+    memory_region_init_io(&s->iomem, &exynos4210_cmu_ops, s,
+                          TYPE_EXYNOS4210_CMU, EXYNOS4210_CMU_REGS_MEM_SIZE);
+    sysbus_init_mmio(dev, &s->iomem);
+
+    switch (s->cmu_id) {
+    case EXYNOS4210_CMU_LEFTBUS:
+        s->regs = exynos4210_cmu_leftbus_regs;
+        s->regs_number = ARRAY_SIZE(exynos4210_cmu_leftbus_regs);
+        break;
+    case EXYNOS4210_CMU_RIGHTBUS:
+        s->regs = exynos4210_cmu_rightbus_regs;
+        s->regs_number = ARRAY_SIZE(exynos4210_cmu_rightbus_regs);
+        break;
+    case EXYNOS4210_CMU_TOP:
+        s->regs = exynos4210_cmu_top_regs;
+        s->regs_number = ARRAY_SIZE(exynos4210_cmu_top_regs);
+        break;
+    case EXYNOS4210_CMU_DMC:
+        s->regs = exynos4210_cmu_dmc_regs;
+        s->regs_number = ARRAY_SIZE(exynos4210_cmu_dmc_regs);
+        break;
+    case EXYNOS4210_CMU_CPU:
+        s->regs = exynos4210_cmu_cpu_regs;
+        s->regs_number = ARRAY_SIZE(exynos4210_cmu_cpu_regs);
+        break;
+    default:
+        hw_error("Wrong CMU: %d\n", s->cmu_id);
+    }
+
+    for (i = 1, n = 0; i < EXYNOS4210_CLOCKS_NUMBER; i++) {
+        if (s->cmu_id == exynos4210_clock[i]->cmu_id) {
+            n++;
+        }
+    }
+
+    s->clock =
+            (Exynos4210ClockState *)g_malloc0(n * sizeof(Exynos4210ClockState));
+
+    for (i = 1, s->clock_number = 0; i < EXYNOS4210_CLOCKS_NUMBER; i++) {
+
+        if (s->cmu_id == exynos4210_clock[i]->cmu_id) {
+
+            memcpy(&s->clock[s->clock_number], exynos4210_clock[i],
+                   sizeof(Exynos4210ClockState));
+
+            QTAILQ_INIT(&s->clock[s->clock_number].clock_change_handler);
+
+            PRINT_DEBUG("Clock %s was added to \"%s\"\n",
+                        s->clock[s->clock_number].name,
+                        exynos4210_cmu_path[s->cmu_id]);
+
+            s->clock_number++;
+
+        }
+    }
+
+    object_property_add_child(object_get_root(), exynos4210_cmu_path[s->cmu_id],
+                              OBJECT(dev), NULL);
+
+    return 0;
+}
+
+static Property exynos4210_cmu_properties[] = {
+    DEFINE_PROP_INT32("cmu_id", Exynos4210CmuState, cmu_id, UNSPECIFIED_CMU),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void exynos4210_cmu_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+    k->init   = exynos4210_cmu_init;
+    dc->reset = exynos4210_cmu_reset;
+    dc->props = exynos4210_cmu_properties;
+    dc->vmsd  = &vmstate_exynos4210_cmu;
+}
+
+static TypeInfo exynos4210_cmu_info = {
+    .name          = TYPE_EXYNOS4210_CMU,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(Exynos4210CmuState),
+    .class_init    = exynos4210_cmu_class_init,
+};
+
+static void exynos4210_cmu_register_types(void)
+{
+    type_register_static(&exynos4210_cmu_info);
+}
+
+type_init(exynos4210_cmu_register_types)
diff --git a/hw/exynos4210_g3d.c b/hw/exynos4210_g3d.c
new file mode 100644 (file)
index 0000000..b66860e
--- /dev/null
@@ -0,0 +1,833 @@
+/*
+ * Samsung exynos4210 MALI400 gpu (G3D) emulation
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu-common.h"
+#include "sysbus.h"
+
+/* Debug messages configuration */
+#define EXY_G3D_DEBUG               0
+
+#if EXY_G3D_DEBUG == 0
+    #define DPRINT_L1(fmt, args...)       do { } while (0)
+    #define DPRINT_L2(fmt, args...)       do { } while (0)
+    #define DPRINT_ERROR(fmt, args...)    do { } while (0)
+#elif EXY_G3D_DEBUG == 1
+    #define DPRINT_L1(fmt, args...) \
+    do {fprintf(stderr, "QEMU G3D: "fmt, ## args); } while (0)
+    #define DPRINT_L2(fmt, args...)       do { } while (0)
+    #define DPRINT_ERROR(fmt, args...)  \
+    do {fprintf(stderr, "QEMU G3D ERROR: "fmt, ## args); } while (0)
+#else
+    #define DPRINT_L1(fmt, args...) \
+    do {fprintf(stderr, "QEMU G3D: "fmt, ## args); } while (0)
+    #define DPRINT_L2(fmt, args...) \
+    do {fprintf(stderr, "QEMU G3D: "fmt, ## args); } while (0)
+    #define DPRINT_ERROR(fmt, args...)  \
+    do {fprintf(stderr, "QEMU G3D ERROR: "fmt, ## args); } while (0)
+#endif
+
+#define NUM_OF_PIXPROC                                  4
+#define EXYNOS4210_G3D_REG_MEM_SIZE                     0x10000
+#define MALIGP_REGS_SIZE                                0x98
+#define MALIPP_REGS_SIZE                                0x10f0
+#define MALIMMU_REGS_SIZE                               0x24
+#define MALI_L2CACHE_REGS_SIZE                          0x30
+
+#define GP_OFF_START                                    0x0000
+#define GP_OFF_END                       (GP_OFF_START + MALIGP_REGS_SIZE)
+#define L2_OFF_START                                    0x1000
+#define L2_OFF_END                       (L2_OFF_START + MALI_L2CACHE_REGS_SIZE)
+#define PMU_OFF_START                                   0x2000
+#define PMU_OFF_END                                     0x2FFC
+#define GP_MMU_OFF_START                                0x3000
+#define GP_MMU_OFF_END                   (GP_MMU_OFF_START + MALIMMU_REGS_SIZE)
+#define PP0_MMU_OFF_START                               0x4000
+#define PP0_MMU_OFF_END                  (PP0_MMU_OFF_START + MALIMMU_REGS_SIZE)
+#define PP1_MMU_OFF_START                               0x5000
+#define PP1_MMU_OFF_END                  (PP1_MMU_OFF_START + MALIMMU_REGS_SIZE)
+#define PP2_MMU_OFF_START                               0x6000
+#define PP2_MMU_OFF_END                  (PP2_MMU_OFF_START + MALIMMU_REGS_SIZE)
+#define PP3_MMU_OFF_START                               0x7000
+#define PP3_MMU_OFF_END                  (PP3_MMU_OFF_START + MALIMMU_REGS_SIZE)
+#define PP0_OFF_START                                   0x8000
+#define PP0_OFF_END                      (PP0_OFF_START + MALIPP_REGS_SIZE)
+#define PP1_OFF_START                                   0xA000
+#define PP1_OFF_END                      (PP1_OFF_START + MALIPP_REGS_SIZE)
+#define PP2_OFF_START                                   0xC000
+#define PP2_OFF_END                      (PP2_OFF_START + MALIPP_REGS_SIZE)
+#define PP3_OFF_START                                   0xE000
+#define PP3_OFF_END                      (PP3_OFF_START + MALIPP_REGS_SIZE)
+
+/**************************************************
+ * MALI geometry processor register defines
+ **************************************************/
+
+#define MALIGP_REG_OFF_VSCL_START_ADDR                  0x00
+#define MALIGP_REG_OFF_VSCL_END_ADDR                    0x04
+#define MALIGP_REG_OFF_PLBUCL_START_ADDR                0x08
+#define MALIGP_REG_OFF_PLBUCL_END_ADDR                  0x0c
+#define MALIGP_REG_OFF_PLBU_ALLOC_START_ADDR            0x10
+#define MALIGP_REG_OFF_PLBU_ALLOC_END_ADDR              0x14
+
+/* Command to geometry processor */
+#define MALIGP_REG_OFF_CMD                              0x20
+#define MALIGP_REG_VAL_CMD_START_VS                     (1 << 0)
+#define MALIGP_REG_VAL_CMD_START_PLBU                   (1 << 1)
+#define MALIGP_REG_VAL_CMD_UPDATE_PLBU_ALLOC            (1 << 4)
+#define MALIGP_REG_VAL_CMD_RESET                        (1 << 5)
+#define MALIGP_REG_VAL_CMD_FORCE_HANG                   (1 << 6)
+#define MALIGP_REG_VAL_CMD_STOP_BUS                     (1 << 9)
+#define MALIGP_REG_VAL_CMD_SOFT_RESET                   (1 << 10)
+
+/* Raw interrupt ststus - can not be masked by INT_MASK */
+#define MALIGP_REG_OFF_INT_RAWSTAT                      0x24
+/* WO register - write 1 to clear irq flag in INT_RAWSTAT */
+#define MALIGP_REG_OFF_INT_CLEAR                        0x28
+/* MASK interrupt requests in IRQ_STATUS */
+#define MALIGP_REG_OFF_INT_MASK                         0x2C
+/* Bits are set only if not masked and if bit in RAWSTAT is set */
+#define MALIGP_REG_OFF_INT_STAT                         0x30
+/* Interrupt statuses of geometry processor */
+#define MALIGP_REG_VAL_IRQ_VS_END_CMD_LST               (1 << 0)
+#define MALIGP_REG_VAL_IRQ_PLBU_END_CMD_LST             (1 << 1)
+#define MALIGP_REG_VAL_IRQ_PLBU_OUT_OF_MEM              (1 << 2)
+#define MALIGP_REG_VAL_IRQ_VS_SEM_IRQ                   (1 << 3)
+#define MALIGP_REG_VAL_IRQ_PLBU_SEM_IRQ                 (1 << 4)
+#define MALIGP_REG_VAL_IRQ_HANG                         (1 << 5)
+#define MALIGP_REG_VAL_IRQ_FORCE_HANG                   (1 << 6)
+#define MALIGP_REG_VAL_IRQ_PERF_CNT_0_LIMIT             (1 << 7)
+#define MALIGP_REG_VAL_IRQ_PERF_CNT_1_LIMIT             (1 << 8)
+#define MALIGP_REG_VAL_IRQ_WRITE_BOUND_ERR              (1 << 9)
+#define MALIGP_REG_VAL_IRQ_SYNC_ERROR                   (1 << 10)
+#define MALIGP_REG_VAL_IRQ_AXI_BUS_ERROR                (1 << 11)
+#define MALIGP_REG_VAL_IRQ_AXI_BUS_STOPPED              (1 << 12)
+#define MALIGP_REG_VAL_IRQ_VS_INVALID_CMD               (1 << 13)
+#define MALIGP_REG_VAL_IRQ_PLB_INVALID_CMD              (1 << 14)
+#define MALIGP_REG_VAL_IRQ_RESET_COMPLETED              (1 << 19)
+#define MALIGP_REG_VAL_IRQ_SEMAPHORE_UNDERFLOW          (1 << 20)
+#define MALIGP_REG_VAL_IRQ_SEMAPHORE_OVERFLOW           (1 << 21)
+#define MALIGP_REG_VAL_IRQ_PTR_ARRAY_OUT_OF_BOUNDS      (1 << 22)
+#define MALIGP_REG_VAL_IRQ_MASK_ALL \
+       (\
+               MALIGP_REG_VAL_IRQ_VS_END_CMD_LST      | \
+               MALIGP_REG_VAL_IRQ_PLBU_END_CMD_LST    | \
+               MALIGP_REG_VAL_IRQ_PLBU_OUT_OF_MEM     | \
+               MALIGP_REG_VAL_IRQ_VS_SEM_IRQ          | \
+               MALIGP_REG_VAL_IRQ_PLBU_SEM_IRQ        | \
+               MALIGP_REG_VAL_IRQ_HANG                | \
+               MALIGP_REG_VAL_IRQ_FORCE_HANG          | \
+               MALIGP_REG_VAL_IRQ_PERF_CNT_0_LIMIT    | \
+               MALIGP_REG_VAL_IRQ_PERF_CNT_1_LIMIT    | \
+               MALIGP_REG_VAL_IRQ_WRITE_BOUND_ERR     | \
+               MALIGP_REG_VAL_IRQ_SYNC_ERROR          | \
+               MALIGP_REG_VAL_IRQ_AXI_BUS_ERROR       | \
+               MALIGP_REG_VAL_IRQ_AXI_BUS_STOPPED     | \
+               MALIGP_REG_VAL_IRQ_VS_INVALID_CMD      | \
+               MALIGP_REG_VAL_IRQ_PLB_INVALID_CMD     | \
+               MALIGP_REG_VAL_IRQ_RESET_COMPLETED     | \
+               MALIGP_REG_VAL_IRQ_SEMAPHORE_UNDERFLOW | \
+               MALIGP_REG_VAL_IRQ_SEMAPHORE_OVERFLOW  | \
+               MALIGP_REG_VAL_IRQ_PTR_ARRAY_OUT_OF_BOUNDS)
+
+
+#define MALIGP_REG_OFF_WRITE_BOUND_LOW                  0x34
+#define MALIGP_REG_OFF_PERF_CNT_0_ENABLE                0x3C
+#define MALIGP_REG_OFF_PERF_CNT_1_ENABLE                0x40
+#define MALIGP_REG_OFF_PERF_CNT_0_SRC                   0x44
+#define MALIGP_REG_OFF_PERF_CNT_1_SRC                   0x48
+#define MALIGP_REG_OFF_PERF_CNT_0_VALUE                 0x4C
+#define MALIGP_REG_OFF_PERF_CNT_1_VALUE                 0x50
+#define MALIGP_REG_OFF_STATUS                           0x68
+
+/* Geometry processor version register */
+#define MALIGP_REG_OFF_VERSION                          0x6C
+#define MALI400_GP_PRODUCT_ID                           0xB07
+#define MALIGP_VERSION_DEFAULT                   (MALI400_GP_PRODUCT_ID << 16)
+
+#define MALIGP_REG_OFF_VSCL_START_ADDR_READ             0x80
+#define MALIGP_REG_OFF_PLBCL_START_ADDR_READ            0x84
+#define MALIGP_CONTR_AXI_BUS_ERROR_STAT                 0x94
+#define MALIGP_REGISTER_ADDRESS_SPACE_SIZE              0x98
+
+
+/**************************************************
+ * MALI pixel processor registers defines
+ **************************************************/
+
+/* MALI Pixel Processor version register */
+#define MALIPP_REG_OFF_VERSION                          0x1000
+#define MALI400_PP_PRODUCT_ID                           0xCD07
+#define MALIPP_MGMT_VERSION_DEFAULT              (MALI400_PP_PRODUCT_ID << 16)
+
+#define MALIPP_REG_OFF_CURRENT_REND_LIST_ADDR           0x1004
+#define MALIPP_REG_OFF_STATUS                           0x1008
+
+/* Control and commands register */
+#define MALIPP_REG_OFF_CTRL_MGMT                        0x100c
+#define MALIPP_REG_VAL_CTRL_MGMT_STOP_BUS               (1 << 0)
+#define MALIPP_REG_VAL_CTRL_MGMT_FORCE_RESET            (1 << 5)
+#define MALIPP_REG_VAL_CTRL_MGMT_START_RENDERING        (1 << 6)
+#define MALIPP_REG_VAL_CTRL_MGMT_SOFT_RESET             (1 << 7)
+
+/* Interrupt control registers */
+#define MALIPP_REG_OFF_INT_RAWSTAT                      0x1020
+#define MALIPP_REG_OFF_INT_CLEAR                        0x1024
+#define MALIPP_REG_OFF_INT_MASK                         0x1028
+#define MALIPP_REG_OFF_INT_STATUS                       0x102c
+/* Interrupt statuses */
+#define MALIPP_REG_VAL_IRQ_END_OF_FRAME                 (1 << 0)
+#define MALIPP_REG_VAL_IRQ_END_OF_TILE                  (1 << 1)
+#define MALIPP_REG_VAL_IRQ_HANG                         (1 << 2)
+#define MALIPP_REG_VAL_IRQ_FORCE_HANG                   (1 << 3)
+#define MALIPP_REG_VAL_IRQ_BUS_ERROR                    (1 << 4)
+#define MALIPP_REG_VAL_IRQ_BUS_STOP                     (1 << 5)
+#define MALIPP_REG_VAL_IRQ_CNT_0_LIMIT                  (1 << 6)
+#define MALIPP_REG_VAL_IRQ_CNT_1_LIMIT                  (1 << 7)
+#define MALIPP_REG_VAL_IRQ_WRITE_BOUNDARY_ERROR         (1 << 8)
+#define MALIPP_REG_VAL_IRQ_INVALID_PLIST_COMMAND        (1 << 9)
+#define MALIPP_REG_VAL_IRQ_CALL_STACK_UNDERFLOW         (1 << 10)
+#define MALIPP_REG_VAL_IRQ_CALL_STACK_OVERFLOW          (1 << 11)
+#define MALIPP_REG_VAL_IRQ_RESET_COMPLETED              (1 << 12)
+
+
+#define MALIPP_REG_OFF_WRITE_BOUNDARY_LOW               0x1044
+#define MALIPP_REG_OFF_BUS_ERROR_STATUS                 0x1050
+#define MALIPP_REG_OFF_PERF_CNT_0_ENABLE                0x1080
+#define MALIPP_REG_OFF_PERF_CNT_0_SRC                   0x1084
+#define MALIPP_REG_OFF_PERF_CNT_0_VALUE                 0x108c
+#define MALIPP_REG_OFF_PERF_CNT_1_ENABLE                0x10a0
+#define MALIPP_REG_OFF_PERF_CNT_1_SRC                   0x10a4
+#define MALIPP_REG_OFF_PERF_CNT_1_VALUE                 0x10ac
+#define MALIPP_REG_SIZEOF_REGISTER_BANK                 0x10f0
+
+/**************************************************
+ * MALI MMU register defines
+ **************************************************/
+
+/* Current Page Directory Pointer */
+#define MALI_MMU_REG_DTE_ADDR                           0x0000
+
+/* Status of the MMU */
+#define MALI_MMU_REG_STATUS                             0x0004
+/* MALI MMU ststus bits */
+#define MALI_MMU_STATUS_PAGING_ENABLED                 (1 << 0)
+#define MALI_MMU_STATUS_PAGE_FAULT_ACTIVE              (1 << 1)
+#define MALI_MMU_STATUS_STALL_ACTIVE                   (1 << 2)
+#define MALI_MMU_STATUS_IDLE                           (1 << 3)
+#define MALI_MMU_STATUS_REPLAY_BUFFER_EMPTY            (1 << 4)
+#define MALI_MMU_STATUS_PAGE_FAULT_IS_WRITE            (1 << 5)
+
+/* Command register, used to control the MMU */
+#define MALI_MMU_REG_COMMAND                            0x0008
+/* MALI MMU commands */
+/* Enable paging (memory translation) */
+#define MALI_MMU_COMMAND_ENABLE_PAGING                  0x00
+/* Disable paging (memory translation) */
+#define MALI_MMU_COMMAND_DISABLE_PAGING                 0x01
+/*  Enable stall on page fault */
+#define MALI_MMU_COMMAND_ENABLE_STALL                   0x02
+/* Disable stall on page fault */
+#define MALI_MMU_COMMAND_DISABLE_STALL                  0x03
+/* Zap the entire page table cache */
+#define MALI_MMU_COMMAND_ZAP_CACHE                      0x04
+/* Page fault processed */
+#define MALI_MMU_COMMAND_PAGE_FAULT_DONE                0x05
+/* Reset the MMU back to power-on settings */
+#define MALI_MMU_COMMAND_SOFT_RESET                     0x06
+
+/* Logical address of the last page fault */
+#define MALI_MMU_REG_PAGE_FAULT_ADDR                    0x000C
+
+/* Used to invalidate the mapping of a single page from the MMU */
+#define MALI_MMU_REG_ZAP_ONE_LINE                       0x0010
+
+/* Raw interrupt status, all interrupts visible */
+#define MALI_MMU_REG_INT_RAWSTAT                        0x0014
+/* Indicate to the MMU that the interrupt has been received */
+#define MALI_MMU_REG_INT_CLEAR                          0x0018
+/* Enable/disable types of interrupts */
+#define MALI_MMU_REG_INT_MASK                           0x001C
+/* Interrupt status based on the mask */
+#define MALI_MMU_REG_INT_STATUS                         0x0020
+/* MALI MMU interrupt registers bits */
+/* A page fault occured */
+#define MALI_MMU_INT_PAGE_FAULT                         0x01
+/* A bus read error occured */
+#define MALI_MMU_INT_READ_BUS_ERROR                     0x02
+
+
+/**************************************************
+ * MALI L2 cache register defines
+ **************************************************/
+
+/* MALI L2 cache status bits */
+#define MALI_L2CACHE_REG_STATUS                         0x0008
+/* Command handler of L2 cache is busy */
+#define MALI_L2CACHE_STATUS_COMMAND_BUSY                0x01
+/* L2 cache is busy handling data requests */
+#define MALI_L2CACHE_STATUS_DATA_BUSY                   0x02
+
+/* Misc cache commands, e.g. clear */
+#define MALI_L2CACHE_REG_COMMAND                        0x0010
+/* Clear the entire cache */
+#define MALI_L2CACHE_CMD_CLEAR_ALL                      0x01
+
+#define MALI_L2CACHE_REG_CLEAR_PAGE                     0x0014
+
+/* Enable misc cache features */
+#define MALI_L2CACHE_REG_ENABLE                         0x001C
+/* Default state of enable register */
+#define MALI_L2CACHE_ENABLE_DEFAULT                     0x0
+/* Permit cacheable accesses */
+#define MALI_L2CACHE_ENABLE_ACCESS                      0x01
+/* Permit cache read allocate */
+#define MALI_L2CACHE_ENABLE_READ_ALLOCATE               0x02
+
+#define MALI_L2CACHE_REG_PERFCNT_SRC0                   0x0020
+#define MALI_L2CACHE_REG_PERFCNT_VAL0                   0x0024
+#define MALI_L2CACHE_REG_PERFCNT_SRC1                   0x0028
+#define MALI_L2CACHE_REG_PERFCNT_VAL1                   0x002C
+
+typedef struct GeometryProc {
+    uint32_t vscl_start;
+    uint32_t vscl_end;
+    uint32_t plbucl_start;
+    uint32_t plbucl_end;
+    uint32_t plbu_alloc_start;
+    uint32_t plbu_alloc_end;
+    uint32_t cmd;
+    uint32_t int_rawstat;
+/*    uint32_t int_clear; Write only register */
+    uint32_t int_mask;
+    uint32_t int_stat;
+    uint32_t write_bound;
+    uint32_t perfcnt0_en;
+    uint32_t perfcnt1_en;
+    uint32_t perfcnt0_src;
+    uint32_t perfcnt1_src;
+    uint32_t perfcnt0_value;
+    uint32_t perfcnt1_value;
+    uint32_t status;
+/*    uint32_t version; RO register */
+    uint32_t vscl_start_read;
+    uint32_t plbcl_start_read;
+    uint32_t axi_error;
+    uint32_t addr_space_size;
+
+    qemu_irq irq_gp;              /* geometry processor interrupt */
+} GeometryProc;
+
+typedef struct PixelProc {
+/*    uint32_t version; RO register */
+    uint32_t cur_rend_list;
+    uint32_t status;
+    uint32_t ctrl_mgmt;
+    uint32_t int_rawstat;
+/*    uint32_t int_clear; Write only register */
+    uint32_t int_mask;
+    uint32_t int_stat;
+    uint32_t write_bound;
+    uint32_t bus_error;
+    uint32_t perfcnt0_en;
+    uint32_t perfcnt0_src;
+    uint32_t perfcnt0_value;
+    uint32_t perfcnt1_en;
+    uint32_t perfcnt1_src;
+    uint32_t perfcnt1_value;
+    uint32_t sizeof_regs;
+
+    qemu_irq irq_pp;             /* pixel processor interrupt */
+} PixelProc;
+
+typedef struct MaliMMU {
+    uint32_t dte_addr;
+    uint32_t status;
+    uint32_t command;
+    uint32_t page_fault_addr;
+    uint32_t zap_one_line;
+    uint32_t int_rawstat;
+    uint32_t int_mask;
+    uint32_t int_status;
+
+    qemu_irq irq_mmu;             /* MALI MMU interrupt */
+} MaliMMU;
+
+typedef struct MaliL2Cache {
+    uint32_t status;
+    uint32_t command;
+    uint32_t clear_page;
+    uint32_t enable;
+    uint32_t perfcnt_src0;
+    uint32_t perfcnt_val0;
+    uint32_t perfcnt_src1;
+    uint32_t perfcnt_val1;
+} MaliL2Cache;
+
+typedef struct Exynos4210G3DState {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+
+    GeometryProc gp;              /* Geometry processor */
+    PixelProc pp[NUM_OF_PIXPROC];
+    MaliMMU gp_mmu;
+    MaliMMU pp_mmu[NUM_OF_PIXPROC];
+    MaliL2Cache l2_cache;
+    qemu_irq irq_pmu;             /* power unit interrupt */
+} Exynos4210G3DState;
+
+/* TODO maybe reset functions must do something else besides clearing reg-s */
+static inline void  exynos4210_maligp_reset(GeometryProc *gp)
+{
+    memset(gp, 0, offsetof(GeometryProc, irq_gp));
+}
+
+static inline void  exynos4210_malipp_reset(PixelProc *pp)
+{
+    memset(pp, 0, offsetof(PixelProc, irq_pp));
+}
+
+static inline void  exynos4210_malimmu_reset(MaliMMU *mmu)
+{
+    memset(mmu, 0, offsetof(MaliMMU, irq_mmu));
+}
+
+static inline void  exynos4210_mali_l2cache_reset(MaliL2Cache *l2cach)
+{
+    memset(l2cach, 0, sizeof(MaliL2Cache));
+}
+
+static void exynos4210_g3d_reset(DeviceState *d)
+{
+       Exynos4210G3DState *s = DO_UPCAST(Exynos4210G3DState, busdev.qdev, d);
+       int i;
+
+    exynos4210_maligp_reset(&s->gp);
+    exynos4210_malimmu_reset(&s->gp_mmu);
+    exynos4210_mali_l2cache_reset(&s->l2_cache);
+    for (i = 0; i < NUM_OF_PIXPROC; i++) {
+        exynos4210_malipp_reset(&s->pp[i]);
+        exynos4210_malimmu_reset(&s->pp_mmu[i]);
+    }
+}
+
+/* Geometry processor register map read */
+static inline uint32_t exynos4210_maligp_read(GeometryProc *gp,
+                                          target_phys_addr_t offset)
+{
+       DPRINT_L2("MALI GEOMETRY PROCESSOR: read offset 0x"
+                       TARGET_FMT_plx "\n", offset);
+
+          switch (offset) {
+           /* Geometry processor */
+           case MALIGP_REG_OFF_CMD:
+               return gp->cmd;
+           case MALIGP_REG_OFF_INT_RAWSTAT:
+               return gp->int_rawstat;
+           case MALIGP_REG_OFF_INT_MASK:
+               return gp->int_mask;
+           case MALIGP_REG_OFF_INT_STAT:
+               return gp->int_stat;
+           case MALIGP_REG_OFF_VERSION:
+               return MALIGP_VERSION_DEFAULT;
+           case MALIGP_REG_OFF_INT_CLEAR:
+               DPRINT_L1("MALI GEOM PROC: read from WO register! Offset 0x"
+                               TARGET_FMT_plx "\n", offset);
+               return 0;
+           default:
+               DPRINT_L1("MALI GEOM PROC: read from non-existing register! "
+                               "Offset 0x" TARGET_FMT_plx "\n", offset);
+               return 0;
+          }
+}
+
+/* Geometry processor register map write */
+static uint32_t exynos4210_malipp_read(PixelProc *pp, target_phys_addr_t offset)
+{
+       DPRINT_L2("read offset 0x" TARGET_FMT_plx "\n", offset);
+
+       switch (offset) {
+    case MALIPP_REG_OFF_VERSION:
+       return MALIPP_MGMT_VERSION_DEFAULT;
+    case MALIPP_REG_OFF_CTRL_MGMT:
+       return pp->ctrl_mgmt;
+    case MALIPP_REG_OFF_INT_RAWSTAT:
+       return pp->int_rawstat;
+    case MALIPP_REG_OFF_INT_MASK:
+       return pp->int_mask;
+    case MALIPP_REG_OFF_INT_STATUS:
+       return pp->int_stat;
+    case MALIPP_REG_OFF_INT_CLEAR:
+       DPRINT_L1("MALI PIX PROC: read from WO register! Offset 0x"
+                       TARGET_FMT_plx "\n", offset);
+       return 0;
+    default:
+       DPRINT_L1("MALI PIX PROC: read from non-existing register! Offset 0x"
+                       TARGET_FMT_plx "\n", offset);
+       return 0;
+    }
+}
+
+/* MALI MMU register map read */
+static uint32_t exynos4210_malimmu_read(MaliMMU *mmu, target_phys_addr_t offset)
+{
+       DPRINT_L2("read offset 0x" TARGET_FMT_plx "\n", offset);
+
+    switch (offset) {
+    case MALI_MMU_REG_DTE_ADDR:
+       return mmu->dte_addr;
+    case MALI_MMU_REG_COMMAND:
+        return mmu->command;
+    case MALI_MMU_REG_INT_RAWSTAT:
+        return mmu->int_rawstat;
+    case MALI_MMU_REG_INT_MASK:
+        return mmu->int_mask;
+    case MALI_MMU_REG_INT_STATUS:
+        return mmu->int_status;
+    case MALI_MMU_REG_INT_CLEAR:
+       DPRINT_L1("MALI MMU: read from WO register! Offset 0x"
+                       TARGET_FMT_plx "\n", offset);
+        return 0;
+    default:
+       DPRINT_L1("MALI MMU: read from non-existing register! Offset 0x"
+                       TARGET_FMT_plx "\n", offset);
+       return 0;
+    }
+}
+
+/* MALI L2 cache register map read */
+static uint32_t exynos4210_mali_l2cache_read(MaliL2Cache *cache,
+                                                     target_phys_addr_t offset)
+{
+       DPRINT_L2("MALI L2 CACHE: read offset 0x"
+                                               TARGET_FMT_plx "\n", offset);
+
+    switch (offset) {
+    case MALI_L2CACHE_REG_STATUS:
+        return cache->status;
+    case MALI_L2CACHE_REG_COMMAND:
+        return cache->command;
+    case MALI_L2CACHE_REG_ENABLE:
+        return cache->enable;
+    default:
+       return 0;
+    }
+}
+
+static uint64_t exynos4210_g3d_read(void *opaque, target_phys_addr_t offset,
+        unsigned size)
+{
+       Exynos4210G3DState *s = (Exynos4210G3DState *)opaque;
+
+    switch (offset) {
+    case GP_OFF_START ... GP_OFF_END:
+        return exynos4210_maligp_read(&s->gp, offset - GP_OFF_START);
+    case PP0_OFF_START ... PP0_OFF_END:
+           DPRINT_L2("MALI PIXEL PROCESSOR#0: ");
+        return exynos4210_malipp_read(&s->pp[0], offset - PP0_OFF_START);
+    case PP1_OFF_START ... PP1_OFF_END:
+        DPRINT_L2("MALI PIXEL PROCESSOR#1: ");
+        return exynos4210_malipp_read(&s->pp[1], offset - PP1_OFF_START);
+    case PP2_OFF_START ... PP2_OFF_END:
+        DPRINT_L2("MALI PIXEL PROCESSOR#2: ");
+        return exynos4210_malipp_read(&s->pp[2], offset - PP2_OFF_START);
+    case PP3_OFF_START ... PP3_OFF_END:
+        DPRINT_L2("MALI PIXEL PROCESSOR#3: ");
+        return exynos4210_malipp_read(&s->pp[3], offset - PP3_OFF_START);
+    case GP_MMU_OFF_START ... GP_MMU_OFF_END:
+        DPRINT_L2("MALI GEOM PROC MMU: ");
+        return exynos4210_malimmu_read(&s->gp_mmu, offset - GP_MMU_OFF_START);
+    case PP0_MMU_OFF_START ... PP0_MMU_OFF_END:
+           DPRINT_L2("MALI PIX PROC#0 MMU: ");
+        return exynos4210_malimmu_read(&s->pp_mmu[0],
+                offset - PP0_MMU_OFF_START);
+    case PP1_MMU_OFF_START ... PP1_MMU_OFF_END:
+        DPRINT_L2("MALI PIX PROC#1 MMU: ");
+        return exynos4210_malimmu_read(&s->pp_mmu[1],
+                offset - PP1_MMU_OFF_START);
+    case PP2_MMU_OFF_START ... PP2_MMU_OFF_END:
+        DPRINT_L2("MALI PIX PROC#2 MMU: ");
+        return exynos4210_malimmu_read(&s->pp_mmu[2],
+                offset - PP2_MMU_OFF_START);
+    case PP3_MMU_OFF_START ... PP3_MMU_OFF_END:
+        DPRINT_L2("MALI PIX PROC#3 MMU: ");
+        return exynos4210_malimmu_read(&s->pp_mmu[3],
+                offset - PP3_MMU_OFF_START);
+    case L2_OFF_START ... L2_OFF_END:
+        return exynos4210_mali_l2cache_read(&s->l2_cache,
+                offset - L2_OFF_START);
+    default:
+       DPRINT_L1("MALI UNKNOWN REGION: read offset 0x"
+                       TARGET_FMT_plx "\n", offset);
+       return 0;
+    }
+}
+
+/* Geometry processor register map write */
+static void exynos4210_maligp_write(GeometryProc *gp,
+                                          target_phys_addr_t offset, uint32_t val)
+{
+       DPRINT_L2("MALI GEOMETRY PROCESSOR: write offset 0x"
+                       TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val);
+
+    switch (offset) {
+    case MALIGP_REG_OFF_CMD:
+       gp->cmd = val;
+       if (val & MALIGP_REG_VAL_CMD_SOFT_RESET) {
+               exynos4210_maligp_reset(gp);
+               gp->int_rawstat |= MALIGP_REG_VAL_IRQ_RESET_COMPLETED;
+               gp->int_stat = gp->int_rawstat & gp->int_mask;
+               qemu_set_irq(gp->irq_gp, gp->int_stat);
+       }
+       break;
+    case MALIGP_REG_OFF_INT_CLEAR:
+       gp->int_rawstat &= ~val;
+       gp->int_stat = gp->int_rawstat & gp->int_mask;
+       qemu_irq_lower(gp->irq_gp);
+       break;
+    case MALIGP_REG_OFF_INT_MASK:
+       gp->int_mask = val;
+       break;
+    case MALIGP_REG_OFF_VERSION: case MALIGP_REG_OFF_INT_RAWSTAT:
+    case MALIGP_REG_OFF_INT_STAT:
+       DPRINT_L1("MALI GEOM PROC: writing to RO register! Offset 0x"
+                       TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val);
+       break;
+    default:
+       DPRINT_L1("MALI GEOM PROC: writing to non-existing register! "
+          "Offset 0x" TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val);
+       break;
+    }
+}
+
+/* Pixel processor register map write */
+static void exynos4210_malipp_write(PixelProc *pp,
+                                             target_phys_addr_t offset, uint32_t val)
+{
+       DPRINT_L2("write offset 0x"
+               TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val);
+
+    switch (offset) {
+    case MALIPP_REG_OFF_CTRL_MGMT:
+       pp->ctrl_mgmt = val;
+       if (val & MALIPP_REG_VAL_CTRL_MGMT_SOFT_RESET) {
+               exynos4210_malipp_reset(pp);
+               pp->int_rawstat |= MALIPP_REG_VAL_IRQ_RESET_COMPLETED;
+               pp->int_stat = pp->int_rawstat & pp->int_mask;
+               qemu_set_irq(pp->irq_pp, pp->int_stat);
+       }
+       break;
+    case MALIPP_REG_OFF_INT_CLEAR:
+       pp->int_rawstat &= ~val;
+       pp->int_stat = pp->int_rawstat & pp->int_mask;
+       qemu_irq_lower(pp->irq_pp);
+        break;
+    case MALIPP_REG_OFF_INT_MASK:
+       pp->int_mask = val;
+       break;
+    case MALIPP_REG_OFF_INT_STATUS: case MALIPP_REG_OFF_VERSION:
+    case MALIPP_REG_OFF_INT_RAWSTAT:
+       DPRINT_L1("MALI PIX PROC: writing to RO register! Offset 0x"
+                       TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val);
+       break;
+    default:
+       DPRINT_L1("MALI PIX PROC: writing to non-existing register! Offset 0x"
+                       TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val);
+       break;
+    }
+}
+
+/* MALI MMU register map write */
+static void exynos4210_malimmu_write(MaliMMU *mmu,
+                                          target_phys_addr_t offset, uint32_t val)
+{
+       DPRINT_L2("write offset 0x"
+                       TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val);
+
+    switch (offset) {
+    case MALI_MMU_REG_DTE_ADDR:
+       mmu->dte_addr = val;
+       break;
+    case MALI_MMU_REG_COMMAND:
+       mmu->command = val;
+       if (val & MALI_MMU_COMMAND_SOFT_RESET) {
+               exynos4210_malimmu_reset(mmu);
+       }
+        break;
+    case MALI_MMU_REG_INT_CLEAR:
+       mmu->int_rawstat &= ~val;
+       mmu->int_status = mmu->int_rawstat & mmu->int_mask;
+       qemu_irq_lower(mmu->irq_mmu);
+       break;
+    case MALI_MMU_REG_INT_MASK:
+       mmu->int_mask = val;
+       break;
+    case MALI_MMU_REG_INT_STATUS: case MALI_MMU_REG_INT_RAWSTAT:
+       DPRINT_L1("MALI MMU: writing to RO register! Offset 0x"
+                       TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val);
+       break;
+    default:
+       DPRINT_L1("MALI MMU: writing to non-existing register! Offset 0x"
+                       TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val);
+       break;
+    }
+}
+
+/* MALI L2 cache register map write */
+static void exynos4210_mali_l2cache_write(MaliL2Cache *cache,
+                                          target_phys_addr_t offset, uint32_t val)
+{
+       DPRINT_L2("MALI L2 CACHE: write offset 0x"
+                       TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val);
+
+    switch (offset) {
+    case MALI_L2CACHE_REG_COMMAND:
+       cache->command = val;
+        break;
+    case MALI_L2CACHE_REG_ENABLE:
+       cache->enable = val;
+        break;
+    case MALI_L2CACHE_REG_STATUS:
+       DPRINT_L1("MALI L2 CACHE: writing to RO register! Offset 0x"
+                       TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val);
+       break;
+    default:
+       DPRINT_L1("MALI L2 CACHE: writing non-existing register! Offset 0x"
+                       TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val);
+       break;
+    }
+}
+
+static void exynos4210_g3d_write(void *opaque, target_phys_addr_t offset,
+                                 uint64_t val, unsigned size)
+{
+       Exynos4210G3DState *s = (Exynos4210G3DState *)opaque;
+
+    switch (offset) {
+    case GP_OFF_START ... GP_OFF_END:
+        exynos4210_maligp_write(&s->gp, offset - GP_OFF_START, val);
+        break;
+    case PP0_OFF_START ... PP0_OFF_END:
+           DPRINT_L2("MALI PIXEL PROCESSOR#0: ");
+        exynos4210_malipp_write(&s->pp[0], offset - PP0_OFF_START, val);
+        break;
+    case PP1_OFF_START ... PP1_OFF_END:
+               DPRINT_L2("MALI PIXEL PROCESSOR#1: ");
+               exynos4210_malipp_write(&s->pp[1], offset - PP1_OFF_START, val);
+        break;
+    case PP2_OFF_START ... PP2_OFF_END:
+               DPRINT_L2("MALI PIXEL PROCESSOR#2: ");
+               exynos4210_malipp_write(&s->pp[2], offset - PP2_OFF_START, val);
+        break;
+    case PP3_OFF_START ... PP3_OFF_END:
+               DPRINT_L2("MALI PIXEL PROCESSOR#3: ");
+               exynos4210_malipp_write(&s->pp[3], offset - PP3_OFF_START, val);
+        break;
+    case GP_MMU_OFF_START ... GP_MMU_OFF_END:
+        DPRINT_L2("MALI GEOM PROC MMU: ");
+        exynos4210_malimmu_write(&s->gp_mmu, offset - GP_MMU_OFF_START, val);
+        break;
+    case PP0_MMU_OFF_START ... PP0_MMU_OFF_END:
+           DPRINT_L2("MALI PIX PROC#0 MMU: ");
+        exynos4210_malimmu_write(&s->pp_mmu[0],
+                offset - PP0_MMU_OFF_START, val);
+        break;
+    case PP1_MMU_OFF_START ... PP1_MMU_OFF_END:
+        DPRINT_L2("MALI PIX PROC#1 MMU: ");
+        exynos4210_malimmu_write(&s->pp_mmu[1],
+                offset - PP1_MMU_OFF_START, val);
+        break;
+    case PP2_MMU_OFF_START ... PP2_MMU_OFF_END:
+        DPRINT_L2("MALI PIX PROC#2 MMU: ");
+        exynos4210_malimmu_write(&s->pp_mmu[2],
+                offset - PP2_MMU_OFF_START, val);
+        break;
+    case PP3_MMU_OFF_START ... PP3_MMU_OFF_END:
+        DPRINT_L2("MALI PIX PROC#3 MMU: ");
+        exynos4210_malimmu_write(&s->pp_mmu[3],
+                offset - PP3_MMU_OFF_START, val);
+        break;
+    case L2_OFF_START ... L2_OFF_END:
+        exynos4210_mali_l2cache_write(&s->l2_cache, offset - L2_OFF_START, val);
+        break;
+    default:
+       DPRINT_L1("MALI UNKNOWN REGION: write offset 0x"
+                       TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val);
+       break;
+    }
+}
+
+static const MemoryRegionOps exynos4210_g3d_mmio_ops = {
+    .read = exynos4210_g3d_read,
+    .write = exynos4210_g3d_write,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false
+    },
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int exynos4210_g3d_init(SysBusDevice *dev)
+{
+    Exynos4210G3DState *s = FROM_SYSBUS(Exynos4210G3DState, dev);
+
+    sysbus_init_irq(dev, &s->pp[0].irq_pp);
+    sysbus_init_irq(dev, &s->pp[1].irq_pp);
+    sysbus_init_irq(dev, &s->pp[2].irq_pp);
+    sysbus_init_irq(dev, &s->pp[3].irq_pp);
+    sysbus_init_irq(dev, &s->gp.irq_gp);
+    sysbus_init_irq(dev, &s->irq_pmu);
+    sysbus_init_irq(dev, &s->pp_mmu[0].irq_mmu);
+    sysbus_init_irq(dev, &s->pp_mmu[1].irq_mmu);
+    sysbus_init_irq(dev, &s->pp_mmu[2].irq_mmu);
+    sysbus_init_irq(dev, &s->pp_mmu[3].irq_mmu);
+    sysbus_init_irq(dev, &s->gp_mmu.irq_mmu);
+
+    memory_region_init_io(&s->iomem, &exynos4210_g3d_mmio_ops, s,
+            "exynos4210.g3d", EXYNOS4210_G3D_REG_MEM_SIZE);
+    sysbus_init_mmio(dev, &s->iomem);
+
+    return 0;
+}
+
+static void exynos4210_g3d_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+    dc->reset = exynos4210_g3d_reset;
+    k->init = exynos4210_g3d_init;
+}
+
+static TypeInfo exynos4210_g3d_info = {
+    .name = "exynos4210.g3d",
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(Exynos4210G3DState),
+    .class_init = exynos4210_g3d_class_init,
+};
+
+static void exynos4210_g3d_register_devices(void)
+{
+    type_register_static(&exynos4210_g3d_info);
+}
+
+type_init(exynos4210_g3d_register_devices)
index e1b215e..ec2a9e0 100644 (file)
@@ -106,7 +106,8 @@ enum ExtInt {
     EXT_GIC_ID_EXTINT12,
     EXT_GIC_ID_EXTINT13,
     EXT_GIC_ID_EXTINT14,
-    EXT_GIC_ID_EXTINT15
+    EXT_GIC_ID_EXTINT15,
+    EXT_GIC_ID_EXTINT16_31,
 };
 
 /*
@@ -160,9 +161,19 @@ combiner_grp_to_gic_id[64-EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ][8] = {
     { 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 */
-    { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { },
+      EXT_GIC_ID_EXTINT7 },
+    /* int combiner group 38 */
+    { 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 },
+    /* int combiner groups 39-43 */
+    { EXT_GIC_ID_EXTINT16_31 },
+    { EXT_GIC_ID_EXTINT0 },
+    { EXT_GIC_ID_EXTINT1 },
+    { EXT_GIC_ID_EXTINT2 },
+    { EXT_GIC_ID_EXTINT3 },
+    /* groups 44-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 */
@@ -369,18 +380,29 @@ type_init(exynos4210_gic_register_types)
 typedef struct {
     SysBusDevice busdev;
 
-    qemu_irq pic_irq[EXYNOS4210_NCPUS]; /* output IRQs to PICs */
-    uint32_t gpio_level[EXYNOS4210_IRQ_GATE_NINPUTS]; /* Input levels */
+    qemu_irq out[QDEV_MAX_IRQ]; /* output IRQs */
+    uint32_t n_out;             /* outputs amount */
+    uint32_t gpio_level[QDEV_MAX_IRQ]; /* Input levels */
+    uint32_t n_in;              /* inputs amount */
+    uint32_t group_size;         /* input group size */
 } Exynos4210IRQGateState;
 
+static Property exynos4210_irq_gate_properties[] = {
+    DEFINE_PROP_UINT32("n_out", Exynos4210IRQGateState, n_out, 1),
+    DEFINE_PROP_UINT32("n_in", Exynos4210IRQGateState, n_in, 1),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
 static const VMStateDescription vmstate_exynos4210_irq_gate = {
     .name = "exynos4210.irq_gate",
     .version_id = 1,
     .minimum_version_id = 1,
     .minimum_version_id_old = 1,
     .fields = (VMStateField[]) {
-        VMSTATE_UINT32_ARRAY(gpio_level, Exynos4210IRQGateState,
-                EXYNOS4210_IRQ_GATE_NINPUTS),
+        VMSTATE_UINT32(n_out, Exynos4210IRQGateState),
+        VMSTATE_UINT32_ARRAY(gpio_level, Exynos4210IRQGateState, QDEV_MAX_IRQ),
+        VMSTATE_UINT32(n_in, Exynos4210IRQGateState),
+        VMSTATE_UINT32(group_size, Exynos4210IRQGateState),
         VMSTATE_END_OF_LIST()
     }
 };
@@ -390,25 +412,24 @@ static void exynos4210_irq_gate_handler(void *opaque, int irq, int level)
 {
     Exynos4210IRQGateState *s =
             (Exynos4210IRQGateState *)opaque;
-    uint32_t odd, even;
-
-    if (irq & 1) {
-        odd = irq;
-        even = irq & ~1;
-    } else {
-        even = irq;
-        odd = irq | 1;
-    }
+    uint32_t n_out, n_group, i;
+
+    assert(irq < s->n_in);
+
+    n_out = irq / s->group_size;
+    n_group = n_out * s->group_size;
 
-    assert(irq < EXYNOS4210_IRQ_GATE_NINPUTS);
     s->gpio_level[irq] = level;
 
-    if (s->gpio_level[odd] >= 1 || s->gpio_level[even] >= 1) {
-        qemu_irq_raise(s->pic_irq[even >> 1]);
-    } else {
-        qemu_irq_lower(s->pic_irq[even >> 1]);
+    for (i = 0; i < s->group_size; i++) {
+        if (s->gpio_level[n_group + i] >= 1) {
+            qemu_irq_raise(s->out[n_out]);
+            return;
+        }
     }
 
+    qemu_irq_lower(s->out[n_out]);
+
     return;
 }
 
@@ -428,14 +449,19 @@ static int exynos4210_irq_gate_init(SysBusDevice *dev)
     Exynos4210IRQGateState *s =
             FROM_SYSBUS(Exynos4210IRQGateState, dev);
 
+    /* Gate will make each input group of size n_in / n_out */
+    assert((s->n_in % s->n_out) == 0);
+
+    s->group_size = s->n_in / s->n_out;
+
     /* Allocate general purpose input signals and connect a handler to each of
      * them */
     qdev_init_gpio_in(&s->busdev.qdev, exynos4210_irq_gate_handler,
-            EXYNOS4210_IRQ_GATE_NINPUTS);
+            s->n_in);
 
-    /* Connect SysBusDev irqs to device specific irqs */
-    for (i = 0; i < EXYNOS4210_NCPUS; i++) {
-        sysbus_init_irq(dev, &s->pic_irq[i]);
+    /* Pass gate outs to SysBusDev */
+    for (i = 0; i < s->n_out; i++) {
+        sysbus_init_irq(dev, &s->out[i]);
     }
 
     return 0;
@@ -449,6 +475,7 @@ static void exynos4210_irq_gate_class_init(ObjectClass *klass, void *data)
     k->init = exynos4210_irq_gate_init;
     dc->reset = exynos4210_irq_gate_reset;
     dc->vmsd = &vmstate_exynos4210_irq_gate;
+    dc->props = exynos4210_irq_gate_properties;
 }
 
 static TypeInfo exynos4210_irq_gate_info = {
diff --git a/hw/exynos4210_i2c.c b/hw/exynos4210_i2c.c
new file mode 100644 (file)
index 0000000..fa10bfb
--- /dev/null
@@ -0,0 +1,334 @@
+/*
+ *  Exynos4210 I2C Bus Serial Interface Emulation
+ *
+ *  Copyright (C) 2012 Samsung Electronics Co Ltd.
+ *    Maksim Kozlov, <m.kozlov@samsung.com>
+ *    Igor Mitsyanko, <i.mitsyanko@samsung.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License as published by the
+ *  Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ *  for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu-timer.h"
+#include "sysbus.h"
+#include "i2c.h"
+
+#ifndef EXYNOS4_I2C_DEBUG
+#define EXYNOS4_I2C_DEBUG                 0
+#endif
+
+#define TYPE_EXYNOS4_I2C                  "exynos4210.i2c"
+#define EXYNOS4_I2C(obj)                  \
+    OBJECT_CHECK(Exynos4210I2CState, (obj), TYPE_EXYNOS4_I2C)
+
+/* Exynos4210 I2C memory map */
+#define EXYNOS4_I2C_MEM_SIZE              0x14
+#define I2CCON_ADDR                       0x00  /* control register */
+#define I2CSTAT_ADDR                      0x04  /* control/status register */
+#define I2CADD_ADDR                       0x08  /* address register */
+#define I2CDS_ADDR                        0x0c  /* data shift register */
+#define I2CLC_ADDR                        0x10  /* line control register */
+
+#define I2CCON_ACK_GEN                    (1 << 7)
+#define I2CCON_INTRS_EN                   (1 << 5)
+#define I2CCON_INT_PEND                   (1 << 4)
+
+#define EXYNOS4_I2C_MODE(reg)             (((reg) >> 6) & 3)
+#define I2C_IN_MASTER_MODE(reg)           (((reg) >> 6) & 2)
+#define I2CMODE_MASTER_Rx                 0x2
+#define I2CMODE_MASTER_Tx                 0x3
+#define I2CSTAT_LAST_BIT                  (1 << 0)
+#define I2CSTAT_OUTPUT_EN                 (1 << 4)
+#define I2CSTAT_START_BUSY                (1 << 5)
+
+
+#if EXYNOS4_I2C_DEBUG
+#define DPRINT(fmt, args...)              \
+    do { fprintf(stderr, "QEMU I2C: "fmt, ## args); } while (0)
+
+static const char *exynos4_i2c_get_regname(unsigned offset)
+{
+    switch (offset) {
+    case I2CCON_ADDR:
+        return "I2CCON";
+    case I2CSTAT_ADDR:
+        return "I2CSTAT";
+    case I2CADD_ADDR:
+        return "I2CADD";
+    case I2CDS_ADDR:
+        return "I2CDS";
+    case I2CLC_ADDR:
+        return "I2CLC";
+    default:
+        return "[?]";
+    }
+}
+
+#else
+#define DPRINT(fmt, args...)              do { } while (0)
+#endif
+
+typedef struct Exynos4210I2CState {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    i2c_bus *bus;
+    qemu_irq irq;
+
+    uint8_t i2ccon;
+    uint8_t i2cstat;
+    uint8_t i2cadd;
+    uint8_t i2cds;
+    uint8_t i2clc;
+    bool scl_free;
+} Exynos4210I2CState;
+
+static inline void exynos4210_i2c_raise_interrupt(Exynos4210I2CState *s)
+{
+    if (s->i2ccon & I2CCON_INTRS_EN) {
+        s->i2ccon |= I2CCON_INT_PEND;
+        qemu_irq_raise(s->irq);
+    }
+}
+
+static void exynos4210_i2c_data_receive(void *opaque)
+{
+    Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
+    int ret;
+
+    s->i2cstat &= ~I2CSTAT_LAST_BIT;
+    s->scl_free = false;
+    ret = i2c_recv(s->bus);
+    if (ret < 0 && (s->i2ccon & I2CCON_ACK_GEN)) {
+        s->i2cstat |= I2CSTAT_LAST_BIT;  /* Data is not acknowledged */
+    } else {
+        s->i2cds = ret;
+    }
+    exynos4210_i2c_raise_interrupt(s);
+}
+
+static void exynos4210_i2c_data_send(void *opaque)
+{
+    Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
+
+    s->i2cstat &= ~I2CSTAT_LAST_BIT;
+    s->scl_free = false;
+    if (i2c_send(s->bus, s->i2cds) < 0 && (s->i2ccon & I2CCON_ACK_GEN)) {
+        s->i2cstat |= I2CSTAT_LAST_BIT;
+    }
+    exynos4210_i2c_raise_interrupt(s);
+}
+
+static uint64_t exynos4210_i2c_read(void *opaque, target_phys_addr_t offset,
+                                 unsigned size)
+{
+    Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
+    uint8_t value;
+
+    switch (offset) {
+    case I2CCON_ADDR:
+        value = s->i2ccon;
+        break;
+    case I2CSTAT_ADDR:
+        value = s->i2cstat;
+        break;
+    case I2CADD_ADDR:
+        value = s->i2cadd;
+        break;
+    case I2CDS_ADDR:
+        value = s->i2cds;
+        s->scl_free = true;
+        if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx &&
+               (s->i2cstat & I2CSTAT_START_BUSY) &&
+               !(s->i2ccon & I2CCON_INT_PEND)) {
+            exynos4210_i2c_data_receive(s);
+        }
+        break;
+    case I2CLC_ADDR:
+        value = s->i2clc;
+        break;
+    default:
+        value = 0;
+        DPRINT("ERROR: Bad read offset 0x%x\n", (unsigned int)offset);
+        break;
+    }
+
+    DPRINT("read %s [0x%02x] -> 0x%02x\n", exynos4_i2c_get_regname(offset),
+            (unsigned int)offset, value);
+    return value;
+}
+
+static void exynos4210_i2c_write(void *opaque, target_phys_addr_t offset,
+                              uint64_t value, unsigned size)
+{
+    Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
+    uint8_t v = value & 0xff;
+
+    DPRINT("write %s [0x%02x] <- 0x%02x\n", exynos4_i2c_get_regname(offset),
+            (unsigned int)offset, v);
+
+    switch (offset) {
+    case I2CCON_ADDR:
+        s->i2ccon = (v & ~I2CCON_INT_PEND) | (s->i2ccon & I2CCON_INT_PEND);
+        if ((s->i2ccon & I2CCON_INT_PEND) && !(v & I2CCON_INT_PEND)) {
+            s->i2ccon &= ~I2CCON_INT_PEND;
+            qemu_irq_lower(s->irq);
+            if (!(s->i2ccon & I2CCON_INTRS_EN)) {
+                s->i2cstat &= ~I2CSTAT_START_BUSY;
+            }
+
+            if (s->i2cstat & I2CSTAT_START_BUSY) {
+                if (s->scl_free) {
+                    if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx) {
+                        exynos4210_i2c_data_send(s);
+                    } else if (EXYNOS4_I2C_MODE(s->i2cstat) ==
+                            I2CMODE_MASTER_Rx) {
+                        exynos4210_i2c_data_receive(s);
+                    }
+                } else {
+                    s->i2ccon |= I2CCON_INT_PEND;
+                    qemu_irq_raise(s->irq);
+                }
+            }
+        }
+        break;
+    case I2CSTAT_ADDR:
+        s->i2cstat =
+                (s->i2cstat & I2CSTAT_START_BUSY) | (v & ~I2CSTAT_START_BUSY);
+
+        if (!(s->i2cstat & I2CSTAT_OUTPUT_EN)) {
+            s->i2cstat &= ~I2CSTAT_START_BUSY;
+            s->scl_free = true;
+            qemu_irq_lower(s->irq);
+            break;
+        }
+
+        /* Nothing to do if in i2c slave mode */
+        if (!I2C_IN_MASTER_MODE(s->i2cstat)) {
+            break;
+        }
+
+        if (v & I2CSTAT_START_BUSY) {
+            s->i2cstat &= ~I2CSTAT_LAST_BIT;
+            s->i2cstat |= I2CSTAT_START_BUSY;    /* Line is busy */
+            s->scl_free = false;
+
+            /* Generate start bit and send slave address */
+            if (i2c_start_transfer(s->bus, s->i2cds >> 1, s->i2cds & 0x1) &&
+                    (s->i2ccon & I2CCON_ACK_GEN)) {
+                s->i2cstat |= I2CSTAT_LAST_BIT;
+            } else if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx) {
+                exynos4210_i2c_data_receive(s);
+            }
+            exynos4210_i2c_raise_interrupt(s);
+        } else {
+            i2c_end_transfer(s->bus);
+            if (!(s->i2ccon & I2CCON_INT_PEND)) {
+                s->i2cstat &= ~I2CSTAT_START_BUSY;
+            }
+            s->scl_free = true;
+        }
+        break;
+    case I2CADD_ADDR:
+        if ((s->i2cstat & I2CSTAT_OUTPUT_EN) == 0) {
+            s->i2cadd = v;
+        }
+        break;
+    case I2CDS_ADDR:
+        if (s->i2cstat & I2CSTAT_OUTPUT_EN) {
+            s->i2cds = v;
+            s->scl_free = true;
+            if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx &&
+                    (s->i2cstat & I2CSTAT_START_BUSY) &&
+                    !(s->i2ccon & I2CCON_INT_PEND)) {
+                exynos4210_i2c_data_send(s);
+            }
+        }
+        break;
+    case I2CLC_ADDR:
+        s->i2clc = v;
+        break;
+    default:
+        DPRINT("ERROR: Bad write offset 0x%x\n", (unsigned int)offset);
+        break;
+    }
+}
+
+static const MemoryRegionOps exynos4210_i2c_ops = {
+    .read = exynos4210_i2c_read,
+    .write = exynos4210_i2c_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription exynos4210_i2c_vmstate = {
+    .name = TYPE_EXYNOS4_I2C,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT8(i2ccon, Exynos4210I2CState),
+        VMSTATE_UINT8(i2cstat, Exynos4210I2CState),
+        VMSTATE_UINT8(i2cds, Exynos4210I2CState),
+        VMSTATE_UINT8(i2cadd, Exynos4210I2CState),
+        VMSTATE_UINT8(i2clc, Exynos4210I2CState),
+        VMSTATE_BOOL(scl_free, Exynos4210I2CState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void exynos4210_i2c_reset(DeviceState *d)
+{
+    Exynos4210I2CState *s = EXYNOS4_I2C(d);
+
+    s->i2ccon  = 0x00;
+    s->i2cstat = 0x00;
+    s->i2cds   = 0xFF;
+    s->i2clc   = 0x00;
+    s->i2cadd  = 0xFF;
+    s->scl_free = true;
+}
+
+static int exynos4210_i2c_realize(SysBusDevice *dev)
+{
+    Exynos4210I2CState *s = EXYNOS4_I2C(dev);
+
+    memory_region_init_io(&s->iomem, &exynos4210_i2c_ops, s, TYPE_EXYNOS4_I2C,
+                          EXYNOS4_I2C_MEM_SIZE);
+    sysbus_init_mmio(dev, &s->iomem);
+    sysbus_init_irq(dev, &s->irq);
+    s->bus = i2c_init_bus(&dev->qdev, "i2c");
+    return 0;
+}
+
+static void exynos4210_i2c_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    SysBusDeviceClass *sbdc = SYS_BUS_DEVICE_CLASS(klass);
+
+    dc->vmsd = &exynos4210_i2c_vmstate;
+    dc->reset = exynos4210_i2c_reset;
+    sbdc->init = exynos4210_i2c_realize;
+}
+
+static const TypeInfo exynos4210_i2c_info = {
+    .name = TYPE_EXYNOS4_I2C,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(Exynos4210I2CState),
+    .class_init = exynos4210_i2c_class_init,
+};
+
+static void exynos4210_i2c_register_types(void)
+{
+    type_register_static(&exynos4210_i2c_info);
+}
+
+type_init(exynos4210_i2c_register_types)
diff --git a/hw/exynos4210_i2s.c b/hw/exynos4210_i2s.c
new file mode 100644 (file)
index 0000000..236cc57
--- /dev/null
@@ -0,0 +1,577 @@
+/*
+ * Samsung exynos4210 I2S driver
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ *  Vorobiov Stanislav <s.vorobiov@samsung.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License as published by the
+ *  Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ *  for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "exynos4210_i2s.h"
+#include "exec-memory.h"
+
+/* #define DEBUG_I2S */
+
+#ifdef DEBUG_I2S
+#define DPRINTF(fmt, ...) \
+    do { \
+        fprintf(stdout, "I2S: [%s:%d] " fmt, __func__, __LINE__, \
+            ## __VA_ARGS__); \
+    } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+/* Registers */
+#define IISCON       0x00
+#define IISMOD       0x04
+#define IISFIC       0x08
+#define IISPSR       0x0C
+#define IISTXD       0x10
+#define IISRXD       0x14
+#define IISFICS      0x18
+#define IISTXDS      0x1C
+#define IISAHB       0x20
+#define IISSTR0      0x24
+#define IISSIZE      0x28
+#define IISTRNCNT    0x2C
+#define IISLVL0ADDR  0x30
+#define IISLVL1ADDR  0x34
+#define IISLVL2ADDR  0x38
+#define IISLVL3ADDR  0x3C
+#define IISSTR1      0x40
+
+#define I2S_REGS_MEM_SIZE 0x44
+#define I2S_REGS_NUM (I2S_REGS_MEM_SIZE >> 2)
+
+#define I_(reg) (reg / sizeof(uint32_t))
+
+/* Interface Control Register */
+#define IISCON_I2SACTIVE    (1 << 0)
+#define IISCON_RXDMACTIVE   (1 << 1)
+#define IISCON_TXDMACTIVE   (1 << 2)
+#define IISCON_RXCHPAUSE    (1 << 3)
+#define IISCON_TXCHPAUSE    (1 << 4)
+#define IISCON_RXDMAPAUSE   (1 << 5)
+#define IISCON_TXDMAPAUSE   (1 << 6)
+#define IISCON_FRXFULL      (1 << 7)
+#define IISCON_FTX0FULL     (1 << 8)
+#define IISCON_FRXEMPT      (1 << 9)
+#define IISCON_FTX0EMPT     (1 << 10)
+#define IISCON_LRI          (1 << 11)
+#define IISCON_FTX1FULL     (1 << 12)
+#define IISCON_FTX2FULL     (1 << 13)
+#define IISCON_FTX1EMPT     (1 << 14)
+#define IISCON_FTX2EMPT     (1 << 15)
+#define IISCON_FTXURINTEN   (1 << 16)
+#define IISCON_FTXURSTATUS  (1 << 17)
+#define IISCON_TXSDMACTIVE  (1 << 18)
+#define IISCON_TXSDMAPAUSE  (1 << 20)
+#define IISCON_FTXSFULL     (1 << 21)
+#define IISCON_FTXSEMPT     (1 << 22)
+#define IISCON_FTXSURINTEN  (1 << 23)
+#define IISCON_FTXSURSTATUS (1 << 24)
+#define IISCON_FRXOFINTEN   (1 << 25)
+#define IISCON_FRXOFSTATUS  (1 << 26)
+#define IISCON_SW_RST       (1 << 31)
+
+#define IISCON_WRITE_MASK   (0x8295007F)
+
+/* AHB DMA Control Register */
+#define IISAHB_WRITE_MASK     0xFF0000FB
+#define IISAHB_LVL3EN         (1 << 27)
+#define IISAHB_LVL2EN         (1 << 26)
+#define IISAHB_LVL1EN         (1 << 25)
+#define IISAHB_LVL0EN         (1 << 24)
+#define IISAHB_LVL3INT        (1 << 23)
+#define IISAHB_LVL2INT        (1 << 22)
+#define IISAHB_LVL1INT        (1 << 21)
+#define IISAHB_LVL0INT        (1 << 20)
+#define IISAHB_LVL3CLR        (1 << 19)
+#define IISAHB_LVL2CLR        (1 << 18)
+#define IISAHB_LVL1CLR        (1 << 17)
+#define IISAHB_LVL0CLR        (1 << 16)
+#define IISAHB_DMA_STRADDRRST (1 << 7)
+#define IISAHB_DMA_STRADDRTOG (1 << 6)
+#define IISAHB_DMARLD         (1 << 5)
+#define IISAHB_INTMASK        (1 << 3)
+#define IISAHB_DMAINT         (1 << 2)
+#define IISAHB_DMACLR         (1 << 1)
+#define IISAHB_DMAEN          (1 << 0)
+
+/* AHB DMA Size Register */
+#define IISSIZE_TRNS_SIZE_OFFSET  16
+#define IISSIZE_TRNS_SIZE_MASK    0xFFFF
+
+/* AHB DMA Transfer Count Register */
+#define IISTRNCNT_OFFSET 0
+#define IISTRNCNT_MASK   0xFFFFFF
+
+typedef struct {
+    const char *name;
+    target_phys_addr_t offset;
+    uint32_t reset_value;
+} Exynos4210I2SReg;
+
+static Exynos4210I2SReg exynos4210_i2s_regs[I2S_REGS_NUM] = {
+    {"IISCON",         IISCON,         0x00000000},
+    {"IISMOD",         IISMOD,         0x00000000},
+    {"IISFIC",         IISFIC,         0x00000000},
+    {"IISPSR",         IISPSR,         0x00000000},
+    {"IISTXD",         IISTXD,         0x00000000},
+    {"IISRXD",         IISRXD,         0x00000000},
+    {"IISFICS",        IISFICS,        0x00000000},
+    {"IISTXDS",        IISTXDS,        0x00000000},
+    {"IISAHB",         IISAHB,         0x00000000},
+    {"IISSTR0",        IISSTR0,        0x00000000},
+    {"IISSIZE",        IISSIZE,        0x7FFF0000},
+    {"IISTRNCNT",      IISTRNCNT,      0x00000000},
+    {"IISLVL0ADDR",    IISLVL0ADDR,    0x00000000},
+    {"IISLVL1ADDR",    IISLVL1ADDR,    0x00000000},
+    {"IISLVL2ADDR",    IISLVL2ADDR,    0x00000000},
+    {"IISLVL3ADDR",    IISLVL3ADDR,    0x00000000},
+    {"IISSTR1",        IISSTR1,        0x00000000},
+};
+
+static struct {
+    uint32_t ahb_en_bit;
+    uint32_t ahb_int_bit;
+    uint32_t ahb_clr_bit;
+    int reg_offset;
+} lvl_regs[] = {
+    { IISAHB_LVL3EN, IISAHB_LVL3INT, IISAHB_LVL3CLR, IISLVL3ADDR },
+    { IISAHB_LVL2EN, IISAHB_LVL2INT, IISAHB_LVL2CLR, IISLVL2ADDR },
+    { IISAHB_LVL1EN, IISAHB_LVL1INT, IISAHB_LVL1CLR, IISLVL1ADDR },
+    { IISAHB_LVL0EN, IISAHB_LVL0INT, IISAHB_LVL0CLR, IISLVL0ADDR },
+};
+
+typedef struct {
+    BusState qbus;
+
+    MemoryRegion iomem;
+    qemu_irq irq;
+    uint32_t reg[I2S_REGS_NUM];
+} Exynos4210I2SBus;
+
+static Exynos4210I2SSlave *get_slave(Exynos4210I2SBus *bus)
+{
+    DeviceState *dev = QTAILQ_FIRST(&bus->qbus.children);
+
+    if (dev) {
+        return EXYNOS4210_I2S_SLAVE_FROM_QDEV(dev);
+    }
+
+    return NULL;
+}
+
+static void reset_internal(BusState *qbus, bool reset_slave)
+{
+    Exynos4210I2SBus *bus = DO_UPCAST(Exynos4210I2SBus, qbus, qbus);
+    Exynos4210I2SSlave *s;
+    int i;
+
+    DPRINTF("enter\n");
+
+    qemu_irq_lower(bus->irq);
+
+    for (i = 0; i < ARRAY_SIZE(exynos4210_i2s_regs); i++) {
+        bus->reg[i] = exynos4210_i2s_regs[i].reset_value;
+    }
+
+    if (reset_slave) {
+        s = get_slave(bus);
+
+        if (s != NULL) {
+            device_reset(&s->qdev);
+        }
+    }
+}
+
+static uint32_t get_dma_size_words(Exynos4210I2SBus *bus)
+{
+    return (bus->reg[I_(IISSIZE)] >> IISSIZE_TRNS_SIZE_OFFSET) &
+            IISSIZE_TRNS_SIZE_MASK;
+}
+
+static uint32_t get_dma_trncnt_words(Exynos4210I2SBus *bus)
+{
+    return (bus->reg[I_(IISTRNCNT)] >> IISTRNCNT_OFFSET) &
+            IISTRNCNT_MASK;
+}
+
+static void set_dma_trncnt_words(Exynos4210I2SBus *bus, uint32_t trncnt_words)
+{
+    bus->reg[I_(IISTRNCNT)] &= ~(IISTRNCNT_MASK << IISTRNCNT_OFFSET);
+    bus->reg[I_(IISTRNCNT)] |=
+        ((trncnt_words & IISTRNCNT_MASK) << IISTRNCNT_OFFSET);
+}
+
+static int exynos4210_i2s_bus_reset(BusState *qbus);
+
+static struct BusInfo exynos4210_i2s_bus_info = {
+    .name = "Exynos4210.I2S",
+    .size = sizeof(Exynos4210I2SBus),
+    .reset = exynos4210_i2s_bus_reset
+};
+
+static const VMStateDescription vmstate_exynos4210_i2s_bus = {
+    .name = "exynos4210.i2s_bus",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields      = (VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(reg, Exynos4210I2SBus,
+                                  I2S_REGS_NUM),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+#ifdef DEBUG_I2S
+static const char *exynos4210_i2s_regname(Exynos4210I2SBus *bus,
+                                          target_phys_addr_t offset)
+{
+    int i;
+
+    for (i = 0; i < ARRAY_SIZE(exynos4210_i2s_regs); i++) {
+        if (offset == exynos4210_i2s_regs[i].offset) {
+            return exynos4210_i2s_regs[i].name;
+        }
+    }
+
+    return NULL;
+}
+#endif
+
+static void exynos4210_i2s_bus_write(void *opaque,
+                                     target_phys_addr_t offset,
+                                     uint64_t value,
+                                     unsigned size)
+{
+    Exynos4210I2SBus *bus = opaque;
+    Exynos4210I2SSlave *s;
+    Exynos4210I2SSlaveClass *sc;
+    int i;
+
+    s = get_slave(bus);
+
+    assert(s != NULL);
+
+    if (s == NULL) {
+        hw_error("Exynos I2S cannot operate without a slave\n");
+    }
+
+    sc = EXYNOS4210_I2S_SLAVE_GET_CLASS(s);
+
+    switch (offset) {
+    case IISCON:
+        if (!(value & IISCON_SW_RST) &&
+             (bus->reg[I_(IISCON)] & IISCON_SW_RST)) {
+            reset_internal(&bus->qbus, 1);
+        }
+
+        bus->reg[I_(offset)] = (bus->reg[I_(offset)] & ~IISCON_WRITE_MASK) |
+                               (value & IISCON_WRITE_MASK);
+
+        DPRINTF("0x%.8X -> %s\n",
+                bus->reg[I_(offset)],
+                exynos4210_i2s_regname(bus, offset));
+
+        break;
+    case IISAHB:
+        if (((value & IISAHB_DMAEN) != 0) && ((value & IISAHB_DMARLD) == 0)) {
+            hw_error("Non auto-reload DMA is not supported\n");
+        }
+
+        if (((value & IISAHB_DMAEN) != 0) && ((value & IISAHB_INTMASK) == 0)) {
+            hw_error("Non buffer level DMA interrupt is not supported\n");
+        }
+
+        if (((value & IISAHB_DMAEN) != 0) &&
+            ((value & IISAHB_DMA_STRADDRTOG) != 0)) {
+            hw_error("DMA start address toggle is not supported\n");
+        }
+
+        for (i = 0; i < ARRAY_SIZE(lvl_regs); ++i) {
+            if ((value & lvl_regs[i].ahb_clr_bit) &&
+                (bus->reg[I_(IISAHB)] & lvl_regs[i].ahb_int_bit)) {
+                qemu_irq_lower(bus->irq);
+                bus->reg[I_(IISAHB)] &= ~lvl_regs[i].ahb_int_bit;
+            }
+        }
+
+        if ((value & IISAHB_DMACLR) &&
+            (bus->reg[I_(IISAHB)] & IISAHB_DMAINT)) {
+            qemu_irq_lower(bus->irq);
+            bus->reg[I_(IISAHB)] &= ~IISAHB_DMAINT;
+        }
+
+        if ((value & IISAHB_DMAEN) !=
+            (bus->reg[I_(IISAHB)] & IISAHB_DMAEN)) {
+            if ((value & IISAHB_DMAEN) == 0) {
+                qemu_irq_lower(bus->irq);
+            }
+            if (sc->dma_enable) {
+                sc->dma_enable(s, ((value & IISAHB_DMAEN) != 0));
+            }
+        }
+
+        bus->reg[I_(offset)] = (bus->reg[I_(offset)] & ~IISAHB_WRITE_MASK) |
+                               (value & IISAHB_WRITE_MASK);
+
+        DPRINTF("0x%.8X -> %s\n",
+                bus->reg[I_(offset)],
+                exynos4210_i2s_regname(bus, offset));
+
+        break;
+    case IISTRNCNT:
+        hw_error("Cannot write IISTRNCNT\n");
+        break;
+    case IISSIZE:
+        if (value == 0) {
+            hw_error("IISSIZE cannot be 0\n");
+        }
+
+        bus->reg[I_(offset)] = value;
+
+        if (get_dma_size_words(bus) <= get_dma_trncnt_words(bus)) {
+            set_dma_trncnt_words(bus, 0);
+        }
+
+        DPRINTF("0x%.8X -> %s\n",
+                bus->reg[I_(offset)],
+                exynos4210_i2s_regname(bus, offset));
+
+        break;
+    case IISLVL0ADDR:
+    case IISLVL1ADDR:
+    case IISLVL2ADDR:
+    case IISLVL3ADDR:
+    case IISTXD:
+    case IISMOD:
+    case IISFIC:
+    case IISPSR:
+    case IISTXDS:
+    case IISFICS:
+    case IISSTR0:
+    case IISSTR1:
+        bus->reg[I_(offset)] = value;
+
+        DPRINTF("0x%.8X -> %s\n",
+                bus->reg[I_(offset)],
+                exynos4210_i2s_regname(bus, offset));
+
+        break;
+    default:
+        hw_error("Bad offset: 0x%X\n", (int)offset);
+    }
+}
+
+static uint64_t exynos4210_i2s_bus_read(void *opaque,
+                                        target_phys_addr_t offset,
+                                        unsigned size)
+{
+    Exynos4210I2SBus *bus = opaque;
+
+    if (offset > (I2S_REGS_MEM_SIZE - sizeof(uint32_t))) {
+        hw_error("Bad offset: 0x%X\n", (int)offset);
+        return 0;
+    }
+
+    DPRINTF("%s -> 0x%.8X\n",
+            exynos4210_i2s_regname(bus, offset),
+            bus->reg[I_(offset)]);
+
+    return bus->reg[I_(offset)];
+}
+
+static int exynos4210_i2s_bus_reset(BusState *qbus)
+{
+    reset_internal(qbus, 0);
+
+    return 0;
+}
+
+static const MemoryRegionOps exynos4210_i2s_bus_ops = {
+    .read = exynos4210_i2s_bus_read,
+    .write = exynos4210_i2s_bus_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .max_access_size = 4,
+        .unaligned = false
+    },
+};
+
+BusState *exynos4210_i2s_bus_new(const char *name,
+                                 target_phys_addr_t addr,
+                                 qemu_irq irq)
+{
+    Exynos4210I2SBus *bus;
+
+    bus = FROM_QBUS(Exynos4210I2SBus,
+                    qbus_create(&exynos4210_i2s_bus_info, NULL, name));
+    vmstate_register(NULL, -1, &vmstate_exynos4210_i2s_bus, bus);
+
+    memory_region_init_io(&bus->iomem,
+                          &exynos4210_i2s_bus_ops,
+                          bus,
+                          "exynos4210.i2s",
+                          I2S_REGS_MEM_SIZE);
+
+    memory_region_add_subregion(get_system_memory(),
+                                addr,
+                                &bus->iomem);
+
+    bus->irq = irq;
+
+    return &bus->qbus;
+}
+
+bool exynos4210_i2s_dma_enabled(BusState *qbus)
+{
+    Exynos4210I2SBus *bus = DO_UPCAST(Exynos4210I2SBus, qbus, qbus);
+
+    return ((bus->reg[I_(IISAHB)] & IISAHB_DMAEN) != 0);
+}
+
+uint32_t exynos4210_i2s_dma_get_words_available(BusState *qbus)
+{
+    Exynos4210I2SBus *bus = DO_UPCAST(Exynos4210I2SBus, qbus, qbus);
+
+    return get_dma_size_words(bus);
+}
+
+void exynos4210_i2s_dma_read(BusState *qbus, void *buf, uint32_t num_words)
+{
+    Exynos4210I2SBus *bus = DO_UPCAST(Exynos4210I2SBus, qbus, qbus);
+    target_phys_addr_t addr;
+    uint32_t size_words, trncnt_words;
+
+    assert(num_words <= exynos4210_i2s_dma_get_words_available(qbus));
+
+    if (num_words > exynos4210_i2s_dma_get_words_available(qbus)) {
+        hw_error("Bad DMA read length: %d\n", num_words);
+    }
+
+    size_words = get_dma_size_words(bus);
+    addr = bus->reg[I_(IISSTR0)];
+    trncnt_words = get_dma_trncnt_words(bus);
+
+    assert(trncnt_words < size_words);
+
+    if (num_words > (size_words - trncnt_words)) {
+        cpu_physical_memory_read(addr +
+                                 (trncnt_words * EXYNOS4210_I2S_WORD_LEN),
+            buf,
+            (size_words - trncnt_words) * EXYNOS4210_I2S_WORD_LEN);
+        num_words -= (size_words - trncnt_words);
+        buf += (size_words - trncnt_words) * EXYNOS4210_I2S_WORD_LEN;
+        trncnt_words = 0;
+    }
+
+    cpu_physical_memory_read(addr + (trncnt_words * EXYNOS4210_I2S_WORD_LEN),
+                             buf,
+                             num_words * EXYNOS4210_I2S_WORD_LEN);
+}
+
+void exynos4210_i2s_dma_advance(BusState *qbus, uint32_t num_words)
+{
+    Exynos4210I2SBus *bus = DO_UPCAST(Exynos4210I2SBus, qbus, qbus);
+    uint32_t size_words, trncnt_words;
+    int i;
+
+    assert(num_words <= exynos4210_i2s_dma_get_words_available(qbus));
+
+    if (num_words > exynos4210_i2s_dma_get_words_available(qbus)) {
+        hw_error("Bad DMA read length: %d\n", num_words);
+    }
+
+    size_words = get_dma_size_words(bus);
+    trncnt_words = get_dma_trncnt_words(bus);
+
+    for (i = 0; i < ARRAY_SIZE(lvl_regs); ++i) {
+        target_phys_addr_t dma_offset;
+        uint32_t tmp_num_words = num_words, tmp_trncnt_words = trncnt_words;
+
+        if ((bus->reg[I_(IISAHB)] & lvl_regs[i].ahb_en_bit) == 0) {
+            continue;
+        }
+
+        if (bus->reg[I_(lvl_regs[i].reg_offset)] < bus->reg[I_(IISSTR0)]) {
+            continue;
+        }
+
+        dma_offset = bus->reg[I_(lvl_regs[i].reg_offset)] -
+                     bus->reg[I_(IISSTR0)];
+
+        if (tmp_num_words > (size_words - tmp_trncnt_words)) {
+            if ((dma_offset >= (tmp_trncnt_words * EXYNOS4210_I2S_WORD_LEN)) &&
+                (dma_offset < size_words * EXYNOS4210_I2S_WORD_LEN)) {
+                bus->reg[I_(IISAHB)] |= lvl_regs[i].ahb_int_bit;
+                qemu_irq_raise(bus->irq);
+                break;
+            }
+
+            tmp_num_words -= (size_words - tmp_trncnt_words);
+            tmp_trncnt_words = 0;
+        }
+
+        if ((dma_offset >= (tmp_trncnt_words * EXYNOS4210_I2S_WORD_LEN)) &&
+            (dma_offset <
+                (tmp_trncnt_words + tmp_num_words) * EXYNOS4210_I2S_WORD_LEN)) {
+            bus->reg[I_(IISAHB)] |= lvl_regs[i].ahb_int_bit;
+            qemu_irq_raise(bus->irq);
+        }
+    }
+
+    set_dma_trncnt_words(bus, (trncnt_words + num_words) % size_words);
+}
+
+const VMStateDescription vmstate_exynos4210_i2s_slave = {
+    .name = "Exynos4210I2SSlave",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1
+};
+
+static int exynos4210_i2s_slave_qdev_init(DeviceState *dev)
+{
+    Exynos4210I2SSlave *s = EXYNOS4210_I2S_SLAVE_FROM_QDEV(dev);
+    Exynos4210I2SSlaveClass *sc = EXYNOS4210_I2S_SLAVE_GET_CLASS(s);
+
+    return sc->init ? sc->init(s) : 0;
+}
+
+static void exynos4210_i2s_slave_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *k = DEVICE_CLASS(klass);
+    k->init = exynos4210_i2s_slave_qdev_init;
+    k->bus_info = &exynos4210_i2s_bus_info;
+}
+
+static TypeInfo exynos4210_i2s_slave_type_info = {
+    .name = TYPE_EXYNOS4210_I2S_SLAVE,
+    .parent = TYPE_DEVICE,
+    .instance_size = sizeof(Exynos4210I2SSlave),
+    .abstract = true,
+    .class_size = sizeof(Exynos4210I2SSlaveClass),
+    .class_init = exynos4210_i2s_slave_class_init,
+};
+
+static void exynos4210_i2s_slave_register_types(void)
+{
+    type_register_static(&exynos4210_i2s_slave_type_info);
+}
+
+type_init(exynos4210_i2s_slave_register_types)
diff --git a/hw/exynos4210_i2s.h b/hw/exynos4210_i2s.h
new file mode 100644 (file)
index 0000000..86b9c30
--- /dev/null
@@ -0,0 +1,57 @@
+#ifndef QEMU_HW_EXYNOS4210_I2S_H
+#define QEMU_HW_EXYNOS4210_I2S_H
+
+#include "qdev.h"
+
+#define EXYNOS4210_I2S_WORD_LEN 4
+
+typedef struct Exynos4210I2SSlave Exynos4210I2SSlave;
+
+#define TYPE_EXYNOS4210_I2S_SLAVE "exynos4210.i2s-slave"
+#define EXYNOS4210_I2S_SLAVE(obj) \
+     OBJECT_CHECK(Exynos4210I2SSlave, (obj), TYPE_EXYNOS4210_I2S_SLAVE)
+#define EXYNOS4210_I2S_SLAVE_CLASS(klass) \
+     OBJECT_CLASS_CHECK(Exynos4210I2SSlaveClass, (klass), \
+                        TYPE_EXYNOS4210_I2S_SLAVE)
+#define EXYNOS4210_I2S_SLAVE_GET_CLASS(obj) \
+     OBJECT_GET_CLASS(Exynos4210I2SSlaveClass, (obj), TYPE_EXYNOS4210_I2S_SLAVE)
+
+typedef struct {
+    DeviceClass parent_class;
+
+    int (*init)(Exynos4210I2SSlave *s);
+
+    void (*dma_enable)(Exynos4210I2SSlave *s, bool enable);
+} Exynos4210I2SSlaveClass;
+
+struct Exynos4210I2SSlave {
+    DeviceState qdev;
+};
+
+BusState *exynos4210_i2s_bus_new(const char *name,
+                                 target_phys_addr_t addr,
+                                 qemu_irq irq);
+
+#define EXYNOS4210_I2S_SLAVE_FROM_QDEV(dev) \
+    DO_UPCAST(Exynos4210I2SSlave, qdev, dev)
+#define FROM_EXYNOS4210_I2S_SLAVE(type, dev) DO_UPCAST(type, i2s, dev)
+
+bool exynos4210_i2s_dma_enabled(BusState *qbus);
+
+uint32_t exynos4210_i2s_dma_get_words_available(BusState *qbus);
+
+void exynos4210_i2s_dma_read(BusState *qbus, void *buf, uint32_t num_words);
+
+void exynos4210_i2s_dma_advance(BusState *qbus, uint32_t num_words);
+
+extern const VMStateDescription vmstate_exynos4210_i2s_slave;
+
+#define VMSTATE_EXYNOS4210_I2S_SLAVE(_field, _state) { \
+    .name       = (stringify(_field)), \
+    .size       = sizeof(Exynos4210I2SSlave), \
+    .vmsd       = &vmstate_exynos4210_i2s_slave, \
+    .flags      = VMS_STRUCT, \
+    .offset     = vmstate_offset_value(_state, _field, Exynos4210I2SSlave), \
+}
+
+#endif
diff --git a/hw/exynos4210_rtc.c b/hw/exynos4210_rtc.c
new file mode 100644 (file)
index 0000000..1114c64
--- /dev/null
@@ -0,0 +1,612 @@
+/*
+ * Samsung exynos4210 Real Time Clock
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ *  Ogurtsov Oleg <o.ogurtsov@samsung.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License as published by the
+ *  Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ *  for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* Description:
+ * Register RTCCON:
+ *  CLKSEL Bit[1] not used
+ *  CLKOUTEN Bit[9] not used
+ */
+
+#include "sysbus.h"
+#include "qemu-timer.h"
+#include "qemu-common.h"
+#include "ptimer.h"
+
+#include "hw.h"
+#include "qemu-timer.h"
+#include "sysemu.h"
+
+#include "exynos4210.h"
+
+#define DEBUG_RTC 0
+/* Get time from host */
+#define EXYNOS4210_RTC_GETSYSTIME 1
+
+#if DEBUG_RTC
+#define DPRINTF(fmt, ...) \
+        do { fprintf(stdout, "RTC: [%24s:%5d] " fmt, __func__, __LINE__, \
+                ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+#define     EXYNOS4210_RTC_REG_MEM_SIZE     0x0100
+
+#define     INTP            0x0030
+#define     RTCCON          0x0040
+#define     TICCNT          0x0044
+#define     RTCALM          0x0050
+#define     ALMSEC          0x0054
+#define     ALMMIN          0x0058
+#define     ALMHOUR         0x005C
+#define     ALMDAY          0x0060
+#define     ALMMON          0x0064
+#define     ALMYEAR         0x0068
+#define     BCDSEC          0x0070
+#define     BDCMIN          0x0074
+#define     BCDHOUR         0x0078
+#define     BCDDAY          0x007C
+#define     BCDDAYWEEK      0x0080
+#define     BCDMON          0x0084
+#define     BCDYEAR         0x0088
+#define     CURTICNT        0x0090
+
+#define     TICK_TIMER_ENABLE   0x0100
+#define     TICNT_THRESHHOLD    2
+
+
+#define     RTC_ENABLE          0x0001
+
+#define     INTP_TICK_ENABLE    0x0001
+#define     INTP_ALM_ENABLE     0x0002
+
+#define     ALARM_INT_ENABLE    0x0040
+
+#define     RTC_BASE_FREQ       32768
+
+typedef struct Exynos4210RTCState {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+
+    /* registers */
+    uint32_t    reg_intp;
+    uint32_t    reg_rtccon;
+    uint32_t    reg_ticcnt;
+    uint32_t    reg_rtcalm;
+    uint32_t    reg_almsec;
+    uint32_t    reg_almmin;
+    uint32_t    reg_almhour;
+    uint32_t    reg_almday;
+    uint32_t    reg_almmon;
+    uint32_t    reg_almyear;
+    uint32_t    reg_curticcnt;
+
+    ptimer_state    *ptimer;        /* tick timer */
+    ptimer_state    *ptimer_1Hz;    /* clock timer */
+    uint32_t        freq;
+
+    qemu_irq        tick_irq;   /* Time Tick Generator irq */
+    qemu_irq        alm_irq;    /* alarm irq */
+
+    struct tm   current_tm;     /* current time */
+} Exynos4210RTCState;
+
+#define TICCKSEL(value) ((value & (0x0F << 4)) >> 4)
+
+/*** VMState ***/
+static const VMStateDescription vmstate_exynos4210_rtc_state = {
+    .name = "exynos4210.rtc",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(reg_intp, Exynos4210RTCState),
+        VMSTATE_UINT32(reg_rtccon, Exynos4210RTCState),
+        VMSTATE_UINT32(reg_ticcnt, Exynos4210RTCState),
+        VMSTATE_UINT32(reg_rtcalm, Exynos4210RTCState),
+        VMSTATE_UINT32(reg_almsec, Exynos4210RTCState),
+        VMSTATE_UINT32(reg_almmin, Exynos4210RTCState),
+        VMSTATE_UINT32(reg_almhour, Exynos4210RTCState),
+        VMSTATE_UINT32(reg_almday, Exynos4210RTCState),
+        VMSTATE_UINT32(reg_almmon, Exynos4210RTCState),
+        VMSTATE_UINT32(reg_almyear, Exynos4210RTCState),
+        VMSTATE_UINT32(reg_curticcnt, Exynos4210RTCState),
+        VMSTATE_PTIMER(ptimer, Exynos4210RTCState),
+        VMSTATE_PTIMER(ptimer_1Hz, Exynos4210RTCState),
+        VMSTATE_UINT32(freq, Exynos4210RTCState),
+        VMSTATE_INT32(current_tm.tm_sec, Exynos4210RTCState),
+        VMSTATE_INT32(current_tm.tm_min, Exynos4210RTCState),
+        VMSTATE_INT32(current_tm.tm_hour, Exynos4210RTCState),
+        VMSTATE_INT32(current_tm.tm_wday, Exynos4210RTCState),
+        VMSTATE_INT32(current_tm.tm_mday, Exynos4210RTCState),
+        VMSTATE_INT32(current_tm.tm_mon, Exynos4210RTCState),
+        VMSTATE_INT32(current_tm.tm_year, Exynos4210RTCState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static inline int rtc_to_bcd(int a, int count)
+{
+    int ret = (((a % 100) / 10) << 4) | (a % 10);
+    if (count == 3) {
+        ret |= (((a % 1000) / 100) << 8);
+    }
+    return ret;
+}
+
+static inline int rtc_from_bcd(uint32_t a, int count)
+{
+    int ret = (((a >> 4) & 0x0f) * 10) + (a & 0x0f);
+    if (count == 3) {
+        ret += (((a >> 8) & 0x0f) * 100);
+    }
+    return ret;
+}
+
+static void check_alarm_raise(Exynos4210RTCState *s)
+{
+    unsigned int alarm_raise = 0;
+    struct tm stm = s->current_tm;
+
+    if ((s->reg_rtcalm & 0x01) &&
+        (rtc_to_bcd(stm.tm_sec, 2) == s->reg_almsec)) {
+        alarm_raise = 1;
+    }
+    if ((s->reg_rtcalm & 0x02) &&
+        (rtc_to_bcd(stm.tm_min, 2) == s->reg_almmin)) {
+        alarm_raise = 1;
+    }
+    if ((s->reg_rtcalm & 0x04) &&
+        (rtc_to_bcd(stm.tm_hour, 2) == s->reg_almhour)) {
+        alarm_raise = 1;
+    }
+    if ((s->reg_rtcalm & 0x08) &&
+        (rtc_to_bcd(stm.tm_mday, 2) == s->reg_almday)) {
+        alarm_raise = 1;
+    }
+    if ((s->reg_rtcalm & 0x10) &&
+         (rtc_to_bcd(stm.tm_mon, 2) == s->reg_almmon)) {
+        alarm_raise = 1;
+    }
+    if ((s->reg_rtcalm & 0x20) &&
+        (rtc_to_bcd(stm.tm_year, 3) == s->reg_almyear)) {
+        alarm_raise = 1;
+    }
+
+    if (alarm_raise) {
+        DPRINTF("ALARM IRQ\n");
+        /* set irq status */
+        s->reg_intp |= INTP_ALM_ENABLE;
+        qemu_irq_raise(s->alm_irq);
+    }
+}
+
+/*
+ * RTC update frequency
+ * Parameters:
+ *     reg_value - current RTCCON register or his new value
+ */
+static void exynos4210_rtc_update_freq(Exynos4210RTCState *s,
+                                       uint32_t reg_value)
+{
+    uint32_t freq;
+
+    freq = s->freq;
+    /* set frequncy for time generator */
+    s->freq = RTC_BASE_FREQ / (1 << TICCKSEL(reg_value));
+
+    if (freq != s->freq) {
+        ptimer_set_freq(s->ptimer, s->freq);
+        DPRINTF("freq=%dHz\n", s->freq);
+    }
+}
+
+/* month is between 0 and 11. */
+static int get_days_in_month(int month, int year)
+{
+    static const int days_tab[12] = {
+        31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+    };
+    int d;
+    if ((unsigned)month >= 12) {
+        return 31;
+    }
+    d = days_tab[month];
+    if (month == 1) {
+        if ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) {
+            d++;
+        }
+    }
+    return d;
+}
+
+/* update 'tm' to the next second */
+static void rtc_next_second(struct tm *tm)
+{
+    int days_in_month;
+
+    tm->tm_sec++;
+    if ((unsigned)tm->tm_sec >= 60) {
+        tm->tm_sec = 0;
+        tm->tm_min++;
+        if ((unsigned)tm->tm_min >= 60) {
+            tm->tm_min = 0;
+            tm->tm_hour++;
+            if ((unsigned)tm->tm_hour >= 24) {
+                tm->tm_hour = 0;
+                /* next day */
+                tm->tm_wday++;
+                if ((unsigned)tm->tm_wday >= 7) {
+                    tm->tm_wday = 0;
+                }
+                days_in_month = get_days_in_month(tm->tm_mon,
+                                                  tm->tm_year + 1900);
+                tm->tm_mday++;
+                if (tm->tm_mday < 1) {
+                    tm->tm_mday = 1;
+                } else if (tm->tm_mday > days_in_month) {
+                    tm->tm_mday = 1;
+                    tm->tm_mon++;
+                    if (tm->tm_mon >= 12) {
+                        tm->tm_mon = 0;
+                        tm->tm_year++;
+                    }
+                }
+            }
+        }
+    }
+}
+
+/*
+ * tick handler
+ */
+static void exynos4210_rtc_tick(void *opaque)
+{
+    Exynos4210RTCState *s = (Exynos4210RTCState *)opaque;
+
+    DPRINTF("TICK IRQ\n");
+    /* set irq status */
+    s->reg_intp |= INTP_TICK_ENABLE;
+    /* raise IRQ */
+    qemu_irq_raise(s->tick_irq);
+
+    /* restart timer */
+    ptimer_set_count(s->ptimer, s->reg_ticcnt);
+    ptimer_run(s->ptimer, 1);
+}
+
+/*
+ * 1Hz clock handler
+ */
+static void exynos4210_rtc_1Hz_tick(void *opaque)
+{
+    Exynos4210RTCState *s = (Exynos4210RTCState *)opaque;
+
+    rtc_next_second(&s->current_tm);
+    /* DPRINTF("1Hz tick\n"); */
+
+    /* raise IRQ */
+    if (s->reg_rtcalm & ALARM_INT_ENABLE) {
+        check_alarm_raise(s);
+    }
+
+    ptimer_set_count(s->ptimer_1Hz, RTC_BASE_FREQ);
+    ptimer_run(s->ptimer_1Hz, 1);
+}
+
+/*
+ * RTC Read
+ */
+static uint64_t exynos4210_rtc_read(void *opaque, target_phys_addr_t offset,
+        unsigned size)
+{
+    uint32_t value = 0;
+    Exynos4210RTCState *s = (Exynos4210RTCState *)opaque;
+
+    switch (offset) {
+    case INTP:
+        value = s->reg_intp;
+        break;
+    case RTCCON:
+        value = s->reg_rtccon;
+        break;
+    case TICCNT:
+        value = s->reg_ticcnt;
+        break;
+    case RTCALM:
+        value = s->reg_rtcalm;
+        break;
+    case ALMSEC:
+        value = s->reg_almsec;
+        break;
+    case ALMMIN:
+        value = s->reg_almmin;
+        break;
+    case ALMHOUR:
+        value = s->reg_almhour;
+        break;
+    case ALMDAY:
+        value = s->reg_almday;
+        break;
+    case ALMMON:
+        value = s->reg_almmon;
+        break;
+    case ALMYEAR:
+        value = s->reg_almyear;
+        break;
+
+    case BCDSEC:
+        value = (uint32_t)rtc_to_bcd(s->current_tm.tm_sec, 2);
+        break;
+    case BDCMIN:
+        value = (uint32_t)rtc_to_bcd(s->current_tm.tm_min, 2);
+        break;
+    case BCDHOUR:
+        value = (uint32_t)rtc_to_bcd(s->current_tm.tm_hour, 2);
+        break;
+    case BCDDAYWEEK:
+        value = (uint32_t)rtc_to_bcd(s->current_tm.tm_wday, 2);
+        break;
+    case BCDDAY:
+        value = (uint32_t)rtc_to_bcd(s->current_tm.tm_mday, 2);
+        break;
+    case BCDMON:
+        value = (uint32_t)rtc_to_bcd(s->current_tm.tm_mon + 1, 2);
+        break;
+    case BCDYEAR:
+        value = (uint32_t)rtc_to_bcd(s->current_tm.tm_year, 3);
+        break;
+
+    case CURTICNT:
+        s->reg_curticcnt = ptimer_get_count(s->ptimer);
+        value = s->reg_curticcnt;
+        break;
+
+    default:
+        fprintf(stderr,
+                "[exynos4210.rtc: bad read offset " TARGET_FMT_plx "]\n",
+                offset);
+        break;
+    }
+    return value;
+}
+
+/*
+ * RTC Write
+ */
+static void exynos4210_rtc_write(void *opaque, target_phys_addr_t offset,
+        uint64_t value, unsigned size)
+{
+    Exynos4210RTCState *s = (Exynos4210RTCState *)opaque;
+
+    switch (offset) {
+    case INTP:
+        if (value & INTP_ALM_ENABLE) {
+            qemu_irq_lower(s->alm_irq);
+            s->reg_intp &= (~INTP_ALM_ENABLE);
+        }
+        if (value & INTP_TICK_ENABLE) {
+            qemu_irq_lower(s->tick_irq);
+            s->reg_intp &= (~INTP_TICK_ENABLE);
+        }
+        break;
+    case RTCCON:
+        if (value & RTC_ENABLE) {
+            exynos4210_rtc_update_freq(s, value);
+        }
+        if ((value & RTC_ENABLE) > (s->reg_rtccon & RTC_ENABLE)) {
+            /* clock timer */
+            ptimer_set_count(s->ptimer_1Hz, RTC_BASE_FREQ);
+            ptimer_run(s->ptimer_1Hz, 1);
+            DPRINTF("run clock timer\n");
+        }
+        if ((value & RTC_ENABLE) < (s->reg_rtccon & RTC_ENABLE)) {
+            /* tick timer */
+            ptimer_stop(s->ptimer);
+            /* clock timer */
+            ptimer_stop(s->ptimer_1Hz);
+            DPRINTF("stop all timers\n");
+        }
+        if (value & RTC_ENABLE) {
+            if ((value & TICK_TIMER_ENABLE) >
+                (s->reg_rtccon & TICK_TIMER_ENABLE) &&
+                (s->reg_ticcnt)) {
+                ptimer_set_count(s->ptimer, s->reg_ticcnt);
+                ptimer_run(s->ptimer, 1);
+                DPRINTF("run tick timer\n");
+            }
+            if ((value & TICK_TIMER_ENABLE) <
+                (s->reg_rtccon & TICK_TIMER_ENABLE)) {
+                ptimer_stop(s->ptimer);
+            }
+        }
+        s->reg_rtccon = value;
+        break;
+    case TICCNT:
+        if (value > TICNT_THRESHHOLD) {
+            s->reg_ticcnt = value;
+        } else {
+            fprintf(stderr,
+                    "[exynos4210.rtc: bad TICNT value %u ]\n",
+                    (uint32_t)value);
+        }
+        break;
+
+    case RTCALM:
+        s->reg_rtcalm = value;
+        break;
+    case ALMSEC:
+        s->reg_almsec = (value & 0x7f);
+        break;
+    case ALMMIN:
+        s->reg_almmin = (value & 0x7f);
+        break;
+    case ALMHOUR:
+        s->reg_almhour = (value & 0x3f);
+        break;
+    case ALMDAY:
+        s->reg_almday = (value & 0x3f);
+        break;
+    case ALMMON:
+        s->reg_almmon = (value & 0x1f);
+        break;
+    case ALMYEAR:
+        s->reg_almyear = (value & 0x0fff);
+        break;
+
+    case BCDSEC:
+        if (s->reg_rtccon & RTC_ENABLE) {
+            s->current_tm.tm_sec = rtc_from_bcd(value, 2);
+        }
+        break;
+    case BDCMIN:
+        if (s->reg_rtccon & RTC_ENABLE) {
+            s->current_tm.tm_min = rtc_from_bcd(value, 2);
+        }
+        break;
+    case BCDHOUR:
+        if (s->reg_rtccon & RTC_ENABLE) {
+            s->current_tm.tm_hour = rtc_from_bcd(value, 2);
+        }
+        break;
+    case BCDDAYWEEK:
+        if (s->reg_rtccon & RTC_ENABLE) {
+            s->current_tm.tm_wday = rtc_from_bcd(value, 2);
+        }
+        break;
+    case BCDDAY:
+        if (s->reg_rtccon & RTC_ENABLE) {
+            s->current_tm.tm_mday = rtc_from_bcd(value, 2);
+        }
+        break;
+    case BCDMON:
+        if (s->reg_rtccon & RTC_ENABLE) {
+            s->current_tm.tm_mon = rtc_from_bcd(value, 2) - 1;
+        }
+        break;
+    case BCDYEAR:
+        if (s->reg_rtccon & RTC_ENABLE) {
+            s->current_tm.tm_year = rtc_from_bcd(value, 3);
+        }
+        break;
+
+    default:
+        fprintf(stderr,
+                "[exynos4210.rtc: bad write offset " TARGET_FMT_plx "]\n",
+                offset);
+        break;
+
+    }
+}
+
+/*
+ * Set default values to timer fields and registers
+ */
+static void exynos4210_rtc_reset(DeviceState *d)
+{
+    Exynos4210RTCState *s = (Exynos4210RTCState *)d;
+
+#if EXYNOS4210_RTC_GETSYSTIME
+    time_t ct;
+
+    time(&ct);
+#ifndef _WIN32
+    gmtime_r(&ct, &s->current_tm);
+    DPRINTF("Get time from host: %d-%d-%d %2d:%02d:%02d\n",
+            s->current_tm.tm_year, s->current_tm.tm_mon, s->current_tm.tm_mday,
+            s->current_tm.tm_hour, s->current_tm.tm_min, s->current_tm.tm_sec);
+#endif
+#endif
+
+    s->reg_intp = 0;
+    s->reg_rtccon = 0;
+    s->reg_ticcnt = 0;
+    s->reg_rtcalm = 0;
+    s->reg_almsec = 0;
+    s->reg_almmin = 0;
+    s->reg_almhour = 0;
+    s->reg_almday = 0;
+    s->reg_almmon = 0;
+    s->reg_almyear = 0;
+
+    s->reg_curticcnt = 0;
+
+    exynos4210_rtc_update_freq(s, s->reg_rtccon);
+    ptimer_stop(s->ptimer);
+    ptimer_stop(s->ptimer_1Hz);
+}
+
+static const MemoryRegionOps exynos4210_rtc_ops = {
+    .read = exynos4210_rtc_read,
+    .write = exynos4210_rtc_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/*
+ * RTC timer initialization
+ */
+static int exynos4210_rtc_init(SysBusDevice *dev)
+{
+    Exynos4210RTCState *s = FROM_SYSBUS(Exynos4210RTCState, dev);
+    QEMUBH *bh;
+
+    bh = qemu_bh_new(exynos4210_rtc_tick, s);
+    s->ptimer = ptimer_init(bh);
+    ptimer_set_freq(s->ptimer, RTC_BASE_FREQ);
+    exynos4210_rtc_update_freq(s, 0);
+
+    bh = qemu_bh_new(exynos4210_rtc_1Hz_tick, s);
+    s->ptimer_1Hz = ptimer_init(bh);
+    ptimer_set_freq(s->ptimer_1Hz, RTC_BASE_FREQ);
+
+    sysbus_init_irq(dev, &s->alm_irq);
+    sysbus_init_irq(dev, &s->tick_irq);
+
+    memory_region_init_io(&s->iomem, &exynos4210_rtc_ops, s, "exynos4210-rtc",
+            EXYNOS4210_RTC_REG_MEM_SIZE);
+    sysbus_init_mmio(dev, &s->iomem);
+
+    return 0;
+}
+
+static void exynos4210_rtc_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+    k->init = exynos4210_rtc_init;
+    dc->reset = exynos4210_rtc_reset;
+    dc->vmsd = &vmstate_exynos4210_rtc_state;
+}
+
+static TypeInfo exynos4210_rtc_info = {
+    .name          = "exynos4210.rtc",
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(Exynos4210RTCState),
+    .class_init    = exynos4210_rtc_class_init,
+};
+
+static void exynos4210_rtc_register_types(void)
+{
+    type_register_static(&exynos4210_rtc_info);
+}
+
+type_init(exynos4210_rtc_register_types)
index ccc4780..155abdb 100644 (file)
 
 #include "exynos4210.h"
 
-#undef DEBUG_UART
-#undef DEBUG_UART_EXTEND
-#undef DEBUG_IRQ
-#undef DEBUG_Rx_DATA
-#undef DEBUG_Tx_DATA
-
 #define DEBUG_UART            0
 #define DEBUG_UART_EXTEND     0
 #define DEBUG_IRQ             0
 #define DEBUG_Rx_DATA         0
 #define DEBUG_Tx_DATA         0
 
-#if DEBUG_UART
+#if DEBUG_UART || DEBUG_UART_EXTEND
 #define  PRINT_DEBUG(fmt, args...)  \
         do { \
             fprintf(stderr, "  [%s:%d]   "fmt, __func__, __LINE__, ##args); \
@@ -160,6 +154,8 @@ static Exynos4210UartReg exynos4210_uart_regs[] = {
 #define UERSTAT_FRAME    0x4
 #define UERSTAT_BREAK    0x8
 
+#define TYPE_EXYNOS4210_UART "exynos4210.uart"
+
 typedef struct {
     uint8_t    *data;
     uint32_t    sp, rp; /* store and retrieve pointers */
@@ -182,7 +178,7 @@ typedef struct {
 } Exynos4210UartState;
 
 
-#if DEBUG_UART
+#if DEBUG_UART || DEBUG_UART_EXTEND
 /* Used only for debugging inside PRINT_DEBUG_... macros */
 static const char *exynos4210_uart_regname(target_phys_addr_t  offset)
 {
@@ -307,6 +303,7 @@ static void exynos4210_uart_update_parameters(Exynos4210UartState *s)
     uint64_t uclk_rate;
 
     if (s->reg[I_(UBRDIV)] == 0) {
+        PRINT_DEBUG("Baud rate division value is 0\n");
         return;
     }
 
@@ -332,7 +329,19 @@ static void exynos4210_uart_update_parameters(Exynos4210UartState *s)
 
     frame_size += data_bits + stop_bits;
 
-    uclk_rate = 24000000;
+    switch (s->channel) {
+    case 0:
+        uclk_rate = exynos4210_cmu_get_rate(EXYNOS4210_SCLK_UART0); break;
+    case 1:
+        uclk_rate = exynos4210_cmu_get_rate(EXYNOS4210_SCLK_UART1); break;
+    case 2:
+        uclk_rate = exynos4210_cmu_get_rate(EXYNOS4210_SCLK_UART2); break;
+    case 3:
+        uclk_rate = exynos4210_cmu_get_rate(EXYNOS4210_SCLK_UART3); 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);
@@ -348,6 +357,15 @@ static void exynos4210_uart_update_parameters(Exynos4210UartState *s)
                 s->channel, speed, parity, data_bits, stop_bits);
 }
 
+static void uclk_rate_changed(void *opaque)
+{
+    Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+
+    PRINT_DEBUG("Clock sclk_uart%d was changed\n", s->channel);
+
+    exynos4210_uart_update_parameters(s);
+}
+
 static void exynos4210_uart_write(void *opaque, target_phys_addr_t offset,
                                uint64_t val, unsigned size)
 {
@@ -542,6 +560,7 @@ static void exynos4210_uart_reset(DeviceState *dev)
             container_of(dev, Exynos4210UartState, busdev.qdev);
     int regs_number = sizeof(exynos4210_uart_regs)/sizeof(Exynos4210UartReg);
     int i;
+    Exynos4210Clock clock_id;
 
     for (i = 0; i < regs_number; i++) {
         s->reg[I_(exynos4210_uart_regs[i].offset)] =
@@ -551,6 +570,17 @@ static void exynos4210_uart_reset(DeviceState *dev)
     fifo_reset(&s->rx);
     fifo_reset(&s->tx);
 
+    switch (s->channel) {
+    case 0: clock_id = EXYNOS4210_SCLK_UART0; break;
+    case 1: clock_id = EXYNOS4210_SCLK_UART1; break;
+    case 2: clock_id = EXYNOS4210_SCLK_UART2; break;
+    case 3: clock_id = EXYNOS4210_SCLK_UART3; break;
+    default:
+        hw_error("Wrong channel number: %d.\n", s->channel);
+    }
+
+    exynos4210_register_clock_handler(uclk_rate_changed, clock_id, s);
+
     PRINT_DEBUG("UART%d: Rx FIFO size: %d\n", s->channel, s->rx.size);
 }
 
@@ -642,6 +672,7 @@ static int exynos4210_uart_init(SysBusDevice *dev)
     return 0;
 }
 
+
 static Property exynos4210_uart_properties[] = {
     DEFINE_PROP_CHR("chardev", Exynos4210UartState, chr),
     DEFINE_PROP_UINT32("channel", Exynos4210UartState, channel, 0),
@@ -662,7 +693,7 @@ static void exynos4210_uart_class_init(ObjectClass *klass, void *data)
 }
 
 static TypeInfo exynos4210_uart_info = {
-    .name          = "exynos4210.uart",
+    .name          = TYPE_EXYNOS4210_UART,
     .parent        = TYPE_SYS_BUS_DEVICE,
     .instance_size = sizeof(Exynos4210UartState),
     .class_init    = exynos4210_uart_class_init,
diff --git a/hw/wm8994.c b/hw/wm8994.c
new file mode 100644 (file)
index 0000000..9d3b9a0
--- /dev/null
@@ -0,0 +1,644 @@
+/*
+ * Samsung exynos4210 wm8994 driver
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ *  Vorobiov Stanislav <s.vorobiov@samsung.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License as published by the
+ *  Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ *  for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "wm8994.h"
+#include "hw.h"
+#include "i2c.h"
+#include "audio/audio.h"
+
+/* #define DEBUG_WM8994 */
+
+#ifdef DEBUG_WM8994
+#define DPRINTF(fmt, ...) \
+    do { \
+        fprintf(stdout, "WM8994: [%s:%d] " fmt, __func__, __LINE__, \
+            ## __VA_ARGS__); \
+    } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+/* Registers */
+#define WM8994_SOFTWARE_RESET                   0x00
+#define WM8994_POWER_MANAGEMENT_2               0x02
+#define WM8994_SPEAKER_VOLUME_LEFT              0x26 /* Speaker */
+#define WM8994_SPEAKER_VOLUME_RIGHT             0x27
+#define WM8994_CHIP_REVISION                    0x100
+#define WM8994_AIF1_RATE                        0x210
+#define WM8994_AIF1_CONTROL_1                   0x300
+#define WM8994_GPIO_1                           0x700
+#define WM8994_GPIO_3                           0x702
+#define WM8994_GPIO_4                           0x703
+#define WM8994_GPIO_5                           0x704
+#define WM8994_GPIO_6                           0x705
+#define WM8994_GPIO_7                           0x706
+#define WM8994_GPIO_8                           0x707
+#define WM8994_GPIO_9                           0x708
+#define WM8994_GPIO_10                          0x709
+#define WM8994_GPIO_11                          0x70A
+#define WM8994_PULL_CONTROL_2                   0x721
+#define WM8994_INPUT_MIXER_1                    0x15
+#define WM8994_LEFT_LINE_INPUT_1_2_VOLUME       0x18
+#define WM8994_LEFT_LINE_INPUT_3_4_VOLUME       0x19
+#define WM8994_RIGHT_LINE_INPUT_1_2_VOLUME      0x1A
+#define WM8994_RIGHT_LINE_INPUT_3_4_VOLUME      0x1B
+#define WM8994_LEFT_OUTPUT_VOLUME               0x1C
+#define WM8994_RIGHT_OUTPUT_VOLUME              0x1D
+#define WM8994_LEFT_OPGA_VOLUME                 0x20
+#define WM8994_RIGHT_OPGA_VOLUME                0x21
+#define WM8994_LINE_MIXER_1                     0x34
+#define WM8994_LINE_MIXER_2                     0x35
+#define WM8994_ANTIPOP_1                        0x38
+#define WM8994_LDO_1                            0x3B
+#define WM8994_LDO_2                            0x3C
+#define WM8994_AIF1_ADC1_LEFT_VOLUME            0x400
+#define WM8994_AIF1_ADC1_RIGHT_VOLUME           0x401
+#define WM8994_AIF1_DAC1_LEFT_VOLUME            0x402
+#define WM8994_AIF1_DAC1_RIGHT_VOLUME           0x403
+#define WM8994_AIF1_ADC2_LEFT_VOLUME            0x404
+#define WM8994_AIF1_ADC2_RIGHT_VOLUME           0x405
+#define WM8994_AIF1_DAC2_LEFT_VOLUME            0x406
+#define WM8994_AIF1_DAC2_RIGHT_VOLUME           0x407
+#define WM8994_AIF1_DAC1_FILTERS_2              0x421
+#define WM8994_AIF1_DAC2_FILTERS_2              0x423
+#define WM8994_AIF2_ADC_LEFT_VOLUME             0x500
+#define WM8994_AIF2_ADC_RIGHT_VOLUME            0x501
+#define WM8994_AIF2_DAC_LEFT_VOLUME             0x502
+#define WM8994_AIF2_DAC_RIGHT_VOLUME            0x503
+#define WM8994_AIF2_DAC_FILTERS_2               0x521
+#define WM8994_DAC1_LEFT_VOLUME                 0x610
+#define WM8994_DAC1_RIGHT_VOLUME                0x611
+#define WM8994_DAC2_LEFT_VOLUME                 0x612
+#define WM8994_DAC2_RIGHT_VOLUME                0x613
+#define WM8994_POWER_MANAGEMENT_1               0x01
+#define WM8994_POWER_MANAGEMENT_3               0x03
+#define WM8994_ANTIPOP_2                        0x39
+#define WM8994_AIF1_CLOCKING_1                  0x200
+#define WM8994_FLL1_CONTROL_1                   0x220
+#define WM8994_FLL1_CONTROL_2                   0x221
+#define WM8994_FLL1_CONTROL_3                   0x222
+#define WM8994_FLL1_CONTROL_4                   0x223
+#define WM8994_FLL1_CONTROL_5                   0x224
+#define WM8994_AIF1_MASTER_SLAVE                0x302
+#define WM8994_AIF1_BCLK                        0x303
+#define WM8994_AIF1DAC_LRCLK                    0x305
+#define WM8994_AIF1_DAC1_FILTERS_1              0x420
+
+#define WM8994_REGS_NUM                         0x74A
+
+/*
+ * R528 (0x210) - AIF1 Rate
+ */
+#define WM8994_AIF1_SR_MASK                  0x00F0  /* AIF1_SR - [7:4] */
+#define WM8994_AIF1_SR_SHIFT                      4  /* AIF1_SR - [7:4] */
+#define WM8994_AIF1_SR_WIDTH                      4  /* AIF1_SR - [7:4] */
+#define WM8994_AIF1CLK_RATE_MASK             0x000F  /* AIF1CLK_RATE - [3:0] */
+#define WM8994_AIF1CLK_RATE_SHIFT                 0  /* AIF1CLK_RATE - [3:0] */
+#define WM8994_AIF1CLK_RATE_WIDTH                 4  /* AIF1CLK_RATE - [3:0] */
+
+/*
+ * R768 (0x300) - AIF1 Control (1)
+ */
+#define WM8994_AIF1ADCL_SRC                     0x8000  /* AIF1ADCL_SRC */
+#define WM8994_AIF1ADCL_SRC_MASK                0x8000  /* AIF1ADCL_SRC */
+#define WM8994_AIF1ADCL_SRC_SHIFT                   15  /* AIF1ADCL_SRC */
+#define WM8994_AIF1ADCL_SRC_WIDTH                    1  /* AIF1ADCL_SRC */
+#define WM8994_AIF1ADCR_SRC                     0x4000  /* AIF1ADCR_SRC */
+#define WM8994_AIF1ADCR_SRC_MASK                0x4000  /* AIF1ADCR_SRC */
+#define WM8994_AIF1ADCR_SRC_SHIFT                   14  /* AIF1ADCR_SRC */
+#define WM8994_AIF1ADCR_SRC_WIDTH                    1  /* AIF1ADCR_SRC */
+#define WM8994_AIF1ADC_TDM                      0x2000  /* AIF1ADC_TDM */
+#define WM8994_AIF1ADC_TDM_MASK                 0x2000  /* AIF1ADC_TDM */
+#define WM8994_AIF1ADC_TDM_SHIFT                    13  /* AIF1ADC_TDM */
+#define WM8994_AIF1ADC_TDM_WIDTH                     1  /* AIF1ADC_TDM */
+#define WM8994_AIF1_BCLK_INV                    0x0100  /* AIF1_BCLK_INV */
+#define WM8994_AIF1_BCLK_INV_MASK               0x0100  /* AIF1_BCLK_INV */
+#define WM8994_AIF1_BCLK_INV_SHIFT                   8  /* AIF1_BCLK_INV */
+#define WM8994_AIF1_BCLK_INV_WIDTH                   1  /* AIF1_BCLK_INV */
+#define WM8994_AIF1_LRCLK_INV                   0x0080  /* AIF1_LRCLK_INV */
+#define WM8994_AIF1_LRCLK_INV_MASK              0x0080  /* AIF1_LRCLK_INV */
+#define WM8994_AIF1_LRCLK_INV_SHIFT                  7  /* AIF1_LRCLK_INV */
+#define WM8994_AIF1_LRCLK_INV_WIDTH                  1  /* AIF1_LRCLK_INV */
+#define WM8994_AIF1_WL_MASK                     0x0060  /* AIF1_WL - [6:5] */
+#define WM8994_AIF1_WL_SHIFT                         5  /* AIF1_WL - [6:5] */
+#define WM8994_AIF1_WL_WIDTH                         2  /* AIF1_WL - [6:5] */
+#define WM8994_AIF1_FMT_MASK                    0x0018  /* AIF1_FMT - [4:3] */
+#define WM8994_AIF1_FMT_SHIFT                        3  /* AIF1_FMT - [4:3] */
+#define WM8994_AIF1_FMT_WIDTH                        2  /* AIF1_FMT - [4:3] */
+
+/*
+ * R1056 (0x420) - AIF1 DAC1 Filters (1)
+ */
+#define WM8994_AIF1DAC1_MUTE                0x0200  /* AIF1DAC1_MUTE */
+#define WM8994_AIF1DAC1_MUTE_MASK           0x0200  /* AIF1DAC1_MUTE */
+#define WM8994_AIF1DAC1_MUTE_SHIFT               9  /* AIF1DAC1_MUTE */
+#define WM8994_AIF1DAC1_MUTE_WIDTH               1  /* AIF1DAC1_MUTE */
+#define WM8994_AIF1DAC1_MONO                0x0080  /* AIF1DAC1_MONO */
+#define WM8994_AIF1DAC1_MONO_MASK           0x0080  /* AIF1DAC1_MONO */
+#define WM8994_AIF1DAC1_MONO_SHIFT               7  /* AIF1DAC1_MONO */
+#define WM8994_AIF1DAC1_MONO_WIDTH               1  /* AIF1DAC1_MONO */
+#define WM8994_AIF1DAC1_MUTERATE            0x0020  /* AIF1DAC1_MUTERATE */
+#define WM8994_AIF1DAC1_MUTERATE_MASK       0x0020  /* AIF1DAC1_MUTERATE */
+#define WM8994_AIF1DAC1_MUTERATE_SHIFT           5  /* AIF1DAC1_MUTERATE */
+#define WM8994_AIF1DAC1_MUTERATE_WIDTH           1  /* AIF1DAC1_MUTERATE */
+#define WM8994_AIF1DAC1_UNMUTE_RAMP         0x0010  /* AIF1DAC1_UNMUTE_RAMP */
+#define WM8994_AIF1DAC1_UNMUTE_RAMP_MASK    0x0010  /* AIF1DAC1_UNMUTE_RAMP */
+#define WM8994_AIF1DAC1_UNMUTE_RAMP_SHIFT        4  /* AIF1DAC1_UNMUTE_RAMP */
+#define WM8994_AIF1DAC1_UNMUTE_RAMP_WIDTH        1  /* AIF1DAC1_UNMUTE_RAMP */
+#define WM8994_AIF1DAC1_DEEMP_MASK          0x0006  /* AIF1DAC1_DEEMP - [2:1] */
+#define WM8994_AIF1DAC1_DEEMP_SHIFT              1  /* AIF1DAC1_DEEMP - [2:1] */
+#define WM8994_AIF1DAC1_DEEMP_WIDTH              2  /* AIF1DAC1_DEEMP - [2:1] */
+
+/*
+ * R38 (0x26), R39 (0x27) - Speaker Volume
+ */
+#define WM8994_SPKOUT_VU                       0x0100  /* SPKOUT_VU */
+#define WM8994_SPKOUT_VU_MASK                  0x0100  /* SPKOUT_VU */
+#define WM8994_SPKOUT_VU_SHIFT                      8  /* SPKOUT_VU */
+#define WM8994_SPKOUT_VU_WIDTH                      1  /* SPKOUT_VU */
+#define WM8994_SPKOUTL_ZC                      0x0080  /* SPKOUTL_ZC */
+#define WM8994_SPKOUTL_ZC_MASK                 0x0080  /* SPKOUTL_ZC */
+#define WM8994_SPKOUTL_ZC_SHIFT                     7  /* SPKOUTL_ZC */
+#define WM8994_SPKOUTL_ZC_WIDTH                     1  /* SPKOUTL_ZC */
+#define WM8994_SPKOUTL_MUTE_N                  0x0040  /* SPKOUTL_MUTE_N */
+#define WM8994_SPKOUTL_MUTE_N_MASK             0x0040  /* SPKOUTL_MUTE_N */
+#define WM8994_SPKOUTL_MUTE_N_SHIFT                 6  /* SPKOUTL_MUTE_N */
+#define WM8994_SPKOUTL_MUTE_N_WIDTH                 1  /* SPKOUTL_MUTE_N */
+#define WM8994_SPKOUTL_VOL_MASK                0x003F  /* SPKOUTL_VOL - [5:0] */
+#define WM8994_SPKOUTL_VOL_SHIFT                    0  /* SPKOUTL_VOL - [5:0] */
+#define WM8994_SPKOUTL_VOL_WIDTH                    6  /* SPKOUTL_VOL - [5:0] */
+
+#define CODEC "wm8994"
+
+typedef struct {
+    const char *name;
+    uint16_t address;
+} WM8994Reg;
+
+#ifdef DEBUG_WM8994
+static WM8994Reg wm8994_regs[] = {
+    {"SOFTWARE_RESET",               WM8994_SOFTWARE_RESET },
+    {"POWER_MANAGEMENT_2",           WM8994_POWER_MANAGEMENT_2 },
+    {"SPEAKER_VOLUME_LEFT",          WM8994_SPEAKER_VOLUME_LEFT },
+    {"SPEAKER_VOLUME_RIGHT",         WM8994_SPEAKER_VOLUME_RIGHT },
+    {"CHIP_REVISION",                WM8994_CHIP_REVISION },
+    {"AIF1_RATE",                    WM8994_AIF1_RATE },
+    {"AIF1_CONTROL_1",               WM8994_AIF1_CONTROL_1 },
+    {"GPIO_1",                       WM8994_GPIO_1 },
+    {"GPIO_3",                       WM8994_GPIO_3 },
+    {"GPIO_4",                       WM8994_GPIO_4 },
+    {"GPIO_5",                       WM8994_GPIO_5 },
+    {"GPIO_6",                       WM8994_GPIO_6 },
+    {"GPIO_7",                       WM8994_GPIO_7 },
+    {"GPIO_8",                       WM8994_GPIO_8 },
+    {"GPIO_9",                       WM8994_GPIO_9 },
+    {"GPIO_10",                      WM8994_GPIO_10 },
+    {"GPIO_11",                      WM8994_GPIO_11 },
+    {"PULL_CONTROL_2",               WM8994_PULL_CONTROL_2 },
+    {"INPUT_MIXER_1",                WM8994_INPUT_MIXER_1 },
+    {"LEFT_LINE_INPUT_1_2_VOLUME",   WM8994_LEFT_LINE_INPUT_1_2_VOLUME },
+    {"LEFT_LINE_INPUT_3_4_VOLUME",   WM8994_LEFT_LINE_INPUT_3_4_VOLUME },
+    {"RIGHT_LINE_INPUT_1_2_VOLUME",  WM8994_RIGHT_LINE_INPUT_1_2_VOLUME },
+    {"RIGHT_LINE_INPUT_3_4_VOLUME",  WM8994_RIGHT_LINE_INPUT_3_4_VOLUME },
+    {"LEFT_OUTPUT_VOLUME",           WM8994_LEFT_OUTPUT_VOLUME },
+    {"RIGHT_OUTPUT_VOLUME",          WM8994_RIGHT_OUTPUT_VOLUME },
+    {"LEFT_OPGA_VOLUME",             WM8994_LEFT_OPGA_VOLUME },
+    {"RIGHT_OPGA_VOLUME",            WM8994_RIGHT_OPGA_VOLUME },
+    {"LINE_MIXER_1",                 WM8994_LINE_MIXER_1 },
+    {"LINE_MIXER_2",                 WM8994_LINE_MIXER_2 },
+    {"ANTIPOP_1",                    WM8994_ANTIPOP_1 },
+    {"LDO_1",                        WM8994_LDO_1 },
+    {"LDO_2",                        WM8994_LDO_2 },
+    {"AIF1_ADC1_LEFT_VOLUME",        WM8994_AIF1_ADC1_LEFT_VOLUME },
+    {"AIF1_ADC1_RIGHT_VOLUME",       WM8994_AIF1_ADC1_RIGHT_VOLUME },
+    {"AIF1_DAC1_LEFT_VOLUME",        WM8994_AIF1_DAC1_LEFT_VOLUME },
+    {"AIF1_DAC1_RIGHT_VOLUME",       WM8994_AIF1_DAC1_RIGHT_VOLUME },
+    {"AIF1_ADC2_LEFT_VOLUME",        WM8994_AIF1_ADC2_LEFT_VOLUME },
+    {"AIF1_ADC2_RIGHT_VOLUME",       WM8994_AIF1_ADC2_RIGHT_VOLUME },
+    {"AIF1_DAC2_LEFT_VOLUME",        WM8994_AIF1_DAC2_LEFT_VOLUME },
+    {"AIF1_DAC2_RIGHT_VOLUME",       WM8994_AIF1_DAC2_RIGHT_VOLUME },
+    {"AIF1_DAC1_FILTERS_2",          WM8994_AIF1_DAC1_FILTERS_2 },
+    {"AIF1_DAC2_FILTERS_2",          WM8994_AIF1_DAC2_FILTERS_2 },
+    {"AIF2_ADC_LEFT_VOLUME",         WM8994_AIF2_ADC_LEFT_VOLUME },
+    {"AIF2_ADC_RIGHT_VOLUME",        WM8994_AIF2_ADC_RIGHT_VOLUME },
+    {"AIF2_DAC_LEFT_VOLUME",         WM8994_AIF2_DAC_LEFT_VOLUME },
+    {"AIF2_DAC_RIGHT_VOLUME",        WM8994_AIF2_DAC_RIGHT_VOLUME },
+    {"AIF2_DAC_FILTERS_2",           WM8994_AIF2_DAC_FILTERS_2 },
+    {"DAC1_LEFT_VOLUME",             WM8994_DAC1_LEFT_VOLUME },
+    {"DAC1_RIGHT_VOLUME",            WM8994_DAC1_RIGHT_VOLUME },
+    {"DAC2_LEFT_VOLUME",             WM8994_DAC2_LEFT_VOLUME },
+    {"DAC2_RIGHT_VOLUME",            WM8994_DAC2_RIGHT_VOLUME },
+    {"POWER_MANAGEMENT_1",           WM8994_POWER_MANAGEMENT_1 },
+    {"POWER_MANAGEMENT_3",           WM8994_POWER_MANAGEMENT_3 },
+    {"ANTIPOP_2",                    WM8994_ANTIPOP_2 },
+    {"AIF1_CLOCKING_1",              WM8994_AIF1_CLOCKING_1 },
+    {"FLL1_CONTROL_1",               WM8994_FLL1_CONTROL_1 },
+    {"FLL1_CONTROL_2",               WM8994_FLL1_CONTROL_2 },
+    {"FLL1_CONTROL_3",               WM8994_FLL1_CONTROL_3 },
+    {"FLL1_CONTROL_4",               WM8994_FLL1_CONTROL_4 },
+    {"FLL1_CONTROL_5",               WM8994_FLL1_CONTROL_5 },
+    {"AIF1_MASTER_SLAVE",            WM8994_AIF1_MASTER_SLAVE },
+    {"AIF1_BCLK",                    WM8994_AIF1_BCLK },
+    {"AIF1DAC_LRCLK",                WM8994_AIF1DAC_LRCLK },
+    {"AIF1_DAC1_FILTERS_1",          WM8994_AIF1_DAC1_FILTERS_1 },
+};
+#endif
+
+typedef struct {
+    I2CSlave i2c;
+    uint16_t i2c_addr;
+    int i2c_idx;
+
+    uint16_t reg[WM8994_REGS_NUM];
+
+    void (*data_req)(void *, int);
+    void *opaque;
+
+    SWVoiceOut *dac_voice;
+    QEMUSoundCard card;
+
+    bool active;
+} WM8994State;
+
+static struct {
+    int val, rate;
+} srs[] = {
+    { 0,   8000 },
+    { 1,  11025 },
+    { 2,  12000 },
+    { 3,  16000 },
+    { 4,  22050 },
+    { 5,  24000 },
+    { 6,  32000 },
+    { 7,  44100 },
+    { 8,  48000 },
+    { 9,  88200 },
+    { 10, 96000 },
+};
+
+#ifdef DEBUG_WM8994
+static char wm8994_regname_buff[50];
+static const char *wm8994_regname(WM8994State *s,
+                                  uint16_t address)
+{
+    int i;
+
+    for (i = 0; i < ARRAY_SIZE(wm8994_regs); i++) {
+        if (address == wm8994_regs[i].address) {
+            return wm8994_regs[i].name;
+        }
+    }
+
+    snprintf(
+        &wm8994_regname_buff[0],
+        sizeof(wm8994_regname_buff),
+        "reg(%.4X)",
+        (unsigned int)address);
+
+    return &wm8994_regname_buff[0];
+}
+#endif
+
+static void wm8994_update_format(WM8994State *s);
+
+static int vmstate_wm8994_post_load(void *opaque, int version)
+{
+    WM8994State *s = opaque;
+
+    DPRINTF("enter\n");
+
+    wm8994_update_format(s);
+
+    return 0;
+}
+
+static const VMStateDescription vmstate_wm8994 = {
+    .name = CODEC,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .post_load = vmstate_wm8994_post_load,
+    .fields      = (VMStateField[]) {
+        VMSTATE_I2C_SLAVE(i2c, WM8994State),
+        VMSTATE_UINT16(i2c_addr, WM8994State),
+        VMSTATE_INT32(i2c_idx, WM8994State),
+        VMSTATE_UINT16_ARRAY(reg, WM8994State,
+                                  WM8994_REGS_NUM),
+        VMSTATE_BOOL(active, WM8994State),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static uint8_t wm8994_volume(WM8994State *s, uint16_t reg)
+{
+    return (s->reg[reg] >> WM8994_SPKOUTL_VOL_SHIFT) &
+           (WM8994_SPKOUTL_VOL_MASK >> WM8994_SPKOUTL_VOL_SHIFT);
+}
+
+static int wm8994_rate(WM8994State *s, uint16_t reg)
+{
+    int i;
+    int rate = (s->reg[reg] >> WM8994_AIF1_SR_SHIFT) &
+               (WM8994_AIF1_SR_MASK >> WM8994_AIF1_SR_SHIFT);
+
+    for (i = 0; i < ARRAY_SIZE(srs); ++i) {
+        if (rate == srs[i].val) {
+            return srs[i].rate;
+        }
+    }
+
+    return 0;
+}
+
+static audfmt_e wm8994_wl(WM8994State *s, uint16_t reg)
+{
+    int wl = (s->reg[reg] >> WM8994_AIF1_WL_SHIFT) &
+             (WM8994_AIF1_WL_MASK >> WM8994_AIF1_WL_SHIFT);
+
+    switch (wl) {
+    case 0: return AUD_FMT_S16;
+    case 3: return AUD_FMT_S32;
+    case 1:
+        /*
+         * Unsupported format (20 bits per channel), but kernel
+         * sets this sometimes when not playing anything
+         */
+        return AUD_FMT_S16;
+    case 2:
+        /*
+         * Unsupported format (24 bits per channel), but kernel
+         * sets this sometimes when not playing anything
+         */
+        return AUD_FMT_S16;
+    default:
+        hw_error("Unknown format\n");
+    }
+}
+
+static void wm8994_audio_out_cb(void *opaque, int free_b)
+{
+    WM8994State *s = (WM8994State *) opaque;
+
+    if (s->data_req) {
+        s->data_req(s->opaque, free_b);
+    }
+}
+
+static void wm8994_update_volume(WM8994State *s)
+{
+    int volume_left  = wm8994_volume(s, WM8994_SPEAKER_VOLUME_LEFT);
+    int volume_right = wm8994_volume(s, WM8994_SPEAKER_VOLUME_RIGHT);
+
+    if (s->dac_voice) {
+        /* Speaker */
+        AUD_set_volume_out(s->dac_voice, 1, volume_left, volume_right);
+    }
+}
+
+static void wm8994_update_active(WM8994State *s)
+{
+    if (s->dac_voice) {
+        AUD_set_active_out(s->dac_voice, s->active);
+    }
+}
+
+static void wm8994_update_format(WM8994State *s)
+{
+    struct audsettings out_fmt;
+
+    if (s->dac_voice) {
+        AUD_set_active_out(s->dac_voice, 0);
+    }
+
+    /* Setup output */
+    out_fmt.endianness = 0;
+    out_fmt.nchannels = 2;
+    out_fmt.freq = wm8994_rate(s, WM8994_AIF1_RATE);
+    out_fmt.fmt = wm8994_wl(s, WM8994_AIF1_CONTROL_1);
+
+    s->dac_voice =
+        AUD_open_out(&s->card,
+                     s->dac_voice,
+                     CODEC ".speaker",
+                     s,
+                     wm8994_audio_out_cb,
+                     &out_fmt);
+
+    wm8994_update_volume(s);
+
+    wm8994_update_active(s);
+}
+
+static void wm8994_reset(DeviceState *dev)
+{
+    WM8994State *s = FROM_I2C_SLAVE(WM8994State, I2C_SLAVE_FROM_QDEV(dev));
+
+    DPRINTF("enter\n");
+
+    s->i2c_addr = 0;
+    s->i2c_idx = 0;
+    s->active = 0;
+
+    memset(s->reg, 0, sizeof(s->reg));
+    s->reg[WM8994_SOFTWARE_RESET]       = 0x8994;
+    s->reg[WM8994_POWER_MANAGEMENT_2]   = 0x6000;
+    s->reg[WM8994_SPEAKER_VOLUME_LEFT]  = (0x79 << WM8994_SPKOUTL_VOL_SHIFT);
+    s->reg[WM8994_SPEAKER_VOLUME_RIGHT] = (0x79 << WM8994_SPKOUTL_VOL_SHIFT);
+    s->reg[WM8994_AIF1_RATE]            = (srs[0].val << WM8994_AIF1_SR_SHIFT);
+    s->reg[WM8994_AIF1_CONTROL_1]       = (0x0 << WM8994_AIF1_WL_SHIFT);
+
+    wm8994_update_format(s);
+}
+
+static int wm8994_tx(I2CSlave *i2c, uint8_t data)
+{
+    WM8994State *s = (WM8994State *) i2c;
+
+    if (s->i2c_idx < sizeof(s->i2c_addr)) {
+        s->i2c_addr |=
+            (uint16_t)data << ((sizeof(s->i2c_addr) - s->i2c_idx - 1) * 8);
+    } else {
+        uint16_t offset = s->i2c_idx - sizeof(s->i2c_addr);
+        uint16_t addr = s->i2c_addr + (offset / 2);
+
+        offset %= 2;
+
+        if (addr >= WM8994_REGS_NUM) {
+            hw_error("illegal write offset 0x%X\n", s->i2c_addr + offset);
+        } else {
+            s->reg[addr] &= ~(0xFF << ((1 - offset) * 8));
+            s->reg[addr] |= (uint16_t)data << ((1 - offset) * 8);
+
+            if (offset == 1) {
+                DPRINTF("0x%.4X -> %s\n",
+                        (unsigned int)s->reg[addr],
+                         wm8994_regname(s, addr));
+                switch (addr) {
+                case WM8994_SOFTWARE_RESET:
+                    wm8994_reset(&s->i2c.qdev);
+                    break;
+
+                case WM8994_SPEAKER_VOLUME_LEFT:
+                case WM8994_SPEAKER_VOLUME_RIGHT:
+                    wm8994_update_volume(s);
+                    break;
+
+                case WM8994_AIF1_RATE:
+                case WM8994_AIF1_CONTROL_1:
+                    wm8994_update_format(s);
+                    break;
+
+                default:
+                    break;
+                }
+            }
+        }
+    }
+
+    ++s->i2c_idx;
+
+    return 1;
+}
+
+static int wm8994_rx(I2CSlave *i2c)
+{
+    WM8994State *s = (WM8994State *) i2c;
+
+    if (s->i2c_idx >= sizeof(uint16_t)) {
+        DPRINTF("too much data requested (%i bytes)\n", (s->i2c_idx + 1));
+
+        return 0;
+    }
+
+    if (s->i2c_addr < WM8994_REGS_NUM) {
+        int ret = (s->reg[s->i2c_addr] >> ((1 - s->i2c_idx) * 8)) & 0xFF;
+
+        if (s->i2c_idx == (sizeof(uint16_t) - 1)) {
+            DPRINTF("%s -> 0x%.4X\n",
+                    wm8994_regname(s, s->i2c_addr),
+                    (unsigned int)s->reg[s->i2c_addr]);
+        }
+
+        ++s->i2c_idx;
+
+        return ret;
+    } else {
+        hw_error(
+            "wm8994: illegal read offset 0x%x\n", s->i2c_addr + s->i2c_idx);
+    }
+}
+
+static void wm8994_event(I2CSlave *i2c, enum i2c_event event)
+{
+    WM8994State *s = (WM8994State *) i2c;
+
+    switch (event) {
+    case I2C_START_SEND:
+        s->i2c_addr = 0;
+    case I2C_START_RECV:
+        s->i2c_idx = 0;
+        break;
+    default:
+        break;
+    }
+}
+
+void wm8994_set_active(DeviceState *dev, bool active)
+{
+    WM8994State *s = FROM_I2C_SLAVE(WM8994State, I2C_SLAVE_FROM_QDEV(dev));
+
+    DPRINTF("enter %d\n", active);
+
+    s->active = active;
+
+    wm8994_update_active(s);
+}
+
+void wm8994_data_req_set(DeviceState *dev,
+                         void (*data_req)(void*, int),
+                         void *opaque)
+{
+    WM8994State *s = FROM_I2C_SLAVE(WM8994State, I2C_SLAVE_FROM_QDEV(dev));
+
+    s->data_req = data_req;
+    s->opaque = opaque;
+}
+
+int wm8994_dac_write(DeviceState *dev, void *buf, int num_bytes)
+{
+    WM8994State *s = FROM_I2C_SLAVE(WM8994State, I2C_SLAVE_FROM_QDEV(dev));
+
+    int sent = 0;
+
+    if (!s->dac_voice) {
+        return 0;
+    }
+
+    while (sent < num_bytes) {
+        int ret = AUD_write(s->dac_voice,
+                            (uint8_t *)buf + sent,
+                            num_bytes - sent);
+
+        sent += ret;
+
+        if (ret == 0) {
+            break;
+        }
+    }
+
+    return sent;
+}
+
+static int wm8994_init(I2CSlave *i2c)
+{
+    WM8994State *s = FROM_I2C_SLAVE(WM8994State, i2c);
+
+    AUD_register_card(CODEC, &s->card);
+
+    return 0;
+}
+
+static void wm8994_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    I2CSlaveClass *sc = I2C_SLAVE_CLASS(klass);
+
+    sc->init = wm8994_init;
+    sc->event = wm8994_event;
+    sc->recv = wm8994_rx;
+    sc->send = wm8994_tx;
+    dc->reset = wm8994_reset;
+    dc->vmsd = &vmstate_wm8994;
+}
+
+static TypeInfo wm8994_info = {
+    .name          = "wm8994",
+    .parent        = TYPE_I2C_SLAVE,
+    .instance_size = sizeof(WM8994State),
+    .class_init    = wm8994_class_init,
+};
+
+static void wm8994_register_types(void)
+{
+    type_register_static(&wm8994_info);
+}
+
+type_init(wm8994_register_types)
diff --git a/hw/wm8994.h b/hw/wm8994.h
new file mode 100644 (file)
index 0000000..c4053f6
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef QEMU_HW_WM8994_H
+#define QEMU_HW_WM8994_H
+
+#include "qdev.h"
+
+void wm8994_set_active(DeviceState *dev, bool active);
+
+void wm8994_data_req_set(DeviceState *dev,
+                         void (*data_req)(void*, int),
+                         void *opaque);
+
+int wm8994_dac_write(DeviceState *dev, void *buf, int num_bytes);
+
+#endif