summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAnthony Liguori <aliguori@us.ibm.com>2012-07-23 13:15:34 -0500
committerAnthony Liguori <aliguori@us.ibm.com>2012-07-23 13:15:34 -0500
commit400006c27622d54ef588e1a9bd5a005d5fc9be5b (patch)
tree7fd03d3b57a7b9aea5e52d01022555d846187e78
parent903f650b0c77827f8d92b35f61419401d648df1e (diff)
parentffbbe7d02fd02a6b563df443e4406de076ac7d93 (diff)
downloadfocaccia-qemu-400006c27622d54ef588e1a9bd5a005d5fc9be5b.tar.gz
focaccia-qemu-400006c27622d54ef588e1a9bd5a005d5fc9be5b.zip
Merge remote-tracking branch 'pmaydell/arm-devs.for-upstream' into staging
* pmaydell/arm-devs.for-upstream:
  exynos4210: add Exynos4210 i2c implementation
  hw/exynos4210_rtc.c: remove unnecessary code
  hw/exynos4210_rtc.c: Fix calculating for value of year
  hw/vexpress.c: Allow >4GB of RAM for Cortex-A15 daughterboard
  hw/arm_boot.c: Support DTBs which use 64 bit addresses
  device_tree: Add support for reading device tree properties
  hw/arm_boot.c: Check for RAM sizes exceeding ATAGS capacity
  hw/arm_boot.c: Consistently use ram_size from arm_boot_info struct
  hw/arm_boot.c: Make ram_size a uint64_t
  hw/pl011.c: Avoid crash on read when no chr backend present
-rw-r--r--device_tree.c30
-rw-r--r--device_tree.h4
-rw-r--r--hw/arm-misc.h2
-rw-r--r--hw/arm/Makefile.objs2
-rw-r--r--hw/arm_boot.c46
-rw-r--r--hw/exynos4210.c27
-rw-r--r--hw/exynos4210.h3
-rw-r--r--hw/exynos4210_i2c.c334
-rw-r--r--hw/exynos4210_rtc.c7
-rw-r--r--hw/pl011.c4
-rw-r--r--hw/vexpress.c13
11 files changed, 456 insertions, 16 deletions
diff --git a/device_tree.c b/device_tree.c
index b366fddeaf..d7a9b6bb89 100644
--- a/device_tree.c
+++ b/device_tree.c
@@ -178,6 +178,36 @@ int qemu_devtree_setprop_string(void *fdt, const char *node_path,
     return r;
 }
 
