qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH v9 0/9] ARM: Samsung Exynos4210-based boards support.
@ 2012-01-20 10:53 Evgeny Voevodin
  2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 1/9] ARM: exynos4210: IRQ subsystem support Evgeny Voevodin
                   ` (8 more replies)
  0 siblings, 9 replies; 13+ messages in thread
From: Evgeny Voevodin @ 2012-01-20 10:53 UTC (permalink / raw)
  To: qemu-devel
  Cc: kyungmin.park, m.kozlov, jehyung.lee, d.solodkiy, Evgeny Voevodin

This set of patches adds support for Samsung S5PC210-based boards NURI and SMDKC210.
Tested on Linux kernel v3.x series.

Usage:
"-smp 2" option is obligatory for now.
To test emulation models user can launch Linux kernel v3.x configured with exynos4_defconfig configuration.
This will allow to boot kernel with initrd.

To get support of framebuffer by kernel, enable Samsung S3C framebuffer driver  in configuration file.
    Note: at current time mainline kernel does not support framebuffer properly
    and to get it worked kernel must be patched a little. This problem will be solved soon
    by Samsung kernel developers

To get support of ethernet for smdkc210 board by kernel, enable SMSC LAN911x/LAN921x families
embedded ethernet driver in configuration file.
    Note: NURI board does not emulate ethernet adapter since it is a mobile device. 


Examples:
Boot smdkc210 with NFS root support.
qemu-system-arm -kernel ./zImage -append "ip=dhcp root=/dev/nfs nfsroot=10.0.2.2:/srv/nfs/ rw " -M smdkc210 -smp 2

Boot smdkc210 with NFS root support and serial redirected to terminal
qemu-system-arm -kernel ./zImage -append "console=ttySAC0,115200n8 ip=dhcp root=/dev/nfs nfsroot=10.0.2.2:/srv/nfs/ rw " -serial stdio -M smdkc210 -smp 2

Boot NURI with initrd root support and serial redirected to terminal
qemu-system-arm -kernel ./zImage -append "console=ttySAC0,115200n8 root=/dev/ram rw " -serial stdio -M nuri -smp 2 -initrd ./rootfs.ext2


Changelog:
 v8->v9
 - exynos4210.c: secondary cpu bootloader memory region allocation is removed (it resides in already allocated IROM),
                 removed hack memory region for secondary CPU boot loader (PMU device added).
                 added l2x0 cache controller
 - exynos4210_pmu.c: PMU registers modelling device added to emulation. It is needed since PMU contains 
                     INFORM5 register which is used to boot the secondary CPUs.
 - exynos4_boards.c: indentation fix
 - exynos4210_uart.c: indentation fix, BREAK event handling code added, fixed size of allocated registers region
 - exynos4210_gic.c: number of IRQs passed to gic_init() due to last mainline update.
 - lan9118.: added VMSTATE fields due to last mainline update.
 v7->v8
 - exynos4_boards.c: lack of spaces fix
 - exynos4210_gic.c: lack of spaces fix
 - exynos4210_combiner.c: lack of spaces fix
 - exynos4210_uart.c: lack of spaces fix, indentation fix
 - exynos4210_mct.c: ULL suffix fix
 v6->v7
 - exynos4210_pwm.c: added usage of "ptimer.h"
 - exynos4210_mct.c: added usage of "ptimer.h"
 v5->v6
 - arm_boot.c, vexpress.c, realview.c: board should specify smp_bootreg_addr if its ncpu > 1
 - patch order changed, "boot secondary CPU" is included in "exynos boards" patch.
 - exynos4210_mct.c: usage of UINTX_MAX, removed excessive property list, fixed indentation,
                     fixed comments
 - exynos4210_pwm.c: spaces and brakcets in macros, removed excessive property list,
                     fixed indentation,
 - exynos4210_combiner.c: removed excessive reset, fixed indentation, fixed comments
 - exynos4210_gic.c: fixed indentation, fixed syntax
 - exynos4210_uart.c: fixed indentation, fixed syntax
 - exynos4210.c: fixed comments
 - Makefile.target: removed "\"
 - hw/exynos4210_fimd.c: rebased against current master: all manipulation with physical pages are dropped and
                         replaced with new memory API functions;

                         added three new members to winow's state: MemoryRegionSection to describe section
                         of system RAM containing current framebuffer, host pointer to framebuffer data and
                         framebuffer length;

                         mapping of framebuffer now performed only on framebuffer settings change
                         instead on every display update;

                         bytes swap in uint64 variable now performed with standard QEMU bswap64 function;

                         blencon register type changed to uint32_t;

                         fixed incorrect spelling of "palette" word;

                         if ... else statements in exynos4210_fimd_read() and exynos4210_fimd_write() are
                         replaced with switch() {} statement.
 
    

 v4->v5
 - hw/exynos4210_gic.c: Use memory aliases for CPU interface and Distributer.
   Excessive RW functions are removed.
 - hw/exynos4210_pwm.c and hw/exynos4210_mct.c: Saving of timers added.
 - hw/exynos4210_uart.c: VMSTATE version_id fixed.
 v3->v4
 - Split Exynos SOC and boards.
 - Temporary removed SD and CMU support to post later.
 - Lan9118 remarks took into account.
 - Secondary CPU bootloader remarks took into account.
 - PWM remarks took into account.
 - UART remarks took into account.
 v2->v3
 - Reverted hw/arm_gic.c modification
 - Added IRQ Gate to Exynos4210 board.

Evgeny Voevodin (6):
  ARM: exynos4210: IRQ subsystem support.
  ARM: Samsung exynos4210-based boards emulation
  ARM: exynos4210: PWM support.
  ARM: exynos4210: MCT support.
  hw/lan9118: Add basic 16-bit mode support.
  hw/exynos4210.c: Add LAN support for SMDKC210.

Maksim Kozlov (2):
  ARM: exynos4210: UART support
  ARM: exynos4210: Added PMU register model.

Mitsyanko Igor (1):
  Exynos4210: added display controller implementation

 Makefile.target          |    3 +
 hw/exynos4210.c          |  272 +++++++
 hw/exynos4210.h          |  131 ++++
 hw/exynos4210_combiner.c |  472 ++++++++++++
 hw/exynos4210_fimd.c     | 1924 ++++++++++++++++++++++++++++++++++++++++++++++
 hw/exynos4210_gic.c      |  436 +++++++++++
 hw/exynos4210_mct.c      | 1479 +++++++++++++++++++++++++++++++++++
 hw/exynos4210_pmu.c      |  549 +++++++++++++
 hw/exynos4210_pwm.c      |  413 ++++++++++
 hw/exynos4210_uart.c     |  661 ++++++++++++++++
 hw/exynos4_boards.c      |  166 ++++
 hw/lan9118.c             |  122 +++-
 12 files changed, 6623 insertions(+), 5 deletions(-)
 create mode 100644 hw/exynos4210.c
 create mode 100644 hw/exynos4210.h
 create mode 100644 hw/exynos4210_combiner.c
 create mode 100644 hw/exynos4210_fimd.c
 create mode 100644 hw/exynos4210_gic.c
 create mode 100644 hw/exynos4210_mct.c
 create mode 100644 hw/exynos4210_pmu.c
 create mode 100644 hw/exynos4210_pwm.c
 create mode 100644 hw/exynos4210_uart.c
 create mode 100644 hw/exynos4_boards.c

-- 
1.7.4.1

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

* [Qemu-devel] [PATCH v9 1/9] ARM: exynos4210: IRQ subsystem support.
  2012-01-20 10:53 [Qemu-devel] [PATCH v9 0/9] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
@ 2012-01-20 10:53 ` Evgeny Voevodin
  2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 2/9] ARM: Samsung exynos4210-based boards emulation Evgeny Voevodin
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 13+ messages in thread
From: Evgeny Voevodin @ 2012-01-20 10:53 UTC (permalink / raw)
  To: qemu-devel
  Cc: kyungmin.park, m.kozlov, jehyung.lee, d.solodkiy, Evgeny Voevodin


Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 Makefile.target          |    1 +
 hw/exynos4210.h          |   82 ++++++++
 hw/exynos4210_combiner.c |  472 ++++++++++++++++++++++++++++++++++++++++++++++
 hw/exynos4210_gic.c      |  436 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 991 insertions(+), 0 deletions(-)
 create mode 100644 hw/exynos4210.h
 create mode 100644 hw/exynos4210_combiner.c
 create mode 100644 hw/exynos4210_gic.c

diff --git a/Makefile.target b/Makefile.target
index 06d79b8..4ac257e 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -339,6 +339,7 @@ obj-arm-y = integratorcp.o versatilepb.o arm_pic.o arm_timer.o
 obj-arm-y += arm_boot.o pl011.o pl031.o pl050.o pl080.o pl110.o pl181.o pl190.o
 obj-arm-y += versatile_pci.o
 obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
+obj-arm-y += exynos4210_gic.o exynos4210_combiner.o
 obj-arm-y += arm_l2x0.o
 obj-arm-y += arm_mptimer.o
 obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
diff --git a/hw/exynos4210.h b/hw/exynos4210.h
new file mode 100644
index 0000000..cef264b
--- /dev/null
+++ b/hw/exynos4210.h
@@ -0,0 +1,82 @@
+/*
+ *  Samsung exynos4210 SoC emulation
+ *
+ *  Copyright (c) 2011 Samsung Electronics Co., Ltd. All rights reserved.
+ *    Maksim Kozlov <m.kozlov@samsung.com>
+ *    Evgeny Voevodin <e.voevodin@samsung.com>
+ *    Igor Mitsyanko <i.mitsyanko@samsung.com>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License as published by the
+ *  Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ *  for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#ifndef EXYNOS4210_H_
+#define EXYNOS4210_H_
+
+#include "qemu-common.h"
+#include "memory.h"
+
+#define EXYNOS4210_MAX_CPUS                 2
+
+/*
+ * exynos4210 IRQ subsystem stub definitions.
+ */
+#define EXYNOS4210_IRQ_GATE_NINPUTS 8
+
+#define EXYNOS4210_MAX_INT_COMBINER_OUT_IRQ  64
+#define EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ  16
+#define EXYNOS4210_MAX_INT_COMBINER_IN_IRQ   \
+    (EXYNOS4210_MAX_INT_COMBINER_OUT_IRQ * 8)
+#define EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ   \
+    (EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ * 8)
+
+#define EXYNOS4210_COMBINER_GET_IRQ_NUM(grp, bit)  ((grp)*8 + (bit))
+#define EXYNOS4210_COMBINER_GET_GRP_NUM(irq)       ((irq) / 8)
+#define EXYNOS4210_COMBINER_GET_BIT_NUM(irq) \
+    ((irq) - 8 * EXYNOS4210_COMBINER_GET_GRP_NUM(irq))
+
+/* IRQs number for external and internal GIC */
+#define EXYNOS4210_EXT_GIC_NIRQ     (160-32)
+#define EXYNOS4210_INT_GIC_NIRQ     64
+
+typedef struct Exynos4210Irq {
+    qemu_irq int_combiner_irq[EXYNOS4210_MAX_INT_COMBINER_IN_IRQ];
+    qemu_irq ext_combiner_irq[EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ];
+    qemu_irq int_gic_irq[EXYNOS4210_INT_GIC_NIRQ];
+    qemu_irq ext_gic_irq[EXYNOS4210_EXT_GIC_NIRQ];
+    qemu_irq board_irqs[EXYNOS4210_MAX_INT_COMBINER_IN_IRQ];
+} Exynos4210Irq;
+
+/* Initialize exynos4210 IRQ subsystem stub */
+qemu_irq *exynos4210_init_irq(Exynos4210Irq *env);
+
+/* Initialize board IRQs.
+ * These IRQs contain splitted Int/External Combiner and External Gic IRQs */
+void exynos4210_init_board_irqs(Exynos4210Irq *s);
+
+/* Get IRQ number from exynos4210 IRQ subsystem stub.
+ * To identify IRQ source use internal combiner group and bit number
+ *  grp - group number
+ *  bit - bit number inside group */
+uint32_t exynos4210_get_irq(uint32_t grp, uint32_t bit);
+
+/*
+ * Get Combiner input GPIO into irqs structure
+ */
+void exynos4210_combiner_get_gpioin(Exynos4210Irq *irqs, DeviceState *dev,
+        int ext);
+
+#endif /* EXYNOS4210_H_ */
diff --git a/hw/exynos4210_combiner.c b/hw/exynos4210_combiner.c
new file mode 100644
index 0000000..84e692b
--- /dev/null
+++ b/hw/exynos4210_combiner.c
@@ -0,0 +1,472 @@
+/*
+ * Samsung exynos4210 Interrupt Combiner
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ *
+ * Evgeny Voevodin <e.voevodin@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Exynos4210 Combiner represents an OR gate for SOC's IRQ lines. It combines
+ * IRQ sources into groups and provides signal output to GIC from each group. It
+ * is driven by common mask and enable/disable logic. Take a note that not all
+ * IRQs are passed to GIC through Combiner.
+ */
+
+#include "sysbus.h"
+
+#include "exynos4210.h"
+
+//#define DEBUG_COMBINER
+
+#ifdef DEBUG_COMBINER
+#define DPRINTF(fmt, ...) \
+        do { fprintf(stdout, "COMBINER: [%s:%d] " fmt, __func__ , __LINE__, \
+                ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+#define    IIC_NGRP        64            /* Internal Interrupt Combiner
+                                            Groups number */
+#define    IIC_NIRQ        (IIC_NGRP * 8)/* Internal Interrupt Combiner
+                                            Interrupts number */
+#define IIC_REGION_SIZE    0x108         /* Size of memory mapped region */
+#define IIC_REGSET_SIZE    0x41
+
+/*
+ * Combiner registers
+ */
+struct CombinerReg {
+    uint32_t iiesr;            /* Internal Interrupt Enable Set register */
+    uint32_t iiecr;            /* Internal Interrupt Enable Clear register */
+    uint32_t iistr;            /* Internal Interrupt Status register.
+     * Shows status of interrupt pending BEFORE masking
+     */
+    uint32_t iimsr;            /* Internal Interrupt Mask Status register.
+     * Shows status of interrupt pending AFTER masking
+     */
+};
+
+/*
+ * State for each output signal of internal combiner
+ */
+typedef struct CombinerGroupState {
+    uint8_t src_mask;            /* 1 - source enabled, 0 - disabled */
+    uint8_t src_pending;        /* Pending source interrupts before masking */
+} CombinerGroupState;
+
+typedef struct Exynos4210CombinerState {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+
+    struct CombinerGroupState group[IIC_NGRP];
+    uint32_t reg_set[IIC_REGSET_SIZE];
+    uint32_t icipsr[2];
+    uint32_t external;          /* 1 means that this combiner is external */
+
+    qemu_irq output_irq[IIC_NGRP];
+} Exynos4210CombinerState;
+
+static const VMStateDescription VMState_Exynos4210CombinerGroupState = {
+    .name = "exynos4210.combiner.groupstate",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT8(src_mask, CombinerGroupState),
+        VMSTATE_UINT8(src_pending, CombinerGroupState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static const VMStateDescription VMState_Exynos4210Combiner = {
+    .name = "exynos4210.combiner",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_STRUCT_ARRAY(group, Exynos4210CombinerState, IIC_NGRP, 0,
+        VMState_Exynos4210CombinerGroupState, CombinerGroupState),
+        VMSTATE_UINT32_ARRAY(reg_set, Exynos4210CombinerState,
+                IIC_REGSET_SIZE),
+        VMSTATE_UINT32_ARRAY(icipsr, Exynos4210CombinerState, 2),
+        VMSTATE_UINT32(external, Exynos4210CombinerState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+/*
+ * Get Combiner input GPIO into irqs structure
+ */
+void exynos4210_combiner_get_gpioin(Exynos4210Irq *irqs, DeviceState *dev,
+        int ext)
+{
+    int n;
+    int bit;
+    int max;
+    qemu_irq *irq;
+
+    max = ext ? EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ :
+        EXYNOS4210_MAX_INT_COMBINER_IN_IRQ;
+    irq = ext ? irqs->ext_combiner_irq : irqs->int_combiner_irq;
+
+    /*
+     * Some IRQs of Int/External Combiner are going to two Combiners groups,
+     * so let split them.
+     */
+    for (n = 0; n < max; n++) {
+
+        bit = EXYNOS4210_COMBINER_GET_BIT_NUM(n);
+
+        switch (n) {
+        /* MDNIE_LCD1 INTG1 */
+        case EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 0) ...
+             EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 3):
+            irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+                    irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(0, bit + 4)]);
+            continue;
+
+        /* TMU INTG3 */
+        case EXYNOS4210_COMBINER_GET_IRQ_NUM(3, 4):
+            irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+                    irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(2, bit)]);
+            continue;
+
+        /* LCD1 INTG12 */
+        case EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 0) ...
+             EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 3):
+            irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+                    irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(11, bit + 4)]);
+            continue;
+
+        /* Multi-Core Timer INTG12 */
+        case EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 4) ...
+             EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 8):
+               irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+                       irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+            continue;
+
+        /* Multi-Core Timer INTG35 */
+        case EXYNOS4210_COMBINER_GET_IRQ_NUM(35, 4) ...
+             EXYNOS4210_COMBINER_GET_IRQ_NUM(35, 8):
+            irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+                    irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+            continue;
+
+        /* Multi-Core Timer INTG51 */
+        case EXYNOS4210_COMBINER_GET_IRQ_NUM(51, 4) ...
+             EXYNOS4210_COMBINER_GET_IRQ_NUM(51, 8):
+            irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+                    irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+            continue;
+
+        /* Multi-Core Timer INTG53 */
+        case EXYNOS4210_COMBINER_GET_IRQ_NUM(53, 4) ...
+             EXYNOS4210_COMBINER_GET_IRQ_NUM(53, 8):
+            irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+                    irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+            continue;
+        }
+
+        irq[n] = qdev_get_gpio_in(dev, n);
+    }
+}
+
+static uint64_t
+exynos4210_combiner_read(void *opaque, target_phys_addr_t offset, unsigned size)
+{
+    struct Exynos4210CombinerState *s =
+            (struct Exynos4210CombinerState *)opaque;
+    uint32_t req_quad_base_n;    /* Base of registers quad. Multiply it by 4 and
+                                   get a start of corresponding group quad */
+    uint32_t grp_quad_base_n;    /* Base of group quad */
+    uint32_t reg_n;              /* Register number inside the quad */
+    uint32_t val;
+
+    if (s->external && (offset > 0x3c && offset != 0x100)) {
+        hw_error("exynos4210.combiner: unallowed read access at offset 0x"
+                TARGET_FMT_plx "\n", offset);
+    }
+
+    req_quad_base_n = offset >> 4;
+    grp_quad_base_n = req_quad_base_n << 2;
+    reg_n = (offset - (req_quad_base_n << 4)) >> 2;
+
+    if (req_quad_base_n >= IIC_NGRP) {
+        /* Read of ICIPSR register */
+        return s->icipsr[reg_n];
+    }
+
+    val = 0;
+
+    switch (reg_n) {
+    /* IISTR */
+    case 2:
+        val |= s->group[grp_quad_base_n].src_pending;
+        val |= s->group[grp_quad_base_n + 1].src_pending << 8;
+        val |= s->group[grp_quad_base_n + 2].src_pending << 16;
+        val |= s->group[grp_quad_base_n + 3].src_pending << 24;
+        break;
+    /* IIMSR */
+    case 3:
+        val |= s->group[grp_quad_base_n].src_mask &
+        s->group[grp_quad_base_n].src_pending;
+        val |= (s->group[grp_quad_base_n + 1].src_mask &
+                s->group[grp_quad_base_n + 1].src_pending) << 8;
+        val |= (s->group[grp_quad_base_n + 2].src_mask &
+                s->group[grp_quad_base_n + 2].src_pending) << 16;
+        val |= (s->group[grp_quad_base_n + 3].src_mask &
+                s->group[grp_quad_base_n + 3].src_pending) << 24;
+        break;
+    default:
+        if (offset >> 2 >= IIC_REGSET_SIZE) {
+            hw_error("exynos4210.combiner: overflow of reg_set by 0x"
+                    TARGET_FMT_plx "offset\n", offset);
+        }
+        val = s->reg_set[offset >> 2];
+        return 0;
+    }
+    return val;
+}
+
+static void exynos4210_combiner_update(void *opaque, uint8_t group_n)
+{
+    struct Exynos4210CombinerState *s =
+            (struct Exynos4210CombinerState *)opaque;
+
+    /* Send interrupt if needed */
+    if (s->group[group_n].src_mask & s->group[group_n].src_pending) {
+#ifdef DEBUG_COMBINER
+        if (group_n != 26) {
+            /* skip uart */
+            DPRINTF("%s raise IRQ[%d]\n", s->external ? "EXT" : "INT", group_n);
+        }
+#endif
+
+        /* Set Combiner interrupt pending status after masking */
+        if (group_n >= 32) {
+            s->icipsr[1] |= 1 << (group_n - 32);
+        } else {
+            s->icipsr[0] |= 1 << group_n;
+        }
+
+        qemu_irq_raise(s->output_irq[group_n]);
+    } else {
+#ifdef DEBUG_COMBINER
+        if (group_n != 26) {
+            /* skip uart */
+            DPRINTF("%s lower IRQ[%d]\n", s->external ? "EXT" : "INT", group_n);
+        }
+#endif
+
+        /* Set Combiner interrupt pending status after masking */
+        if (group_n >= 32) {
+            s->icipsr[1] &= ~(1 << (group_n - 32));
+        } else {
+            s->icipsr[0] &= ~(1 << group_n);
+        }
+
+        qemu_irq_lower(s->output_irq[group_n]);
+    }
+}
+
+static void exynos4210_combiner_write(void *opaque, target_phys_addr_t offset,
+        uint64_t val, unsigned size)
+{
+    struct Exynos4210CombinerState *s =
+            (struct Exynos4210CombinerState *)opaque;
+    uint32_t req_quad_base_n;    /* Base of registers quad. Multiply it by 4 and
+                                   get a start of corresponding group quad */
+    uint32_t grp_quad_base_n;    /* Base of group quad */
+    uint32_t reg_n;              /* Register number inside the quad */
+
+    if (s->external && (offset > 0x3c && offset != 0x100)) {
+        hw_error("exynos4210.combiner: unallowed write access at offset 0x"
+                TARGET_FMT_plx "\n", offset);
+    }
+
+    req_quad_base_n = offset >> 4;
+    grp_quad_base_n = req_quad_base_n << 2;
+    reg_n = (offset - (req_quad_base_n << 4)) >> 2;
+
+    if (req_quad_base_n >= IIC_NGRP) {
+        hw_error("exynos4210.combiner: unallowed write access at offset 0x"
+                TARGET_FMT_plx "\n", offset);
+        return;
+    }
+
+    if (reg_n > 1) {
+        hw_error("exynos4210.combiner: unallowed write access at offset 0x"
+                TARGET_FMT_plx "\n", offset);
+        return;
+    }
+
+    if (offset >> 2 >= IIC_REGSET_SIZE) {
+        hw_error("exynos4210.combiner: overflow of reg_set by 0x"
+                TARGET_FMT_plx "offset\n", offset);
+    }
+    s->reg_set[offset >> 2] = val;
+
+    switch (reg_n) {
+    /* IIESR */
+    case 0:
+        /* FIXME: what if irq is pending, allowed by mask, and we allow it
+         * again. Interrupt will rise again! */
+
+        DPRINTF("%s enable IRQ for groups %d, %d, %d, %d\n",
+                s->external ? "EXT" : "INT",
+                grp_quad_base_n,
+                grp_quad_base_n + 1,
+                grp_quad_base_n + 2,
+                grp_quad_base_n + 3);
+
+        /* Enable interrupt sources */
+        s->group[grp_quad_base_n].src_mask |= val & 0xFF;
+        s->group[grp_quad_base_n + 1].src_mask |= (val & 0xFF00) >> 8;
+        s->group[grp_quad_base_n + 2].src_mask |= (val & 0xFF0000) >> 16;
+        s->group[grp_quad_base_n + 3].src_mask |= (val & 0xFF000000) >> 24;
+
+        exynos4210_combiner_update(s, grp_quad_base_n);
+        exynos4210_combiner_update(s, grp_quad_base_n + 1);
+        exynos4210_combiner_update(s, grp_quad_base_n + 2);
+        exynos4210_combiner_update(s, grp_quad_base_n + 3);
+        break;
+        /* IIECR */
+    case 1:
+        DPRINTF("%s disable IRQ for groups %d, %d, %d, %d\n",
+                s->external ? "EXT" : "INT",
+                grp_quad_base_n,
+                grp_quad_base_n + 1,
+                grp_quad_base_n + 2,
+                grp_quad_base_n + 3);
+
+        /* Disable interrupt sources */
+        s->group[grp_quad_base_n].src_mask &= ~(val & 0xFF);
+        s->group[grp_quad_base_n + 1].src_mask &= ~((val & 0xFF00) >> 8);
+        s->group[grp_quad_base_n + 2].src_mask &= ~((val & 0xFF0000) >> 16);
+        s->group[grp_quad_base_n + 3].src_mask &= ~((val & 0xFF000000) >> 24);
+
+        exynos4210_combiner_update(s, grp_quad_base_n);
+        exynos4210_combiner_update(s, grp_quad_base_n + 1);
+        exynos4210_combiner_update(s, grp_quad_base_n + 2);
+        exynos4210_combiner_update(s, grp_quad_base_n + 3);
+        break;
+    default:
+        hw_error("exynos4210.combiner: unallowed write access at offset 0x"
+                TARGET_FMT_plx "\n", offset);
+        break;
+    }
+
+    return;
+}
+
+/* Get combiner group and bit from irq number */
+static uint8_t get_combiner_group_and_bit(int irq, uint8_t *bit)
+{
+    *bit = irq - ((irq >> 3) << 3);
+    return irq >> 3;
+}
+
+/* Process a change in an external IRQ input.  */
+static void exynos4210_combiner_handler(void *opaque, int irq, int level)
+{
+    struct Exynos4210CombinerState *s =
+            (struct Exynos4210CombinerState *)opaque;
+    uint8_t bit_n, group_n;
+
+    group_n = get_combiner_group_and_bit(irq, &bit_n);
+
+    if (s->external && group_n >= EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ) {
+        DPRINTF("%s unallowed IRQ group 0x%x\n", s->external ? "EXT" : "INT"
+                , group_n);
+        return;
+    }
+
+    if (level) {
+        s->group[group_n].src_pending |= 1 << bit_n;
+    } else {
+        s->group[group_n].src_pending &= ~(1 << bit_n);
+    }
+
+    exynos4210_combiner_update(s, group_n);
+
+    return;
+}
+
+static void exynos4210_combiner_reset(DeviceState *d)
+{
+    struct Exynos4210CombinerState *s = (struct Exynos4210CombinerState *)d;
+
+    memset(&s->group, 0, sizeof(s->group));
+    memset(&s->reg_set, 0, sizeof(s->reg_set));
+
+    s->reg_set[0xC0 >> 2] = 0x01010101;
+    s->reg_set[0xC4 >> 2] = 0x01010101;
+    s->reg_set[0xD0 >> 2] = 0x01010101;
+    s->reg_set[0xD4 >> 2] = 0x01010101;
+}
+
+static const MemoryRegionOps exynos4210_combiner_ops = {
+    .read = exynos4210_combiner_read,
+    .write = exynos4210_combiner_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/*
+ * Internal Combiner initialization.
+ */
+static int exynos4210_combiner_init(SysBusDevice *dev)
+{
+    unsigned int i;
+    struct Exynos4210CombinerState *s =
+            FROM_SYSBUS(struct Exynos4210CombinerState, dev);
+
+    /* Allocate general purpose input signals and connect a handler to each of
+     * them */
+    qdev_init_gpio_in(&s->busdev.qdev, exynos4210_combiner_handler, IIC_NIRQ);
+
+    /* Connect SysBusDev irqs to device specific irqs */
+    for (i = 0; i < IIC_NIRQ; i++) {
+        sysbus_init_irq(dev, &s->output_irq[i]);
+    }
+
+    memory_region_init_io(&s->iomem, &exynos4210_combiner_ops, s,
+            "exynos4210-combiner", IIC_REGION_SIZE);
+    sysbus_init_mmio(dev, &s->iomem);
+
+    return 0;
+}
+
+static SysBusDeviceInfo exynos4210_combiner_info = {
+    .qdev.name  = "exynos4210.combiner",
+    .qdev.size  = sizeof(struct Exynos4210CombinerState),
+    .qdev.reset = exynos4210_combiner_reset,
+    .qdev.vmsd = &VMState_Exynos4210Combiner,
+    .init = exynos4210_combiner_init,
+    .qdev.props = (Property[]) {
+        DEFINE_PROP_UINT32("external", Exynos4210CombinerState, external, 0),
+        DEFINE_PROP_END_OF_LIST(),
+    }
+};
+
+static void exynos4210_combiner_register_devices(void)
+{
+    sysbus_register_withprop(&exynos4210_combiner_info);
+}
+
+device_init(exynos4210_combiner_register_devices)
diff --git a/hw/exynos4210_gic.c b/hw/exynos4210_gic.c
new file mode 100644
index 0000000..60f650f
--- /dev/null
+++ b/hw/exynos4210_gic.c
@@ -0,0 +1,436 @@
+/*
+ * Samsung exynos4210 GIC implementation. Based on hw/arm_gic.c
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ *
+ * Evgeny Voevodin <e.voevodin@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "sysbus.h"
+#include "qemu-common.h"
+#include "irq.h"
+#include "exynos4210.h"
+
+#define    EXT_GIC_ID_TVENC   127
+#define    EXT_GIC_ID_MFC 126
+#define    EXT_GIC_ID_HDMI_I2C    125
+#define    EXT_GIC_ID_HDMI    124
+#define    EXT_GIC_ID_MIXER   123
+#define    EXT_GIC_ID_PCIe    122
+#define    EXT_GIC_ID_2D  121
+#define    EXT_GIC_ID_JPEG    120
+#define    EXT_GIC_ID_FIMC3   119
+#define    EXT_GIC_ID_FIMC2   118
+#define    EXT_GIC_ID_FIMC1   117
+#define    EXT_GIC_ID_FIMC0   116
+#define    EXT_GIC_ID_ROTATOR 115
+#define    EXT_GIC_ID_ONENAND_AUDI    114
+#define    EXT_GIC_ID_MIPI_DSI_2LANE  113
+#define    EXT_GIC_ID_MIPI_CSI_2LANE  112
+#define    EXT_GIC_ID_MIPI_DSI_4LANE  111
+#define    EXT_GIC_ID_MIPI_CSI_4LANE  110
+#define    EXT_GIC_ID_SDMMC   109
+#define    EXT_GIC_ID_HSMMC3  108
+#define    EXT_GIC_ID_HSMMC2  107
+#define    EXT_GIC_ID_HSMMC1  106
+#define    EXT_GIC_ID_HSMMC0  105
+#define    EXT_GIC_ID_MODEMIF 104
+#define    EXT_GIC_ID_USB_DEVICE  103
+#define    EXT_GIC_ID_USB_HOST    102
+#define    EXT_GIC_ID_MCT_G1  101
+#define    EXT_GIC_ID_SPI2    100
+#define    EXT_GIC_ID_SPI1    99
+#define    EXT_GIC_ID_SPI0    98
+#define    EXT_GIC_ID_I2C7    97
+#define    EXT_GIC_ID_I2C6    96
+#define    EXT_GIC_ID_I2C5    95
+#define    EXT_GIC_ID_I2C4    94
+#define    EXT_GIC_ID_I2C3    93
+#define    EXT_GIC_ID_I2C2    92
+#define    EXT_GIC_ID_I2C1    91
+#define    EXT_GIC_ID_I2C0    90
+#define    EXT_GIC_ID_MCT_G0  89
+#define    EXT_GIC_ID_UART4   88
+#define    EXT_GIC_ID_UART3   87
+#define    EXT_GIC_ID_UART2   86
+#define    EXT_GIC_ID_UART1   85
+#define    EXT_GIC_ID_UART0    84
+#define    EXT_GIC_ID_NFC      83
+#define    EXT_GIC_ID_IEM_IEC 82
+#define    EXT_GIC_ID_IEM_APC 81
+#define    EXT_GIC_ID_MCT_L1  80
+#define    EXT_GIC_ID_GPIO_XA 79
+#define    EXT_GIC_ID_GPIO_XB 78
+#define    EXT_GIC_ID_RTC_TIC 77
+#define    EXT_GIC_ID_RTC_ALARM   76
+#define    EXT_GIC_ID_WDT 75
+#define    EXT_GIC_ID_MCT_L0  74
+#define    EXT_GIC_ID_TIMER4  73
+#define    EXT_GIC_ID_TIMER3  72
+#define    EXT_GIC_ID_TIMER2  71
+#define    EXT_GIC_ID_TIMER1  70
+#define    EXT_GIC_ID_TIMER0  69
+#define    EXT_GIC_ID_PDMA1   68
+#define    EXT_GIC_ID_PDMA0   67
+#define    EXT_GIC_ID_MDMA_LCD0   66
+
+enum ext_int {
+    EXT_GIC_ID_EXTINT0 = 48,
+    EXT_GIC_ID_EXTINT1,
+    EXT_GIC_ID_EXTINT2,
+    EXT_GIC_ID_EXTINT3,
+    EXT_GIC_ID_EXTINT4,
+    EXT_GIC_ID_EXTINT5,
+    EXT_GIC_ID_EXTINT6,
+    EXT_GIC_ID_EXTINT7,
+    EXT_GIC_ID_EXTINT8,
+    EXT_GIC_ID_EXTINT9,
+    EXT_GIC_ID_EXTINT10,
+    EXT_GIC_ID_EXTINT11,
+    EXT_GIC_ID_EXTINT12,
+    EXT_GIC_ID_EXTINT13,
+    EXT_GIC_ID_EXTINT14,
+    EXT_GIC_ID_EXTINT15
+};
+
+/*
+ * External GIC sources which are not from External Interrupt Combiner or
+ * External Interrupts are starting from EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ,
+ * which is INTG16 in Internal Interrupt Combiner.
+ */
+
+static uint32_t
+combiner_grp_to_gic_id[64-EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ][8] = {
+    /* int combiner groups 16-19 */
+    {}, {}, {}, {},
+    /* int combiner group 20 */
+    {0, EXT_GIC_ID_MDMA_LCD0},
+    /* int combiner group 21 */
+    {EXT_GIC_ID_PDMA0, EXT_GIC_ID_PDMA1},
+    /* int combiner group 22 */
+    {EXT_GIC_ID_TIMER0, EXT_GIC_ID_TIMER1, EXT_GIC_ID_TIMER2,
+            EXT_GIC_ID_TIMER3, EXT_GIC_ID_TIMER4},
+    /* int combiner group 23 */
+    {EXT_GIC_ID_RTC_ALARM, EXT_GIC_ID_RTC_TIC},
+    /* int combiner group 24 */
+    {EXT_GIC_ID_GPIO_XB, EXT_GIC_ID_GPIO_XA},
+    /* int combiner group 25 */
+    {EXT_GIC_ID_IEM_APC, EXT_GIC_ID_IEM_IEC},
+    /* int combiner group 26 */
+    {EXT_GIC_ID_UART0, EXT_GIC_ID_UART1, EXT_GIC_ID_UART2, EXT_GIC_ID_UART3,
+            EXT_GIC_ID_UART4},
+    /* int combiner group 27 */
+    {EXT_GIC_ID_I2C0, EXT_GIC_ID_I2C1, EXT_GIC_ID_I2C2, EXT_GIC_ID_I2C3,
+            EXT_GIC_ID_I2C4, EXT_GIC_ID_I2C5, EXT_GIC_ID_I2C6,
+            EXT_GIC_ID_I2C7},
+    /* int combiner group 28 */
+    {EXT_GIC_ID_SPI0, EXT_GIC_ID_SPI1, EXT_GIC_ID_SPI2},
+    /* int combiner group 29 */
+    {EXT_GIC_ID_HSMMC0, EXT_GIC_ID_HSMMC1, EXT_GIC_ID_HSMMC2,
+     EXT_GIC_ID_HSMMC3, EXT_GIC_ID_SDMMC},
+    /* int combiner group 30 */
+    {EXT_GIC_ID_MIPI_CSI_4LANE, EXT_GIC_ID_MIPI_CSI_2LANE},
+    /* int combiner group 31 */
+    {EXT_GIC_ID_MIPI_DSI_4LANE, EXT_GIC_ID_MIPI_DSI_2LANE},
+    /* int combiner group 32 */
+    {EXT_GIC_ID_FIMC0, EXT_GIC_ID_FIMC1},
+    /* int combiner group 33 */
+    {EXT_GIC_ID_FIMC2, EXT_GIC_ID_FIMC3},
+    /* int combiner group 34 */
+    {EXT_GIC_ID_ONENAND_AUDI, EXT_GIC_ID_NFC},
+    /* int combiner group 35 */
+    {0, 0, 0, EXT_GIC_ID_MCT_L1, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1},
+    /* int combiner group 36 */
+    {EXT_GIC_ID_MIXER},
+    /* int combiner group 37 */
+    {EXT_GIC_ID_EXTINT4, EXT_GIC_ID_EXTINT5, EXT_GIC_ID_EXTINT6,
+     EXT_GIC_ID_EXTINT7},
+    /* groups 38-50 */
+    {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},
+    /* int combiner group 51 */
+    {EXT_GIC_ID_MCT_L0, 0, 0, 0, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1},
+    /* group 52 */
+    {},
+    /* int combiner group 53 */
+    {EXT_GIC_ID_WDT, 0, 0, 0, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1},
+    /* groups 54-63 */
+    {}, {}, {}, {}, {}, {}, {}, {}, {}, {}
+};
+
+#define EXYNOS4210_GIC_NIRQ 160
+#define NCPU                EXYNOS4210_MAX_CPUS
+
+#define EXYNOS4210_EXT_GIC_CPU_REGION_SIZE     0x10000
+#define EXYNOS4210_EXT_GIC_DIST_REGION_SIZE    0x10000
+
+#define EXYNOS4210_EXT_GIC_PER_CPU_OFFSET      0x8000
+#define EXYNOS4210_EXT_GIC_CPU_GET_OFFSET(n) \
+    ((n) * EXYNOS4210_EXT_GIC_PER_CPU_OFFSET)
+#define EXYNOS4210_EXT_GIC_DIST_GET_OFFSET(n) \
+    ((n) * EXYNOS4210_EXT_GIC_PER_CPU_OFFSET)
+
+#define EXYNOS4210_GIC_CPU_REGION_SIZE  0x100
+#define EXYNOS4210_GIC_DIST_REGION_SIZE 0x1000
+
+static void exynos4210_irq_handler(void *opaque, int irq, int level)
+{
+    Exynos4210Irq *s = (Exynos4210Irq *)opaque;
+
+    /* Bypass */
+    qemu_set_irq(s->board_irqs[irq], level);
+
+    return;
+}
+
+/*
+ * Initialize exynos4210 IRQ subsystem stub.
+ */
+qemu_irq *exynos4210_init_irq(Exynos4210Irq *s)
+{
+    return qemu_allocate_irqs(exynos4210_irq_handler, s,
+            EXYNOS4210_MAX_INT_COMBINER_IN_IRQ);
+}
+
+/*
+ * Initialize board IRQs.
+ * These IRQs contain splitted Int/External Combiner and External Gic IRQs.
+ */
+void exynos4210_init_board_irqs(Exynos4210Irq *s)
+{
+    uint32_t grp, bit, irq_id, n;
+
+    for (n = 0; n < EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ; n++) {
+        s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n],
+                s->ext_combiner_irq[n]);
+
+        irq_id = 0;
+        if (n == EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 4) ||
+                n == EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 4)) {
+            /* MCT_G0 is passed to External GIC */
+            irq_id = EXT_GIC_ID_MCT_G0;
+        }
+        if (n == EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 5) ||
+                n == EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 5)) {
+            /* MCT_G1 is passed to External and GIC */
+            irq_id = EXT_GIC_ID_MCT_G1;
+        }
+        if (irq_id) {
+            s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n],
+                    s->ext_gic_irq[irq_id-32]);
+        }
+
+    }
+    for (; n < EXYNOS4210_MAX_INT_COMBINER_IN_IRQ; n++) {
+        /* these IDs are passed to Internal Combiner and External GIC */
+        grp = EXYNOS4210_COMBINER_GET_GRP_NUM(n);
+        bit = EXYNOS4210_COMBINER_GET_BIT_NUM(n);
+        irq_id = combiner_grp_to_gic_id[grp -
+                     EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ][bit];
+
+        if (irq_id) {
+            s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n],
+                    s->ext_gic_irq[irq_id-32]);
+        }
+    }
+}
+
+/*
+ * Get IRQ number from exynos4210 IRQ subsystem stub.
+ * To identify IRQ source use internal combiner group and bit number
+ *  grp - group number
+ *  bit - bit number inside group
+ */
+uint32_t exynos4210_get_irq(uint32_t grp, uint32_t bit)
+{
+    return EXYNOS4210_COMBINER_GET_IRQ_NUM(grp, bit);
+}
+
+/********* GIC part *********/
+
+static inline int
+gic_get_current_cpu(void)
+{
+    return cpu_single_env->cpu_index;
+}
+
+#include "arm_gic.c"
+
+typedef struct {
+    gic_state gic;
+    MemoryRegion cpu_container;
+    MemoryRegion dist_container;
+    MemoryRegion cpu_alias[NCPU];
+    MemoryRegion dist_alias[NCPU];
+    uint32_t num_cpu;
+} Exynos4210GicState;
+
+static int exynos4210_gic_init(SysBusDevice *dev)
+{
+    Exynos4210GicState *s = FROM_SYSBUSGIC(Exynos4210GicState, dev);
+    uint32_t i;
+    const char cpu_prefix[] = "exynos4210-gic-alias_cpu";
+    const char dist_prefix[] = "exynos4210-gic-alias_dist";
+    char cpu_alias_name[sizeof(cpu_prefix) + 3];
+    char dist_alias_name[sizeof(cpu_prefix) + 3];
+
+    gic_init(&s->gic, s->num_cpu, EXYNOS4210_GIC_NIRQ);
+
+    memory_region_init(&s->cpu_container, "exynos4210-cpu-container",
+            EXYNOS4210_EXT_GIC_CPU_REGION_SIZE);
+    memory_region_init(&s->dist_container, "exynos4210-dist-container",
+            EXYNOS4210_EXT_GIC_DIST_REGION_SIZE);
+
+    for (i = 0; i < s->num_cpu; i++) {
+        /* Map CPU interface per SMP Core */
+        sprintf(cpu_alias_name, "%s%x", cpu_prefix, i);
+        memory_region_init_alias(&s->cpu_alias[i],
+                                 cpu_alias_name,
+                                 &s->gic.cpuiomem[0],
+                                 0,
+                                 EXYNOS4210_GIC_CPU_REGION_SIZE);
+        memory_region_add_subregion(&s->cpu_container,
+                EXYNOS4210_EXT_GIC_CPU_GET_OFFSET(i), &s->cpu_alias[i]);
+
+        /* Map Distributor per SMP Core */
+        sprintf(dist_alias_name, "%s%x", dist_prefix, i);
+        memory_region_init_alias(&s->dist_alias[i],
+                                 dist_alias_name,
+                                 &s->gic.iomem,
+                                 0,
+                                 EXYNOS4210_GIC_DIST_REGION_SIZE);
+        memory_region_add_subregion(&s->dist_container,
+                EXYNOS4210_EXT_GIC_DIST_GET_OFFSET(i), &s->dist_alias[i]);
+    }
+
+    sysbus_init_mmio(dev, &s->cpu_container);
+    sysbus_init_mmio(dev, &s->dist_container);
+
+    gic_cpu_write(&s->gic, 1, 0, 1);
+
+    return 0;
+}
+
+static SysBusDeviceInfo exynos4210_gic_info = {
+    .init = exynos4210_gic_init,
+    .qdev.name  = "exynos4210.gic",
+    .qdev.size  = sizeof(Exynos4210GicState),
+    .qdev.props = (Property[]) {
+        DEFINE_PROP_UINT32("num-cpu", Exynos4210GicState, num_cpu, 1),
+        DEFINE_PROP_END_OF_LIST(),
+    }
+};
+
+static void exynos4210_gic_register_devices(void)
+{
+    sysbus_register_withprop(&exynos4210_gic_info);
+}
+
+device_init(exynos4210_gic_register_devices)
+
+/*
+ * IRQGate struct.
+ * IRQ Gate represents OR gate between GICs to pass IRQ to PIC.
+ */
+typedef struct {
+    SysBusDevice busdev;
+
+    qemu_irq pic_irq[NCPU]; /* output IRQs to PICs */
+    uint32_t gpio_level[EXYNOS4210_IRQ_GATE_NINPUTS]; /* Input levels */
+} Exynos4210IRQGateState;
+
+static const VMStateDescription VMState_Exynos4210IRQGate = {
+    .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_END_OF_LIST()
+    }
+};
+
+/* Process a change in an external IRQ input.  */
+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;
+    }
+
+    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]);
+    }
+
+    return;
+}
+
+static void exynos4210_irq_gate_reset(DeviceState *d)
+{
+    Exynos4210IRQGateState *s = (Exynos4210IRQGateState *)d;
+
+    memset(&s->gpio_level, 0, sizeof(s->gpio_level));
+}
+
+/*
+ * IRQ Gate initialization.
+ */
+static int exynos4210_irq_gate_init(SysBusDevice *dev)
+{
+    unsigned int i;
+    Exynos4210IRQGateState *s =
+            FROM_SYSBUS(Exynos4210IRQGateState, dev);
+
+    /* 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);
+
+    /* Connect SysBusDev irqs to device specific irqs */
+    for (i = 0; i < NCPU; i++) {
+        sysbus_init_irq(dev, &s->pic_irq[i]);
+    }
+
+    return 0;
+}
+
+static SysBusDeviceInfo exynos4210_irq_gate_info = {
+    .qdev.name  = "exynos4210.irq_gate",
+    .qdev.size  = sizeof(Exynos4210IRQGateState),
+    .qdev.reset = exynos4210_irq_gate_reset,
+    .qdev.vmsd = &VMState_Exynos4210IRQGate,
+    .init = exynos4210_irq_gate_init,
+};
+
+static void exynos4210_irq_gate_register_devices(void)
+{
+    sysbus_register_withprop(&exynos4210_irq_gate_info);
+}
+
+device_init(exynos4210_irq_gate_register_devices)
-- 
1.7.4.1

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

* [Qemu-devel] [PATCH v9 2/9] ARM: Samsung exynos4210-based boards emulation
  2012-01-20 10:53 [Qemu-devel] [PATCH v9 0/9] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
  2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 1/9] ARM: exynos4210: IRQ subsystem support Evgeny Voevodin
@ 2012-01-20 10:53 ` Evgeny Voevodin
  2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 3/9] ARM: exynos4210: UART support Evgeny Voevodin
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 13+ messages in thread
From: Evgeny Voevodin @ 2012-01-20 10:53 UTC (permalink / raw)
  To: qemu-devel
  Cc: kyungmin.park, m.kozlov, jehyung.lee, d.solodkiy, Evgeny Voevodin

Add initial support of NURI and SMDKC210 boards

Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 Makefile.target     |    3 +-
 hw/exynos4210.c     |  193 +++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/exynos4210.h     |   40 +++++++++++
 hw/exynos4_boards.c |  143 +++++++++++++++++++++++++++++++++++++
 4 files changed, 378 insertions(+), 1 deletions(-)
 create mode 100644 hw/exynos4210.c
 create mode 100644 hw/exynos4_boards.c

diff --git a/Makefile.target b/Makefile.target
index 4ac257e..6199d44 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -339,7 +339,8 @@ obj-arm-y = integratorcp.o versatilepb.o arm_pic.o arm_timer.o
 obj-arm-y += arm_boot.o pl011.o pl031.o pl050.o pl080.o pl110.o pl181.o pl190.o
 obj-arm-y += versatile_pci.o
 obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
-obj-arm-y += exynos4210_gic.o exynos4210_combiner.o
+obj-arm-y += exynos4210_gic.o exynos4210_combiner.o exynos4210.o
+obj-arm-y += exynos4_boards.o
 obj-arm-y += arm_l2x0.o
 obj-arm-y += arm_mptimer.o
 obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
new file mode 100644
index 0000000..aff0081
--- /dev/null
+++ b/hw/exynos4210.c
@@ -0,0 +1,193 @@
+/*
+ *  Samsung exynos4210 SoC emulation
+ *
+ *  Copyright (c) 2011 Samsung Electronics Co., Ltd. All rights reserved.
+ *    Maksim Kozlov <m.kozlov@samsung.com>
+ *    Evgeny Voevodin <e.voevodin@samsung.com>
+ *    Igor Mitsyanko  <i.mitsyanko@samsung.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License as published by the
+ *  Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ *  for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "boards.h"
+#include "sysemu.h"
+#include "sysbus.h"
+#include "arm-misc.h"
+#include "exynos4210.h"
+
+#define EXYNOS4210_CHIPID_ADDR         0x10000000
+
+/* External GIC */
+#define EXYNOS4210_EXT_GIC_CPU_BASE_ADDR    0x10480000
+#define EXYNOS4210_EXT_GIC_DIST_BASE_ADDR   0x10490000
+
+/* Combiner */
+#define EXYNOS4210_EXT_COMBINER_BASE_ADDR   0x10440000
+#define EXYNOS4210_INT_COMBINER_BASE_ADDR   0x10448000
+
+static uint8_t chipid_and_omr[] = { 0x11, 0x02, 0x21, 0x43,
+                                    0x09, 0x00, 0x00, 0x00 };
+
+Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
+        unsigned long ram_size)
+{
+    qemu_irq cpu_irq[4];
+    int n;
+    Exynos4210State *s = g_new(Exynos4210State, 1);
+    qemu_irq *irq_table;
+    qemu_irq *irqp;
+    qemu_irq gate_irq[EXYNOS4210_IRQ_GATE_NINPUTS];
+    unsigned long mem_size;
+    DeviceState *dev;
+    SysBusDevice *busdev;
+
+    for (n = 0; n < smp_cpus; n++) {
+        s->env[n] = cpu_init("cortex-a9");
+        if (!s->env[n]) {
+            fprintf(stderr, "Unable to find CPU %d definition\n", n);
+            exit(1);
+        }
+        /* Create PIC controller for each processor instance */
+        irqp = arm_pic_init_cpu(s->env[n]);
+
+        /*
+         * Get GICs gpio_in cpu_irq to connect a combiner to them later.
+         * Use only IRQ for a while.
+         */
+        cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ];
+    }
+
+    /*** IRQs ***/
+
+    s->irq_table = exynos4210_init_irq(&s->irqs);
+    irq_table = s->irq_table;
+
+    /* IRQ Gate */
+    dev = qdev_create(NULL, "exynos4210.irq_gate");
+    qdev_init_nofail(dev);
+    /* Get IRQ Gate input in gate_irq */
+    for (n = 0; n < EXYNOS4210_IRQ_GATE_NINPUTS; n++) {
+        gate_irq[n] = qdev_get_gpio_in(dev, n);
+    }
+    busdev = sysbus_from_qdev(dev);
+    /* Connect IRQ Gate output to cpu_irq */
+    for (n = 0; n < smp_cpus; n++) {
+        sysbus_connect_irq(busdev, n, cpu_irq[n]);
+    }
+
+    /* Private memory region and Internal GIC */
+    dev = qdev_create(NULL, "a9mpcore_priv");
+    qdev_prop_set_uint32(dev, "num-cpu", smp_cpus);
+    qdev_init_nofail(dev);
+    busdev = sysbus_from_qdev(dev);
+    sysbus_mmio_map(busdev, 0, EXYNOS4210_SMP_PRIVATE_BASE_ADDR);
+    for (n = 0; n < smp_cpus; n++) {
+        sysbus_connect_irq(busdev, n, gate_irq[n * 2]);
+    }
+    for (n = 0; n < EXYNOS4210_INT_GIC_NIRQ; n++) {
+        s->irqs.int_gic_irq[n] = qdev_get_gpio_in(dev, n);
+    }
+
+    /* Cache controller */
+    sysbus_create_simple("l2x0", EXYNOS4210_L2X0_BASE_ADDR, NULL);
+
+    /* External GIC */
+    dev = qdev_create(NULL, "exynos4210.gic");
+    qdev_prop_set_uint32(dev, "num-cpu", smp_cpus);
+    qdev_init_nofail(dev);
+    busdev = sysbus_from_qdev(dev);
+    /* Map CPU interface */
+    sysbus_mmio_map(busdev, 0, EXYNOS4210_EXT_GIC_CPU_BASE_ADDR);
+    /* Map Distributer interface */
+    sysbus_mmio_map(busdev, 1, EXYNOS4210_EXT_GIC_DIST_BASE_ADDR);
+    for (n = 0; n < smp_cpus; n++) {
+        sysbus_connect_irq(busdev, n, gate_irq[n * 2 + 1]);
+    }
+    for (n = 0; n < EXYNOS4210_EXT_GIC_NIRQ; n++) {
+        s->irqs.ext_gic_irq[n] = qdev_get_gpio_in(dev, n);
+    }
+
+    /* Internal Interrupt Combiner */
+    dev = qdev_create(NULL, "exynos4210.combiner");
+    qdev_init_nofail(dev);
+    busdev = sysbus_from_qdev(dev);
+    for (n = 0; n < EXYNOS4210_MAX_INT_COMBINER_OUT_IRQ; n++) {
+        sysbus_connect_irq(busdev, n, s->irqs.int_gic_irq[n]);
+    }
+    exynos4210_combiner_get_gpioin(&s->irqs, dev, 0);
+    sysbus_mmio_map(busdev, 0, EXYNOS4210_INT_COMBINER_BASE_ADDR);
+
+    /* External Interrupt Combiner */
+    dev = qdev_create(NULL, "exynos4210.combiner");
+    qdev_init_nofail(dev);
+    busdev = sysbus_from_qdev(dev);
+    for (n = 0; n < EXYNOS4210_MAX_INT_COMBINER_OUT_IRQ; n++) {
+        sysbus_connect_irq(busdev, n, s->irqs.ext_gic_irq[n]);
+    }
+    exynos4210_combiner_get_gpioin(&s->irqs, dev, 1);
+    sysbus_mmio_map(busdev, 0, EXYNOS4210_EXT_COMBINER_BASE_ADDR);
+    qdev_prop_set_uint32(dev, "external", 1);
+
+    /* Initialize board IRQs. */
+    exynos4210_init_board_irqs(&s->irqs);
+
+    /*** Memory ***/
+
+    /* Chip-ID and OMR */
+    memory_region_init_ram_ptr(&s->chipid_mem, "exynos4210.chipid",
+            sizeof(chipid_and_omr), chipid_and_omr);
+    memory_region_set_readonly(&s->chipid_mem, true);
+    memory_region_add_subregion(system_mem, EXYNOS4210_CHIPID_ADDR,
+                                &s->chipid_mem);
+
+    /* Internal ROM */
+    memory_region_init_ram(&s->irom_mem, "exynos4210.irom",
+                           EXYNOS4210_IROM_SIZE);
+    memory_region_set_readonly(&s->irom_mem, true);
+    memory_region_add_subregion(system_mem, EXYNOS4210_IROM_BASE_ADDR,
+                                &s->irom_mem);
+    /* mirror of iROM */
+    memory_region_init_alias(&s->irom_alias_mem, "exynos4210.irom_alias",
+                             &s->irom_mem,
+                             EXYNOS4210_IROM_BASE_ADDR,
+                             EXYNOS4210_IROM_SIZE);
+    memory_region_set_readonly(&s->irom_alias_mem, true);
+    memory_region_add_subregion(system_mem, EXYNOS4210_IROM_MIRROR_BASE_ADDR,
+                                &s->irom_alias_mem);
+
+    /* Internal RAM */
+    memory_region_init_ram(&s->iram_mem, "exynos4210.iram",
+                           EXYNOS4210_IRAM_SIZE);
+    vmstate_register_ram_global(&s->iram_mem);
+    memory_region_add_subregion(system_mem, EXYNOS4210_IRAM_BASE_ADDR,
+                                &s->iram_mem);
+
+    /* DRAM */
+    mem_size = ram_size;
+    if (mem_size > EXYNOS4210_DRAM_MAX_SIZE) {
+        memory_region_init_ram(&s->dram1_mem, "exynos4210.dram1",
+                mem_size - EXYNOS4210_DRAM_MAX_SIZE);
+        vmstate_register_ram_global(&s->dram1_mem);
+        memory_region_add_subregion(system_mem, EXYNOS4210_DRAM1_BASE_ADDR,
+                &s->dram1_mem);
+        mem_size = EXYNOS4210_DRAM_MAX_SIZE;
+    }
+    memory_region_init_ram(&s->dram0_mem, "exynos4210.dram0", mem_size);
+    vmstate_register_ram_global(&s->dram0_mem);
+    memory_region_add_subregion(system_mem, EXYNOS4210_DRAM0_BASE_ADDR,
+            &s->dram0_mem);
+
+    return s;
+}
diff --git a/hw/exynos4210.h b/hw/exynos4210.h
index cef264b..621e75a 100644
--- a/hw/exynos4210.h
+++ b/hw/exynos4210.h
@@ -31,6 +31,28 @@
 
 #define EXYNOS4210_MAX_CPUS                 2
 
+#define EXYNOS4210_DRAM0_BASE_ADDR          0x40000000
+#define EXYNOS4210_DRAM1_BASE_ADDR          0xa0000000
+#define EXYNOS4210_DRAM_MAX_SIZE            0x60000000  /* 1.5 GB */
+
+#define EXYNOS4210_IROM_BASE_ADDR           0x00000000
+#define EXYNOS4210_IROM_SIZE                0x00010000  /* 64 KB */
+#define EXYNOS4210_IROM_MIRROR_BASE_ADDR    0x02000000
+#define EXYNOS4210_IROM_MIRROR_SIZE         0x00010000  /* 64 KB */
+
+#define EXYNOS4210_IRAM_BASE_ADDR           0x02020000
+#define EXYNOS4210_IRAM_SIZE                0x00020000  /* 128 KB */
+
+/* Secondary CPU startup code is in IROM memory */
+#define EXYNOS4210_SMP_BOOT_ADDR            EXYNOS4210_IROM_BASE_ADDR
+#define EXYNOS4210_SMP_BOOT_SIZE            0x1000
+#define EXYNOS4210_BASE_BOOT_ADDR           EXYNOS4210_DRAM0_BASE_ADDR
+/* Secondary CPU polling address to get loader start from */
+#define EXYNOS4210_SECOND_CPU_BOOTREG       0x10020814
+
+#define EXYNOS4210_SMP_PRIVATE_BASE_ADDR    0x10500000
+#define EXYNOS4210_L2X0_BASE_ADDR           0x10502000
+
 /*
  * exynos4210 IRQ subsystem stub definitions.
  */
@@ -60,6 +82,24 @@ typedef struct Exynos4210Irq {
     qemu_irq board_irqs[EXYNOS4210_MAX_INT_COMBINER_IN_IRQ];
 } Exynos4210Irq;
 
+typedef struct Exynos4210State {
+    CPUState * env[EXYNOS4210_MAX_CPUS];
+    Exynos4210Irq irqs;
+    qemu_irq *irq_table;
+
+    MemoryRegion chipid_mem;
+    MemoryRegion iram_mem;
+    MemoryRegion irom_mem;
+    MemoryRegion irom_alias_mem;
+    MemoryRegion dram0_mem;
+    MemoryRegion dram1_mem;
+    MemoryRegion boot_secondary;
+    MemoryRegion bootreg_mem;
+} Exynos4210State;
+
+Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
+        unsigned long ram_size);
+
 /* Initialize exynos4210 IRQ subsystem stub */
 qemu_irq *exynos4210_init_irq(Exynos4210Irq *env);
 
diff --git a/hw/exynos4_boards.c b/hw/exynos4_boards.c
new file mode 100644
index 0000000..55fca06
--- /dev/null
+++ b/hw/exynos4_boards.c
@@ -0,0 +1,143 @@
+/*
+ *  Samsung exynos4 SoC based boards emulation
+ *
+ *  Copyright (c) 2011 Samsung Electronics Co., Ltd. All rights reserved.
+ *    Maksim Kozlov <m.kozlov@samsung.com>
+ *    Evgeny Voevodin <e.voevodin@samsung.com>
+ *    Igor Mitsyanko  <i.mitsyanko@samsung.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License as published by the
+ *  Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ *  for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "sysemu.h"
+#include "sysbus.h"
+#include "arm-misc.h"
+#include "exec-memory.h"
+#include "exynos4210.h"
+#include "boards.h"
+
+#undef DEBUG
+
+//#define DEBUG
+
+#ifdef DEBUG
+    #undef PRINT_DEBUG
+    #define  PRINT_DEBUG(fmt, args...) \
+        do { \
+            fprintf(stderr, "  [%s:%d]   "fmt, __func__, __LINE__, ##args); \
+        } while (0)
+#else
+    #define  PRINT_DEBUG(fmt, args...)  do {} while (0)
+#endif
+
+typedef enum Exynos4BoardType {
+    EXYNOS4_BOARD_NURI,
+    EXYNOS4_BOARD_SMDKC210,
+    EXYNOS4_NUM_OF_BOARDS
+} Exynos4BoardType;
+
+static int exynos4_board_id[EXYNOS4_NUM_OF_BOARDS] = {
+    [EXYNOS4_BOARD_NURI]     = 0xD33,
+    [EXYNOS4_BOARD_SMDKC210] = 0xB16,
+};
+
+static int exynos4_board_smp_bootreg_addr[EXYNOS4_NUM_OF_BOARDS] = {
+    [EXYNOS4_BOARD_NURI]     = EXYNOS4210_SECOND_CPU_BOOTREG,
+    [EXYNOS4_BOARD_SMDKC210] = EXYNOS4210_SECOND_CPU_BOOTREG,
+};
+
+static unsigned long exynos4_board_ram_size[EXYNOS4_NUM_OF_BOARDS] = {
+    [EXYNOS4_BOARD_NURI]     = 0x40000000,
+    [EXYNOS4_BOARD_SMDKC210] = 0x40000000,
+};
+
+static struct arm_boot_info exynos4_board_binfo = {
+    .loader_start     = EXYNOS4210_BASE_BOOT_ADDR,
+    .smp_loader_start = EXYNOS4210_SMP_BOOT_ADDR,
+};
+
+static Exynos4210State *exynos4_boards_init_common(
+        const char *kernel_filename,
+        const char *kernel_cmdline,
+        const char *initrd_filename,
+        Exynos4BoardType board_type)
+{
+    exynos4_board_binfo.ram_size = exynos4_board_ram_size[board_type];
+    exynos4_board_binfo.board_id = exynos4_board_id[board_type];
+    exynos4_board_binfo.smp_bootreg_addr =
+            exynos4_board_smp_bootreg_addr[board_type];
+    exynos4_board_binfo.nb_cpus = smp_cpus;
+    exynos4_board_binfo.kernel_filename = kernel_filename;
+    exynos4_board_binfo.initrd_filename = initrd_filename;
+    exynos4_board_binfo.kernel_cmdline = kernel_cmdline;
+    exynos4_board_binfo.smp_priv_base = EXYNOS4210_SMP_PRIVATE_BASE_ADDR;
+
+    PRINT_DEBUG("\n ram_size: %luMiB [0x%08lx]\n"
+            " kernel_filename: %s\n"
+            " kernel_cmdline: %s\n"
+            " initrd_filename: %s\n",
+            exynos4_board_ram_size[board_type] / 1048576,
+            exynos4_board_ram_size[board_type],
+            kernel_filename,
+            kernel_cmdline,
+            initrd_filename);
+
+    return exynos4210_init(get_system_memory(),
+            exynos4_board_ram_size[board_type]);
+}
+
+static void nuri_init(ram_addr_t ram_size,
+        const char *boot_device,
+        const char *kernel_filename, const char *kernel_cmdline,
+        const char *initrd_filename, const char *cpu_model)
+{
+    exynos4_boards_init_common(kernel_filename, kernel_cmdline,
+                initrd_filename, EXYNOS4_BOARD_NURI);
+
+    arm_load_kernel(first_cpu, &exynos4_board_binfo);
+}
+
+static void smdkc210_init(ram_addr_t ram_size,
+        const char *boot_device,
+        const char *kernel_filename, const char *kernel_cmdline,
+        const char *initrd_filename, const char *cpu_model)
+{
+    exynos4_boards_init_common(kernel_filename, kernel_cmdline,
+                initrd_filename, EXYNOS4_BOARD_SMDKC210);
+
+    arm_load_kernel(first_cpu, &exynos4_board_binfo);
+}
+
+static QEMUMachine nuri_machine = {
+    .name = "nuri",
+    .desc = "Samsung NURI board (Exynos4210)",
+    .init = nuri_init,
+    .max_cpus = EXYNOS4210_MAX_CPUS,
+};
+
+static QEMUMachine smdkc210_machine = {
+    .name = "smdkc210",
+    .desc = "Samsung SMDKC210 board (Exynos4210)",
+    .init = smdkc210_init,
+    .max_cpus = EXYNOS4210_MAX_CPUS,
+};
+
+static void exynos4_machine_init(void)
+{
+    qemu_register_machine(&nuri_machine);
+    qemu_register_machine(&smdkc210_machine);
+}
+
+machine_init(exynos4_machine_init);
-- 
1.7.4.1

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

* [Qemu-devel] [PATCH v9 3/9] ARM: exynos4210: UART support
  2012-01-20 10:53 [Qemu-devel] [PATCH v9 0/9] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
  2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 1/9] ARM: exynos4210: IRQ subsystem support Evgeny Voevodin
  2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 2/9] ARM: Samsung exynos4210-based boards emulation Evgeny Voevodin
@ 2012-01-20 10:53 ` Evgeny Voevodin
  2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 4/9] ARM: exynos4210: PWM support Evgeny Voevodin
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 13+ messages in thread
From: Evgeny Voevodin @ 2012-01-20 10:53 UTC (permalink / raw)
  To: qemu-devel
  Cc: kyungmin.park, m.kozlov, jehyung.lee, d.solodkiy, Evgeny Voevodin

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