+const void *qemu_devtree_getprop(void *fdt, const char *node_path,
+                                 const char *property, int *lenp)
+{
+    int len;
+    const void *r;
+    if (!lenp) {
+        lenp = &len;
+    }
+    r = fdt_getprop(fdt, findnode_nofail(fdt, node_path), property, lenp);
+    if (!r) {
+        fprintf(stderr, "%s: Couldn't get %s/%s: %s\n", __func__,
+                node_path, property, fdt_strerror(*lenp));
+        exit(1);
+    }
+    return r;
+}
+
+uint32_t qemu_devtree_getprop_cell(void *fdt, const char *node_path,
+                                   const char *property)
+{
+    int len;
+    const uint32_t *p = qemu_devtree_getprop(fdt, node_path, property, &len);
+    if (len != 4) {
+        fprintf(stderr, "%s: %s/%s not 4 bytes long (not a cell?)\n",
+                __func__, node_path, property);
+        exit(1);
+    }
+    return be32_to_cpu(*p);
+}
+
 uint32_t qemu_devtree_get_phandle(void *fdt, const char *path)
 {
     uint32_t r;
diff --git a/device_tree.h b/device_tree.h
index 2244270b2d..f7a3e6cfc5 100644
--- a/device_tree.h
+++ b/device_tree.h
@@ -28,6 +28,10 @@ int qemu_devtree_setprop_string(void *fdt, const char *node_path,
 int qemu_devtree_setprop_phandle(void *fdt, const char *node_path,
                                  const char *property,
                                  const char *target_node_path);
+const void *qemu_devtree_getprop(void *fdt, const char *node_path,
+                                 const char *property, int *lenp);
+uint32_t qemu_devtree_getprop_cell(void *fdt, const char *node_path,
+                                   const char *property);
 uint32_t qemu_devtree_get_phandle(void *fdt, const char *path);
 uint32_t qemu_devtree_alloc_phandle(void *fdt);
 int qemu_devtree_nop_node(void *fdt, const char *node_path);
diff --git a/hw/arm-misc.h b/hw/arm-misc.h
index 1f96229d3c..bdd8fecc99 100644
--- a/hw/arm-misc.h
+++ b/hw/arm-misc.h
@@ -25,7 +25,7 @@ qemu_irq *armv7m_init(MemoryRegion *address_space_mem,
 
 /* arm_boot.c */
 struct arm_boot_info {
-    int ram_size;
+    uint64_t ram_size;
     const char *kernel_filename;
     const char *kernel_cmdline;
     const char *initrd_filename;
diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
index 236786eb5a..c413780784 100644
--- a/hw/arm/Makefile.objs
+++ b/hw/arm/Makefile.objs
@@ -11,7 +11,7 @@ obj-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
 obj-y += exynos4210_gic.o exynos4210_combiner.o exynos4210.o
 obj-y += exynos4_boards.o exynos4210_uart.o exynos4210_pwm.o
 obj-y += exynos4210_pmu.o exynos4210_mct.o exynos4210_fimd.o
-obj-y += exynos4210_rtc.o
+obj-y += exynos4210_rtc.o exynos4210_i2c.o
 obj-y += arm_l2x0.o
 obj-y += arm_mptimer.o a15mpcore.o
 obj-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
diff --git a/hw/arm_boot.c b/hw/arm_boot.c
index a1e6ddbc1c..a6e9143662 100644
--- a/hw/arm_boot.c
+++ b/hw/arm_boot.c
@@ -216,11 +216,12 @@ static void set_kernel_args_old(const struct arm_boot_info *info)
 static int load_dtb(target_phys_addr_t addr, const struct arm_boot_info *binfo)
 {
 #ifdef CONFIG_FDT
-    uint32_t mem_reg_property[] = { cpu_to_be32(binfo->loader_start),
-                                    cpu_to_be32(binfo->ram_size) };
+    uint32_t *mem_reg_property;
+    uint32_t mem_reg_propsize;
     void *fdt = NULL;
     char *filename;
     int size, rc;
+    uint32_t acells, scells, hival;
 
     filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, binfo->dtb_filename);
     if (!filename) {
@@ -236,8 +237,36 @@ static int load_dtb(target_phys_addr_t addr, const struct arm_boot_info *binfo)
     }
     g_free(filename);
 
+    acells = qemu_devtree_getprop_cell(fdt, "/", "#address-cells");
+    scells = qemu_devtree_getprop_cell(fdt, "/", "#size-cells");
+    if (acells == 0 || scells == 0) {
+        fprintf(stderr, "dtb file invalid (#address-cells or #size-cells 0)\n");
+        return -1;
+    }
+
+    mem_reg_propsize = acells + scells;
+    mem_reg_property = g_new0(uint32_t, mem_reg_propsize);
+    mem_reg_property[acells - 1] = cpu_to_be32(binfo->loader_start);
+    hival = cpu_to_be32(binfo->loader_start >> 32);
+    if (acells > 1) {
+        mem_reg_property[acells - 2] = hival;
+    } else if (hival != 0) {
+        fprintf(stderr, "qemu: dtb file not compatible with "
+                "RAM start address > 4GB\n");
+        exit(1);
+    }
+    mem_reg_property[acells + scells - 1] = cpu_to_be32(binfo->ram_size);
+    hival = cpu_to_be32(binfo->ram_size >> 32);
+    if (scells > 1) {
+        mem_reg_property[acells + scells - 2] = hival;
+    } else if (hival != 0) {
+        fprintf(stderr, "qemu: dtb file not compatible with "
+                "RAM size > 4GB\n");
+        exit(1);
+    }
+
     rc = qemu_devtree_setprop(fdt, "/memory", "reg", mem_reg_property,
-                               sizeof(mem_reg_property));
+                              mem_reg_propsize * sizeof(uint32_t));
     if (rc < 0) {
         fprintf(stderr, "couldn't set /memory/reg\n");
     }
@@ -357,7 +386,7 @@ void arm_load_kernel(ARMCPU *cpu, struct arm_boot_info *info)
     if (kernel_size < 0) {
         entry = info->loader_start + KERNEL_LOAD_ADDR;
         kernel_size = load_image_targphys(info->kernel_filename, entry,
-                                          ram_size - KERNEL_LOAD_ADDR);
+                                          info->ram_size - KERNEL_LOAD_ADDR);
         is_linux = 1;
     }
     if (kernel_size < 0) {
@@ -371,7 +400,8 @@ void arm_load_kernel(ARMCPU *cpu, struct arm_boot_info *info)
             initrd_size = load_image_targphys(info->initrd_filename,
                                               info->loader_start
                                               + INITRD_LOAD_ADDR,
-                                              ram_size - INITRD_LOAD_ADDR);
+                                              info->ram_size
+                                              - INITRD_LOAD_ADDR);
             if (initrd_size < 0) {
                 fprintf(stderr, "qemu: could not load initrd '%s'\n",
                         info->initrd_filename);
@@ -398,6 +428,12 @@ void arm_load_kernel(ARMCPU *cpu, struct arm_boot_info *info)
             bootloader[5] = dtb_start;
         } else {
             bootloader[5] = info->loader_start + KERNEL_ARGS_ADDR;
+            if (info->ram_size >= (1ULL << 32)) {
+                fprintf(stderr, "qemu: RAM size must be less than 4GB to boot"
+                        " Linux kernel using ATAGS (try passing a device tree"
+                        " using -dtb)\n");
+                exit(1);
+            }
         }
         bootloader[6] = entry;
         for (n = 0; n < sizeof(bootloader) / 4; n++) {
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index 7c58c906de..00d4db8871 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -39,6 +39,13 @@
 /* MCT */
 #define EXYNOS4210_MCT_BASE_ADDR       0x10050000
 
+/* I2C */
+#define EXYNOS4210_I2C_SHIFT           0x00010000
+#define EXYNOS4210_I2C_BASE_ADDR       0x13860000
+/* Interrupt Group of External Interrupt Combiner for I2C */
+#define EXYNOS4210_I2C_INTG            27
+#define EXYNOS4210_HDMI_INTG           16
+
 /* UART's definitions */
 #define EXYNOS4210_UART0_BASE_ADDR     0x13800000
 #define EXYNOS4210_UART1_BASE_ADDR     0x13810000
@@ -283,6 +290,26 @@ Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
             s->irq_table[exynos4210_get_irq(35, 3)]);
     sysbus_mmio_map(busdev, 0, EXYNOS4210_MCT_BASE_ADDR);
 
+    /*** I2C ***/
+    for (n = 0; n < EXYNOS4210_I2C_NUMBER; n++) {
+        uint32_t addr = EXYNOS4210_I2C_BASE_ADDR + EXYNOS4210_I2C_SHIFT * n;
+        qemu_irq i2c_irq;
+
+        if (n < 8) {
+            i2c_irq = s->irq_table[exynos4210_get_irq(EXYNOS4210_I2C_INTG, n)];
+        } else {
+            i2c_irq = s->irq_table[exynos4210_get_irq(EXYNOS4210_HDMI_INTG, 1)];
+        }
+
+        dev = qdev_create(NULL, "exynos4210.i2c");
+        qdev_init_nofail(dev);
+        busdev = sysbus_from_qdev(dev);
+        sysbus_connect_irq(busdev, 0, i2c_irq);
+        sysbus_mmio_map(busdev, 0, addr);
+        s->i2c_if[n] = (i2c_bus *)qdev_get_child_bus(dev, "i2c");
+    }
+
+
     /*** UARTs ***/
     exynos4210_uart_create(EXYNOS4210_UART0_BASE_ADDR,
                            EXYNOS4210_UART0_FIFO_SIZE, 0, NULL,
diff --git a/hw/exynos4210.h b/hw/exynos4210.h
index 9b1ae4c8b1..a43ba3aedc 100644
--- a/hw/exynos4210.h
+++ b/hw/exynos4210.h
@@ -74,6 +74,8 @@
 #define EXYNOS4210_EXT_GIC_NIRQ     (160-32)
 #define EXYNOS4210_INT_GIC_NIRQ     64
 
+#define EXYNOS4210_I2C_NUMBER               9
+
 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];
@@ -95,6 +97,7 @@ typedef struct Exynos4210State {
     MemoryRegion dram1_mem;
     MemoryRegion boot_secondary;
     MemoryRegion bootreg_mem;
+    i2c_bus *i2c_if[EXYNOS4210_I2C_NUMBER];
 } Exynos4210State;
 
 void exynos4210_write_secondary(ARMCPU *cpu,
diff --git a/hw/exynos4210_i2c.c b/hw/exynos4210_i2c.c
new file mode 100644
index 0000000000..3f72a5c464
--- /dev/null
+++ b/hw/exynos4210_i2c.c
@@ -0,0 +1,334 @@
+/*
+ *  Exynos4210 I2C Bus Serial Interface Emulation
+ *
+ *  Copyright (C) 2012 Samsung Electronics Co Ltd.
+ *    Maksim Kozlov, <m.kozlov@samsung.com>
+ *    Igor Mitsyanko, <i.mitsyanko@samsung.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License as published by the
+ *  Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ *  for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu-timer.h"
+#include "sysbus.h"
+#include "i2c.h"
+
+#ifndef EXYNOS4_I2C_DEBUG
+#define EXYNOS4_I2C_DEBUG                 0
+#endif
+
+#define TYPE_EXYNOS4_I2C                  "exynos4210.i2c"
+#define EXYNOS4_I2C(obj)                  \
+    OBJECT_CHECK(Exynos4210I2CState, (obj), TYPE_EXYNOS4_I2C)
+
+/* Exynos4210 I2C memory map */
+#define EXYNOS4_I2C_MEM_SIZE              0x14
+#define I2CCON_ADDR                       0x00  /* control register */
+#define I2CSTAT_ADDR                      0x04  /* control/status register */
+#define I2CADD_ADDR                       0x08  /* address register */
+#define I2CDS_ADDR                        0x0c  /* data shift register */
+#define I2CLC_ADDR                        0x10  /* line control register */
+
+#define I2CCON_ACK_GEN                    (1 << 7)
+#define I2CCON_INTRS_EN                   (1 << 5)
+#define I2CCON_INT_PEND                   (1 << 4)
+
+#define EXYNOS4_I2C_MODE(reg)             (((reg) >> 6) & 3)
+#define I2C_IN_MASTER_MODE(reg)           (((reg) >> 6) & 2)
+#define I2CMODE_MASTER_Rx                 0x2
+#define I2CMODE_MASTER_Tx                 0x3
+#define I2CSTAT_LAST_BIT                  (1 << 0)
+#define I2CSTAT_OUTPUT_EN                 (1 << 4)
+#define I2CSTAT_START_BUSY                (1 << 5)
+
+
+#if EXYNOS4_I2C_DEBUG
+#define DPRINT(fmt, args...)              \
+    do { fprintf(stderr, "QEMU I2C: "fmt, ## args); } while (0)
+
+static const char *exynos4_i2c_get_regname(unsigned offset)
+{
+    switch (offset) {
+    case I2CCON_ADDR:
+        return "I2CCON";
+    case I2CSTAT_ADDR:
+        return "I2CSTAT";
+    case I2CADD_ADDR:
+        return "I2CADD";
+    case I2CDS_ADDR:
+        return "I2CDS";
+    case I2CLC_ADDR:
+        return "I2CLC";
+    default:
+        return "[?]";
+    }
+}
+
+#else
+#define DPRINT(fmt, args...)              do { } while (0)
+#endif
+
+typedef struct Exynos4210I2CState {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    i2c_bus *bus;
+    qemu_irq irq;
+
+    uint8_t i2ccon;
+    uint8_t i2cstat;
+    uint8_t i2cadd;
+    uint8_t i2cds;
+    uint8_t i2clc;
+    bool scl_free;
+} Exynos4210I2CState;
+
+static inline void exynos4210_i2c_raise_interrupt(Exynos4210I2CState *s)
+{
+    if (s->i2ccon & I2CCON_INTRS_EN) {
+        s->i2ccon |= I2CCON_INT_PEND;
+        qemu_irq_raise(s->irq);
+    }
+}
+
+static void exynos4210_i2c_data_receive(void *opaque)
+{
+    Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
+    int ret;
+
+    s->i2cstat &= ~I2CSTAT_LAST_BIT;
+    s->scl_free = false;
+    ret = i2c_recv(s->bus);
+    if (ret < 0 && (s->i2ccon & I2CCON_ACK_GEN)) {
+        s->i2cstat |= I2CSTAT_LAST_BIT;  /* Data is not acknowledged */
+    } else {
+        s->i2cds = ret;
+    }
+    exynos4210_i2c_raise_interrupt(s);
+}
+
+static void exynos4210_i2c_data_send(void *opaque)
+{
+    Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
+
+    s->i2cstat &= ~I2CSTAT_LAST_BIT;
+    s->scl_free = false;
+    if (i2c_send(s->bus, s->i2cds) < 0 && (s->i2ccon & I2CCON_ACK_GEN)) {
+        s->i2cstat |= I2CSTAT_LAST_BIT;
+    }
+    exynos4210_i2c_raise_interrupt(s);
+}
+
+static uint64_t exynos4210_i2c_read(void *opaque, target_phys_addr_t offset,
+                                 unsigned size)
+{
+    Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
+    uint8_t value;
+
+    switch (offset) {
+    case I2CCON_ADDR:
+        value = s->i2ccon;
+        break;
+    case I2CSTAT_ADDR:
+        value = s->i2cstat;
+        break;
+    case I2CADD_ADDR:
+        value = s->i2cadd;
+        break;
+    case I2CDS_ADDR:
+        value = s->i2cds;
+        s->scl_free = true;
+        if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx &&
+               (s->i2cstat & I2CSTAT_START_BUSY) &&
+               !(s->i2ccon & I2CCON_INT_PEND)) {
+            exynos4210_i2c_data_receive(s);
+        }
+        break;
+    case I2CLC_ADDR:
+        value = s->i2clc;
+        break;
+    default:
+        value = 0;
+        DPRINT("ERROR: Bad read offset 0x%x\n", (unsigned int)offset);
+        break;
+    }
+
+    DPRINT("read %s [0x%02x] -> 0x%02x\n", exynos4_i2c_get_regname(offset),
+            (unsigned int)offset, value);
+    return value;
+}
+
+static void exynos4210_i2c_write(void *opaque, target_phys_addr_t offset,
+                              uint64_t value, unsigned size)
+{
+    Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
+    uint8_t v = value & 0xff;
+
+    DPRINT("write %s [0x%02x] <- 0x%02x\n", exynos4_i2c_get_regname(offset),
+            (unsigned int)offset, v);
+
+    switch (offset) {
+    case I2CCON_ADDR:
+        s->i2ccon = (v & ~I2CCON_INT_PEND) | (s->i2ccon & I2CCON_INT_PEND);
+        if ((s->i2ccon & I2CCON_INT_PEND) && !(v & I2CCON_INT_PEND)) {
+            s->i2ccon &= ~I2CCON_INT_PEND;
+            qemu_irq_lower(s->irq);
+            if (!(s->i2ccon & I2CCON_INTRS_EN)) {
+                s->i2cstat &= ~I2CSTAT_START_BUSY;
+            }
+
+            if (s->i2cstat & I2CSTAT_START_BUSY) {
+                if (s->scl_free) {
+                    if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx) {
+                        exynos4210_i2c_data_send(s);
+                    } else if (EXYNOS4_I2C_MODE(s->i2cstat) ==
+                            I2CMODE_MASTER_Rx) {
+                        exynos4210_i2c_data_receive(s);
+                    }
+                } else {
+                    s->i2ccon |= I2CCON_INT_PEND;
+                    qemu_irq_raise(s->irq);
+                }
+            }
+        }
+        break;
+    case I2CSTAT_ADDR:
+        s->i2cstat =
+                (s->i2cstat & I2CSTAT_START_BUSY) | (v & ~I2CSTAT_START_BUSY);
+
+        if (!(s->i2cstat & I2CSTAT_OUTPUT_EN)) {
+            s->i2cstat &= ~I2CSTAT_START_BUSY;
+            s->scl_free = true;
+            qemu_irq_lower(s->irq);
+            break;
+        }
+
+        /* Nothing to do if in i2c slave mode */
+        if (!I2C_IN_MASTER_MODE(s->i2cstat)) {
+            break;
+        }
+
+        if (v & I2CSTAT_START_BUSY) {
+            s->i2cstat &= ~I2CSTAT_LAST_BIT;
+            s->i2cstat |= I2CSTAT_START_BUSY;    /* Line is busy */
+            s->scl_free = false;
+
+            /* Generate start bit and send slave address */
+            if (i2c_start_transfer(s->bus, s->i2cds >> 1, s->i2cds & 0x1) &&
+                    (s->i2ccon & I2CCON_ACK_GEN)) {
+                s->i2cstat |= I2CSTAT_LAST_BIT;
+            } else if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx) {
+                exynos4210_i2c_data_receive(s);
+            }
+            exynos4210_i2c_raise_interrupt(s);
+        } else {
+            i2c_end_transfer(s->bus);
+            if (!(s->i2ccon & I2CCON_INT_PEND)) {
+                s->i2cstat &= ~I2CSTAT_START_BUSY;
+            }
+            s->scl_free = true;
+        }
+        break;
+    case I2CADD_ADDR:
+        if ((s->i2cstat & I2CSTAT_OUTPUT_EN) == 0) {
+            s->i2cadd = v;
+        }
+        break;
+    case I2CDS_ADDR:
+        if (s->i2cstat & I2CSTAT_OUTPUT_EN) {
+            s->i2cds = v;
+            s->scl_free = true;
+            if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx &&
+                    (s->i2cstat & I2CSTAT_START_BUSY) &&
+                    !(s->i2ccon & I2CCON_INT_PEND)) {
+                exynos4210_i2c_data_send(s);
+            }
+        }
+        break;
+    case I2CLC_ADDR:
+        s->i2clc = v;
+        break;
+    default:
+        DPRINT("ERROR: Bad write offset 0x%x\n", (unsigned int)offset);
+        break;
+    }
+}
+
+static const MemoryRegionOps exynos4210_i2c_ops = {
+    .read = exynos4210_i2c_read,
+    .write = exynos4210_i2c_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription exynos4210_i2c_vmstate = {
+    .name = TYPE_EXYNOS4_I2C,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT8(i2ccon, Exynos4210I2CState),
+        VMSTATE_UINT8(i2cstat, Exynos4210I2CState),
+        VMSTATE_UINT8(i2cds, Exynos4210I2CState),
+        VMSTATE_UINT8(i2cadd, Exynos4210I2CState),
+        VMSTATE_UINT8(i2clc, Exynos4210I2CState),
+        VMSTATE_BOOL(scl_free, Exynos4210I2CState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void exynos4210_i2c_reset(DeviceState *d)
+{
+    Exynos4210I2CState *s = EXYNOS4_I2C(d);
+
+    s->i2ccon  = 0x00;
+    s->i2cstat = 0x00;
+    s->i2cds   = 0xFF;
+    s->i2clc   = 0x00;
+    s->i2cadd  = 0xFF;
+    s->scl_free = true;
+}
+
+static int exynos4210_i2c_realize(SysBusDevice *dev)
+{
+    Exynos4210I2CState *s = EXYNOS4_I2C(dev);
+
+    memory_region_init_io(&s->iomem, &exynos4210_i2c_ops, s, TYPE_EXYNOS4_I2C,
+                          EXYNOS4_I2C_MEM_SIZE);
+    sysbus_init_mmio(dev, &s->iomem);
+    sysbus_init_irq(dev, &s->irq);
+    s->bus = i2c_init_bus(&dev->qdev, "i2c");
+    return 0;
+}
+
+static void exynos4210_i2c_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    SysBusDeviceClass *sbdc = SYS_BUS_DEVICE_CLASS(klass);
+
+    dc->vmsd = &exynos4210_i2c_vmstate;
+    dc->reset = exynos4210_i2c_reset;
+    sbdc->init = exynos4210_i2c_realize;
+}
+
+static const TypeInfo exynos4210_i2c_type_info = {
+    .name = TYPE_EXYNOS4_I2C,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(Exynos4210I2CState),
+    .class_init = exynos4210_i2c_class_init,
+};
+
+static void exynos4210_i2c_register_types(void)
+{
+    type_register_static(&exynos4210_i2c_type_info);
+}
+
+type_init(exynos4210_i2c_register_types)
diff --git a/hw/exynos4210_rtc.c b/hw/exynos4210_rtc.c
index f78102049b..42a4ddc327 100644
--- a/hw/exynos4210_rtc.c
+++ b/hw/exynos4210_rtc.c
@@ -142,7 +142,7 @@ static const VMStateDescription vmstate_exynos4210_rtc_state = {
 };
 
 #define BCD3DIGITS(x) \
-    ((uint32_t)to_bcd((uint8_t)x) + \
+    ((uint32_t)to_bcd((uint8_t)(x % 100)) + \
     ((uint32_t)to_bcd((uint8_t)((x % 1000) / 100)) << 8))
 
 static void check_alarm_raise(Exynos4210RTCState *s)
@@ -510,10 +510,7 @@ static void exynos4210_rtc_reset(DeviceState *d)
 {
     Exynos4210RTCState *s = (Exynos4210RTCState *)d;
 
-    struct tm tm;
-
-    qemu_get_timedate(&tm, 0);
-    s->current_tm = tm;
+    qemu_get_timedate(&s->current_tm, 0);
 
     DPRINTF("Get time from host: %d-%d-%d %2d:%02d:%02d\n",
             s->current_tm.tm_year, s->current_tm.tm_mon, s->current_tm.tm_mday,
diff --git a/hw/pl011.c b/hw/pl011.c
index 8a5a8f554a..3245702df0 100644
--- a/hw/pl011.c
+++ b/hw/pl011.c
@@ -78,7 +78,9 @@ static uint64_t pl011_read(void *opaque, target_phys_addr_t offset,
         if (s->read_count == s->read_trigger - 1)
             s->int_level &= ~ PL011_INT_RX;
         pl011_update(s);
-        qemu_chr_accept_input(s->chr);
+        if (s->chr) {
+            qemu_chr_accept_input(s->chr);
+        }
         return c;
     case 1: /* UARTCR */
         return 0;
diff --git a/hw/vexpress.c b/hw/vexpress.c
index 8072c5ada9..b2dc8a5ab3 100644
--- a/hw/vexpress.c
+++ b/hw/vexpress.c
@@ -284,9 +284,16 @@ static void a15_daughterboard_init(const VEDBoardInfo *daughterboard,
         cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ];
     }
 
-    if (ram_size > 0x80000000) {
-        fprintf(stderr, "vexpress-a15: cannot model more than 2GB RAM\n");
-        exit(1);
+    {
+        /* We have to use a separate 64 bit variable here to avoid the gcc
+         * "comparison is always false due to limited range of data type"
+         * warning if we are on a host where ram_addr_t is 32 bits.
+         */
+        uint64_t rsz = ram_size;
+        if (rsz > (30ULL * 1024 * 1024 * 1024)) {
+            fprintf(stderr, "vexpress-a15: cannot model more than 30GB RAM\n");
+            exit(1);
+        }
     }
 
     memory_region_init_ram(ram, "vexpress.highmem", ram_size);