Add basic support of exynos4210 UART

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

diff --git a/Makefile.target b/Makefile.target
index 6199d44..c856de3 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -340,7 +340,7 @@ obj-arm-y += arm_boot.o pl011.o pl031.o pl050.o pl080.o pl110.o pl181.o pl190.o
 obj-arm-y += versatile_pci.o
 obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
 obj-arm-y += exynos4210_gic.o exynos4210_combiner.o exynos4210.o
-obj-arm-y += exynos4_boards.o
+obj-arm-y += exynos4_boards.o exynos4210_uart.o
 obj-arm-y += arm_l2x0.o
 obj-arm-y += arm_mptimer.o
 obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index aff0081..3838b96 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -29,6 +29,18 @@
 
 #define EXYNOS4210_CHIPID_ADDR         0x10000000
 
+/* UART's definitions */
+#define EXYNOS4210_UART0_BASE_ADDR     0x13800000
+#define EXYNOS4210_UART1_BASE_ADDR     0x13810000
+#define EXYNOS4210_UART2_BASE_ADDR     0x13820000
+#define EXYNOS4210_UART3_BASE_ADDR     0x13830000
+#define EXYNOS4210_UART0_FIFO_SIZE     256
+#define EXYNOS4210_UART1_FIFO_SIZE     64
+#define EXYNOS4210_UART2_FIFO_SIZE     16
+#define EXYNOS4210_UART3_FIFO_SIZE     16
+/* Interrupt Group of External Interrupt Combiner for UART */
+#define EXYNOS4210_UART_INT_GRP        26
+
 /* External GIC */
 #define EXYNOS4210_EXT_GIC_CPU_BASE_ADDR    0x10480000
 #define EXYNOS4210_EXT_GIC_DIST_BASE_ADDR   0x10490000
@@ -189,5 +201,22 @@ Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
     memory_region_add_subregion(system_mem, EXYNOS4210_DRAM0_BASE_ADDR,
             &s->dram0_mem);
 
+    /*** UARTs ***/
+    exynos4210_uart_create(EXYNOS4210_UART0_BASE_ADDR,
+                           EXYNOS4210_UART0_FIFO_SIZE, 0, NULL,
+                    irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 0)]);
+
+    exynos4210_uart_create(EXYNOS4210_UART1_BASE_ADDR,
+                           EXYNOS4210_UART1_FIFO_SIZE, 1, NULL,
+                    irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 1)]);
+
+    exynos4210_uart_create(EXYNOS4210_UART2_BASE_ADDR,
+                           EXYNOS4210_UART2_FIFO_SIZE, 2, NULL,
+                    irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 2)]);
+
+    exynos4210_uart_create(EXYNOS4210_UART3_BASE_ADDR,
+                           EXYNOS4210_UART3_FIFO_SIZE, 3, NULL,
+                    irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 3)]);
+
     return s;
 }
diff --git a/hw/exynos4210.h b/hw/exynos4210.h
index 621e75a..11368e8 100644
--- a/hw/exynos4210.h
+++ b/hw/exynos4210.h
@@ -119,4 +119,13 @@ uint32_t exynos4210_get_irq(uint32_t grp, uint32_t bit);
 void exynos4210_combiner_get_gpioin(Exynos4210Irq *irqs, DeviceState *dev,
         int ext);
 
+/*
+ * exynos4210 UART
+ */
+DeviceState *exynos4210_uart_create(target_phys_addr_t addr,
+                                    int fifo_size,
+                                    int channel,
+                                    CharDriverState *chr,
+                                    qemu_irq irq);
+
 #endif /* EXYNOS4210_H_ */
diff --git a/hw/exynos4210_uart.c b/hw/exynos4210_uart.c
new file mode 100644
index 0000000..01ccf5a
--- /dev/null
+++ b/hw/exynos4210_uart.c
@@ -0,0 +1,661 @@
+/*
+ *  Exynos4210 UART 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 "sysemu.h"
+#include "qemu-char.h"
+
+#include "exynos4210.h"
+
+#undef DEBUG_UART
+#undef DEBUG_UART_EXTEND
+#undef DEBUG_IRQ
+#undef DEBUG_Rx_DATA
+#undef DEBUG_Tx_DATA
+
+#define DEBUG_UART            0
+#define DEBUG_UART_EXTEND     0
+#define DEBUG_IRQ             0
+#define DEBUG_Rx_DATA         0
+#define DEBUG_Tx_DATA         0
+
+#if DEBUG_UART
+#define  PRINT_DEBUG(fmt, args...)  \
+        do { \
+            fprintf(stderr, "  [%s:%d]   "fmt, __func__, __LINE__, ##args); \
+        } while (0)
+
+#if DEBUG_UART_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_EXTEND(fmt, args...) \
+        do {} while (0)
+#endif
+
+#define  PRINT_ERROR(fmt, args...) \
+        do { \
+            fprintf(stderr, "  [%s:%d]   "fmt, __func__, __LINE__, ##args); \
+        } while (0)
+
+/*
+ *  Offsets for UART registers relative to SFR base address
+ *  for UARTn
+ *
+ */
+#define ULCON      0x0000 /* Line Control             */
+#define UCON       0x0004 /* Control                  */
+#define UFCON      0x0008 /* FIFO Control             */
+#define UMCON      0x000C /* Modem Control            */
+#define UTRSTAT    0x0010 /* Tx/Rx Status             */
+#define UERSTAT    0x0014 /* UART Error Status        */
+#define UFSTAT     0x0018 /* FIFO Status              */
+#define UMSTAT     0x001C /* Modem Status             */
+#define UTXH       0x0020 /* Transmit Buffer          */
+#define URXH       0x0024 /* Receive Buffer           */
+#define UBRDIV     0x0028 /* Baud Rate Divisor        */
+#define UFRACVAL   0x002C /* Divisor Fractional Value */
+#define UINTP      0x0030 /* Interrupt Pending        */
+#define UINTSP     0x0034 /* Interrupt Source Pending */
+#define UINTM      0x0038 /* Interrupt Mask           */
+
+/*
+ * for indexing register in the uint32_t array
+ *
+ * 'reg' - register offset (see offsets definitions above)
+ *
+ */
+#define I_(reg) (reg / sizeof(uint32_t))
+
+typedef struct Exynos4210UartReg {
+    const char         *name; /* the only reason is the debug output */
+    target_phys_addr_t  offset;
+    uint32_t            reset_value;
+} Exynos4210UartReg;
+
+static Exynos4210UartReg exynos4210_uart_regs[] = {
+    {"ULCON",    ULCON,    0x00000000},
+    {"UCON",     UCON,     0x00003000},
+    {"UFCON",    UFCON,    0x00000000},
+    {"UMCON",    UMCON,    0x00000000},
+    {"UTRSTAT",  UTRSTAT,  0x00000006}, /* RO */
+    {"UERSTAT",  UERSTAT,  0x00000000}, /* RO */
+    {"UFSTAT",   UFSTAT,   0x00000000}, /* RO */
+    {"UMSTAT",   UMSTAT,   0x00000000}, /* RO */
+    {"UTXH",     UTXH,     0x5c5c5c5c}, /* WO, undefined reset value*/
+    {"URXH",     URXH,     0x00000000}, /* RO */
+    {"UBRDIV",   UBRDIV,   0x00000000},
+    {"UFRACVAL", UFRACVAL, 0x00000000},
+    {"UINTP",    UINTP,    0x00000000},
+    {"UINTSP",   UINTSP,   0x00000000},
+    {"UINTM",    UINTM,    0x00000000},
+};
+
+#define EXYNOS4210_UART_REGS_MEM_SIZE    0x3C
+
+/* UART FIFO Control */
+#define UFCON_FIFO_ENABLE                    0x1
+#define UFCON_Rx_FIFO_RESET                  0x2
+#define UFCON_Tx_FIFO_RESET                  0x4
+#define UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT    8
+#define UFCON_Tx_FIFO_TRIGGER_LEVEL (7 << UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT)
+#define UFCON_Rx_FIFO_TRIGGER_LEVEL_SHIFT    4
+#define UFCON_Rx_FIFO_TRIGGER_LEVEL (7 << UFCON_Rx_FIFO_TRIGGER_LEVEL_SHIFT)
+
+/* Uart FIFO Status */
+#define UFSTAT_Rx_FIFO_COUNT        0xff
+#define UFSTAT_Rx_FIFO_FULL         0x100
+#define UFSTAT_Rx_FIFO_ERROR        0x200
+#define UFSTAT_Tx_FIFO_COUNT_SHIFT  16
+#define UFSTAT_Tx_FIFO_COUNT        (0xff << UFSTAT_Tx_FIFO_COUNT_SHIFT)
+#define UFSTAT_Tx_FIFO_FULL_SHIFT   24
+#define UFSTAT_Tx_FIFO_FULL         (1 << UFSTAT_Tx_FIFO_FULL_SHIFT)
+
+/* UART Interrupt Source Pending */
+#define UINTSP_RXD      0x1 /* Receive interrupt  */
+#define UINTSP_ERROR    0x2 /* Error interrupt    */
+#define UINTSP_TXD      0x4 /* Transmit interrupt */
+#define UINTSP_MODEM    0x8 /* Modem interrupt    */
+
+/* UART Line Control */
+#define ULCON_IR_MODE_SHIFT   6
+#define ULCON_PARITY_SHIFT    3
+#define ULCON_STOP_BIT_SHIFT  1
+
+/* UART Tx/Rx Status */
+#define UTRSTAT_TRANSMITTER_EMPTY       0x4
+#define UTRSTAT_Tx_BUFFER_EMPTY         0x2
+#define UTRSTAT_Rx_BUFFER_DATA_READY    0x1
+
+/* UART Error Status */
+#define UERSTAT_OVERRUN  0x1
+#define UERSTAT_PARITY   0x2
+#define UERSTAT_FRAME    0x4
+#define UERSTAT_BREAK    0x8
+
+typedef struct {
+    uint8_t    *data;
+    uint32_t    sp, rp; /* store and retrieve pointers */
+    uint32_t    size;
+} Exynos4210UartFIFO;
+
+typedef struct {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+
+    uint32_t             reg[EXYNOS4210_UART_REGS_MEM_SIZE / sizeof(uint32_t)];
+    Exynos4210UartFIFO   rx;
+    Exynos4210UartFIFO   tx;
+
+    CharDriverState  *chr;
+    qemu_irq          irq;
+
+    uint32_t channel;
+
+} Exynos4210UartState;
+
+
+#if DEBUG_UART
+/* Used only for debugging inside PRINT_DEBUG_... macros */
+static const char *exynos4210_uart_regname(target_phys_addr_t  offset)
+{
+
+    int regs_number = sizeof(exynos4210_uart_regs) / sizeof(Exynos4210UartReg);
+    int i;
+
+    for (i = 0; i < regs_number; i++) {
+        if (offset == exynos4210_uart_regs[i].offset) {
+            return exynos4210_uart_regs[i].name;
+        }
+    }
+
+    return NULL;
+}
+#endif
+
+
+static void fifo_store(Exynos4210UartFIFO *q, uint8_t ch)
+{
+    q->data[q->sp] = ch;
+    q->sp = (q->sp + 1) % q->size;
+}
+
+static uint8_t fifo_retrieve(Exynos4210UartFIFO *q)
+{
+    uint8_t ret = q->data[q->rp];
+    q->rp = (q->rp + 1) % q->size;
+    return  ret;
+}
+
+static int fifo_elements_number(Exynos4210UartFIFO *q)
+{
+    if (q->sp < q->rp) {
+        return q->size - q->rp + q->sp;
+    }
+
+    return q->sp - q->rp;
+}
+
+static int fifo_empty_elements_number(Exynos4210UartFIFO *q)
+{
+    return q->size - fifo_elements_number(q);
+}
+
+static void fifo_reset(Exynos4210UartFIFO *q)
+{
+    if (q->data != NULL) {
+        g_free(q->data);
+        q->data = NULL;
+    }
+
+    q->data = (uint8_t *)g_malloc0(q->size);
+
+    q->sp = 0;
+    q->rp = 0;
+}
+
+static uint32_t exynos4210_uart_Tx_FIFO_trigger_level(Exynos4210UartState *s)
+{
+    uint32_t level = 0;
+    uint32_t reg;
+
+    reg = (s->reg[I_(UFCON)] && UFCON_Tx_FIFO_TRIGGER_LEVEL) >>
+            UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT;
+
+    switch (s->channel) {
+    case 0:
+        level = reg * 32;
+        break;
+    case 1:
+    case 4:
+        level = reg * 8;
+        break;
+    case 2:
+    case 3:
+        level = reg * 2;
+        break;
+    default:
+        level = 0;
+        PRINT_ERROR("Wrong UART channel number: %d\n", s->channel);
+    }
+
+    return level;
+}
+
+static void exynos4210_uart_update_irq(Exynos4210UartState *s)
+{
+    /*
+     * The Tx interrupt is always requested if the number of data in the
+     * transmit FIFO is smaller than the trigger level.
+     */
+    if (s->reg[I_(UFCON)] && UFCON_FIFO_ENABLE) {
+
+        uint32_t count = (s->reg[I_(UFSTAT)] && UFSTAT_Tx_FIFO_COUNT) >>
+                UFSTAT_Tx_FIFO_COUNT_SHIFT;
+
+        if (count <= exynos4210_uart_Tx_FIFO_trigger_level(s)) {
+            s->reg[I_(UINTSP)] |= UINTSP_TXD;
+        }
+    }
+
+    s->reg[I_(UINTP)] = s->reg[I_(UINTSP)] & ~s->reg[I_(UINTM)];
+
+    if (s->reg[I_(UINTP)]) {
+        qemu_irq_raise(s->irq);
+
+#if DEBUG_IRQ
+        fprintf(stderr, "UART%d: IRQ has been raised: %08x\n",
+                s->channel, s->reg[I_(UINTP)]);
+#endif
+
+    } else {
+        qemu_irq_lower(s->irq);
+    }
+}
+
+static void exynos4210_uart_update_parameters(Exynos4210UartState *s)
+{
+    int speed, parity, data_bits, stop_bits, frame_size;
+    QEMUSerialSetParams ssp;
+    uint64_t uclk_rate;
+
+    if (s->reg[I_(UBRDIV)] == 0) {
+        return;
+    }
+
+    frame_size = 1; /* start bit */
+    if (s->reg[I_(ULCON)] & 0x20) {
+        frame_size++; /* parity bit */
+        if (s->reg[I_(ULCON)] & 0x28) {
+            parity = 'E';
+        } else {
+            parity = 'O';
+        }
+    } else {
+        parity = 'N';
+    }
+
+    if (s->reg[I_(ULCON)] & 0x4) {
+        stop_bits = 2;
+    } else {
+        stop_bits = 1;
+    }
+
+    data_bits = (s->reg[I_(ULCON)] & 0x3) + 5;
+
+    frame_size += data_bits + stop_bits;
+
+    uclk_rate = 24000000;
+
+    speed = uclk_rate / ((16 * (s->reg[I_(UBRDIV)]) & 0xffff) +
+            (s->reg[I_(UFRACVAL)] & 0x7) + 16);
+
+    ssp.speed     = speed;
+    ssp.parity    = parity;
+    ssp.data_bits = data_bits;
+    ssp.stop_bits = stop_bits;
+
+    qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp);
+
+    PRINT_DEBUG("UART%d: speed: %d, parity: %c, data: %d, stop: %d\n",
+                s->channel, speed, parity, data_bits, stop_bits);
+}
+
+static void exynos4210_uart_write(void *opaque, target_phys_addr_t offset,
+                               uint64_t val, unsigned size)
+{
+    Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+    uint8_t ch;
+
+    PRINT_DEBUG_EXTEND("UART%d: <0x%04x> %s <- 0x%08llx\n", s->channel,
+        offset, exynos4210_uart_regname(offset), (long long unsigned int)val);
+
+    switch (offset) {
+    case ULCON:
+    case UBRDIV:
+    case UFRACVAL:
+        s->reg[I_(offset)] = val;
+        exynos4210_uart_update_parameters(s);
+        break;
+    case UFCON:
+        s->reg[I_(UFCON)] = val;
+        if (val & UFCON_Rx_FIFO_RESET) {
+            fifo_reset(&s->rx);
+            s->reg[I_(UFCON)] &= ~UFCON_Rx_FIFO_RESET;
+            PRINT_DEBUG("UART%d: Rx FIFO Reset\n", s->channel);
+        }
+        if (val & UFCON_Tx_FIFO_RESET) {
+            fifo_reset(&s->tx);
+            s->reg[I_(UFCON)] &= ~UFCON_Tx_FIFO_RESET;
+            PRINT_DEBUG("UART%d: Tx FIFO Reset\n", s->channel);
+        }
+        break;
+
+    case UTXH:
+        if (s->chr) {
+            s->reg[I_(UTRSTAT)] &= ~(UTRSTAT_TRANSMITTER_EMPTY |
+                    UTRSTAT_Tx_BUFFER_EMPTY);
+            ch = (uint8_t)val;
+            qemu_chr_fe_write(s->chr, &ch, 1);
+#if DEBUG_Tx_DATA
+            fprintf(stderr, "%c", ch);
+#endif
+            s->reg[I_(UTRSTAT)] |= UTRSTAT_TRANSMITTER_EMPTY |
+                    UTRSTAT_Tx_BUFFER_EMPTY;
+            s->reg[I_(UINTSP)]  |= UINTSP_TXD;
+            exynos4210_uart_update_irq(s);
+        }
+        break;
+
+    case UINTP:
+        s->reg[I_(UINTP)] &= ~val;
+        s->reg[I_(UINTSP)] &= ~val;
+        PRINT_DEBUG("UART%d: UINTP [%04x] have been cleared: %08x\n",
+                    s->channel, offset, s->reg[I_(UINTP)]);
+        exynos4210_uart_update_irq(s);
+        break;
+    case UTRSTAT:
+    case UERSTAT:
+    case UFSTAT:
+    case UMSTAT:
+    case URXH:
+        PRINT_DEBUG("UART%d: Trying to write into RO register: %s [%04x]\n",
+                    s->channel, exynos4210_uart_regname(offset), offset);
+        break;
+    case UINTSP:
+        s->reg[I_(UINTSP)]  &= ~val;
+        break;
+    case UINTM:
+        s->reg[I_(UINTM)] = val;
+        exynos4210_uart_update_irq(s);
+        break;
+    case UCON:
+    case UMCON:
+    default:
+        s->reg[I_(offset)] = val;
+        break;
+    }
+}
+static uint64_t exynos4210_uart_read(void *opaque, target_phys_addr_t offset,
+                                  unsigned size)
+{
+    Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+    uint32_t res;
+
+    switch (offset) {
+    case UERSTAT: /* Read Only */
+        res = s->reg[I_(UERSTAT)];
+        s->reg[I_(UERSTAT)] = 0;
+        return res;
+    case UFSTAT: /* Read Only */
+        s->reg[I_(UFSTAT)] = fifo_elements_number(&s->rx) & 0xff;
+        if (fifo_empty_elements_number(&s->rx) == 0) {
+            s->reg[I_(UFSTAT)] |= UFSTAT_Rx_FIFO_FULL;
+            s->reg[I_(UFSTAT)] &= ~0xff;
+        }
+        return s->reg[I_(UFSTAT)];
+    case URXH:
+        if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) {
+            if (fifo_elements_number(&s->rx)) {
+                res = fifo_retrieve(&s->rx);
+#if DEBUG_Rx_DATA
+                fprintf(stderr, "%c", res);
+#endif
+                if (!fifo_elements_number(&s->rx)) {
+                    s->reg[I_(UTRSTAT)] &= ~UTRSTAT_Rx_BUFFER_DATA_READY;
+                } else {
+                    s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
+                }
+            } else {
+                s->reg[I_(UINTSP)] |= UINTSP_ERROR;
+                exynos4210_uart_update_irq(s);
+                res = 0;
+            }
+        } else {
+            s->reg[I_(UTRSTAT)] &= ~UTRSTAT_Rx_BUFFER_DATA_READY;
+            res = s->reg[I_(URXH)];
+        }
+        return res;
+    case UTXH:
+        PRINT_DEBUG("UART%d: Trying to read from WO register: %s [%04x]\n",
+                    s->channel, exynos4210_uart_regname(offset), offset);
+        break;
+    default:
+        return s->reg[I_(offset)];
+    }
+
+    return 0;
+}
+
+static const MemoryRegionOps exynos4210_uart_ops = {
+    .read = exynos4210_uart_read,
+    .write = exynos4210_uart_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .max_access_size = 4,
+        .unaligned = false
+    },
+};
+
+static int exynos4210_uart_can_receive(void *opaque)
+{
+    Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+
+    return fifo_empty_elements_number(&s->rx);
+}
+
+
+static void exynos4210_uart_receive(void *opaque, const uint8_t *buf, int size)
+{
+    Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+    int i;
+
+    if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) {
+        if (fifo_empty_elements_number(&s->rx) < size) {
+            for (i = 0; i < fifo_empty_elements_number(&s->rx); i++) {
+                fifo_store(&s->rx, buf[i]);
+            }
+            s->reg[I_(UINTSP)] |= UINTSP_ERROR;
+            s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
+        } else {
+            for (i = 0; i < size; i++) {
+                fifo_store(&s->rx, buf[i]);
+            }
+            s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
+        }
+        /* XXX: Around here we maybe should check Rx trigger level */
+        s->reg[I_(UINTSP)] |= UINTSP_RXD;
+    } else {
+        s->reg[I_(URXH)] = buf[0];
+        s->reg[I_(UINTSP)] |= UINTSP_RXD;
+        s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
+    }
+
+    exynos4210_uart_update_irq(s);
+}
+
+
+static void exynos4210_uart_event(void *opaque, int event)
+{
+    Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+
+    if (event == CHR_EVENT_BREAK) {
+        /* When the RxDn is held in logic 0, then a null byte is pushed into the
+         * fifo */
+        fifo_store(&s->rx, '\0');
+        s->reg[I_(UERSTAT)] |= UERSTAT_BREAK;
+        exynos4210_uart_update_irq(s);
+    }
+}
+
+
+static void exynos4210_uart_reset(DeviceState *dev)
+{
+    Exynos4210UartState *s =
+            container_of(dev, Exynos4210UartState, busdev.qdev);
+    int regs_number = sizeof(exynos4210_uart_regs)/sizeof(Exynos4210UartReg);
+    int i;
+
+    for (i = 0; i < regs_number; i++) {
+        s->reg[I_(exynos4210_uart_regs[i].offset)] =
+                exynos4210_uart_regs[i].reset_value;
+    }
+
+    fifo_reset(&s->rx);
+    fifo_reset(&s->tx);
+
+    PRINT_DEBUG("UART%d: Rx FIFO size: %d\n", s->channel, s->rx.size);
+}
+
+static const VMStateDescription vmstate_exynos4210_uart_fifo = {
+    .name = "exynos4210.uart.fifo",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(sp, Exynos4210UartFIFO),
+        VMSTATE_UINT32(rp, Exynos4210UartFIFO),
+        VMSTATE_VBUFFER_UINT32(data, Exynos4210UartFIFO, 1, NULL, 0, size),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static const VMStateDescription vmstate_exynos4210_uart = {
+    .name = "exynos4210.uart",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_STRUCT(rx, Exynos4210UartState, 1,
+                       vmstate_exynos4210_uart_fifo, Exynos4210UartFIFO),
+        VMSTATE_UINT32_ARRAY(reg, Exynos4210UartState,
+                             EXYNOS4210_UART_REGS_MEM_SIZE / sizeof(uint32_t)),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+DeviceState *exynos4210_uart_create(target_phys_addr_t addr,
+                                 int fifo_size,
+                                 int channel,
+                                 CharDriverState *chr,
+                                 qemu_irq irq)
+{
+    DeviceState  *dev;
+    SysBusDevice *bus;
+
+    dev = qdev_create(NULL, "exynos4210.uart");
+
+    if (!chr) {
+        if (channel >= MAX_SERIAL_PORTS) {
+            hw_error("Only %d serial ports are supported by QEMU.\n",
+                     MAX_SERIAL_PORTS);
+        }
+        chr = serial_hds[channel];
+        if (!chr) {
+            chr = qemu_chr_new("exynos4210.uart", "null", NULL);
+            if (!(chr)) {
+                hw_error("Can't assign serial port to UART%d.\n", channel);
+            }
+        }
+    }
+
+    qdev_prop_set_chr(dev, "chardev", chr);
+    qdev_prop_set_uint32(dev, "channel", channel);
+    qdev_prop_set_uint32(dev, "rx-size", fifo_size);
+    qdev_prop_set_uint32(dev, "tx-size", fifo_size);
+
+    bus = sysbus_from_qdev(dev);
+    qdev_init_nofail(dev);
+    if (addr != (target_phys_addr_t)-1) {
+        sysbus_mmio_map(bus, 0, addr);
+    }
+    sysbus_connect_irq(bus, 0, irq);
+
+    return dev;
+}
+
+static int exynos4210_uart_init(SysBusDevice *dev)
+{
+    Exynos4210UartState *s = FROM_SYSBUS(Exynos4210UartState, dev);
+
+    /* memory mapping */
+    memory_region_init_io(&s->iomem, &exynos4210_uart_ops, s, "exynos4210.uart",
+                          EXYNOS4210_UART_REGS_MEM_SIZE);
+    sysbus_init_mmio(dev, &s->iomem);
+
+    sysbus_init_irq(dev, &s->irq);
+
+    qemu_chr_add_handlers(s->chr, exynos4210_uart_can_receive,
+                          exynos4210_uart_receive, exynos4210_uart_event, s);
+
+    return 0;
+}
+
+static SysBusDeviceInfo exynos4210_uart_info = {
+    .init       = exynos4210_uart_init,
+    .qdev.name  = "exynos4210.uart",
+    .qdev.size  = sizeof(Exynos4210UartState),
+    .qdev.vmsd  = &vmstate_exynos4210_uart,
+    .qdev.reset = exynos4210_uart_reset,
+    .qdev.props = (Property[]) {
+        DEFINE_PROP_CHR("chardev", Exynos4210UartState, chr),
+        DEFINE_PROP_UINT32("channel", Exynos4210UartState, channel, 0),
+        DEFINE_PROP_UINT32("rx-size", Exynos4210UartState, rx.size, 16),
+        DEFINE_PROP_UINT32("tx-size", Exynos4210UartState, tx.size, 16),
+        DEFINE_PROP_END_OF_LIST(),
+    }
+};
+
+static void exynos4210_uart_register(void)
+{
+    sysbus_register_withprop(&exynos4210_uart_info);
+}
+
+device_init(exynos4210_uart_register)
-- 
1.7.4.1

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

* [Qemu-devel] [PATCH v9 4/9] ARM: exynos4210: PWM support.
  2012-01-20 10:53 [Qemu-devel] [PATCH v9 0/9] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
                   ` (2 preceding siblings ...)
  2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 3/9] ARM: exynos4210: UART support Evgeny Voevodin
@ 2012-01-20 10:53 ` Evgeny Voevodin
  2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 5/9] ARM: exynos4210: Added PMU register model Evgeny Voevodin
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 13+ messages in thread
From: Evgeny Voevodin @ 2012-01-20 10:53 UTC (permalink / raw)
  To: qemu-devel
  Cc: kyungmin.park, m.kozlov, jehyung.lee, d.solodkiy, Evgeny Voevodin


Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
---
 Makefile.target     |    2 +-
 hw/exynos4210.c     |   12 ++
 hw/exynos4210_pwm.c |  413 +++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 426 insertions(+), 1 deletions(-)
 create mode 100644 hw/exynos4210_pwm.c

diff --git a/Makefile.target b/Makefile.target
index c856de3..1914870 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -340,7 +340,7 @@ obj-arm-y += arm_boot.o pl011.o pl031.o pl050.o pl080.o pl110.o pl181.o pl190.o
 obj-arm-y += versatile_pci.o
 obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
 obj-arm-y += exynos4210_gic.o exynos4210_combiner.o exynos4210.o
-obj-arm-y += exynos4_boards.o exynos4210_uart.o
+obj-arm-y += exynos4_boards.o exynos4210_uart.o exynos4210_pwm.o
 obj-arm-y += arm_l2x0.o
 obj-arm-y += arm_mptimer.o
 obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index 3838b96..ea5a1f8 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -29,6 +29,9 @@
 
 #define EXYNOS4210_CHIPID_ADDR         0x10000000
 
+/* PWM */
+#define EXYNOS4210_PWM_BASE_ADDR       0x139D0000
+
 /* UART's definitions */
 #define EXYNOS4210_UART0_BASE_ADDR     0x13800000
 #define EXYNOS4210_UART1_BASE_ADDR     0x13810000
@@ -201,6 +204,15 @@ Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
     memory_region_add_subregion(system_mem, EXYNOS4210_DRAM0_BASE_ADDR,
             &s->dram0_mem);
 
+    /* PWM */
+    sysbus_create_varargs("exynos4210.pwm", EXYNOS4210_PWM_BASE_ADDR,
+            irq_table[exynos4210_get_irq(22, 0)],
+            irq_table[exynos4210_get_irq(22, 1)],
+            irq_table[exynos4210_get_irq(22, 2)],
+            irq_table[exynos4210_get_irq(22, 3)],
+            irq_table[exynos4210_get_irq(22, 4)],
+            NULL);
+
     /*** UARTs ***/
     exynos4210_uart_create(EXYNOS4210_UART0_BASE_ADDR,
                            EXYNOS4210_UART0_FIFO_SIZE, 0, NULL,
diff --git a/hw/exynos4210_pwm.c b/hw/exynos4210_pwm.c
new file mode 100644
index 0000000..29504d2
--- /dev/null
+++ b/hw/exynos4210_pwm.c
@@ -0,0 +1,413 @@
+/*
+ * Samsung exynos4210 Pulse Width Modulation Timer
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ *
+ * Evgeny Voevodin <e.voevodin@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "sysbus.h"
+#include "qemu-timer.h"
+#include "qemu-common.h"
+#include "ptimer.h"
+
+#include "exynos4210.h"
+
+//#define DEBUG_PWM
+
+#ifdef DEBUG_PWM
+#define DPRINTF(fmt, ...) \
+        do { fprintf(stdout, "PWM: [%24s:%5d] " fmt, __func__, __LINE__, \
+                ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+#define     EXYNOS4210_PWM_TIMERS_NUM      5
+#define     EXYNOS4210_PWM_REG_MEM_SIZE    0x50
+
+#define     TCFG0        0x0000
+#define     TCFG1        0x0004
+#define     TCON         0x0008
+#define     TCNTB0       0x000C
+#define     TCMPB0       0x0010
+#define     TCNTO0       0x0014
+#define     TCNTB1       0x0018
+#define     TCMPB1       0x001C
+#define     TCNTO1       0x0020
+#define     TCNTB2       0x0024
+#define     TCMPB2       0x0028
+#define     TCNTO2       0x002C
+#define     TCNTB3       0x0030
+#define     TCMPB3       0x0034
+#define     TCNTO3       0x0038
+#define     TCNTB4       0x003C
+#define     TCNTO4       0x0040
+#define     TINT_CSTAT   0x0044
+
+#define     TCNTB(x)    (0xC * (x))
+#define     TCMPB(x)    (0xC * (x) + 1)
+#define     TCNTO(x)    (0xC * (x) + 2)
+
+#define GET_PRESCALER(reg, x) (((reg) & (0xFF << (8 * (x)))) >> 8 * (x))
+#define GET_DIVIDER(reg, x) (1 << (((reg) & (0xF << (4 * (x)))) >> (4 * (x))))
+
+/*
+ * Attention! Timer4 doesn't have OUTPUT_INVERTER,
+ * so Auto Reload bit is not accessible by macros!
+ */
+#define     TCON_TIMER_BASE(x)          (((x) ? 1 : 0) * 4 + 4 * (x))
+#define     TCON_TIMER_START(x)         (1 << (TCON_TIMER_BASE(x) + 0))
+#define     TCON_TIMER_MANUAL_UPD(x)    (1 << (TCON_TIMER_BASE(x) + 1))
+#define     TCON_TIMER_OUTPUT_INV(x)    (1 << (TCON_TIMER_BASE(x) + 2))
+#define     TCON_TIMER_AUTO_RELOAD(x)   (1 << (TCON_TIMER_BASE(x) + 3))
+#define     TCON_TIMER4_AUTO_RELOAD     (1 << 22)
+
+#define     TINT_CSTAT_STATUS(x)        (1 << (5 + (x)))
+#define     TINT_CSTAT_ENABLE(x)        (1 << (x))
+
+/* timer struct */
+typedef struct {
+    uint32_t    id;             /* timer id */
+    qemu_irq    irq;            /* local timer irq */
+    uint32_t    freq;           /* timer frequency */
+
+    /* use ptimer.c to represent count down timer */
+    ptimer_state *ptimer;       /* timer  */
+
+    /* registers */
+    uint32_t    reg_tcntb;      /* counter register buffer */
+    uint32_t    reg_tcmpb;      /* compare register buffer */
+
+    struct Exynos4210PWMState *parent;
+
+} Exynos4210_pwm;
+
+
+typedef struct Exynos4210PWMState {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+
+    uint32_t    reg_tcfg[2];
+    uint32_t    reg_tcon;
+    uint32_t    reg_tint_cstat;
+
+    Exynos4210_pwm timer[EXYNOS4210_PWM_TIMERS_NUM];
+
+} Exynos4210PWMState;
+
+/*** VMState ***/
+static const VMStateDescription VMState_Exynos4210_pwm = {
+    .name = "exynos4210.pwm.pwm",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(id, Exynos4210_pwm),
+        VMSTATE_UINT32(freq, Exynos4210_pwm),
+        VMSTATE_PTIMER(ptimer, Exynos4210_pwm),
+        VMSTATE_UINT32(reg_tcntb, Exynos4210_pwm),
+        VMSTATE_UINT32(reg_tcmpb, Exynos4210_pwm),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static const VMStateDescription VMState_Exynos4210PWMState = {
+    .name = "exynos4210.pwm",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(reg_tcfg, Exynos4210PWMState, 2),
+        VMSTATE_UINT32(reg_tcon, Exynos4210PWMState),
+        VMSTATE_UINT32(reg_tint_cstat, Exynos4210PWMState),
+        VMSTATE_STRUCT_ARRAY(timer, Exynos4210PWMState,
+            EXYNOS4210_PWM_TIMERS_NUM, 0,
+        VMState_Exynos4210_pwm, Exynos4210_pwm),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+/*
+ * PWM update frequency
+ */
+static void exynos4210_pwm_update_freq(Exynos4210PWMState *s, uint32_t id)
+{
+    uint32_t freq;
+    freq = s->timer[id].freq;
+    if (id > 1) {
+        s->timer[id].freq = 24000000 /
+        ((GET_PRESCALER(s->reg_tcfg[0], 1) + 1) *
+                (GET_DIVIDER(s->reg_tcfg[1], id)));
+    } else {
+        s->timer[id].freq = 24000000 /
+        ((GET_PRESCALER(s->reg_tcfg[0], 0) + 1) *
+                (GET_DIVIDER(s->reg_tcfg[1], id)));
+    }
+
+    if (freq != s->timer[id].freq) {
+        ptimer_set_freq(s->timer[id].ptimer, s->timer[id].freq);
+        DPRINTF("freq=%dHz\n", s->timer[id].freq);
+    }
+}
+
+/*
+ * Counter tick handler
+ */
+static void exynos4210_pwm_tick(void *opaque)
+{
+    Exynos4210_pwm *s = (Exynos4210_pwm *)opaque;
+    Exynos4210PWMState *p = (Exynos4210PWMState *)s->parent;
+    uint32_t id = s->id;
+    bool cmp;
+
+    DPRINTF("timer %d tick\n", id);
+
+    /* set irq status */
+    p->reg_tint_cstat |= TINT_CSTAT_STATUS(id);
+
+    /* raise IRQ */
+    if (p->reg_tint_cstat & TINT_CSTAT_ENABLE(id)) {
+        DPRINTF("timer %d IRQ\n", id);
+        qemu_irq_raise(p->timer[id].irq);
+    }
+
+    /* reload timer */
+    if (id != 4) {
+        cmp = p->reg_tcon & TCON_TIMER_AUTO_RELOAD(id);
+    } else {
+        cmp = p->reg_tcon & TCON_TIMER4_AUTO_RELOAD;
+    }
+
+    if (cmp) {
+        DPRINTF("auto reload timer %d count to %x\n", id,
+                p->timer[id].reg_tcntb);
+        ptimer_set_count(p->timer[id].ptimer, p->timer[id].reg_tcntb);
+        ptimer_run(p->timer[id].ptimer, 1);
+    } else {
+        /* stop timer, set status to STOP, see Basic Timer Operation */
+        p->reg_tcon = ~TCON_TIMER_START(id);
+        ptimer_stop(p->timer[id].ptimer);
+    }
+}
+
+/*
+ * PWM Read
+ */
+static uint64_t exynos4210_pwm_read(void *opaque, target_phys_addr_t offset,
+        unsigned size)
+{
+    Exynos4210PWMState *s = (Exynos4210PWMState *)opaque;
+    uint32_t value = 0;
+    int index;
+
+    switch (offset) {
+    case TCFG0: case TCFG1:
+        index = (offset - TCFG0) >> 2;
+        value = s->reg_tcfg[index];
+        break;
+
+    case TCON:
+        value = s->reg_tcon;
+        break;
+
+    case TCNTB0: case TCNTB1:
+    case TCNTB2: case TCNTB3: case TCNTB4:
+        index = (offset - TCNTB0) / 0xC;
+        value = s->timer[index].reg_tcntb;
+        break;
+
+    case TCMPB0: case TCMPB1:
+    case TCMPB2: case TCMPB3:
+        index = (offset - TCMPB0) / 0xC;
+        value = s->timer[index].reg_tcmpb;
+        break;
+
+    case TCNTO0: case TCNTO1:
+    case TCNTO2: case TCNTO3: case TCNTO4:
+        index = (offset == TCNTO4) ? 4 : (offset - TCNTO0) / 0xC;
+        value = ptimer_get_count(s->timer[index].ptimer);
+        break;
+
+    case TINT_CSTAT:
+        value = s->reg_tint_cstat;
+        break;
+
+    default:
+        fprintf(stderr,
+                "[exynos4210.pwm: bad read offset " TARGET_FMT_plx "]\n",
+                offset);
+        break;
+    }
+    return value;
+}
+
+/*
+ * PWM Write
+ */
+static void exynos4210_pwm_write(void *opaque, target_phys_addr_t offset,
+        uint64_t value, unsigned size)
+{
+    Exynos4210PWMState *s = (Exynos4210PWMState *)opaque;
+    int index;
+    uint32_t new_val;
+    int i;
+
+    switch (offset) {
+    case TCFG0: case TCFG1:
+        index = (offset - TCFG0) >> 2;
+        s->reg_tcfg[index] = value;
+
+        /* update timers frequencies */
+        for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
+            exynos4210_pwm_update_freq(s, s->timer[i].id);
+        }
+        break;
+
+    case TCON:
+        for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
+            if ((value & TCON_TIMER_MANUAL_UPD(i)) >
+            (s->reg_tcon & TCON_TIMER_MANUAL_UPD(i))) {
+                /*
+                 * TCNTB and TCMPB are loaded into TCNT and TCMP.
+                 * Update timers.
+                 */
+
+                /* this will start timer to run, this ok, because
+                 * during processing start bit timer will be stopped
+                 * if needed */
+                ptimer_set_count(s->timer[i].ptimer, s->timer[i].reg_tcntb);
+                DPRINTF("set timer %d count to %x\n", i,
+                        s->timer[i].reg_tcntb);
+            }
+
+            if ((value & TCON_TIMER_START(i)) >
+            (s->reg_tcon & TCON_TIMER_START(i))) {
+                /* changed to start */
+                ptimer_run(s->timer[i].ptimer, 1);
+                DPRINTF("run timer %d\n", i);
+            }
+
+            if ((value & TCON_TIMER_START(i)) <
+                    (s->reg_tcon & TCON_TIMER_START(i))) {
+                /* changed to stop */
+                ptimer_stop(s->timer[i].ptimer);
+                DPRINTF("stop timer %d\n", i);
+            }
+        }
+        s->reg_tcon = value;
+        break;
+
+    case TCNTB0: case TCNTB1:
+    case TCNTB2: case TCNTB3: case TCNTB4:
+        index = (offset - TCNTB0) / 0xC;
+        s->timer[index].reg_tcntb = value;
+        break;
+
+    case TCMPB0: case TCMPB1:
+    case TCMPB2: case TCMPB3:
+        index = (offset - TCMPB0) / 0xC;
+        s->timer[index].reg_tcmpb = value;
+        break;
+
+    case TINT_CSTAT:
+        new_val = (s->reg_tint_cstat & 0x3E0) + (0x1F & value);
+        new_val &= ~(0x3E0 & value);
+
+        for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
+            if ((new_val & TINT_CSTAT_STATUS(i)) <
+                    (s->reg_tint_cstat & TINT_CSTAT_STATUS(i))) {
+                qemu_irq_lower(s->timer[i].irq);
+            }
+        }
+
+        s->reg_tint_cstat = new_val;
+        break;
+
+    default:
+        fprintf(stderr,
+                "[exynos4210.pwm: bad write offset " TARGET_FMT_plx "]\n",
+                offset);
+        break;
+
+    }
+}
+
+/*
+ * Set default values to timer fields and registers
+ */
+static void exynos4210_pwm_reset(DeviceState *d)
+{
+    Exynos4210PWMState *s = (Exynos4210PWMState *)d;
+    int i;
+    s->reg_tcfg[0] = 0x0101;
+    s->reg_tcfg[1] = 0x0;
+    s->reg_tcon = 0;
+    s->reg_tint_cstat = 0;
+    for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
+        s->timer[i].reg_tcmpb = 0;
+        s->timer[i].reg_tcntb = 0;
+
+        exynos4210_pwm_update_freq(s, s->timer[i].id);
+        ptimer_stop(s->timer[i].ptimer);
+    }
+}
+
+static const MemoryRegionOps exynos4210_pwm_ops = {
+    .read = exynos4210_pwm_read,
+    .write = exynos4210_pwm_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/*
+ * PWM timer initialization
+ */
+static int exynos4210_pwm_init(SysBusDevice *dev)
+{
+    Exynos4210PWMState *s = FROM_SYSBUS(Exynos4210PWMState, dev);
+    int i;
+    QEMUBH *bh;
+
+    for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
+        bh = qemu_bh_new(exynos4210_pwm_tick, &s->timer[i]);
+        sysbus_init_irq(dev, &s->timer[i].irq);
+        s->timer[i].ptimer = ptimer_init(bh);
+        s->timer[i].id = i;
+        s->timer[i].parent = s;
+    }
+
+    memory_region_init_io(&s->iomem, &exynos4210_pwm_ops, s, "exynos4210-pwm",
+            EXYNOS4210_PWM_REG_MEM_SIZE);
+    sysbus_init_mmio(dev, &s->iomem);
+
+    return 0;
+}
+
+static SysBusDeviceInfo exynos4210_pwm_info = {
+    .qdev.name  = "exynos4210.pwm",
+    .qdev.size  = sizeof(struct Exynos4210PWMState),
+    .qdev.reset = exynos4210_pwm_reset,
+    .qdev.vmsd = &VMState_Exynos4210PWMState,
+    .init = exynos4210_pwm_init,
+};
+
+static void exynos4210_pwm_register_devices(void)
+{
+    sysbus_register_withprop(&exynos4210_pwm_info);
+}
+
+device_init(exynos4210_pwm_register_devices)
-- 
1.7.4.1

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

* [Qemu-devel] [PATCH v9 5/9] ARM: exynos4210: Added PMU register model.
  2012-01-20 10:53 [Qemu-devel] [PATCH v9 0/9] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
                   ` (3 preceding siblings ...)
  2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 4/9] ARM: exynos4210: PWM support Evgeny Voevodin
@ 2012-01-20 10:53 ` Evgeny Voevodin
  2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 6/9] ARM: exynos4210: MCT support Evgeny Voevodin
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 13+ messages in thread
From: Evgeny Voevodin @ 2012-01-20 10:53 UTC (permalink / raw)
  To: qemu-devel
  Cc: kyungmin.park, m.kozlov, jehyung.lee, d.solodkiy, Evgeny Voevodin

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

This model just implements PMU registers as a bulk of memory.
The only reason of existence in such form is that secondary CPU
boot loader uses PMU INFORM5 register as a holding pen.

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

diff --git a/Makefile.target b/Makefile.target
index 1914870..6cddf0c 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -341,6 +341,7 @@ obj-arm-y += versatile_pci.o
 obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
 obj-arm-y += exynos4210_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
 obj-arm-y += arm_l2x0.o
 obj-arm-y += arm_mptimer.o
 obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index ea5a1f8..2e2dbf0 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -52,6 +52,9 @@
 #define EXYNOS4210_EXT_COMBINER_BASE_ADDR   0x10440000
 #define EXYNOS4210_INT_COMBINER_BASE_ADDR   0x10448000
 
+/* PMU SFR base address */
+#define EXYNOS4210_PMU_BASE_ADDR            0x10020000
+
 static uint8_t chipid_and_omr[] = { 0x11, 0x02, 0x21, 0x43,
                                     0x09, 0x00, 0x00, 0x00 };
 
@@ -204,6 +207,12 @@ Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
     memory_region_add_subregion(system_mem, EXYNOS4210_DRAM0_BASE_ADDR,
             &s->dram0_mem);
 
+   /* PMU.
+    * The only reason of existence at the moment is that secondary CPU boot
+    * loader uses PMU INFORM5 register as a holding pen.
+    */
+    sysbus_create_simple("exynos4210.pmu", EXYNOS4210_PMU_BASE_ADDR, NULL);
+
     /* PWM */
     sysbus_create_varargs("exynos4210.pwm", EXYNOS4210_PWM_BASE_ADDR,
             irq_table[exynos4210_get_irq(22, 0)],
diff --git a/hw/exynos4210_pmu.c b/hw/exynos4210_pmu.c
new file mode 100644
index 0000000..235c3c3
--- /dev/null
+++ b/hw/exynos4210_pmu.c
@@ -0,0 +1,549 @@
+/*
+ *  exynos4210 Power Management Unit (PMU) 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/>.
+ */
+
+/*
+ * This model just implements PMU registers as a bulk of memory. The only reason
+ * of existence in such form is that secondary CPU boot loader uses PMU INFORM5
+ * register as a holding pen.
+ */
+
+#include "sysbus.h"
+
+#undef DEBUG_PMU
+#undef DEBUG_PMU_EXTEND
+
+//#define DEBUG_PMU
+//#define DEBUG_PMU_EXTEND
+
+#define  PRINT_DEBUG(fmt, args...)  \
+        do {} while (0)
+#define  PRINT_DEBUG_EXTEND(fmt, args...) \
+        do {} while (0)
+
+#ifdef DEBUG_PMU
+
+#undef PRINT_DEBUG
+#define  PRINT_DEBUG(fmt, args...)  \
+        do { \
+            fprintf(stderr, "  [%s:%d]   "fmt, __func__, __LINE__, ##args); \
+        } while (0)
+
+#ifdef DEBUG_PMU_EXTEND
+
+#undef PRINT_DEBUG_EXTEND
+#define  PRINT_DEBUG_EXTEND(fmt, args...) \
+        do { \
+            fprintf(stderr, "  [%s:%d]   "fmt, __func__, __LINE__, ##args); \
+        } while (0)
+
+#endif /* EXTEND */
+#endif
+
+/*
+ *  Offsets for PMU registers
+ */
+#define OM_STAT                  0x0000 /* OM status register */
+#define RTC_CLKO_SEL             0x000C /* Controls RTCCLKOUT */
+#define GNSS_RTC_OUT_CTRL        0x0010 /* Controls GNSS_RTC_OUT */
+#define SYSTEM_POWER_DOWN_CTRL   0x0200 /* Decides whether system-level
+                                           low-power mode is used. */
+#define SYSTEM_POWER_DOWN_OPTION 0x0208 /* Sets control options for
+                                           CENTRAL_SEQ */
+#define SWRESET                  0x0400 /* Generate software reset */
+#define RST_STAT                 0x0404 /* Reset status register */
+#define WAKEUP_STAT              0x0600 /* Wakeup status register  */
+#define EINT_WAKEUP_MASK         0x0604 /* Configure External INTerrupt mask */
+#define WAKEUP_MASK              0x0608 /* Configure wakeup source mask */
+#define HDMI_PHY_CONTROL         0x0700 /* HDMI PHY control register */
+#define USBDEVICE_PHY_CONTROL    0x0704 /* USB Device PHY control register */
+#define USBHOST_PHY_CONTROL      0x0708 /* USB HOST PHY control register */
+#define DAC_PHY_CONTROL          0x070C /* DAC control register  */
+#define MIPI_PHY0_CONTROL        0x0710 /* MIPI PHY control register */
+#define MIPI_PHY1_CONTROL        0x0714 /* MIPI PHY control register */
+#define ADC_PHY_CONTROL          0x0718 /* TS-ADC control register */
+#define PCIe_PHY_CONTROL         0x071C /* TS-PCIe control register */
+#define SATA_PHY_CONTROL         0x0720 /* TS-SATA control register */
+#define INFORM0                  0x0800 /* Information register 0  */
+#define INFORM1                  0x0804 /* Information register 1  */
+#define INFORM2                  0x0808 /* Information register 2  */
+#define INFORM3                  0x080C /* Information register 3  */
+#define INFORM4                  0x0810 /* Information register 4  */
+#define INFORM5                  0x0814 /* Information register 5  */
+#define INFORM6                  0x0818 /* Information register 6  */
+#define INFORM7                  0x081C /* Information register 7  */
+#define PMU_DEBUG                0x0A00 /* PMU debug register */
+#define ARM_CORE0_SYS_PWR_REG    0x1000 /* Sets system-level low-power option */
+#define ARM_CORE1_SYS_PWR_REG    0x1010 /* Sets system-level low-power option */
+#define ARM_COMMON_SYS_PWR_REG   0x1080 /* Sets system-level low-power option */
+#define ARM_CPU_L2_0_SYS_PWR_REG 0x10C0 /* Sets system-level low-power option */
+#define ARM_CPU_L2_1_SYS_PWR_REG 0x10C4 /* Sets system-level low-power option */
+#define CMU_ACLKSTOP_SYS_PWR_REG 0x1100 /* Sets system-level low-power option */
+#define CMU_SCLKSTOP_SYS_PWR_REG 0x1104 /* Sets system-level low-power option */
+#define CMU_RESET_SYS_PWR_REG    0x110C /* Sets system-level low-power option */
+#define APLL_SYSCLK_SYS_PWR_REG  0x1120 /* Sets system-level low-power option */
+#define MPLL_SYSCLK_SYS_PWR_REG  0x1124 /* Sets system-level low-power option */
+#define VPLL_SYSCLK_SYS_PWR_REG  0x1128 /* Sets system-level low-power option */
+#define EPLL_SYSCLK_SYS_PWR_REG  0x112C /* Sets system-level low-power option */
+#define CMU_CLKSTOP_GPS_ALIVE_SYS_PWR_REG  0x1138 /* Sets system-level low-power
+                                                     option */
+#define CMU_RESET_GPS_ALIVE_SYS_PWR_REG    0x113C /* Sets system-level low-power
+                                                     option */
+#define CMU_CLKSTOP_CAM_SYS_PWR_REG 0x1140 /* Sets system-level low-power option
+ */
+#define CMU_CLKSTOP_TV_SYS_PWR_REG  0x1144 /* Sets system-level low-power option
+ */
+#define CMU_CLKSTOP_MFC_SYS_PWR_REG 0x1148 /* Sets system-level low-power option
+ */
+#define CMU_CLKSTOP_G3D_SYS_PWR_REG 0x114C /* Sets system-level low-power option
+ */
+#define CMU_CLKSTOP_LCD0_SYS_PWR_REG 0x1150 /* Sets system-level low-power
+                                               option */
+#define CMU_CLKSTOP_LCD1_SYS_PWR_REG 0x1154 /* Sets system-level low-power
+                                               option */
+#define CMU_CLKSTOP_MAUDIO_SYS_PWR_REG 0x1158 /* Sets system-level low-power
+                                                 option */
+#define CMU_CLKSTOP_GPS_SYS_PWR_REG    0x115C /* Sets system-level low-power
+                                                 option */
+#define CMU_RESET_CAM_SYS_PWR_REG      0x1160 /* Sets system-level low-power
+                                                 option */
+#define CMU_RESET_TV_SYS_PWR_REG       0x1164 /* Sets system-level low-power
+                                                 option */
+#define CMU_RESET_MFC_SYS_PWR_REG      0x1168 /* Sets system-level low-power
+                                                 option */
+#define CMU_RESET_G3D_SYS_PWR_REG      0x116C /* Sets system-level low-power
+                                                 option */
+#define CMU_RESET_LCD0_SYS_PWR_REG     0x1170 /* Sets system-level low-power
+                                                 option */
+#define CMU_RESET_LCD1_SYS_PWR_REG     0x1174 /* Sets system-level low-power
+                                                 option */
+#define CMU_RESET_MAUDIO_SYS_PWR_REG   0x1178 /* Sets system-level low-power
+                                                 option */
+#define CMU_RESET_GPS_SYS_PWR_REG      0x117C /* Sets system-level low-power
+                                                 option */
+#define TOP_BUS_SYS_PWR_REG            0x1180 /* Sets system-level low-power
+                                                 option */
+#define TOP_RETENTION_SYS_PWR_REG      0x1184 /* Sets system-level low-power
+                                                 option */
+#define TOP_PWR_SYS_PWR_REG            0x1188 /* Sets system-level low-power
+                                                 option */
+#define LOGIC_RESET_SYS_PWR_REG        0x11A0 /* Sets system-level low-power
+                                                 option */
+#define OneNANDXL_MEM_SYS_PWR_REG      0x11C0 /* Sets system-level low-power
+                                                 option */
+#define MODEMIF_MEM_SYS_PWR_REG        0x11C4 /* Sets system-level low-power
+                                                 option */
+#define USBDEVICE_MEM_SYS_PWR_REG      0x11CC /* Sets system-level low-power
+                                                 option */
+#define SDMMC_MEM_SYS_PWR_REG   0x11D0 /* Sets system-level low-power
+                                          option */
+#define CSSYS_MEM_SYS_PWR_REG   0x11D4 /* Sets system-level low-power
+                                          option */
+#define SECSS_MEM_SYS_PWR_REG   0x11D8 /* Sets system-level low-power
+                                          option */
+#define PCIe_MEM_SYS_PWR_REG    0x11E0 /* Sets system-level low-power
+                                          option */
+#define SATA_MEM_SYS_PWR_REG    0x11E4 /* Sets system-level low-power
+                                          option */
+#define PAD_RETENTION_DRAM_SYS_PWR_REG     0x1200 /* Sets system-level low-power
+                                                     option */
+#define PAD_RETENTION_MAUDIO_SYS_PWR_REG   0x1204 /* Sets system-level low-power
+                                                     option */
+#define PAD_RETENTION_GPIO_SYS_PWR_REG     0x1220 /* Sets system-level low-power
+                                                     option */
+#define PAD_RETENTION_UART_SYS_PWR_REG     0x1224 /* Sets system-level low-power
+                                                     option */
+#define PAD_RETENTION_MMCA_SYS_PWR_REG     0x1228 /* Sets system-level low-power
+                                                     option */
+#define PAD_RETENTION_MMCB_SYS_PWR_REG     0x122C /* Sets system-level low-power
+                                                     option */
+#define PAD_RETENTION_EBIA_SYS_PWR_REG     0x1230 /* Sets system-level low-power
+                                                     option */
+#define PAD_RETENTION_EBIB_SYS_PWR_REG     0x1234 /* Sets system-level low-power
+                                                     option */
+#define PAD_ISOLATION_SYS_PWR_REG          0x1240 /* Sets system-level low-power
+                                                     option */
+#define PAD_ALV_SEL_SYS_PWR_REG 0x1260 /* Sets system-level low-power option */
+#define XUSBXTI_SYS_PWR_REG     0x1280 /* Sets system-level low-power option */
+#define XXTI_SYS_PWR_REG        0x1284 /* Sets system-level low-power option */
+#define EXT_REGULATOR_SYS_PWR_REG 0x12C0 /* Sets system-level low-power
+                                            option */
+#define GPIO_MODE_SYS_PWR_REG   0x1300 /* Sets system-level low-power option */
+#define GPIO_MODE_MAUDIO_SYS_PWR_REG 0x1340 /* Sets system-level low-power
+                                               option */
+#define CAM_SYS_PWR_REG         0x1380 /* Sets system-level low-power option */
+#define TV_SYS_PWR_REG          0x1384 /* Sets system-level low-power option */
+#define MFC_SYS_PWR_REG         0x1388 /* Sets system-level low-power option */
+#define G3D_SYS_PWR_REG         0x138C /* Sets system-level low-power option */
+#define LCD0_SYS_PWR_REG        0x1390 /* Sets system-level low-power option */
+#define LCD1_SYS_PWR_REG        0x1394 /* Sets system-level low-power option */
+#define MAUDIO_SYS_PWR_REG      0x1398 /* Sets system-level low-power option */
+#define GPS_SYS_PWR_REG         0x139C /* Sets system-level low-power option */
+#define GPS_ALIVE_SYS_PWR_REG   0x13A0 /* Sets system-level low-power option */
+#define ARM_CORE0_CONFIGURATION 0x2000 /* Configure power mode of ARM_CORE0 */
+#define ARM_CORE0_STATUS        0x2004 /* Check power mode of ARM_CORE0 */
+#define ARM_CORE0_OPTION        0x2008 /* Sets control options for ARM_CORE0 */
+#define ARM_CORE1_CONFIGURATION 0x2080 /* Configure power mode of ARM_CORE1 */
+#define ARM_CORE1_STATUS        0x2084 /* Check power mode of ARM_CORE1 */
+#define ARM_CORE1_OPTION        0x2088 /* Sets control options for ARM_CORE0 */
+#define ARM_COMMON_OPTION       0x2408 /* Sets control options for ARM_COMMON */
+#define ARM_CPU_L2_0_CONFIGURATION 0x2600 /* Configure power mode of
+                                             ARM_CPU_L2_0 */
+#define ARM_CPU_L2_0_STATUS     0x2604 /* Check power mode of ARM_CPU_L2_0 */
+#define ARM_CPU_L2_1_CONFIGURATION 0x2620 /* Configure power mode of
+                                             ARM_CPU_L2_1 */
+#define ARM_CPU_L2_1_STATUS     0x2624 /* Check power mode of ARM_CPU_L2_1 */
+#define PAD_RETENTION_MAUDIO_OPTION 0x3028 /* Sets control options for
+                                              PAD_RETENTION_MAUDIO */
+#define PAD_RETENTION_GPIO_OPTION   0x3108 /* Sets control options for
+                                              PAD_RETENTION_GPIO */
+#define PAD_RETENTION_UART_OPTION   0x3128 /* Sets control options for
+                                              PAD_RETENTION_UART */
+#define PAD_RETENTION_MMCA_OPTION   0x3148 /* Sets control options for
+                                              PAD_RETENTION_MMCA */
+#define PAD_RETENTION_MMCB_OPTION   0x3168 /* Sets control options for
+                                              PAD_RETENTION_MMCB */
+#define PAD_RETENTION_EBIA_OPTION   0x3188 /* Sets control options for
+                                              PAD_RETENTION_EBIA */
+#define PAD_RETENTION_EBIB_OPTION   0x31A8 /* Sets control options for
+                                              PAD_RETENTION_EBIB */
+#define PS_HOLD_CONTROL         0x330C /* PS_HOLD control register */
+#define XUSBXTI_CONFIGURATION   0x3400 /* Configure the pad of XUSBXTI */
+#define XUSBXTI_STATUS          0x3404 /* Check the pad of XUSBXTI */
+#define XUSBXTI_DURATION        0x341C /* Sets time required for XUSBXTI to be
+                                          stabilized */
+#define XXTI_CONFIGURATION      0x3420 /* Configure the pad of XXTI */
+#define XXTI_STATUS             0x3424 /* Check the pad of XXTI */
+#define XXTI_DURATION           0x343C /* Sets time required for XXTI to be
+                                          stabilized */
+#define EXT_REGULATOR_DURATION  0x361C /* Sets time required for EXT_REGULATOR
+                                          to be stabilized */
+#define CAM_CONFIGURATION       0x3C00 /* Configure power mode of CAM */
+#define CAM_STATUS              0x3C04 /* Check power mode of CAM */
+#define CAM_OPTION              0x3C08 /* Sets control options for CAM */
+#define TV_CONFIGURATION        0x3C20 /* Configure power mode of TV */
+#define TV_STATUS               0x3C24 /* Check power mode of TV */
+#define TV_OPTION               0x3C28 /* Sets control options for TV */
+#define MFC_CONFIGURATION       0x3C40 /* Configure power mode of MFC */
+#define MFC_STATUS              0x3C44 /* Check power mode of MFC */
+#define MFC_OPTION              0x3C48 /* Sets control options for MFC */
+#define G3D_CONFIGURATION       0x3C60 /* Configure power mode of G3D */
+#define G3D_STATUS              0x3C64 /* Check power mode of G3D */
+#define G3D_OPTION              0x3C68 /* Sets control options for G3D */
+#define LCD0_CONFIGURATION      0x3C80 /* Configure power mode of LCD0 */
+#define LCD0_STATUS             0x3C84 /* Check power mode of LCD0 */
+#define LCD0_OPTION             0x3C88 /* Sets control options for LCD0 */
+#define LCD1_CONFIGURATION      0x3CA0 /* Configure power mode of LCD1 */
+#define LCD1_STATUS             0x3CA4 /* Check power mode of LCD1 */
+#define LCD1_OPTION             0x3CA8 /* Sets control options for LCD1 */
+#define GPS_CONFIGURATION       0x3CE0 /* Configure power mode of GPS */
+#define GPS_STATUS              0x3CE4 /* Check power mode of GPS */
+#define GPS_OPTION              0x3CE8 /* Sets control options for GPS */
+#define GPS_ALIVE_CONFIGURATION 0x3D00 /* Configure power mode of GPS */
+#define GPS_ALIVE_STATUS        0x3D04 /* Check power mode of GPS */
+#define GPS_ALIVE_OPTION        0x3D08 /* Sets control options for GPS */
+
+#define EXYNOS4210_PMU_REGS_MEM_SIZE 0x3d0c
+
+typedef struct Exynos4210PmuReg {
+    const char  *name; /* the only reason is the debug output */
+    uint32_t     offset;
+    uint32_t     reset_value;
+} Exynos4210PmuReg;
+
+static const Exynos4210PmuReg exynos4210_pmu_regs[] = {
+    {"OM_STAT", OM_STAT, 0x00000000},
+    {"RTC_CLKO_SEL", RTC_CLKO_SEL, 0x00000000},
+    {"GNSS_RTC_OUT_CTRL", GNSS_RTC_OUT_CTRL, 0x00000001},
+    {"SYSTEM_POWER_DOWN_CTRL", SYSTEM_POWER_DOWN_CTRL, 0x00010000},
+    {"SYSTEM_POWER_DOWN_OPTION", SYSTEM_POWER_DOWN_OPTION, 0x03030000},
+    {"SWRESET", SWRESET, 0x00000000},
+    {"RST_STAT", RST_STAT, 0x00000000},
+    {"WAKEUP_STAT", WAKEUP_STAT, 0x00000000},
+    {"EINT_WAKEUP_MASK", EINT_WAKEUP_MASK, 0x00000000},
+    {"WAKEUP_MASK", WAKEUP_MASK, 0x00000000},
+    {"HDMI_PHY_CONTROL", HDMI_PHY_CONTROL, 0x00960000},
+    {"USBDEVICE_PHY_CONTROL", USBDEVICE_PHY_CONTROL, 0x00000000},
+    {"USBHOST_PHY_CONTROL", USBHOST_PHY_CONTROL, 0x00000000},
+    {"DAC_PHY_CONTROL", DAC_PHY_CONTROL, 0x00000000},
+    {"MIPI_PHY0_CONTROL", MIPI_PHY0_CONTROL, 0x00000000},
+    {"MIPI_PHY1_CONTROL", MIPI_PHY1_CONTROL, 0x00000000},
+    {"ADC_PHY_CONTROL", ADC_PHY_CONTROL, 0x00000001},
+    {"PCIe_PHY_CONTROL", PCIe_PHY_CONTROL, 0x00000000},
+    {"SATA_PHY_CONTROL", SATA_PHY_CONTROL, 0x00000000},
+    {"INFORM0", INFORM0, 0x00000000},
+    {"INFORM1", INFORM1, 0x00000000},
+    {"INFORM2", INFORM2, 0x00000000},
+    {"INFORM3", INFORM3, 0x00000000},
+    {"INFORM4", INFORM4, 0x00000000},
+    {"INFORM5", INFORM5, 0x00000000},
+    {"INFORM6", INFORM6, 0x00000000},
+    {"INFORM7", INFORM7, 0x00000000},
+    {"PMU_DEBUG", PMU_DEBUG, 0x00000000},
+    {"ARM_CORE0_SYS_PWR_REG", ARM_CORE0_SYS_PWR_REG, 0xFFFFFFFF},
+    {"ARM_CORE1_SYS_PWR_REG", ARM_CORE1_SYS_PWR_REG, 0xFFFFFFFF},
+    {"ARM_COMMON_SYS_PWR_REG", ARM_COMMON_SYS_PWR_REG, 0xFFFFFFFF},
+    {"ARM_CPU_L2_0_SYS_PWR_REG", ARM_CPU_L2_0_SYS_PWR_REG, 0xFFFFFFFF},
+    {"ARM_CPU_L2_1_SYS_PWR_REG", ARM_CPU_L2_1_SYS_PWR_REG, 0xFFFFFFFF},
+    {"CMU_ACLKSTOP_SYS_PWR_REG", CMU_ACLKSTOP_SYS_PWR_REG, 0xFFFFFFFF},
+    {"CMU_SCLKSTOP_SYS_PWR_REG", CMU_SCLKSTOP_SYS_PWR_REG, 0xFFFFFFFF},
+    {"CMU_RESET_SYS_PWR_REG", CMU_RESET_SYS_PWR_REG, 0xFFFFFFFF},
+    {"APLL_SYSCLK_SYS_PWR_REG", APLL_SYSCLK_SYS_PWR_REG, 0xFFFFFFFF},
+    {"MPLL_SYSCLK_SYS_PWR_REG", MPLL_SYSCLK_SYS_PWR_REG, 0xFFFFFFFF},
+    {"VPLL_SYSCLK_SYS_PWR_REG", VPLL_SYSCLK_SYS_PWR_REG, 0xFFFFFFFF},
+    {"EPLL_SYSCLK_SYS_PWR_REG", EPLL_SYSCLK_SYS_PWR_REG, 0xFFFFFFFF},
+    {"CMU_CLKSTOP_GPS_ALIVE_SYS_PWR_REG", CMU_CLKSTOP_GPS_ALIVE_SYS_PWR_REG,
+            0xFFFFFFFF},
+    {"CMU_RESET_GPS_ALIVE_SYS_PWR_REG", CMU_RESET_GPS_ALIVE_SYS_PWR_REG,
+            0xFFFFFFFF},
+    {"CMU_CLKSTOP_CAM_SYS_PWR_REG", CMU_CLKSTOP_CAM_SYS_PWR_REG, 0xFFFFFFFF},
+    {"CMU_CLKSTOP_TV_SYS_PWR_REG", CMU_CLKSTOP_TV_SYS_PWR_REG, 0xFFFFFFFF},
+    {"CMU_CLKSTOP_MFC_SYS_PWR_REG", CMU_CLKSTOP_MFC_SYS_PWR_REG, 0xFFFFFFFF},
+    {"CMU_CLKSTOP_G3D_SYS_PWR_REG", CMU_CLKSTOP_G3D_SYS_PWR_REG, 0xFFFFFFFF},
+    {"CMU_CLKSTOP_LCD0_SYS_PWR_REG", CMU_CLKSTOP_LCD0_SYS_PWR_REG, 0xFFFFFFFF},
+    {"CMU_CLKSTOP_LCD1_SYS_PWR_REG", CMU_CLKSTOP_LCD1_SYS_PWR_REG, 0xFFFFFFFF},
+    {"CMU_CLKSTOP_MAUDIO_SYS_PWR_REG", CMU_CLKSTOP_MAUDIO_SYS_PWR_REG,
+            0xFFFFFFFF},
+    {"CMU_CLKSTOP_GPS_SYS_PWR_REG", CMU_CLKSTOP_GPS_SYS_PWR_REG, 0xFFFFFFFF},
+    {"CMU_RESET_CAM_SYS_PWR_REG", CMU_RESET_CAM_SYS_PWR_REG, 0xFFFFFFFF},
+    {"CMU_RESET_TV_SYS_PWR_REG", CMU_RESET_TV_SYS_PWR_REG, 0xFFFFFFFF},
+    {"CMU_RESET_MFC_SYS_PWR_REG", CMU_RESET_MFC_SYS_PWR_REG, 0xFFFFFFFF},
+    {"CMU_RESET_G3D_SYS_PWR_REG", CMU_RESET_G3D_SYS_PWR_REG, 0xFFFFFFFF},
+    {"CMU_RESET_LCD0_SYS_PWR_REG", CMU_RESET_LCD0_SYS_PWR_REG, 0xFFFFFFFF},
+    {"CMU_RESET_LCD1_SYS_PWR_REG", CMU_RESET_LCD1_SYS_PWR_REG, 0xFFFFFFFF},
+    {"CMU_RESET_MAUDIO_SYS_PWR_REG", CMU_RESET_MAUDIO_SYS_PWR_REG, 0xFFFFFFFF},
+    {"CMU_RESET_GPS_SYS_PWR_REG", CMU_RESET_GPS_SYS_PWR_REG, 0xFFFFFFFF},
+    {"TOP_BUS_SYS_PWR_REG", TOP_BUS_SYS_PWR_REG, 0xFFFFFFFF},
+    {"TOP_RETENTION_SYS_PWR_REG", TOP_RETENTION_SYS_PWR_REG, 0xFFFFFFFF},
+    {"TOP_PWR_SYS_PWR_REG", TOP_PWR_SYS_PWR_REG, 0xFFFFFFFF},
+    {"LOGIC_RESET_SYS_PWR_REG", LOGIC_RESET_SYS_PWR_REG, 0xFFFFFFFF},
+    {"OneNANDXL_MEM_SYS_PWR_REG", OneNANDXL_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+    {"MODEMIF_MEM_SYS_PWR_REG", MODEMIF_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+    {"USBDEVICE_MEM_SYS_PWR_REG", USBDEVICE_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+    {"SDMMC_MEM_SYS_PWR_REG", SDMMC_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+    {"CSSYS_MEM_SYS_PWR_REG", CSSYS_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+    {"SECSS_MEM_SYS_PWR_REG", SECSS_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+    {"PCIe_MEM_SYS_PWR_REG", PCIe_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+    {"SATA_MEM_SYS_PWR_REG", SATA_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+    {"PAD_RETENTION_DRAM_SYS_PWR_REG", PAD_RETENTION_DRAM_SYS_PWR_REG,
+            0xFFFFFFFF},
+    {"PAD_RETENTION_MAUDIO_SYS_PWR_REG", PAD_RETENTION_MAUDIO_SYS_PWR_REG,
+            0xFFFFFFFF},
+    {"PAD_RETENTION_GPIO_SYS_PWR_REG", PAD_RETENTION_GPIO_SYS_PWR_REG,
+            0xFFFFFFFF},
+    {"PAD_RETENTION_UART_SYS_PWR_REG", PAD_RETENTION_UART_SYS_PWR_REG,
+            0xFFFFFFFF},
+    {"PAD_RETENTION_MMCA_SYS_PWR_REG", PAD_RETENTION_MMCA_SYS_PWR_REG,
+            0xFFFFFFFF},
+    {"PAD_RETENTION_MMCB_SYS_PWR_REG", PAD_RETENTION_MMCB_SYS_PWR_REG,
+            0xFFFFFFFF},
+    {"PAD_RETENTION_EBIA_SYS_PWR_REG", PAD_RETENTION_EBIA_SYS_PWR_REG,
+            0xFFFFFFFF},
+    {"PAD_RETENTION_EBIB_SYS_PWR_REG", PAD_RETENTION_EBIB_SYS_PWR_REG,
+            0xFFFFFFFF},
+    {"PAD_ISOLATION_SYS_PWR_REG", PAD_ISOLATION_SYS_PWR_REG, 0xFFFFFFFF},
+    {"PAD_ALV_SEL_SYS_PWR_REG", PAD_ALV_SEL_SYS_PWR_REG, 0xFFFFFFFF},
+    {"XUSBXTI_SYS_PWR_REG", XUSBXTI_SYS_PWR_REG, 0xFFFFFFFF},
+    {"XXTI_SYS_PWR_REG", XXTI_SYS_PWR_REG, 0xFFFFFFFF},
+    {"EXT_REGULATOR_SYS_PWR_REG", EXT_REGULATOR_SYS_PWR_REG, 0xFFFFFFFF},
+    {"GPIO_MODE_SYS_PWR_REG", GPIO_MODE_SYS_PWR_REG, 0xFFFFFFFF},
+    {"GPIO_MODE_MAUDIO_SYS_PWR_REG", GPIO_MODE_MAUDIO_SYS_PWR_REG, 0xFFFFFFFF},
+    {"CAM_SYS_PWR_REG", CAM_SYS_PWR_REG, 0xFFFFFFFF},
+    {"TV_SYS_PWR_REG", TV_SYS_PWR_REG, 0xFFFFFFFF},
+    {"MFC_SYS_PWR_REG", MFC_SYS_PWR_REG, 0xFFFFFFFF},
+    {"G3D_SYS_PWR_REG", G3D_SYS_PWR_REG, 0xFFFFFFFF},
+    {"LCD0_SYS_PWR_REG", LCD0_SYS_PWR_REG, 0xFFFFFFFF},
+    {"LCD1_SYS_PWR_REG", LCD1_SYS_PWR_REG, 0xFFFFFFFF},
+    {"MAUDIO_SYS_PWR_REG", MAUDIO_SYS_PWR_REG, 0xFFFFFFFF},
+    {"GPS_SYS_PWR_REG", GPS_SYS_PWR_REG, 0xFFFFFFFF},
+    {"GPS_ALIVE_SYS_PWR_REG", GPS_ALIVE_SYS_PWR_REG, 0xFFFFFFFF},
+    {"ARM_CORE0_CONFIGURATION", ARM_CORE0_CONFIGURATION, 0x00000003},
+    {"ARM_CORE0_STATUS", ARM_CORE0_STATUS, 0x00030003},
+    {"ARM_CORE0_OPTION", ARM_CORE0_OPTION, 0x01010001},
+    {"ARM_CORE1_CONFIGURATION", ARM_CORE1_CONFIGURATION, 0x00000003},
+    {"ARM_CORE1_STATUS", ARM_CORE1_STATUS, 0x00030003},
+    {"ARM_CORE1_OPTION", ARM_CORE1_OPTION, 0x01010001},
+    {"ARM_COMMON_OPTION", ARM_COMMON_OPTION, 0x00000001},
+    {"ARM_CPU_L2_0_CONFIGURATION", ARM_CPU_L2_0_CONFIGURATION, 0x00000003},
+    {"ARM_CPU_L2_0_STATUS", ARM_CPU_L2_0_STATUS, 0x00000003},
+    {"ARM_CPU_L2_1_CONFIGURATION", ARM_CPU_L2_1_CONFIGURATION, 0x00000003},
+    {"ARM_CPU_L2_1_STATUS", ARM_CPU_L2_1_STATUS, 0x00000003},
+    {"PAD_RETENTION_MAUDIO_OPTION", PAD_RETENTION_MAUDIO_OPTION, 0x00000000},
+    {"PAD_RETENTION_GPIO_OPTION", PAD_RETENTION_GPIO_OPTION, 0x00000000},
+    {"PAD_RETENTION_UART_OPTION", PAD_RETENTION_UART_OPTION, 0x00000000},
+    {"PAD_RETENTION_MMCA_OPTION", PAD_RETENTION_MMCA_OPTION, 0x00000000},
+    {"PAD_RETENTION_MMCB_OPTION", PAD_RETENTION_MMCB_OPTION, 0x00000000},
+    {"PAD_RETENTION_EBIA_OPTION", PAD_RETENTION_EBIA_OPTION, 0x00000000},
+    {"PAD_RETENTION_EBIB_OPTION", PAD_RETENTION_EBIB_OPTION, 0x00000000},
+    {"PS_HOLD_CONTROL", PS_HOLD_CONTROL, 0x00005200},
+    {"XUSBXTI_CONFIGURATION", XUSBXTI_CONFIGURATION, 0x00000001},
+    {"XUSBXTI_STATUS", XUSBXTI_STATUS, 0x00000001},
+    {"XUSBXTI_DURATION", XUSBXTI_DURATION, 0xFFF00000},
+    {"XXTI_CONFIGURATION", XXTI_CONFIGURATION, 0x00000001},
+    {"XXTI_STATUS", XXTI_STATUS, 0x00000001},
+    {"XXTI_DURATION", XXTI_DURATION, 0xFFF00000},
+    {"EXT_REGULATOR_DURATION", EXT_REGULATOR_DURATION, 0xFFF03FFF},
+    {"CAM_CONFIGURATION", CAM_CONFIGURATION, 0x00000007},
+    {"CAM_STATUS", CAM_STATUS, 0x00060007},
+    {"CAM_OPTION", CAM_OPTION, 0x00000001},
+    {"TV_CONFIGURATION", TV_CONFIGURATION, 0x00000007},
+    {"TV_STATUS", TV_STATUS, 0x00060007},
+    {"TV_OPTION", TV_OPTION, 0x00000001},
+    {"MFC_CONFIGURATION", MFC_CONFIGURATION, 0x00000007},
+    {"MFC_STATUS", MFC_STATUS, 0x00060007},
+    {"MFC_OPTION", MFC_OPTION, 0x00000001},
+    {"G3D_CONFIGURATION", G3D_CONFIGURATION, 0x00000007},
+    {"G3D_STATUS", G3D_STATUS, 0x00060007},
+    {"G3D_OPTION", G3D_OPTION, 0x00000001},
+    {"LCD0_CONFIGURATION", LCD0_CONFIGURATION, 0x00000007},
+    {"LCD0_STATUS", LCD0_STATUS, 0x00060007},
+    {"LCD0_OPTION", LCD0_OPTION, 0x00000001},
+    {"LCD1_CONFIGURATION", LCD1_CONFIGURATION, 0x00000007},
+    {"LCD1_STATUS", LCD1_STATUS, 0x00060007},
+    {"LCD1_OPTION", LCD1_OPTION, 0x00000001},
+    {"GPS_CONFIGURATION", GPS_CONFIGURATION, 0x00000007},
+    {"GPS_STATUS", GPS_STATUS, 0x00060007},
+    {"GPS_OPTION", GPS_OPTION, 0x00000001},
+    {"GPS_ALIVE_CONFIGURATION", GPS_ALIVE_CONFIGURATION, 0x00000007},
+    {"GPS_ALIVE_STATUS", GPS_ALIVE_STATUS, 0x00060007},
+    {"GPS_ALIVE_OPTION", GPS_ALIVE_OPTION, 0x00000001},
+};
+
+/*
+ * for indexing register in the uint32_t array
+ *
+ * 'reg' - register offset (see offsets definitions above)
+ *
+ */
+#define I_(reg) (reg / sizeof(uint32_t))
+
+typedef struct Exynos4210PmuState {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    uint32_t reg[EXYNOS4210_PMU_REGS_MEM_SIZE];
+} Exynos4210PmuState;
+
+#ifdef DEBUG_PMU
+/* The only meaning of life - debugging. This functions should be only used
+ * inside PRINT_DEBUG_... macros
+ */
+static const char *exynos4210_pmu_regname(target_phys_addr_t  offset)
+{
+    int regs_number = sizeof(exynos4210_pmu_regs) / sizeof(Exynos4210PmuReg);
+    int i;
+
+    for (i = 0; i < regs_number; i++) {
+        if (offset == exynos4210_pmu_regs[i].offset) {
+            return exynos4210_pmu_regs[i].name;
+        }
+    }
+
+    return NULL;
+}
+#endif
+
+static uint64_t exynos4210_pmu_read(void *opaque, target_phys_addr_t offset,
+                                    unsigned size)
+{
+    Exynos4210PmuState *s = (Exynos4210PmuState *)opaque;
+
+    if (offset > (EXYNOS4210_PMU_REGS_MEM_SIZE - sizeof(uint32_t))) {
+        hw_error("%s: Bad offset 0x%x\n", __func__, (int)offset);
+    }
+
+#ifdef DEBUG_PMU
+    if (exynos4210_pmu_regname(offset)) {
+        PRINT_DEBUG_EXTEND("%s [0x%04x] -> %08x\n",
+                           exynos4210_pmu_regname(offset),
+                           offset, s->reg[I_(offset)]);
+    } else {
+        PRINT_DEBUG_EXTEND("Bad offset 0x%x\n", (int)offset);
+    }
+#endif
+    return s->reg[I_(offset)];
+}
+
+
+static void exynos4210_pmu_write(void *opaque, target_phys_addr_t offset,
+                                 uint64_t val, unsigned size)
+{
+    Exynos4210PmuState *s = (Exynos4210PmuState *)opaque;
+
+    if (offset > (EXYNOS4210_PMU_REGS_MEM_SIZE - sizeof(uint32_t))) {
+        hw_error("%s: Bad offset 0x%x\n", __func__, (int)offset);
+    }
+
+    s->reg[I_(offset)] = val;
+#ifdef DEBUG_PMU
+    if (exynos4210_pmu_regname(offset)) {
+        PRINT_DEBUG_EXTEND("%s <0x%04x> <- %08x\n",
+                           exynos4210_pmu_regname(offset),
+                           offset, s->reg[I_(offset)]);
+    } else {
+        PRINT_DEBUG_EXTEND("Bad offset 0x%x\n", (int)offset);
+    }
+#endif
+
+}
+
+
+static const MemoryRegionOps exynos4210_pmu_ops = {
+    .read = exynos4210_pmu_read,
+    .write = exynos4210_pmu_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+
+static void exynos4210_pmu_reset(void *opaque)
+{
+    Exynos4210PmuState *s = (Exynos4210PmuState *)opaque;
+    int regs_number = sizeof(exynos4210_pmu_regs) / sizeof(Exynos4210PmuReg);
+    int i;
+
+    /* Set default values for registers */
+    for (i = 0; i < regs_number; i++) {
+        s->reg[I_(exynos4210_pmu_regs[i].offset)] =
+                exynos4210_pmu_regs[i].reset_value;
+    }
+}
+
+static int exynos4210_pmu_init(SysBusDevice *dev)
+{
+    Exynos4210PmuState *s = FROM_SYSBUS(Exynos4210PmuState, dev);
+
+    /* memory mapping */
+    memory_region_init_io(&s->iomem, &exynos4210_pmu_ops, s, "exynos4210.pmu",
+                          EXYNOS4210_PMU_REGS_MEM_SIZE);
+    sysbus_init_mmio(dev, &s->iomem);
+
+    qemu_register_reset(exynos4210_pmu_reset, s);
+
+    exynos4210_pmu_reset(s);
+
+    return 0;
+}
+
+static void exynos4210_pmu_register(void)
+{
+    sysbus_register_dev("exynos4210.pmu", sizeof(Exynos4210PmuState),
+                        exynos4210_pmu_init);
+}
+
+
+device_init(exynos4210_pmu_register)
-- 
1.7.4.1

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

* [Qemu-devel] [PATCH v9 6/9] ARM: exynos4210: MCT support.
  2012-01-20 10:53 [Qemu-devel] [PATCH v9 0/9] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
                   ` (4 preceding siblings ...)
  2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 5/9] ARM: exynos4210: Added PMU register model Evgeny Voevodin
@ 2012-01-20 10:53 ` Evgeny Voevodin
  2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 7/9] hw/lan9118: Add basic 16-bit mode support Evgeny Voevodin
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 13+ messages in thread
From: Evgeny Voevodin @ 2012-01-20 10:53 UTC (permalink / raw)
  To: qemu-devel
  Cc: kyungmin.park, m.kozlov, jehyung.lee, d.solodkiy, Evgeny Voevodin


Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
---
 Makefile.target     |    2 +-
 hw/exynos4210.c     |   19 +
 hw/exynos4210_mct.c | 1479 +++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1499 insertions(+), 1 deletions(-)
 create mode 100644 hw/exynos4210_mct.c

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

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

* [Qemu-devel] [PATCH v9 7/9] hw/lan9118: Add basic 16-bit mode support.
  2012-01-20 10:53 [Qemu-devel] [PATCH v9 0/9] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
                   ` (5 preceding siblings ...)
  2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 6/9] ARM: exynos4210: MCT support Evgeny Voevodin
@ 2012-01-20 10:53 ` Evgeny Voevodin
  2012-01-25 16:35   ` Peter Maydell
  2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 8/9] hw/exynos4210.c: Add LAN support for SMDKC210 Evgeny Voevodin
  2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 9/9] Exynos4210: added display controller implementation Evgeny Voevodin
  8 siblings, 1 reply; 13+ messages in thread
From: Evgeny Voevodin @ 2012-01-20 10:53 UTC (permalink / raw)
  To: qemu-devel
  Cc: kyungmin.park, m.kozlov, jehyung.lee, d.solodkiy, Evgeny Voevodin


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

diff --git a/hw/lan9118.c b/hw/lan9118.c
index 9b199d0..d5e1551 100644
--- a/hw/lan9118.c
+++ b/hw/lan9118.c
@@ -235,6 +235,16 @@ typedef struct {
     int32_t rxp_offset;
     int32_t rxp_size;
     int32_t rxp_pad;
+
+    uint32_t write_word_prev_offset;
+    uint32_t write_word_n;
+    uint16_t write_word_l;
+    uint16_t write_word_h;
+    uint32_t read_word_prev_offset;
+    uint32_t read_word_n;
+    uint32_t read_long;
+
+    uint32_t mode_16bit;
 } lan9118_state;
 
 static const VMStateDescription vmstate_lan9118 = {
@@ -294,6 +304,14 @@ static const VMStateDescription vmstate_lan9118 = {
         VMSTATE_INT32(rxp_offset, lan9118_state),
         VMSTATE_INT32(rxp_size, lan9118_state),
         VMSTATE_INT32(rxp_pad, lan9118_state),
+        VMSTATE_UINT32(write_word_prev_offset, lan9118_state),
+        VMSTATE_UINT32(write_word_n, lan9118_state),
+        VMSTATE_UINT16(write_word_l, lan9118_state),
+        VMSTATE_UINT16(write_word_h, lan9118_state),
+        VMSTATE_UINT32(read_word_prev_offset, lan9118_state),
+        VMSTATE_UINT32(read_word_n, lan9118_state),
+        VMSTATE_UINT32(read_long, lan9118_state),
+        VMSTATE_UINT32(mode_16bit, lan9118_state),
         VMSTATE_END_OF_LIST()
     }
 };
@@ -390,7 +408,7 @@ static void lan9118_reset(DeviceState *d)
     s->fifo_int = 0x48000000;
     s->rx_cfg = 0;
     s->tx_cfg = 0;
-    s->hw_cfg = 0x00050000;
+    s->hw_cfg = s->mode_16bit ? 0x00050000 : 0x00050004;
     s->pmt_ctrl &= 0x45;
     s->gpio_cfg = 0;
     s->txp->fifo_used = 0;
@@ -429,6 +447,9 @@ static void lan9118_reset(DeviceState *d)
     s->mac_mii_data = 0;
     s->mac_flow = 0;
 
+    s->read_word_n = 0;
+    s->write_word_n = 0;
+
     phy_reset(s);
 
     s->eeprom_writable = 0;
@@ -984,7 +1005,7 @@ static void lan9118_writel(void *opaque, target_phys_addr_t offset,
 {
     lan9118_state *s = (lan9118_state *)opaque;
     offset &= 0xff;
-    
+
     //DPRINTF("Write reg 0x%02x = 0x%08x\n", (int)offset, val);
     if (offset >= 0x20 && offset < 0x40) {
         /* TX FIFO */
@@ -1034,7 +1055,7 @@ static void lan9118_writel(void *opaque, target_phys_addr_t offset,
             /* SRST */
             lan9118_reset(&s->busdev.qdev);
         } else {
-            s->hw_cfg = val & 0x003f300;
+            s->hw_cfg = (val & 0x003f300) | (s->hw_cfg & 0x4);
         }
         break;
     case CSR_RX_DP_CTRL:
@@ -1113,6 +1134,46 @@ static void lan9118_writel(void *opaque, target_phys_addr_t offset,
     lan9118_update(s);
 }
 
+static void lan9118_writew(void *opaque, target_phys_addr_t offset,
+                           uint32_t val)
+{
+    lan9118_state *s = (lan9118_state *)opaque;
+    offset &= 0xff;
+
+    if (s->write_word_prev_offset != (offset & ~0x3)) {
+        /* New offset, reset word counter */
+        s->write_word_n = 0;
+        s->write_word_prev_offset = offset & ~0x3;
+    }
+
+    if (offset & 0x2) {
+        s->write_word_h = val;
+    } else {
+        s->write_word_l = val;
+    }
+
+    //DPRINTF("Writew reg 0x%02x = 0x%08x\n", (int)offset, val);
+    s->write_word_n++;
+    if (s->write_word_n == 2) {
+        s->write_word_n = 0;
+        lan9118_writel(s, offset & ~3, s->write_word_l +
+                (s->write_word_h << 16), 4);
+    }
+}
+
+static void lan9118_16bit_mode_write(void *opaque, target_phys_addr_t offset,
+                                     uint64_t val, unsigned size)
+{
+    switch (size) {
+    case 2:
+        return lan9118_writew(opaque, offset, (uint32_t)val);
+    case 4:
+        return lan9118_writel(opaque, offset, val, size);
+    }
+
+    hw_error("lan9118_write: Bad size 0x%x\n", size);
+}
+
 static uint64_t lan9118_readl(void *opaque, target_phys_addr_t offset,
                               unsigned size)
 {
@@ -1149,7 +1210,7 @@ static uint64_t lan9118_readl(void *opaque, target_phys_addr_t offset,
     case CSR_TX_CFG:
         return s->tx_cfg;
     case CSR_HW_CFG:
-        return s->hw_cfg | 0x4;
+        return s->hw_cfg;
     case CSR_RX_DP_CTRL:
         return 0;
     case CSR_RX_FIFO_INF:
@@ -1187,12 +1248,60 @@ static uint64_t lan9118_readl(void *opaque, target_phys_addr_t offset,
     return 0;
 }
 
+static uint32_t lan9118_readw(void *opaque, target_phys_addr_t offset)
+{
+    lan9118_state *s = (lan9118_state *)opaque;
+    uint32_t val;
+
+    if (s->read_word_prev_offset != (offset & ~0x3)) {
+        /* New offset, reset word counter */
+        s->read_word_n = 0;
+        s->read_word_prev_offset = offset & ~0x3;
+    }
+
+    s->read_word_n++;
+    if (s->read_word_n == 1) {
+        s->read_long = lan9118_readl(s, offset & ~3, 4);
+    } else {
+        s->read_word_n = 0;
+    }
+
+    if (offset & 2) {
+        val = s->read_long >> 16;
+    } else {
+        val = s->read_long & 0xFFFF;
+    }
+
+    //DPRINTF("Readw reg 0x%02x, val 0x%x\n", (int)offset, val);
+    return val;
+}
+
+static uint64_t lan9118_16bit_mode_read(void *opaque, target_phys_addr_t offset,
+                                        unsigned size)
+{
+    switch (size) {
+    case 2:
+        return lan9118_readw(opaque, offset);
+    case 4:
+        return lan9118_readl(opaque, offset, size);
+    }
+
+    hw_error("lan9118_read: Bad size 0x%x\n", size);
+    return 0;
+}
+
 static const MemoryRegionOps lan9118_mem_ops = {
     .read = lan9118_readl,
     .write = lan9118_writel,
     .endianness = DEVICE_NATIVE_ENDIAN,
 };
 
+static const MemoryRegionOps lan9118_16bit_mem_ops = {
+    .read = lan9118_16bit_mode_read,
+    .write = lan9118_16bit_mode_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
 static void lan9118_cleanup(VLANClientState *nc)
 {
     lan9118_state *s = DO_UPCAST(NICState, nc, nc)->opaque;
@@ -1214,8 +1323,10 @@ static int lan9118_init1(SysBusDevice *dev)
     lan9118_state *s = FROM_SYSBUS(lan9118_state, dev);
     QEMUBH *bh;
     int i;
+    const MemoryRegionOps *mem_ops =
+            s->mode_16bit ? &lan9118_16bit_mem_ops : &lan9118_mem_ops;
 
-    memory_region_init_io(&s->mmio, &lan9118_mem_ops, s, "lan9118-mmio", 0x100);
+    memory_region_init_io(&s->mmio, mem_ops, s, "lan9118-mmio", 0x100);
     sysbus_init_mmio(dev, &s->mmio);
     sysbus_init_irq(dev, &s->irq);
     qemu_macaddr_default_if_unset(&s->conf.macaddr);
@@ -1246,6 +1357,7 @@ static SysBusDeviceInfo lan9118_info = {
     .qdev.vmsd = &vmstate_lan9118,
     .qdev.props = (Property[]) {
         DEFINE_NIC_PROPERTIES(lan9118_state, conf),
+        DEFINE_PROP_UINT32("mode_16bit", lan9118_state, mode_16bit, 0),
         DEFINE_PROP_END_OF_LIST(),
     }
 };
-- 
1.7.4.1

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

* [Qemu-devel] [PATCH v9 8/9] hw/exynos4210.c: Add LAN support for SMDKC210.
  2012-01-20 10:53 [Qemu-devel] [PATCH v9 0/9] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
                   ` (6 preceding siblings ...)
  2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 7/9] hw/lan9118: Add basic 16-bit mode support Evgeny Voevodin
@ 2012-01-20 10:53 ` Evgeny Voevodin
  2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 9/9] Exynos4210: added display controller implementation Evgeny Voevodin
  8 siblings, 0 replies; 13+ messages in thread
From: Evgeny Voevodin @ 2012-01-20 10:53 UTC (permalink / raw)
  To: qemu-devel
  Cc: kyungmin.park, m.kozlov, jehyung.lee, d.solodkiy, Evgeny Voevodin

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

Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
---
 hw/exynos4_boards.c |   27 +++++++++++++++++++++++++--
 1 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/hw/exynos4_boards.c b/hw/exynos4_boards.c
index 55fca06..8befe8b 100644
--- a/hw/exynos4_boards.c
+++ b/hw/exynos4_boards.c
@@ -23,6 +23,7 @@
 
 #include "sysemu.h"
 #include "sysbus.h"
+#include "net.h"
 #include "arm-misc.h"
 #include "exec-memory.h"
 #include "exynos4210.h"
@@ -42,6 +43,8 @@
     #define  PRINT_DEBUG(fmt, args...)  do {} while (0)
 #endif
 
+#define SMDK_LAN9118_BASE_ADDR      0x05000000
+
 typedef enum Exynos4BoardType {
     EXYNOS4_BOARD_NURI,
     EXYNOS4_BOARD_SMDKC210,
@@ -68,6 +71,24 @@ static struct arm_boot_info exynos4_board_binfo = {
     .smp_loader_start = EXYNOS4210_SMP_BOOT_ADDR,
 };
 
+static void lan9215_init(uint32_t base, qemu_irq irq)
+{
+    DeviceState *dev;
+    SysBusDevice *s;
+
+    /* This should be a 9215 but the 9118 is close enough */
+    if (nd_table[0].vlan) {
+        qemu_check_nic_model(&nd_table[0], "lan9118");
+        dev = qdev_create(NULL, "lan9118");
+        qdev_set_nic_properties(dev, &nd_table[0]);
+        qdev_prop_set_uint32(dev, "mode_16bit", 1);
+        qdev_init_nofail(dev);
+        s = sysbus_from_qdev(dev);
+        sysbus_mmio_map(s, 0, base);
+        sysbus_connect_irq(s, 0, irq);
+    }
+}
+
 static Exynos4210State *exynos4_boards_init_common(
         const char *kernel_filename,
         const char *kernel_cmdline,
@@ -114,9 +135,11 @@ static void smdkc210_init(ram_addr_t ram_size,
         const char *kernel_filename, const char *kernel_cmdline,
         const char *initrd_filename, const char *cpu_model)
 {
-    exynos4_boards_init_common(kernel_filename, kernel_cmdline,
-                initrd_filename, EXYNOS4_BOARD_SMDKC210);
+    Exynos4210State *s = exynos4_boards_init_common(kernel_filename,
+            kernel_cmdline, initrd_filename, EXYNOS4_BOARD_SMDKC210);
 
+    lan9215_init(SMDK_LAN9118_BASE_ADDR,
+            qemu_irq_invert(s->irq_table[exynos4210_get_irq(37, 1)]));
     arm_load_kernel(first_cpu, &exynos4_board_binfo);
 }
 
-- 
1.7.4.1

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

* [Qemu-devel] [PATCH v9 9/9] Exynos4210: added display controller implementation
  2012-01-20 10:53 [Qemu-devel] [PATCH v9 0/9] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
                   ` (7 preceding siblings ...)
  2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 8/9] hw/exynos4210.c: Add LAN support for SMDKC210 Evgeny Voevodin
@ 2012-01-20 10:53 ` Evgeny Voevodin
  8 siblings, 0 replies; 13+ messages in thread
From: Evgeny Voevodin @ 2012-01-20 10:53 UTC (permalink / raw)
  To: qemu-devel
  Cc: Mitsyanko Igor, Evgeny Voevodin, kyungmin.park, d.solodkiy,
	m.kozlov, jehyung.lee

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

Exynos4210 display controller (FIMD) has 5 hardware windows with alpha and
chroma key blending functions.

Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
---
 Makefile.target      |    2 +-
 hw/exynos4210.c      |   10 +
 hw/exynos4210_fimd.c | 1924 ++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1935 insertions(+), 1 deletions(-)
 create mode 100644 hw/exynos4210_fimd.c

diff --git a/Makefile.target b/Makefile.target
index 24e7e99..5e314c6 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -341,7 +341,7 @@ obj-arm-y += versatile_pci.o
 obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
 obj-arm-y += exynos4210_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
+obj-arm-y += exynos4210_pmu.o exynos4210_mct.o exynos4210_fimd.o
 obj-arm-y += arm_l2x0.o
 obj-arm-y += arm_mptimer.o
 obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index aceec2a..7727578 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -58,6 +58,9 @@
 /* PMU SFR base address */
 #define EXYNOS4210_PMU_BASE_ADDR            0x10020000
 
+/* Display controllers (FIMD) */
+#define EXYNOS4210_FIMD0_BASE_ADDR          0x11C00000
+
 static uint8_t chipid_and_omr[] = { 0x11, 0x02, 0x21, 0x43,
                                     0x09, 0x00, 0x00, 0x00 };
 
@@ -258,5 +261,12 @@ Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
                            EXYNOS4210_UART3_FIFO_SIZE, 3, NULL,
                     irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 3)]);
 
+    /*** Display controller (FIMD) ***/
+    sysbus_create_varargs("exynos4210.fimd", EXYNOS4210_FIMD0_BASE_ADDR,
+            irq_table[exynos4210_get_irq(11, 0)],
+            irq_table[exynos4210_get_irq(11, 1)],
+            irq_table[exynos4210_get_irq(11, 2)],
+            NULL);
+
     return s;
 }
diff --git a/hw/exynos4210_fimd.c b/hw/exynos4210_fimd.c
new file mode 100644
index 0000000..7a749b2
--- /dev/null
+++ b/hw/exynos4210_fimd.c
@@ -0,0 +1,1924 @@
+/*
+ * Samsung exynos4210 Display Controller (FIMD)
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ * Based on LCD controller for Samsung S5PC1xx-based board emulation
+ * by Kirill Batuzov <batuzovk@ispras.ru>
+ *
+ * Contributed by Mitsyanko Igor <i.mitsyanko@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu-common.h"
+#include "cpu-all.h"
+#include "sysbus.h"
+#include "console.h"
+#include "pixel_ops.h"
+#include "bswap.h"
+
+/* Debug messages configuration */
+#define EXYNOS4210_FIMD_DEBUG              0
+#define EXYNOS4210_FIMD_MODE_TRACE         0
+
+#if EXYNOS4210_FIMD_DEBUG == 0
+    #define DPRINT_L1(fmt, args...)       do { } while (0)
+    #define DPRINT_L2(fmt, args...)       do { } while (0)
+    #define DPRINT_ERROR(fmt, args...)    do { } while (0)
+#elif EXYNOS4210_FIMD_DEBUG == 1
+    #define DPRINT_L1(fmt, args...) \
+        do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+    #define DPRINT_L2(fmt, args...)       do { } while (0)
+    #define DPRINT_ERROR(fmt, args...)  \
+        do {fprintf(stderr, "QEMU FIMD ERROR: "fmt, ## args); } while (0)
+#else
+    #define DPRINT_L1(fmt, args...) \
+        do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+    #define DPRINT_L2(fmt, args...) \
+        do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+    #define DPRINT_ERROR(fmt, args...)  \
+        do {fprintf(stderr, "QEMU FIMD ERROR: "fmt, ## args); } while (0)
+#endif
+
+#if EXYNOS4210_FIMD_MODE_TRACE == 0
+    #define DPRINT_TRACE(fmt, args...)        do { } while (0)
+#else
+    #define DPRINT_TRACE(fmt, args...)        \
+        do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+#endif
+
+#define NUM_OF_WINDOWS              5
+#define FIMD_REGS_SIZE              0x4114
+
+/* Video main control registers */
+#define FIMD_VIDCON0                0x0000
+#define FIMD_VIDCON1                0x0004
+#define FIMD_VIDCON2                0x0008
+#define FIMD_VIDCON3                0x000C
+#define FIMD_VIDCON0_ENVID_F        (1 << 0)
+#define FIMD_VIDCON0_ENVID          (1 << 1)
+#define FIMD_VIDCON0_ENVID_MASK     ((1 << 0) | (1 << 1))
+#define FIMD_VIDCON1_ROMASK         0x07FFE000
+
+/* Video time control registers */
+#define FIMD_VIDTCON_START          0x10
+#define FIMD_VIDTCON_END            0x1C
+#define FIMD_VIDTCON2_SIZE_MASK     0x07FF
+#define FIMD_VIDTCON2_HOR_SHIFT     0
+#define FIMD_VIDTCON2_VER_SHIFT     11
+
+/* Window control registers */
+#define FIMD_WINCON_START           0x0020
+#define FIMD_WINCON_END             0x0030
+#define FIMD_WINCON_ROMASK          0x82200000
+#define FIMD_WINCON_ENWIN           (1 << 0)
+#define FIMD_WINCON_BLD_PIX         (1 << 6)
+#define FIMD_WINCON_ALPHA_MUL       (1 << 7)
+#define FIMD_WINCON_ALPHA_SEL       (1 << 1)
+#define FIMD_WINCON_SWAP            0x078000
+#define FIMD_WINCON_SWAP_SHIFT      15
+#define FIMD_WINCON_SWAP_WORD       0x1
+#define FIMD_WINCON_SWAP_HWORD      0x2
+#define FIMD_WINCON_SWAP_BYTE       0x4
+#define FIMD_WINCON_SWAP_BITS       0x8
+#define FIMD_WINCON_BUFSTAT_L       (1 << 21)
+#define FIMD_WINCON_BUFSTAT_H       (1 << 31)
+#define FIMD_WINCON_BUFSTATUS       ((1 << 21) | (1 << 31))
+#define FIMD_WINCON_BUF0_STAT       ((0 << 21) | (0 << 31))
+#define FIMD_WINCON_BUF1_STAT       ((1 << 21) | (0 << 31))
+#define FIMD_WINCON_BUF2_STAT       ((0 << 21) | (1 << 31))
+#define FIMD_WINCON_BUFSELECT       ((1 << 20) | (1 << 30))
+#define FIMD_WINCON_BUF0_SEL        ((0 << 20) | (0 << 30))
+#define FIMD_WINCON_BUF1_SEL        ((1 << 20) | (0 << 30))
+#define FIMD_WINCON_BUF2_SEL        ((0 << 20) | (1 << 30))
+#define FIMD_WINCON_BUFMODE         (1 << 14)
+#define IS_PALETTIZED_MODE(w)       (w->wincon & 0xC)
+#define PAL_MODE_WITH_ALPHA(x)       ((x) == 7)
+#define WIN_BPP_MODE(w)             ((w->wincon >> 2) & 0xF)
+#define WIN_BPP_MODE_WITH_ALPHA(w)     \
+    (WIN_BPP_MODE(w) == 0xD || WIN_BPP_MODE(w) == 0xE)
+
+/* Shadow control register */
+#define FIMD_SHADOWCON              0x0034
+#define FIMD_WINDOW_PROTECTED(s, w) ((s) & (1 << (10 + (w))))
+/* Channel mapping control register */
+#define FIMD_WINCHMAP               0x003C
+
+/* Window position control registers */
+#define FIMD_VIDOSD_START           0x0040
+#define FIMD_VIDOSD_END             0x0088
+#define FIMD_VIDOSD_COORD_MASK      0x07FF
+#define FIMD_VIDOSD_HOR_SHIFT       11
+#define FIMD_VIDOSD_VER_SHIFT       0
+#define FIMD_VIDOSD_ALPHA_AEN0      0xFFF000
+#define FIMD_VIDOSD_AEN0_SHIFT      12
+#define FIMD_VIDOSD_ALPHA_AEN1      0x000FFF
+
+/* Frame buffer address registers */
+#define FIMD_VIDWADD0_START         0x00A0
+#define FIMD_VIDWADD0_END           0x00C4
+#define FIMD_VIDWADD0_END           0x00C4
+#define FIMD_VIDWADD1_START         0x00D0
+#define FIMD_VIDWADD1_END           0x00F4
+#define FIMD_VIDWADD2_START         0x0100
+#define FIMD_VIDWADD2_END           0x0110
+#define FIMD_VIDWADD2_PAGEWIDTH     0x1FFF
+#define FIMD_VIDWADD2_OFFSIZE       0x1FFF
+#define FIMD_VIDWADD2_OFFSIZE_SHIFT 13
+#define FIMD_VIDW0ADD0_B2           0x20A0
+#define FIMD_VIDW4ADD0_B2           0x20C0
+
+/* Video interrupt control registers */
+#define FIMD_VIDINTCON0             0x130
+#define FIMD_VIDINTCON1             0x134
+
+/* Window color key registers */
+#define FIMD_WKEYCON_START          0x140
+#define FIMD_WKEYCON_END            0x15C
+#define FIMD_WKEYCON0_COMPKEY       0x00FFFFFF
+#define FIMD_WKEYCON0_CTL_SHIFT     24
+#define FIMD_WKEYCON0_DIRCON        (1 << 24)
+#define FIMD_WKEYCON0_KEYEN         (1 << 25)
+#define FIMD_WKEYCON0_KEYBLEN       (1 << 26)
+/* Window color key alpha control register */
+#define FIMD_WKEYALPHA_START        0x160
+#define FIMD_WKEYALPHA_END          0x16C
+
+/* Dithering control register */
+#define FIMD_DITHMODE               0x170
+
+/* Window alpha control registers */
+#define FIMD_VIDALPHA_ALPHA_LOWER   0x000F0F0F
+#define FIMD_VIDALPHA_ALPHA_UPPER   0x00F0F0F0
+#define FIMD_VIDWALPHA_START        0x21C
+#define FIMD_VIDWALPHA_END          0x240
+
+/* Window color map registers */
+#define FIMD_WINMAP_START           0x180
+#define FIMD_WINMAP_END             0x190
+#define FIMD_WINMAP_EN              (1 << 24)
+#define FIMD_WINMAP_COLOR_MASK      0x00FFFFFF
+
+/* Window palette control registers */
+#define FIMD_WPALCON_HIGH           0x019C
+#define FIMD_WPALCON_LOW            0x01A0
+#define FIMD_WPALCON_UPDATEEN       (1 << 9)
+#define FIMD_WPAL_W0PAL_L           0x07
+#define FIMD_WPAL_W0PAL_L_SHT        0
+#define FIMD_WPAL_W1PAL_L           0x07
+#define FIMD_WPAL_W1PAL_L_SHT       3
+#define FIMD_WPAL_W2PAL_L           0x01
+#define FIMD_WPAL_W2PAL_L_SHT       6
+#define FIMD_WPAL_W2PAL_H           0x06
+#define FIMD_WPAL_W2PAL_H_SHT       8
+#define FIMD_WPAL_W3PAL_L           0x01
+#define FIMD_WPAL_W3PAL_L_SHT       7
+#define FIMD_WPAL_W3PAL_H           0x06
+#define FIMD_WPAL_W3PAL_H_SHT       12
+#define FIMD_WPAL_W4PAL_L           0x01
+#define FIMD_WPAL_W4PAL_L_SHT       8
+#define FIMD_WPAL_W4PAL_H           0x06
+#define FIMD_WPAL_W4PAL_H_SHT       16
+
+/* Trigger control registers */
+#define FIMD_TRIGCON                0x01A4
+#define FIMD_TRIGCON_ROMASK         0x00000004
+
+/* LCD I80 Interface Control */
+#define FIMD_I80IFCON_START         0x01B0
+#define FIMD_I80IFCON_END           0x01BC
+/* Color gain control register */
+#define FIMD_COLORGAINCON           0x01C0
+/* LCD i80 Interface Command Control */
+#define FIMD_LDI_CMDCON0            0x01D0
+#define FIMD_LDI_CMDCON1            0x01D4
+/* I80 System Interface Manual Command Control */
+#define FIMD_SIFCCON0               0x01E0
+#define FIMD_SIFCCON2               0x01E8
+
+/* Hue Control Registers */
+#define FIMD_HUECOEFCR_START        0x01EC
+#define FIMD_HUECOEFCR_END          0x01F4
+#define FIMD_HUECOEFCB_START        0x01FC
+#define FIMD_HUECOEFCB_END          0x0208
+#define FIMD_HUEOFFSET              0x020C
+
+/* Video interrupt control registers */
+#define FIMD_VIDINT_INTFIFOPEND     (1 << 0)
+#define FIMD_VIDINT_INTFRMPEND      (1 << 1)
+#define FIMD_VIDINT_INTI80PEND      (1 << 2)
+#define FIMD_VIDINT_INTEN           (1 << 0)
+#define FIMD_VIDINT_INTFIFOEN       (1 << 1)
+#define FIMD_VIDINT_INTFRMEN        (1 << 12)
+#define FIMD_VIDINT_I80IFDONE       (1 << 17)
+
+/* Window blend equation control registers */
+#define FIMD_BLENDEQ_START          0x0244
+#define FIMD_BLENDEQ_END            0x0250
+#define FIMD_BLENDCON               0x0260
+#define FIMD_ALPHA_8BIT             (1 << 0)
+#define FIMD_BLENDEQ_COEF_MASK      0xF
+
+/* Window RTQOS Control Registers */
+#define FIMD_WRTQOSCON_START        0x0264
+#define FIMD_WRTQOSCON_END          0x0274
+
+/* LCD I80 Interface Command */
+#define FIMD_I80IFCMD_START         0x0280
+#define FIMD_I80IFCMD_END           0x02AC
+
+/* Shadow windows control registers */
+#define FIMD_SHD_ADD0_START         0x40A0
+#define FIMD_SHD_ADD0_END           0x40C0
+#define FIMD_SHD_ADD1_START         0x40D0
+#define FIMD_SHD_ADD1_END           0x40F0
+#define FIMD_SHD_ADD2_START         0x4100
+#define FIMD_SHD_ADD2_END           0x4110
+
+/* Palette memory */
+#define FIMD_PAL_MEM_START          0x2400
+#define FIMD_PAL_MEM_END            0x37FC
+/* Palette memory aliases for windows 0 and 1 */
+#define FIMD_PALMEM_AL_START        0x0400
+#define FIMD_PALMEM_AL_END          0x0BFC
+
+typedef struct {
+    uint8_t r, g, b;
+    /* D[31..24]dummy, D[23..16]rAlpha, D[15..8]gAlpha, D[7..0]bAlpha */
+    uint32_t a;
+} rgba;
+#define RGBA_SIZE  7
+
+typedef void pixel_to_rgb_func(uint32_t pixel, rgba *p);
+typedef struct Exynos4210fimdWindow Exynos4210fimdWindow;
+
+struct Exynos4210fimdWindow {
+    uint32_t wincon;        /* Window control register */
+    uint32_t buf_start[3];  /* Start address for video frame buffer */
+    uint32_t buf_end[3];    /* End address for video frame buffer */
+    uint32_t keycon[2];     /* Window color key registers */
+    uint32_t keyalpha;      /* Color key alpha control register */
+    uint32_t winmap;        /* Window color map register */
+    uint32_t blendeq;       /* Window blending equation control register */
+    uint32_t rtqoscon;      /* Window RTQOS Control Registers */
+    uint32_t palette[256];  /* Palette RAM */
+    uint32_t shadow_buf_start;      /* Start address of shadow frame buffer */
+    uint32_t shadow_buf_end;        /* End address of shadow frame buffer */
+    uint32_t shadow_buf_size;       /* Virtual shadow screen width */
+
+    pixel_to_rgb_func *pixel_to_rgb;
+    void (*draw_line)(Exynos4210fimdWindow *w, uint8_t *src, uint8_t *dst,
+            bool blend);
+    uint32_t (*get_alpha)(Exynos4210fimdWindow *w, uint32_t pix_a);
+    uint16_t lefttop_x, lefttop_y;   /* VIDOSD0 register */
+    uint16_t rightbot_x, rightbot_y; /* VIDOSD1 register */
+    uint32_t osdsize;                /* VIDOSD2&3 register */
+    uint32_t alpha_val[2];           /* VIDOSD2&3, VIDWALPHA registers */
+    uint16_t virtpage_width;         /* VIDWADD2 register */
+    uint16_t virtpage_offsize;       /* VIDWADD2 register */
+    MemoryRegionSection mem_section; /* RAM fragment containing framebuffer */
+    uint8_t *host_fb_addr;           /* Host pointer to window's framebuffer */
+    target_phys_addr_t fb_len;       /* Framebuffer length */
+};
+
+typedef struct {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    DisplayState *console;
+    qemu_irq irq[3];
+
+    uint32_t vidcon[4];     /* Video main control registers 0-3 */
+    uint32_t vidtcon[4];    /* Video time control registers 0-3 */
+    uint32_t shadowcon;     /* Window shadow control register */
+    uint32_t winchmap;      /* Channel mapping control register */
+    uint32_t vidintcon[2];  /* Video interrupt control registers */
+    uint32_t dithmode;      /* Dithering control register */
+    uint32_t wpalcon[2];    /* Window palette control registers */
+    uint32_t trigcon;       /* Trigger control register */
+    uint32_t i80ifcon[4];   /* I80 interface control registers */
+    uint32_t colorgaincon;  /* Color gain control register */
+    uint32_t ldi_cmdcon[2]; /* LCD I80 interface command control */
+    uint32_t sifccon[3];    /* I80 System Interface Manual Command Control */
+    uint32_t huecoef_cr[4]; /* Hue control registers */
+    uint32_t huecoef_cb[4]; /* Hue control registers */
+    uint32_t hueoffset;     /* Hue offset control register */
+    uint32_t blendcon;      /* Blending control register */
+    uint32_t i80ifcmd[12];  /* LCD I80 Interface Command */
+
+    Exynos4210fimdWindow window[5];    /* Window-specific registers */
+    uint8_t *ifb;           /* Internal frame buffer */
+    bool invalidate;        /* Image needs to be redrawn */
+    bool enabled;           /* Display controller is enabled */
+} Exynos4210fimdState;
+
+/* Perform byte/halfword/word swap of data according to WINCON */
+static inline void fimd_swap_data(unsigned int swap_ctl, uint64_t *data)
+{
+    int i;
+    uint64_t res;
+    uint64_t x = *data;
+
+    if (swap_ctl & FIMD_WINCON_SWAP_BITS) {
+        res = 0;
+        for (i = 0; i < 64; i++) {
+            if (x & (1ULL << (64 - i))) {
+                res |= (1ULL << i);
+            }
+        }
+        x = res;
+    }
+
+    if (swap_ctl & FIMD_WINCON_SWAP_BYTE) {
+        x = bswap64(x);
+    }
+
+    if (swap_ctl & FIMD_WINCON_SWAP_HWORD) {
+        x = ((x & 0x000000000000FFFFULL) << 48) |
+            ((x & 0x00000000FFFF0000ULL) << 16) |
+            ((x & 0x0000FFFF00000000ULL) >> 16) |
+            ((x & 0xFFFF000000000000ULL) >> 48);
+    }
+
+    if (swap_ctl & FIMD_WINCON_SWAP_WORD) {
+        x = ((x & 0x00000000FFFFFFFFULL) << 32) |
+            ((x & 0xFFFFFFFF00000000ULL) >> 32);
+    }
+
+    *data = x;
+}
+
+/* Conversion routines of Pixel data from frame buffer area to internal RGBA
+ * pixel representation.
+ * Every color component internally represented as 8-bit value. If original
+ * data has less than 8 bit for component, data is extended to 8 bit. For
+ * example, if blue component has only two possible values 0 and 1 it will be
+ * extended to 0 and 0xFF */
+
+/* One bit for alpha representation */
+#define DEF_PIXEL_TO_RGB_A1(N, R, G, B) \
+static void N(uint32_t pixel, rgba *p) \
+{ \
+    p->b = ((pixel & ((1 << (B)) - 1)) << (8 - (B))) | \
+           ((pixel >> (2 * (B) - 8)) & ((1 << (8 - (B))) - 1)); \
+    pixel >>= (B); \
+    p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)) | \
+           ((pixel >> (2 * (G) - 8)) & ((1 << (8 - (G))) - 1)); \
+    pixel >>= (G); \
+    p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)) | \
+           ((pixel >> (2 * (R) - 8)) & ((1 << (8 - (R))) - 1)); \
+    pixel >>= (R); \
+    p->a = (pixel & 0x1); \
+}
+
+DEF_PIXEL_TO_RGB_A1(pixel_a444_to_rgb, 4, 4, 4)
+DEF_PIXEL_TO_RGB_A1(pixel_a555_to_rgb, 5, 5, 5)
+DEF_PIXEL_TO_RGB_A1(pixel_a666_to_rgb, 6, 6, 6)
+DEF_PIXEL_TO_RGB_A1(pixel_a665_to_rgb, 6, 6, 5)
+DEF_PIXEL_TO_RGB_A1(pixel_a888_to_rgb, 8, 8, 8)
+DEF_PIXEL_TO_RGB_A1(pixel_a887_to_rgb, 8, 8, 7)
+
+/* Alpha component is always zero */
+#define DEF_PIXEL_TO_RGB_A0(N, R, G, B) \
+static void N(uint32_t pixel, rgba *p) \
+{ \
+    p->b = ((pixel & ((1 << (B)) - 1)) << (8 - (B))) | \
+           ((pixel >> (2 * (B) - 8)) & ((1 << (8 - (B))) - 1)); \
+    pixel >>= (B); \
+    p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)) | \
+           ((pixel >> (2 * (G) - 8)) & ((1 << (8 - (G))) - 1)); \
+    pixel >>= (G); \
+    p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)) | \
+           ((pixel >> (2 * (R) - 8)) & ((1 << (8 - (R))) - 1)); \
+    p->a = 0x0; \
+}
+
+DEF_PIXEL_TO_RGB_A0(pixel_565_to_rgb,  5, 6, 5)
+DEF_PIXEL_TO_RGB_A0(pixel_555_to_rgb,  5, 5, 5)
+DEF_PIXEL_TO_RGB_A0(pixel_666_to_rgb,  6, 6, 6)
+DEF_PIXEL_TO_RGB_A0(pixel_888_to_rgb,  8, 8, 8)
+
+/* Alpha component has some meaningful value */
+#define DEF_PIXEL_TO_RGB_A(N, R, G, B, A) \
+static void N(uint32_t pixel, rgba *p) \
+{ \
+    p->b = ((pixel & ((1 << (B)) - 1)) << (8 - (B))) | \
+           ((pixel >> (2 * (B) - 8)) & ((1 << (8 - (B))) - 1)); \
+    pixel >>= (B); \
+    p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)) | \
+           ((pixel >> (2 * (G) - 8)) & ((1 << (8 - (G))) - 1)); \
+    pixel >>= (G); \
+    p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)) | \
+           ((pixel >> (2 * (R) - 8)) & ((1 << (8 - (R))) - 1)); \
+    pixel >>= (R); \
+    p->a = (pixel & ((1 << (A)) - 1)) << (8 - (A)) | \
+           ((pixel >> (2 * (A) - 8)) & ((1 << (8 - (A))) - 1)); \
+    p->a = p->a | (p->a << 8) | (p->a << 16); \
+}
+
+DEF_PIXEL_TO_RGB_A(pixel_4444_to_rgb, 4, 4, 4, 4)
+DEF_PIXEL_TO_RGB_A(pixel_8888_to_rgb, 8, 8, 8, 8)
+
+/* Lookup table to extent 2-bit color component to 8 bit */
+static const uint8_t pixel_lutable_2b[4] = {
+     0x0, 0x55, 0xAA, 0xFF
+};
+/* Lookup table to extent 3-bit color component to 8 bit */
+static const uint8_t pixel_lutable_3b[8] = {
+     0x0, 0x24, 0x49, 0x6D, 0x92, 0xB6, 0xDB, 0xFF
+};
+/* Special case for a232 bpp mode */
+static void pixel_a232_to_rgb(uint32_t pixel, rgba *p)
+{
+    p->b = pixel_lutable_2b[(pixel & 0x3)];
+    pixel >>= 2;
+    p->g = pixel_lutable_3b[(pixel & 0x7)];
+    pixel >>= 3;
+    p->r = pixel_lutable_2b[(pixel & 0x3)];
+    pixel >>= 2;
+    p->a = (pixel & 0x1);
+}
+
+/* Special case for (5+1, 5+1, 5+1) mode. Data bit 15 is common LSB
+ * for all three color components */
+static void pixel_1555_to_rgb(uint32_t pixel, rgba *p)
+{
+    uint8_t comm = (pixel >> 15) & 1;
+    p->b = ((((pixel & 0x1F) << 1) | comm) << 2) | ((pixel >> 3) & 0x3);
+    pixel >>= 5;
+    p->g = ((((pixel & 0x1F) << 1) | comm) << 2) | ((pixel >> 3) & 0x3);
+    pixel >>= 5;
+    p->r = ((((pixel & 0x1F) << 1) | comm) << 2) | ((pixel >> 3) & 0x3);
+    p->a = 0x0;
+}
+
+/* Put/get pixel to/from internal LCD Controller framebuffer */
+
+static int put_pixel_ifb(const rgba p, uint8_t *d)
+{
+    *(uint8_t *)d++ = p.r;
+    *(uint8_t *)d++ = p.g;
+    *(uint8_t *)d++ = p.b;
+    *(uint32_t *)d = p.a;
+    return RGBA_SIZE;
+}
+
+static int get_pixel_ifb(const uint8_t *s, rgba *p)
+{
+    p->r = *(uint8_t *)s++;
+    p->g = *(uint8_t *)s++;
+    p->b = *(uint8_t *)s++;
+    p->a = (*(uint32_t *)s) & 0x00FFFFFF;
+    return RGBA_SIZE;
+}
+
+static pixel_to_rgb_func *palette_data_format[8] = {
+    [0] = pixel_565_to_rgb,
+    [1] = pixel_a555_to_rgb,
+    [2] = pixel_666_to_rgb,
+    [3] = pixel_a665_to_rgb,
+    [4] = pixel_a666_to_rgb,
+    [5] = pixel_888_to_rgb,
+    [6] = pixel_a888_to_rgb,
+    [7] = pixel_8888_to_rgb
+};
+
+/* Returns Index in palette data formats table for given window number WINDOW */
+static uint32_t
+exynos4210_fimd_palette_format(Exynos4210fimdState *s, int window)
+{
+    uint32_t ret;
+
+    switch (window) {
+    case 0:
+        ret = (s->wpalcon[1] >> FIMD_WPAL_W0PAL_L_SHT) & FIMD_WPAL_W0PAL_L;
+        if (ret != 7) {
+            ret = 6 - ret;
+        }
+        break;
+    case 1:
+        ret = (s->wpalcon[1] >> FIMD_WPAL_W1PAL_L_SHT) & FIMD_WPAL_W1PAL_L;
+        if (ret != 7) {
+            ret = 6 - ret;
+        }
+        break;
+    case 2:
+        ret = ((s->wpalcon[0] >> FIMD_WPAL_W2PAL_H_SHT) & FIMD_WPAL_W2PAL_H) |
+            ((s->wpalcon[1] >> FIMD_WPAL_W2PAL_L_SHT) & FIMD_WPAL_W2PAL_L);
+        break;
+    case 3:
+        ret = ((s->wpalcon[0] >> FIMD_WPAL_W3PAL_H_SHT) & FIMD_WPAL_W3PAL_H) |
+            ((s->wpalcon[1] >> FIMD_WPAL_W3PAL_L_SHT) & FIMD_WPAL_W3PAL_L);
+        break;
+    case 4:
+        ret = ((s->wpalcon[0] >> FIMD_WPAL_W4PAL_H_SHT) & FIMD_WPAL_W4PAL_H) |
+            ((s->wpalcon[1] >> FIMD_WPAL_W4PAL_L_SHT) & FIMD_WPAL_W4PAL_L);
+        break;
+    default:
+        hw_error("exynos4210.fimd: incorrect window number %d\n", window);
+        ret = 0;
+        break;
+    }
+    return ret;
+}
+
+#define FIMD_1_MINUS_COLOR(x)    \
+            ((0xFF - ((x) & 0xFF)) | (0xFF00 - ((x) & 0xFF00)) | \
+                                  (0xFF0000 - ((x) & 0xFF0000)))
+#define EXTEND_LOWER_HALFBYTE(x) (((x) & 0xF0F0F) | (((x) << 4) & 0xF0F0F0))
+#define EXTEND_UPPER_HALFBYTE(x) (((x) & 0xF0F0F0) | (((x) >> 4) & 0xF0F0F))
+
+/* Multiply three lower bytes of two 32-bit words with each other.
+ * Each byte with values 0-255 is considered as a number with possible values
+ * in a range [0 - 1] */
+static inline uint32_t fimd_mult_each_byte(uint32_t a, uint32_t b)
+{
+    uint32_t tmp;
+    uint32_t ret;
+
+    ret = ((tmp = (((a & 0xFF) * (b & 0xFF)) / 0xFF)) > 0xFF) ? 0xFF : tmp;
+    ret |= ((tmp = ((((a >> 8) & 0xFF) * ((b >> 8) & 0xFF)) / 0xFF)) > 0xFF) ?
+            0xFF00 : tmp << 8;
+    ret |= ((tmp = ((((a >> 16) & 0xFF) * ((b >> 16) & 0xFF)) / 0xFF)) > 0xFF) ?
+            0xFF0000 : tmp << 16;
+    return ret;
+}
+
+/* For each corresponding bytes of two 32-bit words: (a*b + c*d)
+ * Byte values 0-255 are mapped to a range [0 .. 1] */
+static inline uint32_t
+fimd_mult_and_sum_each_byte(uint32_t a, uint32_t b, uint32_t c, uint32_t d)
+{
+    uint32_t tmp;
+    uint32_t ret;
+
+    ret = ((tmp = (((a & 0xFF) * (b & 0xFF) + (c & 0xFF) * (d & 0xFF)) / 0xFF))
+            > 0xFF) ? 0xFF : tmp;
+    ret |= ((tmp = ((((a >> 8) & 0xFF) * ((b >> 8) & 0xFF) + ((c >> 8) & 0xFF) *
+            ((d >> 8) & 0xFF)) / 0xFF)) > 0xFF) ? 0xFF00 : tmp << 8;
+    ret |= ((tmp = ((((a >> 16) & 0xFF) * ((b >> 16) & 0xFF) +
+            ((c >> 16) & 0xFF) * ((d >> 16) & 0xFF)) / 0xFF)) > 0xFF) ?
+                    0xFF0000 : tmp << 16;
+    return ret;
+}
+
+/* These routines cover all possible sources of window's transparent factor
+ * used in blending equation. Choice of routine is affected by WPALCON
+ * registers, BLENDCON register and window's WINCON register */
+
+static uint32_t fimd_get_alpha_pix(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+    return pix_a;
+}
+
+static uint32_t
+fimd_get_alpha_pix_extlow(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+    return EXTEND_LOWER_HALFBYTE(pix_a);
+}
+
+static uint32_t
+fimd_get_alpha_pix_exthigh(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+    return EXTEND_UPPER_HALFBYTE(pix_a);
+}
+
+static uint32_t fimd_get_alpha_mult(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+    return fimd_mult_each_byte(pix_a, w->alpha_val[0]);
+}
+
+static uint32_t fimd_get_alpha_mult_ext(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+    return fimd_mult_each_byte(EXTEND_LOWER_HALFBYTE(pix_a),
+            EXTEND_UPPER_HALFBYTE(w->alpha_val[0]));
+}
+
+static uint32_t fimd_get_alpha_aen(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+    return w->alpha_val[pix_a];
+}
+
+static uint32_t fimd_get_alpha_aen_ext(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+    return EXTEND_UPPER_HALFBYTE(w->alpha_val[pix_a]);
+}
+
+static uint32_t fimd_get_alpha_sel(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+    return w->alpha_val[(w->wincon & FIMD_WINCON_ALPHA_SEL) ? 1 : 0];
+}
+
+static uint32_t fimd_get_alpha_sel_ext(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+    return EXTEND_UPPER_HALFBYTE(w->alpha_val[(w->wincon &
+            FIMD_WINCON_ALPHA_SEL) ? 1 : 0]);
+}
+
+/* Updates currently active alpha value get function for specified window */
+static void fimd_update_get_alpha(Exynos4210fimdState *s, int win)
+{
+    Exynos4210fimdWindow *w = &s->window[win];
+    const bool alpha_is_8bit = s->blendcon & FIMD_ALPHA_8BIT;
+
+    if (w->wincon & FIMD_WINCON_BLD_PIX) {
+        if ((w->wincon & FIMD_WINCON_ALPHA_SEL) && WIN_BPP_MODE_WITH_ALPHA(w)) {
+            /* In this case, alpha component contains meaningful value */
+            if (w->wincon & FIMD_WINCON_ALPHA_MUL) {
+                w->get_alpha = alpha_is_8bit ?
+                        fimd_get_alpha_mult : fimd_get_alpha_mult_ext;
+            } else {
+                w->get_alpha = alpha_is_8bit ?
+                        fimd_get_alpha_pix : fimd_get_alpha_pix_extlow;
+            }
+        } else {
+            if (IS_PALETTIZED_MODE(w) &&
+                  PAL_MODE_WITH_ALPHA(exynos4210_fimd_palette_format(s, win))) {
+                /* Alpha component has 8-bit numeric value */
+                w->get_alpha = alpha_is_8bit ?
+                        fimd_get_alpha_pix : fimd_get_alpha_pix_exthigh;
+            } else {
+                /* Alpha has only two possible values (AEN) */
+                w->get_alpha = alpha_is_8bit ?
+                        fimd_get_alpha_aen : fimd_get_alpha_aen_ext;
+            }
+        }
+    } else {
+        w->get_alpha = alpha_is_8bit ? fimd_get_alpha_sel :
+                fimd_get_alpha_sel_ext;
+    }
+}
+
+/* Blends current window's (w) pixel (foreground pixel *ret) with background
+ * window (w_blend) pixel p_bg according to formula:
+ * NEW_COLOR = a_coef x FG_PIXEL_COLOR + b_coef x BG_PIXEL_COLOR
+ * NEW_ALPHA = p_coef x FG_ALPHA + q_coef x BG_ALPHA
+ */
+static void
+exynos4210_fimd_blend_pixel(Exynos4210fimdWindow *w, rgba p_bg, rgba *ret)
+{
+    rgba p_fg = *ret;
+    uint32_t bg_color = ((p_bg.r & 0xFF) << 16) | ((p_bg.g & 0xFF) << 8) |
+            (p_bg.b & 0xFF);
+    uint32_t fg_color = ((p_fg.r & 0xFF) << 16) | ((p_fg.g & 0xFF) << 8) |
+            (p_fg.b & 0xFF);
+    uint32_t alpha_fg = p_fg.a;
+    int i;
+    /* It is possible that blending equation parameters a and b do not
+     * depend on window BLENEQ register. Account for this with first_coef */
+    enum { A_COEF = 0, B_COEF = 1, P_COEF = 2, Q_COEF = 3, COEF_NUM = 4};
+    uint32_t first_coef = A_COEF;
+    uint32_t blend_param[COEF_NUM];
+
+    if (w->keycon[0] & FIMD_WKEYCON0_KEYEN) {
+        uint32_t colorkey = (w->keycon[1] &
+              ~(w->keycon[0] & FIMD_WKEYCON0_COMPKEY)) & FIMD_WKEYCON0_COMPKEY;
+
+        if ((w->keycon[0] & FIMD_WKEYCON0_DIRCON) &&
+            (bg_color & ~(w->keycon[0] & FIMD_WKEYCON0_COMPKEY)) == colorkey) {
+            /* Foreground pixel is displayed */
+            if (w->keycon[0] & FIMD_WKEYCON0_KEYBLEN) {
+                alpha_fg = w->keyalpha;
+                blend_param[A_COEF] = alpha_fg;
+                blend_param[B_COEF] = FIMD_1_MINUS_COLOR(alpha_fg);
+            } else {
+                alpha_fg = 0;
+                blend_param[A_COEF] = 0xFFFFFF;
+                blend_param[B_COEF] = 0x0;
+            }
+            first_coef = P_COEF;
+        } else if ((w->keycon[0] & FIMD_WKEYCON0_DIRCON) == 0 &&
+            (fg_color & ~(w->keycon[0] & FIMD_WKEYCON0_COMPKEY)) == colorkey) {
+            /* Background pixel is displayed */
+            if (w->keycon[0] & FIMD_WKEYCON0_KEYBLEN) {
+                alpha_fg = w->keyalpha;
+                blend_param[A_COEF] = alpha_fg;
+                blend_param[B_COEF] = FIMD_1_MINUS_COLOR(alpha_fg);
+            } else {
+                alpha_fg = 0;
+                blend_param[A_COEF] = 0x0;
+                blend_param[B_COEF] = 0xFFFFFF;
+            }
+            first_coef = P_COEF;
+        }
+    }
+
+    for (i = first_coef; i < COEF_NUM; i++) {
+        switch ((w->blendeq >> i * 6) & FIMD_BLENDEQ_COEF_MASK) {
+        case 0:
+            blend_param[i] = 0;
+            break;
+        case 1:
+            blend_param[i] = 0xFFFFFF;
+            break;
+        case 2:
+            blend_param[i] = alpha_fg;
+            break;
+        case 3:
+            blend_param[i] = FIMD_1_MINUS_COLOR(alpha_fg);
+            break;
+        case 4:
+            blend_param[i] = p_bg.a;
+            break;
+        case 5:
+            blend_param[i] = FIMD_1_MINUS_COLOR(p_bg.a);
+            break;
+        case 6:
+            blend_param[i] = w->alpha_val[0];
+            break;
+        case 10:
+            blend_param[i] = fg_color;
+            break;
+        case 11:
+            blend_param[i] = FIMD_1_MINUS_COLOR(fg_color);
+            break;
+        case 12:
+            blend_param[i] = bg_color;
+            break;
+        case 13:
+            blend_param[i] = FIMD_1_MINUS_COLOR(bg_color);
+            break;
+        default:
+            hw_error("exynos4210.fimd: blend equation coef illegal value\n");
+            break;
+        }
+    }
+
+    fg_color = fimd_mult_and_sum_each_byte(bg_color, blend_param[B_COEF],
+            fg_color, blend_param[A_COEF]);
+    ret->b = fg_color & 0xFF;
+    fg_color >>= 8;
+    ret->g = fg_color & 0xFF;
+    fg_color >>= 8;
+    ret->r = fg_color & 0xFF;
+    ret->a = fimd_mult_and_sum_each_byte(alpha_fg, blend_param[P_COEF],
+            p_bg.a, blend_param[Q_COEF]);
+}
+
+/* These routines read data from video frame buffer in system RAM, convert
+ * this data to display controller internal representation, if necessary,
+ * perform pixel blending with data, currently presented in internal buffer.
+ * Result is stored in display controller internal frame buffer. */
+
+/* Draw line with index in palette table in RAM frame buffer data */
+#define DEF_DRAW_LINE_PALETTE(N) \
+static void glue(draw_line_palette_, N)(Exynos4210fimdWindow *w, uint8_t *src, \
+               uint8_t *dst, bool blend) \
+{ \
+    int width = w->rightbot_x - w->lefttop_x + 1; \
+    uint8_t *ifb = dst; \
+    uint8_t swap = (w->wincon & FIMD_WINCON_SWAP) >> FIMD_WINCON_SWAP_SHIFT; \
+    uint64_t data; \
+    rgba p, p_old; \
+    int i; \
+    do { \
+        data = ldq_raw((void *)src); \
+        src += 8; \
+        fimd_swap_data(swap, &data); \
+        for (i = (64 / (N) - 1); i >= 0; i--) { \
+            w->pixel_to_rgb(w->palette[(data >> ((N) * i)) & \
+                                   ((1ULL << (N)) - 1)], &p); \
+            p.a = w->get_alpha(w, p.a); \
+            if (blend) { \
+                ifb +=  get_pixel_ifb(ifb, &p_old); \
+                exynos4210_fimd_blend_pixel(w, p_old, &p); \
+            } \
+            dst += put_pixel_ifb(p, dst); \
+        } \
+        width -= (64 / (N)); \
+    } while (width > 0); \
+}
+
+/* Draw line with direct color value in RAM frame buffer data */
+#define DEF_DRAW_LINE_NOPALETTE(N) \
+static void glue(draw_line_, N)(Exynos4210fimdWindow *w, uint8_t *src, \
+                    uint8_t *dst, bool blend) \
+{ \
+    int width = w->rightbot_x - w->lefttop_x + 1; \
+    uint8_t *ifb = dst; \
+    uint8_t swap = (w->wincon & FIMD_WINCON_SWAP) >> FIMD_WINCON_SWAP_SHIFT; \
+    uint64_t data; \
+    rgba p, p_old; \
+    int i; \
+    do { \
+        data = ldq_raw((void *)src); \
+        src += 8; \
+        fimd_swap_data(swap, &data); \
+        for (i = (64 / (N) - 1); i >= 0; i--) { \
+            w->pixel_to_rgb((data >> ((N) * i)) & ((1ULL << (N)) - 1), &p); \
+            p.a = w->get_alpha(w, p.a); \
+            if (blend) { \
+                ifb += get_pixel_ifb(ifb, &p_old); \
+                exynos4210_fimd_blend_pixel(w, p_old, &p); \
+            } \
+            dst += put_pixel_ifb(p, dst); \
+        } \
+        width -= (64 / (N)); \
+    } while (width > 0); \
+}
+
+DEF_DRAW_LINE_PALETTE(1)
+DEF_DRAW_LINE_PALETTE(2)
+DEF_DRAW_LINE_PALETTE(4)
+DEF_DRAW_LINE_PALETTE(8)
+DEF_DRAW_LINE_NOPALETTE(8)  /* 8bpp mode has palette and non-palette versions */
+DEF_DRAW_LINE_NOPALETTE(16)
+DEF_DRAW_LINE_NOPALETTE(32)
+
+/* Special draw line routine for window color map case */
+static void draw_line_mapcolor(Exynos4210fimdWindow *w, uint8_t *src,
+                       uint8_t *dst, bool blend)
+{
+    rgba p, p_old;
+    uint8_t *ifb = dst;
+    int width = w->rightbot_x - w->lefttop_x + 1;
+    uint32_t map_color = w->winmap & FIMD_WINMAP_COLOR_MASK;
+
+    do {
+        pixel_888_to_rgb(map_color, &p);
+        p.a = w->get_alpha(w, p.a);
+        if (blend) {
+            ifb += get_pixel_ifb(ifb, &p_old);
+            exynos4210_fimd_blend_pixel(w, p_old, &p);
+        }
+        dst += put_pixel_ifb(p, dst);
+    } while (--width);
+}
+
+/* Write RGB to QEMU's GraphicConsole framebuffer */
+
+static int put_to_qemufb_pixel8(const rgba p, uint8_t *d)
+{
+    uint32_t pixel = rgb_to_pixel8(p.r, p.g, p.b);
+    *(uint8_t *)d = pixel;
+    return 1;
+}
+
+static int put_to_qemufb_pixel15(const rgba p, uint8_t *d)
+{
+    uint32_t pixel = rgb_to_pixel15(p.r, p.g, p.b);
+    *(uint16_t *)d = pixel;
+    return 2;
+}
+
+static int put_to_qemufb_pixel16(const rgba p, uint8_t *d)
+{
+    uint32_t pixel = rgb_to_pixel16(p.r, p.g, p.b);
+    *(uint16_t *)d = pixel;
+    return 2;
+}
+
+static int put_to_qemufb_pixel24(const rgba p, uint8_t *d)
+{
+    uint32_t pixel = rgb_to_pixel24(p.r, p.g, p.b);
+    *(uint8_t *)d++ = (pixel >>  0) & 0xFF;
+    *(uint8_t *)d++ = (pixel >>  8) & 0xFF;
+    *(uint8_t *)d++ = (pixel >> 16) & 0xFF;
+    return 3;
+}
+
+static int put_to_qemufb_pixel32(const rgba p, uint8_t *d)
+{
+    uint32_t pixel = rgb_to_pixel24(p.r, p.g, p.b);
+    *(uint32_t *)d = pixel;
+    return 4;
+}
+
+/* Routine to copy pixel from internal buffer to QEMU buffer */
+static int (*put_pixel_toqemu)(const rgba p, uint8_t *pixel);
+static inline void fimd_update_putpix_qemu(int bpp)
+{
+    switch (bpp) {
+    case 8:
+        put_pixel_toqemu = put_to_qemufb_pixel8;
+        break;
+    case 15:
+        put_pixel_toqemu = put_to_qemufb_pixel15;
+        break;
+    case 16:
+        put_pixel_toqemu = put_to_qemufb_pixel16;
+        break;
+    case 24:
+        put_pixel_toqemu = put_to_qemufb_pixel24;
+        break;
+    case 32:
+        put_pixel_toqemu = put_to_qemufb_pixel32;
+        break;
+    default:
+        hw_error("exynos4210.fimd: unsupported BPP (%d)", bpp);
+        break;
+    }
+}
+
+/* Routine to copy a line from internal frame buffer to QEMU display */
+static void fimd_copy_line_toqemu(int width, uint8_t *src, uint8_t *dst)
+{
+    rgba p;
+
+    do {
+        src += get_pixel_ifb(src, &p);
+        dst += put_pixel_toqemu(p, dst);
+    } while (--width);
+}
+
+/* Parse BPPMODE_F = WINCON1[5:2] bits */
+static void exynos4210_fimd_update_win_bppmode(Exynos4210fimdState *s, int win)
+{
+    Exynos4210fimdWindow *w = &s->window[win];
+
+    if (w->winmap & FIMD_WINMAP_EN) {
+        w->draw_line = draw_line_mapcolor;
+        return;
+    }
+
+    switch (WIN_BPP_MODE(w)) {
+    case 0:
+        w->draw_line = draw_line_palette_1;
+        w->pixel_to_rgb =
+                palette_data_format[exynos4210_fimd_palette_format(s, win)];
+        break;
+    case 1:
+        w->draw_line = draw_line_palette_2;
+        w->pixel_to_rgb =
+                palette_data_format[exynos4210_fimd_palette_format(s, win)];
+        break;
+    case 2:
+        w->draw_line = draw_line_palette_4;
+        w->pixel_to_rgb =
+                palette_data_format[exynos4210_fimd_palette_format(s, win)];
+        break;
+    case 3:
+        w->draw_line = draw_line_palette_8;
+        w->pixel_to_rgb =
+                palette_data_format[exynos4210_fimd_palette_format(s, win)];
+        break;
+    case 4:
+        w->draw_line = draw_line_8;
+        w->pixel_to_rgb = pixel_a232_to_rgb;
+        break;
+    case 5:
+        w->draw_line = draw_line_16;
+        w->pixel_to_rgb = pixel_565_to_rgb;
+        break;
+    case 6:
+        w->draw_line = draw_line_16;
+        w->pixel_to_rgb = pixel_a555_to_rgb;
+        break;
+    case 7:
+        w->draw_line = draw_line_16;
+        w->pixel_to_rgb = pixel_1555_to_rgb;
+        break;
+    case 8:
+        w->draw_line = draw_line_32;
+        w->pixel_to_rgb = pixel_666_to_rgb;
+        break;
+    case 9:
+        w->draw_line = draw_line_32;
+        w->pixel_to_rgb = pixel_a665_to_rgb;
+        break;
+    case 10:
+        w->draw_line = draw_line_32;
+        w->pixel_to_rgb = pixel_a666_to_rgb;
+        break;
+    case 11:
+        w->draw_line = draw_line_32;
+        w->pixel_to_rgb = pixel_888_to_rgb;
+        break;
+    case 12:
+        w->draw_line = draw_line_32;
+        w->pixel_to_rgb = pixel_a887_to_rgb;
+        break;
+    case 13:
+        w->draw_line = draw_line_32;
+        if ((w->wincon & FIMD_WINCON_BLD_PIX) && (w->wincon &
+                FIMD_WINCON_ALPHA_SEL)) {
+            w->pixel_to_rgb = pixel_8888_to_rgb;
+        } else {
+            w->pixel_to_rgb = pixel_a888_to_rgb;
+        }
+        break;
+    case 14:
+        w->draw_line = draw_line_16;
+        if ((w->wincon & FIMD_WINCON_BLD_PIX) && (w->wincon &
+                FIMD_WINCON_ALPHA_SEL)) {
+            w->pixel_to_rgb = pixel_4444_to_rgb;
+        } else {
+            w->pixel_to_rgb = pixel_a444_to_rgb;
+        }
+        break;
+    case 15:
+        w->draw_line = draw_line_16;
+        w->pixel_to_rgb = pixel_555_to_rgb;
+        break;
+    }
+}
+
+#if EXYNOS4210_FIMD_MODE_TRACE > 0
+static const char *exynos4210_fimd_get_bppmode(int mode_code)
+{
+    switch (mode_code) {
+    case 0:
+        return "1 bpp";
+    case 1:
+        return "2 bpp";
+    case 2:
+        return "4 bpp";
+    case 3:
+        return "8 bpp (palettized)";
+    case 4:
+        return "8 bpp (non-palettized, A: 1-R:2-G:3-B:2)";
+    case 5:
+        return "16 bpp (non-palettized, R:5-G:6-B:5)";
+    case 6:
+        return "16 bpp (non-palettized, A:1-R:5-G:5-B:5)";
+    case 7:
+        return "16 bpp (non-palettized, I :1-R:5-G:5-B:5)";
+    case 8:
+        return "Unpacked 18 bpp (non-palettized, R:6-G:6-B:6)";
+    case 9:
+        return "Unpacked 18bpp (non-palettized,A:1-R:6-G:6-B:5)";
+    case 10:
+        return "Unpacked 19bpp (non-palettized,A:1-R:6-G:6-B:6)";
+    case 11:
+        return "Unpacked 24 bpp (non-palettized R:8-G:8-B:8)";
+    case 12:
+        return "Unpacked 24 bpp (non-palettized A:1-R:8-G:8-B:7)";
+    case 13:
+        return "Unpacked 25 bpp (non-palettized A:1-R:8-G:8-B:8)";
+    case 14:
+        return "Unpacked 13 bpp (non-palettized A:1-R:4-G:4-B:4)";
+    case 15:
+        return "Unpacked 15 bpp (non-palettized R:5-G:5-B:5)";
+    default:
+        return "Non-existing bpp mode";
+    }
+}
+
+static inline void exynos4210_fimd_trace_bppmode(Exynos4210fimdState *s,
+                int win_num, uint32_t val)
+{
+    Exynos4210fimdWindow *w = &s->window[win_num];
+
+    if (w->winmap & FIMD_WINMAP_EN) {
+        printf("QEMU FIMD: Window %d is mapped with MAPCOLOR=0x%x\n",
+                win_num, w->winmap & 0xFFFFFF);
+        return;
+    }
+
+    if ((val != 0xFFFFFFFF) && ((w->wincon >> 2) & 0xF) == ((val >> 2) & 0xF)) {
+        return;
+    }
+    printf("QEMU FIMD: Window %d BPP mode set to %s\n", win_num,
+        exynos4210_fimd_get_bppmode((val >> 2) & 0xF));
+}
+#else
+static inline void exynos4210_fimd_trace_bppmode(Exynos4210fimdState *s,
+        int win_num, uint32_t val)
+{
+
+}
+#endif
+
+static inline int fimd_get_buffer_id(Exynos4210fimdWindow *w)
+{
+    switch (w->wincon & FIMD_WINCON_BUFSTATUS) {
+    case FIMD_WINCON_BUF0_STAT:
+        return 0;
+    case FIMD_WINCON_BUF1_STAT:
+        return 1;
+    case FIMD_WINCON_BUF2_STAT:
+        return 2;
+    default:
+        DPRINT_ERROR("Non-existent buffer index\n");
+        return 0;
+    }
+}
+
+/* Updates specified window's MemorySection based on values of WINCON,
+ * VIDOSDA, VIDOSDB, VIDWADDx and SHADOWCON registers */
+static void fimd_update_memory_section(Exynos4210fimdState *s, unsigned win)
+{
+    Exynos4210fimdWindow *w = &s->window[win];
+    target_phys_addr_t fb_start_addr, fb_mapped_len;
+
+    if (!s->enabled || !(w->wincon & FIMD_WINCON_ENWIN) ||
+            FIMD_WINDOW_PROTECTED(s->shadowcon, win)) {
+        return;
+    }
+
+    if (w->host_fb_addr) {
+        cpu_physical_memory_unmap(w->host_fb_addr, w->fb_len, 0, 0);
+        w->host_fb_addr = NULL;
+        w->fb_len = 0;
+    }
+
+    fb_start_addr = w->buf_start[fimd_get_buffer_id(w)];
+    /* Total number of bytes of virtual screen used by current window */
+    w->fb_len = fb_mapped_len = (w->virtpage_width + w->virtpage_offsize) *
+            (w->rightbot_y - w->lefttop_y + 1);
+    w->mem_section = memory_region_find(sysbus_address_space(&s->busdev),
+            fb_start_addr, w->fb_len);
+    assert(w->mem_section.mr);
+    assert(w->mem_section.offset_within_address_space == fb_start_addr);
+    DPRINT_TRACE("Window %u framebuffer changed: address=0x%08x, len=0x%x\n",
+            win, fb_start_addr, w->fb_len);
+
+    if (w->mem_section.size != w->fb_len ||
+            !memory_region_is_ram(w->mem_section.mr)) {
+        DPRINT_ERROR("Failed to find window %u framebuffer region\n", win);
+        goto error_return;
+    }
+
+    w->host_fb_addr = cpu_physical_memory_map(fb_start_addr, &fb_mapped_len, 0);
+    if (!w->host_fb_addr) {
+        DPRINT_ERROR("Failed to map window %u framebuffer\n", win);
+        goto error_return;
+    }
+
+    if (fb_mapped_len != w->fb_len) {
+        DPRINT_ERROR("Window %u mapped framebuffer length is less then "
+                "expected\n", win);
+        cpu_physical_memory_unmap(w->host_fb_addr, fb_mapped_len, 0, 0);
+        goto error_return;
+    }
+    return;
+
+error_return:
+    w->mem_section.mr = NULL;
+    w->mem_section.size = 0;
+    w->host_fb_addr = NULL;
+    w->fb_len = 0;
+}
+
+static void exynos4210_fimd_enable(Exynos4210fimdState *s, bool enabled)
+{
+    if (enabled && !s->enabled) {
+        unsigned w;
+        s->enabled = true;
+        for (w = 0; w < NUM_OF_WINDOWS; w++) {
+            fimd_update_memory_section(s, w);
+        }
+    }
+    s->enabled = enabled;
+    DPRINT_TRACE("display controller %s\n", enabled ? "enabled" : "disabled");
+}
+
+static inline uint32_t unpack_upper_4(uint32_t x)
+{
+    return ((x & 0xF00) << 12) | ((x & 0xF0) << 8) | ((x & 0xF) << 4);
+}
+
+static inline uint32_t pack_upper_4(uint32_t x)
+{
+    return (((x & 0xF00000) >> 12) | ((x & 0xF000) >> 8) |
+            ((x & 0xF0) >> 4)) & 0xFFF;
+}
+
+static void exynos4210_fimd_update_irq(Exynos4210fimdState *s)
+{
+    if (!(s->vidintcon[0] & FIMD_VIDINT_INTEN)) {
+        qemu_irq_lower(s->irq[0]);
+        qemu_irq_lower(s->irq[1]);
+        qemu_irq_lower(s->irq[2]);
+        return;
+    }
+    if ((s->vidintcon[0] & FIMD_VIDINT_INTFIFOEN) &&
+            (s->vidintcon[1] & FIMD_VIDINT_INTFIFOPEND)) {
+        qemu_irq_raise(s->irq[0]);
+    } else {
+        qemu_irq_lower(s->irq[0]);
+    }
+    if ((s->vidintcon[0] & FIMD_VIDINT_INTFRMEN) &&
+            (s->vidintcon[1] & FIMD_VIDINT_INTFRMPEND)) {
+        qemu_irq_raise(s->irq[1]);
+    } else {
+        qemu_irq_lower(s->irq[1]);
+    }
+    if ((s->vidintcon[0] & FIMD_VIDINT_I80IFDONE) &&
+            (s->vidintcon[1] & FIMD_VIDINT_INTI80PEND)) {
+        qemu_irq_raise(s->irq[2]);
+    } else {
+        qemu_irq_lower(s->irq[2]);
+    }
+}
+
+static void exynos4210_fimd_invalidate(void *opaque)
+{
+    Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+    s->invalidate = true;
+}
+
+static void exynos4210_update_resolution(Exynos4210fimdState *s)
+{
+    /* LCD resolution is stored in VIDEO TIME CONTROL REGISTER 2 */
+    uint32_t width = ((s->vidtcon[2] >> FIMD_VIDTCON2_HOR_SHIFT) &
+            FIMD_VIDTCON2_SIZE_MASK) + 1;
+    uint32_t height = ((s->vidtcon[2] >> FIMD_VIDTCON2_VER_SHIFT) &
+            FIMD_VIDTCON2_SIZE_MASK) + 1;
+
+    if (s->ifb == NULL || ds_get_width(s->console) != width ||
+            ds_get_height(s->console) != height) {
+        DPRINT_L1("Resolution changed from %ux%u to %ux%u\n",
+           ds_get_width(s->console), ds_get_height(s->console), width, height);
+        qemu_console_resize(s->console, width, height);
+        s->ifb = g_realloc(s->ifb, width * height * RGBA_SIZE + 1);
+        memset(s->ifb, 0, width * height * RGBA_SIZE + 1);
+        exynos4210_fimd_invalidate(s);
+    }
+}
+
+static void exynos4210_fimd_update(void *opaque)
+{
+    Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+    Exynos4210fimdWindow *w;
+    int i, line;
+    target_phys_addr_t fb_line_addr, inc_size, x;
+    int scrn_height;
+    int first_line = -1, last_line = -1, scrn_width;
+    bool blend = false;
+    uint8_t *host_fb_addr;
+    bool is_dirty = false;
+    const int global_width = (s->vidtcon[2] & FIMD_VIDTCON2_SIZE_MASK) + 1;
+    const int global_height = ((s->vidtcon[2] >> FIMD_VIDTCON2_VER_SHIFT) &
+            FIMD_VIDTCON2_SIZE_MASK) + 1;
+
+    if (!s || !s->console || !ds_get_bits_per_pixel(s->console) ||
+            !s->enabled) {
+        return;
+    }
+    exynos4210_update_resolution(s);
+
+    for (i = 0; i < NUM_OF_WINDOWS; i++) {
+        w = &s->window[i];
+        if ((w->wincon & FIMD_WINCON_ENWIN) && w->host_fb_addr) {
+            scrn_height = w->rightbot_y - w->lefttop_y + 1;
+            scrn_width = w->virtpage_width;
+            /* Total width of virtual screen page in bytes */
+            inc_size = scrn_width + w->virtpage_offsize;
+            memory_region_sync_dirty_bitmap(w->mem_section.mr);
+            host_fb_addr = w->host_fb_addr;
+            fb_line_addr = w->mem_section.offset_within_region;
+
+            for (line = 0; line < scrn_height; line++) {
+                for (x = fb_line_addr;
+                     x < TARGET_PAGE_ALIGN(fb_line_addr + scrn_width);
+                     x += TARGET_PAGE_SIZE) {
+                    is_dirty = is_dirty ||
+                        memory_region_get_dirty(w->mem_section.mr,
+                                x, DIRTY_MEMORY_VGA);
+                }
+
+                if (s->invalidate || is_dirty) {
+                    if (first_line == -1) {
+                        first_line = line;
+                    }
+                    last_line = line;
+                    w->draw_line(w, host_fb_addr, s->ifb +
+                        w->lefttop_x * RGBA_SIZE + (w->lefttop_y + line) *
+                        global_width * RGBA_SIZE, blend);
+                }
+                host_fb_addr += inc_size;
+                fb_line_addr += inc_size;
+                is_dirty = false;
+            }
+            memory_region_reset_dirty(w->mem_section.mr,
+                w->mem_section.offset_within_region,
+                w->fb_len, DIRTY_MEMORY_VGA);
+            blend = true;
+        }
+    }
+
+    /* Copy resulting image to QEMU_CONSOLE. */
+    if (first_line >= 0) {
+        uint8_t *d;
+        int bpp;
+
+        bpp = ds_get_bits_per_pixel(s->console);
+        fimd_update_putpix_qemu(bpp);
+        bpp = (bpp + 1) >> 3;
+        d = ds_get_data(s->console);
+        for (line = first_line; line <= last_line; line++) {
+            fimd_copy_line_toqemu(global_width, s->ifb + global_width * line *
+                    RGBA_SIZE, d + global_width * line * bpp);
+        }
+        dpy_update(s->console, 0, 0, global_width, global_height);
+    }
+    s->invalidate = false;
+    s->vidintcon[1] |= FIMD_VIDINT_INTFRMPEND;
+    if ((s->vidcon[0] & FIMD_VIDCON0_ENVID_F) == 0) {
+        exynos4210_fimd_enable(s, false);
+    }
+    exynos4210_fimd_update_irq(s);
+}
+
+static void exynos4210_fimd_reset(DeviceState *d)
+{
+    Exynos4210fimdState *s = container_of(d, Exynos4210fimdState, busdev.qdev);
+    unsigned w;
+
+    DPRINT_TRACE("Display controller reset\n");
+    /* Set all display controller registers to 0 */
+    memset(&s->vidcon, 0, (uint8_t *)&s->window - (uint8_t *)&s->vidcon);
+    for (w = 0; w < NUM_OF_WINDOWS; w++) {
+        memset(&s->window[w], 0, sizeof(Exynos4210fimdWindow));
+        s->window[w].blendeq = 0xC2;
+        exynos4210_fimd_update_win_bppmode(s, w);
+        exynos4210_fimd_trace_bppmode(s, w, 0xFFFFFFFF);
+        fimd_update_get_alpha(s, w);
+    }
+
+    if (s->ifb != NULL) {
+        g_free(s->ifb);
+    }
+    s->ifb = NULL;
+
+    exynos4210_fimd_invalidate(s);
+    exynos4210_fimd_enable(s, false);
+    /* Some registers have non-zero initial values */
+    s->winchmap = 0x7D517D51;
+    s->colorgaincon = 0x10040100;
+    s->huecoef_cr[0] = s->huecoef_cr[3] = 0x01000100;
+    s->huecoef_cb[0] = s->huecoef_cb[3] = 0x01000100;
+    s->hueoffset = 0x01800080;
+}
+
+static void exynos4210_fimd_write(void *opaque, target_phys_addr_t offset,
+                              uint64_t val, unsigned size)
+{
+    Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+    unsigned w, i;
+    uint32_t old_value;
+
+    DPRINT_L2("write offset 0x%08x, value=%llu(0x%08llx)\n", offset,
+            (long long unsigned int)val, (long long unsigned int)val);
+
+    switch (offset) {
+    case FIMD_VIDCON0:
+        if ((val & FIMD_VIDCON0_ENVID_MASK) == FIMD_VIDCON0_ENVID_MASK) {
+            exynos4210_fimd_enable(s, true);
+        } else {
+            if ((val & FIMD_VIDCON0_ENVID) == 0) {
+                exynos4210_fimd_enable(s, false);
+            }
+        }
+        s->vidcon[0] = val;
+        break;
+    case FIMD_VIDCON1:
+        /* Leave read-only bits as is */
+        val = (val & (~FIMD_VIDCON1_ROMASK)) |
+                (s->vidcon[1] & FIMD_VIDCON1_ROMASK);
+        s->vidcon[1] = val;
+        break;
+    case FIMD_VIDCON2 ... FIMD_VIDCON3:
+        s->vidcon[(offset) >> 2] = val;
+        break;
+    case FIMD_VIDTCON_START ... FIMD_VIDTCON_END:
+        s->vidtcon[(offset - FIMD_VIDTCON_START) >> 2] = val;
+        break;
+    case FIMD_WINCON_START ... FIMD_WINCON_END:
+        w = (offset - FIMD_WINCON_START) >> 2;
+        /* Window's current buffer ID */
+        i = fimd_get_buffer_id(&s->window[w]);
+        old_value = s->window[w].wincon;
+        val = (val & ~FIMD_WINCON_ROMASK) |
+                (s->window[w].wincon & FIMD_WINCON_ROMASK);
+        if (w == 0) {
+            /* Window 0 wincon ALPHA_MUL bit must always be 0 */
+            val &= ~FIMD_WINCON_ALPHA_MUL;
+        }
+        exynos4210_fimd_trace_bppmode(s, w, val);
+        switch (val & FIMD_WINCON_BUFSELECT) {
+        case FIMD_WINCON_BUF0_SEL:
+            val &= ~FIMD_WINCON_BUFSTATUS;
+            break;
+        case FIMD_WINCON_BUF1_SEL:
+            val = (val & ~FIMD_WINCON_BUFSTAT_H) | FIMD_WINCON_BUFSTAT_L;
+            break;
+        case FIMD_WINCON_BUF2_SEL:
+            if (val & FIMD_WINCON_BUFMODE) {
+                val = (val & ~FIMD_WINCON_BUFSTAT_L) | FIMD_WINCON_BUFSTAT_H;
+            }
+            break;
+        default:
+            break;
+        }
+        s->window[w].wincon = val;
+        exynos4210_fimd_update_win_bppmode(s, w);
+        fimd_update_get_alpha(s, w);
+        if ((i != fimd_get_buffer_id(&s->window[w])) ||
+                (!(old_value & FIMD_WINCON_ENWIN) && (s->window[w].wincon &
+                        FIMD_WINCON_ENWIN))) {
+            fimd_update_memory_section(s, w);
+        }
+        break;
+    case FIMD_SHADOWCON:
+        old_value = s->shadowcon;
+        s->shadowcon = val;
+        for (w = 0; w < NUM_OF_WINDOWS; w++) {
+            if (FIMD_WINDOW_PROTECTED(old_value, w) &&
+                    !FIMD_WINDOW_PROTECTED(s->shadowcon, w)) {
+                fimd_update_memory_section(s, w);
+            }
+        }
+        break;
+    case FIMD_WINCHMAP:
+        s->winchmap = val;
+        break;
+    case FIMD_VIDOSD_START ... FIMD_VIDOSD_END:
+        w = (offset - FIMD_VIDOSD_START) >> 4;
+        i = ((offset - FIMD_VIDOSD_START) & 0xF) >> 2;
+        switch (i) {
+        case 0:
+            old_value = s->window[w].lefttop_y;
+            s->window[w].lefttop_x = (val >> FIMD_VIDOSD_HOR_SHIFT) &
+                                      FIMD_VIDOSD_COORD_MASK;
+            s->window[w].lefttop_y = (val >> FIMD_VIDOSD_VER_SHIFT) &
+                                      FIMD_VIDOSD_COORD_MASK;
+            if (s->window[w].lefttop_y != old_value) {
+                fimd_update_memory_section(s, w);
+            }
+            break;
+        case 1:
+            old_value = s->window[w].rightbot_y;
+            s->window[w].rightbot_x = (val >> FIMD_VIDOSD_HOR_SHIFT) &
+                                       FIMD_VIDOSD_COORD_MASK;
+            s->window[w].rightbot_y = (val >> FIMD_VIDOSD_VER_SHIFT) &
+                                       FIMD_VIDOSD_COORD_MASK;
+            if (s->window[w].rightbot_y != old_value) {
+                fimd_update_memory_section(s, w);
+            }
+            break;
+        case 2:
+            if (w == 0) {
+                s->window[w].osdsize = val;
+            } else {
+                s->window[w].alpha_val[0] =
+                    unpack_upper_4((val & FIMD_VIDOSD_ALPHA_AEN0) >>
+                    FIMD_VIDOSD_AEN0_SHIFT) |
+                    (s->window[w].alpha_val[0] & FIMD_VIDALPHA_ALPHA_LOWER);
+                s->window[w].alpha_val[1] =
+                    unpack_upper_4(val & FIMD_VIDOSD_ALPHA_AEN1) |
+                    (s->window[w].alpha_val[1] & FIMD_VIDALPHA_ALPHA_LOWER);
+            }
+            break;
+        case 3:
+            if (w != 1 && w != 2) {
+                DPRINT_ERROR("Bad write offset 0x%08x\n", offset);
+                return;
+            }
+            s->window[w].osdsize = val;
+            break;
+        }
+        break;
+    case FIMD_VIDWADD0_START ... FIMD_VIDWADD0_END:
+        w = (offset - FIMD_VIDWADD0_START) >> 3;
+        i = ((offset - FIMD_VIDWADD0_START) >> 2) & 1;
+        if (i == fimd_get_buffer_id(&s->window[w]) &&
+                s->window[w].buf_start[i] != val) {
+            s->window[w].buf_start[i] = val;
+            fimd_update_memory_section(s, w);
+            break;
+        }
+        s->window[w].buf_start[i] = val;
+        break;
+    case FIMD_VIDWADD1_START ... FIMD_VIDWADD1_END:
+        w = (offset - FIMD_VIDWADD1_START) >> 3;
+        i = ((offset - FIMD_VIDWADD1_START) >> 2) & 1;
+        s->window[w].buf_end[i] = val;
+        break;
+    case FIMD_VIDWADD2_START ... FIMD_VIDWADD2_END:
+        w = (offset - FIMD_VIDWADD2_START) >> 2;
+        if (((val & FIMD_VIDWADD2_PAGEWIDTH) != s->window[w].virtpage_width) ||
+            (((val >> FIMD_VIDWADD2_OFFSIZE_SHIFT) & FIMD_VIDWADD2_OFFSIZE) !=
+                        s->window[w].virtpage_offsize)) {
+            s->window[w].virtpage_width = val & FIMD_VIDWADD2_PAGEWIDTH;
+            s->window[w].virtpage_offsize =
+                (val >> FIMD_VIDWADD2_OFFSIZE_SHIFT) & FIMD_VIDWADD2_OFFSIZE;
+            fimd_update_memory_section(s, w);
+        }
+        break;
+    case FIMD_VIDINTCON0:
+        s->vidintcon[0] = val;
+        break;
+    case FIMD_VIDINTCON1:
+        s->vidintcon[1] &= ~(val & 7);
+        exynos4210_fimd_update_irq(s);
+        break;
+    case FIMD_WKEYCON_START ... FIMD_WKEYCON_END:
+        w = ((offset - FIMD_WKEYCON_START) >> 3) + 1;
+        i = ((offset - FIMD_WKEYCON_START) >> 2) & 1;
+        s->window[w].keycon[i] = val;
+        break;
+    case FIMD_WKEYALPHA_START ... FIMD_WKEYALPHA_END:
+        w = ((offset - FIMD_WKEYALPHA_START) >> 2) + 1;
+        s->window[w].keyalpha = val;
+        break;
+    case FIMD_DITHMODE:
+        s->dithmode = val;
+        break;
+    case FIMD_WINMAP_START ... FIMD_WINMAP_END:
+        w = (offset - FIMD_WINMAP_START) >> 2;
+        old_value = s->window[w].winmap;
+        s->window[w].winmap = val;
+        if ((val & FIMD_WINMAP_EN) ^ (old_value & FIMD_WINMAP_EN)) {
+            exynos4210_fimd_invalidate(s);
+            exynos4210_fimd_update_win_bppmode(s, w);
+            exynos4210_fimd_trace_bppmode(s, w, 0xFFFFFFFF);
+            exynos4210_fimd_update(s);
+        }
+        break;
+    case FIMD_WPALCON_HIGH ... FIMD_WPALCON_LOW:
+        i = (offset - FIMD_WPALCON_HIGH) >> 2;
+        s->wpalcon[i] = val;
+        if (s->wpalcon[1] & FIMD_WPALCON_UPDATEEN) {
+            for (w = 0; w < NUM_OF_WINDOWS; w++) {
+                exynos4210_fimd_update_win_bppmode(s, w);
+                fimd_update_get_alpha(s, w);
+            }
+        }
+        break;
+    case FIMD_TRIGCON:
+        val = (val & ~FIMD_TRIGCON_ROMASK) | (s->trigcon & FIMD_TRIGCON_ROMASK);
+        s->trigcon = val;
+        break;
+    case FIMD_I80IFCON_START ... FIMD_I80IFCON_END:
+        s->i80ifcon[(offset - FIMD_I80IFCON_START) >> 2] = val;
+        break;
+    case FIMD_COLORGAINCON:
+        s->colorgaincon = val;
+        break;
+    case FIMD_LDI_CMDCON0 ... FIMD_LDI_CMDCON1:
+        s->ldi_cmdcon[(offset - FIMD_LDI_CMDCON0) >> 2] = val;
+        break;
+    case FIMD_SIFCCON0 ... FIMD_SIFCCON2:
+        i = (offset - FIMD_SIFCCON0) >> 2;
+        if (i != 2) {
+            s->sifccon[i] = val;
+        }
+        break;
+    case FIMD_HUECOEFCR_START ... FIMD_HUECOEFCR_END:
+        i = (offset - FIMD_HUECOEFCR_START) >> 2;
+        s->huecoef_cr[i] = val;
+        break;
+    case FIMD_HUECOEFCB_START ... FIMD_HUECOEFCB_END:
+        i = (offset - FIMD_HUECOEFCB_START) >> 2;
+        s->huecoef_cb[i] = val;
+        break;
+    case FIMD_HUEOFFSET:
+        s->hueoffset = val;
+        break;
+    case FIMD_VIDWALPHA_START ... FIMD_VIDWALPHA_END:
+        w = ((offset - FIMD_VIDWALPHA_START) >> 3);
+        i = ((offset - FIMD_VIDWALPHA_START) >> 2) & 1;
+        if (w == 0) {
+            s->window[w].alpha_val[i] = val;
+        } else {
+            s->window[w].alpha_val[i] = (val & FIMD_VIDALPHA_ALPHA_LOWER) |
+                (s->window[w].alpha_val[i] & FIMD_VIDALPHA_ALPHA_UPPER);
+        }
+        break;
+    case FIMD_BLENDEQ_START ... FIMD_BLENDEQ_END:
+        s->window[(offset - FIMD_BLENDEQ_START) >> 2].blendeq = val;
+        break;
+    case FIMD_BLENDCON:
+        old_value = s->blendcon;
+        s->blendcon = val;
+        if ((s->blendcon & FIMD_ALPHA_8BIT) != (old_value & FIMD_ALPHA_8BIT)) {
+            for (w = 0; w < NUM_OF_WINDOWS; w++) {
+                fimd_update_get_alpha(s, w);
+            }
+        }
+        break;
+    case FIMD_WRTQOSCON_START ... FIMD_WRTQOSCON_END:
+        s->window[(offset - FIMD_WRTQOSCON_START) >> 2].rtqoscon = val;
+        break;
+    case FIMD_I80IFCMD_START ... FIMD_I80IFCMD_END:
+        s->i80ifcmd[(offset - FIMD_I80IFCMD_START) >> 2] = val;
+        break;
+    case FIMD_VIDW0ADD0_B2 ... FIMD_VIDW4ADD0_B2:
+        if (offset & 0x0004) {
+            DPRINT_ERROR("bad write offset 0x%08x\n", offset);
+            break;
+        }
+        w = (offset - FIMD_VIDW0ADD0_B2) >> 3;
+        if (fimd_get_buffer_id(&s->window[w]) == 2 &&
+                s->window[w].buf_start[2] != val) {
+            s->window[w].buf_start[2] = val;
+            fimd_update_memory_section(s, w);
+            break;
+        }
+        s->window[w].buf_start[2] = val;
+        break;
+    case FIMD_SHD_ADD0_START ... FIMD_SHD_ADD0_END:
+        if (offset & 0x0004) {
+            DPRINT_ERROR("bad write offset 0x%08x\n", offset);
+            break;
+        }
+        s->window[(offset - FIMD_SHD_ADD0_START) >> 3].shadow_buf_start = val;
+        break;
+    case FIMD_SHD_ADD1_START ... FIMD_SHD_ADD1_END:
+        if (offset & 0x0004) {
+            DPRINT_ERROR("bad write offset 0x%08x\n", offset);
+            break;
+        }
+        s->window[(offset - FIMD_SHD_ADD1_START) >> 3].shadow_buf_end = val;
+        break;
+    case FIMD_SHD_ADD2_START ... FIMD_SHD_ADD2_END:
+        s->window[(offset - FIMD_SHD_ADD2_START) >> 2].shadow_buf_size = val;
+        break;
+    case FIMD_PAL_MEM_START ... FIMD_PAL_MEM_END:
+        w = (offset - FIMD_PAL_MEM_START) >> 10;
+        i = ((offset - FIMD_PAL_MEM_START) >> 2) & 0xFF;
+        s->window[w].palette[i] = val;
+        break;
+    case FIMD_PALMEM_AL_START ... FIMD_PALMEM_AL_END:
+        /* Palette memory aliases for windows 0 and 1 */
+        w = (offset - FIMD_PALMEM_AL_START) >> 10;
+        i = ((offset - FIMD_PALMEM_AL_START) >> 2) & 0xFF;
+        s->window[w].palette[i] = val;
+        break;
+    default:
+        DPRINT_ERROR("bad write offset 0x%08x\n", offset);
+        break;
+    }
+}
+
+static uint64_t exynos4210_fimd_read(void *opaque, target_phys_addr_t offset,
+                                  unsigned size)
+{
+    Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+    int w, i;
+    uint32_t ret = 0;
+
+    DPRINT_L2("read offset 0x%08x\n", offset);
+
+    switch (offset) {
+    case FIMD_VIDCON0 ... FIMD_VIDCON3:
+        return s->vidcon[(offset - FIMD_VIDCON0) >> 2];
+    case FIMD_VIDTCON_START ... FIMD_VIDTCON_END:
+        return s->vidtcon[(offset - FIMD_VIDTCON_START) >> 2];
+    case FIMD_WINCON_START ... FIMD_WINCON_END:
+        return s->window[(offset - FIMD_WINCON_START) >> 2].wincon;
+    case FIMD_SHADOWCON:
+        return s->shadowcon;
+    case FIMD_WINCHMAP:
+        return s->winchmap;
+    case FIMD_VIDOSD_START ... FIMD_VIDOSD_END:
+        w = (offset - FIMD_VIDOSD_START) >> 4;
+        i = ((offset - FIMD_VIDOSD_START) & 0xF) >> 2;
+        switch (i) {
+        case 0:
+            ret = ((s->window[w].lefttop_x & FIMD_VIDOSD_COORD_MASK) <<
+            FIMD_VIDOSD_HOR_SHIFT) |
+            (s->window[w].lefttop_y & FIMD_VIDOSD_COORD_MASK);
+            break;
+        case 1:
+            ret = ((s->window[w].rightbot_x & FIMD_VIDOSD_COORD_MASK) <<
+                FIMD_VIDOSD_HOR_SHIFT) |
+                (s->window[w].rightbot_y & FIMD_VIDOSD_COORD_MASK);
+            break;
+        case 2:
+            if (w == 0) {
+                ret = s->window[w].osdsize;
+            } else {
+                ret = (pack_upper_4(s->window[w].alpha_val[0]) <<
+                    FIMD_VIDOSD_AEN0_SHIFT) |
+                    pack_upper_4(s->window[w].alpha_val[1]);
+            }
+            break;
+        case 3:
+            if (w != 1 && w != 2) {
+                DPRINT_ERROR("bad read offset 0x%08x\n", offset);
+                return 0xBAADBAAD;
+            }
+            ret = s->window[w].osdsize;
+            break;
+        }
+        return ret;
+    case FIMD_VIDWADD0_START ... FIMD_VIDWADD0_END:
+        w = (offset - FIMD_VIDWADD0_START) >> 3;
+        i = ((offset - FIMD_VIDWADD0_START) >> 2) & 1;
+        return s->window[w].buf_start[i];
+    case FIMD_VIDWADD1_START ... FIMD_VIDWADD1_END:
+        w = (offset - FIMD_VIDWADD1_START) >> 3;
+        i = ((offset - FIMD_VIDWADD1_START) >> 2) & 1;
+        return s->window[w].buf_end[i];
+    case FIMD_VIDWADD2_START ... FIMD_VIDWADD2_END:
+        w = (offset - FIMD_VIDWADD2_START) >> 2;
+        return s->window[w].virtpage_width | (s->window[w].virtpage_offsize <<
+            FIMD_VIDWADD2_OFFSIZE_SHIFT);
+    case FIMD_VIDINTCON0 ... FIMD_VIDINTCON1:
+        return s->vidintcon[(offset - FIMD_VIDINTCON0) >> 2];
+    case FIMD_WKEYCON_START ... FIMD_WKEYCON_END:
+        w = ((offset - FIMD_WKEYCON_START) >> 3) + 1;
+        i = ((offset - FIMD_WKEYCON_START) >> 2) & 1;
+        return s->window[w].keycon[i];
+    case FIMD_WKEYALPHA_START ... FIMD_WKEYALPHA_END:
+        w = ((offset - FIMD_WKEYALPHA_START) >> 2) + 1;
+        return s->window[w].keyalpha;
+    case FIMD_DITHMODE:
+        return s->dithmode;
+    case FIMD_WINMAP_START ... FIMD_WINMAP_END:
+        return s->window[(offset - FIMD_WINMAP_START) >> 2].winmap;
+    case FIMD_WPALCON_HIGH ... FIMD_WPALCON_LOW:
+        return s->wpalcon[(offset - FIMD_WPALCON_HIGH) >> 2];
+    case FIMD_TRIGCON:
+        return s->trigcon;
+    case FIMD_I80IFCON_START ... FIMD_I80IFCON_END:
+        return s->i80ifcon[(offset - FIMD_I80IFCON_START) >> 2];
+    case FIMD_COLORGAINCON:
+        return s->colorgaincon;
+    case FIMD_LDI_CMDCON0 ... FIMD_LDI_CMDCON1:
+        return s->ldi_cmdcon[(offset - FIMD_LDI_CMDCON0) >> 2];
+    case FIMD_SIFCCON0 ... FIMD_SIFCCON2:
+        i = (offset - FIMD_SIFCCON0) >> 2;
+        return s->sifccon[i];
+    case FIMD_HUECOEFCR_START ... FIMD_HUECOEFCR_END:
+        i = (offset - FIMD_HUECOEFCR_START) >> 2;
+        return s->huecoef_cr[i];
+    case FIMD_HUECOEFCB_START ... FIMD_HUECOEFCB_END:
+        i = (offset - FIMD_HUECOEFCB_START) >> 2;
+        return s->huecoef_cb[i];
+    case FIMD_HUEOFFSET:
+        return s->hueoffset;
+    case FIMD_VIDWALPHA_START ... FIMD_VIDWALPHA_END:
+        w = ((offset - FIMD_VIDWALPHA_START) >> 3);
+        i = ((offset - FIMD_VIDWALPHA_START) >> 2) & 1;
+        return s->window[w].alpha_val[i] &
+                (w == 0 ? 0xFFFFFF : FIMD_VIDALPHA_ALPHA_LOWER);
+    case FIMD_BLENDEQ_START ... FIMD_BLENDEQ_END:
+        return s->window[(offset - FIMD_BLENDEQ_START) >> 2].blendeq;
+    case FIMD_BLENDCON:
+        return s->blendcon;
+    case FIMD_WRTQOSCON_START ... FIMD_WRTQOSCON_END:
+        return s->window[(offset - FIMD_WRTQOSCON_START) >> 2].rtqoscon;
+    case FIMD_I80IFCMD_START ... FIMD_I80IFCMD_END:
+        return s->i80ifcmd[(offset - FIMD_I80IFCMD_START) >> 2];
+    case FIMD_VIDW0ADD0_B2 ... FIMD_VIDW4ADD0_B2:
+        if (offset & 0x0004) {
+            break;
+        }
+        return s->window[(offset - FIMD_VIDW0ADD0_B2) >> 3].buf_start[2];
+    case FIMD_SHD_ADD0_START ... FIMD_SHD_ADD0_END:
+        if (offset & 0x0004) {
+            break;
+        }
+        return s->window[(offset - FIMD_SHD_ADD0_START) >> 3].shadow_buf_start;
+    case FIMD_SHD_ADD1_START ... FIMD_SHD_ADD1_END:
+        if (offset & 0x0004) {
+            break;
+        }
+        return s->window[(offset - FIMD_SHD_ADD1_START) >> 3].shadow_buf_end;
+    case FIMD_SHD_ADD2_START ... FIMD_SHD_ADD2_END:
+        return s->window[(offset - FIMD_SHD_ADD2_START) >> 2].shadow_buf_size;
+    case FIMD_PAL_MEM_START ... FIMD_PAL_MEM_END:
+        w = (offset - FIMD_PAL_MEM_START) >> 10;
+        i = ((offset - FIMD_PAL_MEM_START) >> 2) & 0xFF;
+        return s->window[w].palette[i];
+    case FIMD_PALMEM_AL_START ... FIMD_PALMEM_AL_END:
+        /* Palette aliases for win 0,1 */
+        w = (offset - FIMD_PALMEM_AL_START) >> 10;
+        i = ((offset - FIMD_PALMEM_AL_START) >> 2) & 0xFF;
+        return s->window[w].palette[i];
+    }
+
+    DPRINT_ERROR("bad read offset 0x%08x\n", offset);
+    return 0xBAADBAAD;
+}
+
+static const MemoryRegionOps exynos4210_fimd_mmio_ops = {
+    .read = exynos4210_fimd_read,
+    .write = exynos4210_fimd_write,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false
+    },
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int exynos4210_fimd_load(void *opaque, int version_id)
+{
+    Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+    int w;
+
+    if (version_id != 1) {
+        return -EINVAL;
+    }
+
+    for (w = 0; w < NUM_OF_WINDOWS; w++) {
+        exynos4210_fimd_update_win_bppmode(s, w);
+        fimd_update_get_alpha(s, w);
+        fimd_update_memory_section(s, w);
+    }
+
+    /* Redraw the whole screen */
+    exynos4210_update_resolution(s);
+    exynos4210_fimd_invalidate(s);
+    exynos4210_fimd_enable(s, (s->vidcon[0] & FIMD_VIDCON0_ENVID_MASK) ==
+            FIMD_VIDCON0_ENVID_MASK);
+    return 0;
+}
+
+static const VMStateDescription exynos4210_fimd_window_vmstate = {
+    .name = "exynos4210.fimd_window",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields      = (VMStateField[]) {
+        VMSTATE_UINT32(wincon, Exynos4210fimdWindow),
+        VMSTATE_UINT32_ARRAY(buf_start, Exynos4210fimdWindow, 3),
+        VMSTATE_UINT32_ARRAY(buf_end, Exynos4210fimdWindow, 3),
+        VMSTATE_UINT32_ARRAY(keycon, Exynos4210fimdWindow, 2),
+        VMSTATE_UINT32(keyalpha, Exynos4210fimdWindow),
+        VMSTATE_UINT32(winmap, Exynos4210fimdWindow),
+        VMSTATE_UINT32(blendeq, Exynos4210fimdWindow),
+        VMSTATE_UINT32(rtqoscon, Exynos4210fimdWindow),
+        VMSTATE_UINT32_ARRAY(palette, Exynos4210fimdWindow, 256),
+        VMSTATE_UINT32(shadow_buf_start, Exynos4210fimdWindow),
+        VMSTATE_UINT32(shadow_buf_end, Exynos4210fimdWindow),
+        VMSTATE_UINT32(shadow_buf_size, Exynos4210fimdWindow),
+        VMSTATE_UINT16(lefttop_x, Exynos4210fimdWindow),
+        VMSTATE_UINT16(lefttop_y, Exynos4210fimdWindow),
+        VMSTATE_UINT16(rightbot_x, Exynos4210fimdWindow),
+        VMSTATE_UINT16(rightbot_y, Exynos4210fimdWindow),
+        VMSTATE_UINT32(osdsize, Exynos4210fimdWindow),
+        VMSTATE_UINT32_ARRAY(alpha_val, Exynos4210fimdWindow, 2),
+        VMSTATE_UINT16(virtpage_width, Exynos4210fimdWindow),
+        VMSTATE_UINT16(virtpage_offsize, Exynos4210fimdWindow),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static const VMStateDescription exynos4210_fimd_vmstate = {
+    .name = "exynos4210.fimd",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .post_load = exynos4210_fimd_load,
+    .fields      = (VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(vidcon, Exynos4210fimdState, 4),
+        VMSTATE_UINT32_ARRAY(vidtcon, Exynos4210fimdState, 4),
+        VMSTATE_UINT32(shadowcon, Exynos4210fimdState),
+        VMSTATE_UINT32(winchmap, Exynos4210fimdState),
+        VMSTATE_UINT32_ARRAY(vidintcon, Exynos4210fimdState, 2),
+        VMSTATE_UINT32(dithmode, Exynos4210fimdState),
+        VMSTATE_UINT32_ARRAY(wpalcon, Exynos4210fimdState, 2),
+        VMSTATE_UINT32(trigcon, Exynos4210fimdState),
+        VMSTATE_UINT32_ARRAY(i80ifcon, Exynos4210fimdState, 4),
+        VMSTATE_UINT32(colorgaincon, Exynos4210fimdState),
+        VMSTATE_UINT32_ARRAY(ldi_cmdcon, Exynos4210fimdState, 2),
+        VMSTATE_UINT32_ARRAY(sifccon, Exynos4210fimdState, 3),
+        VMSTATE_UINT32_ARRAY(huecoef_cr, Exynos4210fimdState, 4),
+        VMSTATE_UINT32_ARRAY(huecoef_cb, Exynos4210fimdState, 4),
+        VMSTATE_UINT32(hueoffset, Exynos4210fimdState),
+        VMSTATE_UINT32_ARRAY(i80ifcmd, Exynos4210fimdState, 12),
+        VMSTATE_UINT32(blendcon, Exynos4210fimdState),
+        VMSTATE_STRUCT_ARRAY(window, Exynos4210fimdState, 5, 1,
+                exynos4210_fimd_window_vmstate, Exynos4210fimdWindow),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static int exynos4210_fimd_init(SysBusDevice *dev)
+{
+    Exynos4210fimdState *s = FROM_SYSBUS(Exynos4210fimdState, dev);
+
+    s->ifb = NULL;
+
+    sysbus_init_irq(dev, &s->irq[0]);
+    sysbus_init_irq(dev, &s->irq[1]);
+    sysbus_init_irq(dev, &s->irq[2]);
+
+    memory_region_init_io(&s->iomem, &exynos4210_fimd_mmio_ops, s,
+            "exynos4210.fimd", FIMD_REGS_SIZE);
+    sysbus_init_mmio(dev, &s->iomem);
+    s->console = graphic_console_init(exynos4210_fimd_update,
+                                  exynos4210_fimd_invalidate, NULL, NULL, s);
+
+    return 0;
+}
+
+static SysBusDeviceInfo exynos4210_fimd_info = {
+    .init = exynos4210_fimd_init,
+    .qdev.name  = "exynos4210.fimd",
+    .qdev.size  = sizeof(Exynos4210fimdState),
+    .qdev.vmsd  = &exynos4210_fimd_vmstate,
+    .qdev.reset = exynos4210_fimd_reset,
+};
+
+static void exynos4210_fimd_register_devices(void)
+{
+    sysbus_register_withprop(&exynos4210_fimd_info);
+}
+
+device_init(exynos4210_fimd_register_devices)
-- 
1.7.4.1

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

* Re: [Qemu-devel] [PATCH v9 7/9] hw/lan9118: Add basic 16-bit mode support.
  2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 7/9] hw/lan9118: Add basic 16-bit mode support Evgeny Voevodin
@ 2012-01-25 16:35   ` Peter Maydell
  2012-01-25 17:04     ` Andreas Färber
  0 siblings, 1 reply; 13+ messages in thread
From: Peter Maydell @ 2012-01-25 16:35 UTC (permalink / raw)
  To: Evgeny Voevodin
  Cc: kyungmin.park, m.kozlov, jehyung.lee, qemu-devel, d.solodkiy

On 20 January 2012 10:53, Evgeny Voevodin <e.voevodin@samsung.com> wrote:
> @@ -294,6 +304,14 @@ static const VMStateDescription vmstate_lan9118 = {
>         VMSTATE_INT32(rxp_offset, lan9118_state),
>         VMSTATE_INT32(rxp_size, lan9118_state),
>         VMSTATE_INT32(rxp_pad, lan9118_state),
> +        VMSTATE_UINT32(write_word_prev_offset, lan9118_state),
> +        VMSTATE_UINT32(write_word_n, lan9118_state),
> +        VMSTATE_UINT16(write_word_l, lan9118_state),
> +        VMSTATE_UINT16(write_word_h, lan9118_state),
> +        VMSTATE_UINT32(read_word_prev_offset, lan9118_state),
> +        VMSTATE_UINT32(read_word_n, lan9118_state),
> +        VMSTATE_UINT32(read_long, lan9118_state),
> +        VMSTATE_UINT32(mode_16bit, lan9118_state),
>         VMSTATE_END_OF_LIST()

You need to bump .version_id and make your new fields
   VMSTATE_UINT32(write_word_prev_offset, lan9118_state, 2),
etc.

-- PMM

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

* Re: [Qemu-devel] [PATCH v9 7/9] hw/lan9118: Add basic 16-bit mode support.
  2012-01-25 16:35   ` Peter Maydell
@ 2012-01-25 17:04     ` Andreas Färber
  2012-01-25 17:20       ` Peter Maydell
  0 siblings, 1 reply; 13+ messages in thread
From: Andreas Färber @ 2012-01-25 17:04 UTC (permalink / raw)
  To: Evgeny Voevodin
  Cc: Peter Maydell, qemu-devel, kyungmin.park, d.solodkiy, m.kozlov,
	jehyung.lee

Am 25.01.2012 17:35, schrieb Peter Maydell:
> On 20 January 2012 10:53, Evgeny Voevodin <e.voevodin@samsung.com> wrote:
>> @@ -294,6 +304,14 @@ static const VMStateDescription vmstate_lan9118 = {
>>         VMSTATE_INT32(rxp_offset, lan9118_state),
>>         VMSTATE_INT32(rxp_size, lan9118_state),
>>         VMSTATE_INT32(rxp_pad, lan9118_state),
>> +        VMSTATE_UINT32(write_word_prev_offset, lan9118_state),
>> +        VMSTATE_UINT32(write_word_n, lan9118_state),
>> +        VMSTATE_UINT16(write_word_l, lan9118_state),
>> +        VMSTATE_UINT16(write_word_h, lan9118_state),
>> +        VMSTATE_UINT32(read_word_prev_offset, lan9118_state),
>> +        VMSTATE_UINT32(read_word_n, lan9118_state),
>> +        VMSTATE_UINT32(read_long, lan9118_state),
>> +        VMSTATE_UINT32(mode_16bit, lan9118_state),
>>         VMSTATE_END_OF_LIST()
> 
> You need to bump .version_id and make your new fields
>    VMSTATE_UINT32(write_word_prev_offset, lan9118_state, 2),

    VMSTATE_UINT32_V(write_word_prev_offset, lan9118_state, 2),

> etc.

Andreas

-- 
SUSE LINUX Products GmbH, Maxfeldstr. 5, 90409 Nürnberg, Germany
GF: Jeff Hawn, Jennifer Guild, Felix Imendörffer; HRB 16746 AG Nürnberg

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

* Re: [Qemu-devel] [PATCH v9 7/9] hw/lan9118: Add basic 16-bit mode support.
  2012-01-25 17:04     ` Andreas Färber
@ 2012-01-25 17:20       ` Peter Maydell
  0 siblings, 0 replies; 13+ messages in thread
From: Peter Maydell @ 2012-01-25 17:20 UTC (permalink / raw)
  To: Andreas Färber
  Cc: Evgeny Voevodin, qemu-devel, kyungmin.park, d.solodkiy, m.kozlov,
	jehyung.lee

On 25 January 2012 17:04, Andreas Färber <afaerber@suse.de> wrote:
> Am 25.01.2012 17:35, schrieb Peter Maydell:
>>
>> You need to bump .version_id and make your new fields
>>    VMSTATE_UINT32(write_word_prev_offset, lan9118_state, 2),
>
>    VMSTATE_UINT32_V(write_word_prev_offset, lan9118_state, 2),

Doh, yes.

-- PMM

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

end of thread, other threads:[~2012-01-25 17:20 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2012-01-20 10:53 [Qemu-devel] [PATCH v9 0/9] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 1/9] ARM: exynos4210: IRQ subsystem support Evgeny Voevodin
2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 2/9] ARM: Samsung exynos4210-based boards emulation Evgeny Voevodin
2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 3/9] ARM: exynos4210: UART support Evgeny Voevodin
2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 4/9] ARM: exynos4210: PWM support Evgeny Voevodin
2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 5/9] ARM: exynos4210: Added PMU register model Evgeny Voevodin
2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 6/9] ARM: exynos4210: MCT support Evgeny Voevodin
2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 7/9] hw/lan9118: Add basic 16-bit mode support Evgeny Voevodin
2012-01-25 16:35   ` Peter Maydell
2012-01-25 17:04     ` Andreas Färber
2012-01-25 17:20       ` Peter Maydell
2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 8/9] hw/exynos4210.c: Add LAN support for SMDKC210 Evgeny Voevodin
2012-01-20 10:53 ` [Qemu-devel] [PATCH v9 9/9] Exynos4210: added display controller implementation Evgeny Voevodin

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