Patchwork Help needed to run Exynos 4210 linux kernel on qemu?

login
register
mail settings
Submitter Mitsyanko Igor
Date Sept. 5, 2012, 12:38 p.m.
Message ID <504747E0.9050200@samsung.com>
Download mbox | patch
Permalink /patch/181854/
State New
Headers show

Comments

Mitsyanko Igor - Sept. 5, 2012, 12:38 p.m.
On 09/04/2012 02:13 AM, Jean-Christophe DUBOIS wrote:
> Hi,
>
> I am trying to run linux over qemu-system-arm emulating a nuri or
> smdkc210 Exynos 4210 based board.
>
> To do this I fetched the last qemu (from git://git.qemu.org/qemu.git)
> and built it with "./configure --target-list=arm-softmmu.
>
> Then I fetched the latest linux image for samsung processors (from
> git://git.kernel.org/pub/scm/linux/kernel/git/kgene/linux-samsung.git)
> and built it using exynos4_defconfig config file.
>
> Then I tried to run the 2 together:
>
> $ qemu-system-arm -kernel zImage -append "console=ttySAC0,115200n8
> root=/dev/ram rw earlyprintk" -serial stdio -M smdkc210 -initrd rootfs.img
> smdkc210 board supports only 2 CPU cores. Ignoring smp_cpus value.
> VNC server running on `127.0.0.1:5900'
> Booting Linux on physical CPU 0
> Linux version 3.6.0-rc3 (jcd@jcd-P31SG) (gcc version 4.6.3
> (Ubuntu/Linaro 4.6.3-1ubuntu5) ) #1 SMP PREEMPT Mon Sep 3 22:44:05 CEST
> 2012
> CPU: ARMv7 Processor [410fc090] revision 0 (ARMv7), cr=10c5387d
> CPU: PIPT / VIPT nonaliasing data cache, VIPT aliasing instruction cache
> Machine: SMDKC210
> bootconsole [earlycon0] enabled
> Truncating RAM at 40000000-7fffffff to -6f7fffff (vmalloc region overlap).
> Memory policy: ECC disabled, Data cache writealloc
>
> The kernel hangs here because it got a data abort exception while trying
> to print the "next" message (which is "CPU EXYNOS4210 (id 0x43210211)").
> The data abort is generated somewhere in the console_unlock() function.
>
> I tried the same thing with some less up to date source code of qemu
> and/or linux with the same result. I even tried the linaro versions of
> qemu and linux without luck.
>
> So there has to be something I am doing wrong. I would really appreciate
> it if you could point me in the right direction.
>
> Thanks.
>
> JC
>
>

Hello, Jean!

I've just tried to do the same thing you're trying to do, it works for 
me if I remove earlyprintk from append. Probably this is due to the fact 
that kernel expects uart0 to be configured by u-boot prior to kernel 
start execution. Interestingly, if you change default exynos4_defconfig 
CONFIG_DEBUG_S3C_UART0 to, for example, CONFIG_DEBUG_S3C_UART2 in linux 
kernel configuration, everything starting to work fine even with 
earlyprintk.

But using ramdisks is always a pain for me, I would suggest you two 
different approaches:

1) The most convenient way is to use emulated SD card for rootfs 
storage. There is no SD card support for exynos4210 in current QEMU 
master, but you can apply SD host controller patches I attached to this 
email to get this support. These patches already have been thoroughly 
reviewed in qemu-devel mailing list and hopefully would be applied to 
QEMU master soon enough.
If you want to use SD card instead of RAMDISK, apply these patches to 
QEMU master with "git am", and enable MMC/SD support in linux kernel 
configuration (it is disabled by default in exynos4_defconfig). You need 
to check CONFIG_MMC, CONFIG_MMC_SDHC, CONFIG_MMC_SDHCI_S3C and 
ONFIG_MMC_SDHCI_S3C_DMA.
Then you can launch QEMU with -sd /path/to/your/rootfs and, for example, 
append line -append "root=/dev/mmcblk0 rootfstype=ext4 rw rootwait".

2) If you want to work only with what current QEMU master has, you can 
use network filesystem as rootfs, but only for smdkc210 board (nuri 
board doesn't have any net devices). You're gonna have to check these 
options in Linux configuration:
CONFIG_NET, CONFIG_PACKET, CONFIG_INET, CONFIG_IP_PNP, 
CONFIG_IP_PNP_DHCP, CONFIG_NETDEVICES, CONFIG_SMSC911X, CONFIG_NFS_FS, 
CONFIG_ROOT_NFS.
Then, if, for example, you have directory /mnt/nfsroot exported by NFS 
server on your host machine, you can launch QEMU guest with following 
command line:

qemu-system-arm -kernel ./zImage -M smdkc210 -append 
"console=ttySAC0,115200n8 ip=dhcp root=/dev/nfs 
nfsroot=10.0.2.2:/mnt/nfsroot/ rw" -serial stdio

Also, maybe you noticed, graphic support is disabled in 
exynos4_defconfig by default. To enable it, check these options in Linux 
configuration:
CONFIG_DRM, CONFIG_DRM_EXYNOS, CONFIG_DRM_EXYNOS_DMABUF, 
CONFIG_DRM_EXYNOS_FIMD. You can also check CONFIG_LOGO to get a fancy 
penguins during guest VM startup. And if you need to start X server on 
your guest VM, you also need to enable UNIX domain sockets in linux 
kernel CONFIG_UNIX.
Jean-Christophe DUBOIS - Sept. 5, 2012, 6:50 p.m.
Hello Igor,

Thanks for you reply and your time.

JC

On 09/05/2012 02:38 PM, Igor Mitsyanko wrote:
> On 09/04/2012 02:13 AM, Jean-Christophe DUBOIS wrote:
>
> Hello, Jean!
>
> I've just tried to do the same thing you're trying to do, it works for 
> me if I remove earlyprintk from append.

Yes, I found out about this in the mean time.

> Probably this is due to the fact that kernel expects uart0 to be 
> configured by u-boot prior to kernel start execution. Interestingly, 
> if you change default exynos4_defconfig CONFIG_DEBUG_S3C_UART0 to, for 
> example, CONFIG_DEBUG_S3C_UART2 in linux kernel configuration, 
> everything starting to work fine even with earlyprintk.

Seems strange. It would be nice if "earlyprintk" was not hanging though 
(is it possible to work around the actual LSP expectation?).

>
> But using ramdisks is always a pain for me, I would suggest you two 
> different approaches:

Ramdisk is fine for what I need to do at this time.

>
> 1) The most convenient way is to use emulated SD card for rootfs 
> storage. There is no SD card support for exynos4210 in current QEMU 
> master, but you can apply SD host controller patches I attached to 
> this email to get this support. These patches already have been 
> thoroughly reviewed in qemu-devel mailing list and hopefully would be 
> applied to QEMU master soon enough.
> If you want to use SD card instead of RAMDISK, apply these patches to 
> QEMU master with "git am", and enable MMC/SD support in linux kernel 
> configuration (it is disabled by default in exynos4_defconfig). You 
> need to check CONFIG_MMC, CONFIG_MMC_SDHC, CONFIG_MMC_SDHCI_S3C and 
> ONFIG_MMC_SDHCI_S3C_DMA.
> Then you can launch QEMU with -sd /path/to/your/rootfs and, for 
> example, append line -append "root=/dev/mmcblk0 rootfstype=ext4 rw 
> rootwait".

Thanks. That seems interesting but I don't need it right now. I can wait 
for this patch to hit mainline.

>
> 2) If you want to work only with what current QEMU master has, you can 
> use network filesystem as rootfs, but only for smdkc210 board (nuri 
> board doesn't have any net devices). You're gonna have to check these 
> options in Linux configuration:
> CONFIG_NET, CONFIG_PACKET, CONFIG_INET, CONFIG_IP_PNP, 
> CONFIG_IP_PNP_DHCP, CONFIG_NETDEVICES, CONFIG_SMSC911X, CONFIG_NFS_FS, 
> CONFIG_ROOT_NFS.
> Then, if, for example, you have directory /mnt/nfsroot exported by NFS 
> server on your host machine, you can launch QEMU guest with following 
> command line:
>
> qemu-system-arm -kernel ./zImage -M smdkc210 -append 
> "console=ttySAC0,115200n8 ip=dhcp root=/dev/nfs 
> nfsroot=10.0.2.2:/mnt/nfsroot/ rw" -serial stdio

Unfortunately, network will have to wait too. But it will come in a 
second step.

>
> Also, maybe you noticed, graphic support is disabled in 
> exynos4_defconfig by default. To enable it, check these options in 
> Linux configuration:
> CONFIG_DRM, CONFIG_DRM_EXYNOS, CONFIG_DRM_EXYNOS_DMABUF, 
> CONFIG_DRM_EXYNOS_FIMD. You can also check CONFIG_LOGO to get a fancy 
> penguins during guest VM startup. And if you need to start X server on 
> your guest VM, you also need to enable UNIX domain sockets in linux 
> kernel CONFIG_UNIX.

Thanks. I'll look at this later. No graphic is needed for now. A serial 
console is all I need.

Patch

From 27ba5740f4d937cc1bb6a8abcb010a932eef16f5 Mon Sep 17 00:00:00 2001
From: Igor Mitsyanko <i.mitsyanko@samsung.com>
Date: Mon, 6 Aug 2012 13:25:35 +1000
Subject: [PATCH 2/2] exynos4210: Added SD host controller model

Custom Exynos4210 SD/MMC host controller, based on SD association standard host
controller ver. 2.00.

Signed-off-by: Igor Mitsyanko <i.mitsyanko@samsung.com>
---
 hw/arm/Makefile.objs  |    2 +-
 hw/exynos4210.c       |   20 +++
 hw/exynos4210_sdhci.c |  444 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 465 insertions(+), 1 deletions(-)
 create mode 100644 hw/exynos4210_sdhci.c

diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
index 2b39fb3..c65ac9e 100644
--- a/hw/arm/Makefile.objs
+++ b/hw/arm/Makefile.objs
@@ -6,7 +6,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 exynos4210_i2c.o
+obj-y += exynos4210_rtc.o exynos4210_i2c.o exynos4210_sdhci.o
 obj-y += arm_mptimer.o a15mpcore.o
 obj-y += armv7m.o armv7m_nvic.o stellaris.o stellaris_enet.o
 obj-y += highbank.o
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index 00d4db8..2611aaf 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -66,6 +66,12 @@ 
 #define EXYNOS4210_EXT_COMBINER_BASE_ADDR   0x10440000
 #define EXYNOS4210_INT_COMBINER_BASE_ADDR   0x10448000
 
+/* SD/MMC host controllers SFR base addresses */
+#define EXYNOS4210_SDHC0_BASE_ADDR          0x12510000
+#define EXYNOS4210_SDHC1_BASE_ADDR          0x12520000
+#define EXYNOS4210_SDHC2_BASE_ADDR          0x12530000
+#define EXYNOS4210_SDHC3_BASE_ADDR          0x12540000
+
 /* PMU SFR base address */
 #define EXYNOS4210_PMU_BASE_ADDR            0x10020000
 
@@ -327,6 +333,20 @@  Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
                            EXYNOS4210_UART3_FIFO_SIZE, 3, NULL,
                   s->irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 3)]);
 
+    /*** SD/MMC host controllers ***/
+
+    sysbus_create_simple("exynos4210.sdhci", EXYNOS4210_SDHC0_BASE_ADDR,
+            s->irq_table[exynos4210_get_irq(29, 0)]);
+
+    sysbus_create_simple("exynos4210.sdhci", EXYNOS4210_SDHC1_BASE_ADDR,
+            s->irq_table[exynos4210_get_irq(29, 1)]);
+
+    sysbus_create_simple("exynos4210.sdhci", EXYNOS4210_SDHC2_BASE_ADDR,
+            s->irq_table[exynos4210_get_irq(29, 2)]);
+
+    sysbus_create_simple("exynos4210.sdhci", EXYNOS4210_SDHC3_BASE_ADDR,
+            s->irq_table[exynos4210_get_irq(29, 3)]);
+
     /*** Display controller (FIMD) ***/
     sysbus_create_varargs("exynos4210.fimd", EXYNOS4210_FIMD0_BASE_ADDR,
             s->irq_table[exynos4210_get_irq(11, 0)],
diff --git a/hw/exynos4210_sdhci.c b/hw/exynos4210_sdhci.c
new file mode 100644
index 0000000..84917f9
--- /dev/null
+++ b/hw/exynos4210_sdhci.c
@@ -0,0 +1,444 @@ 
+/*
+ * Samsung Exynos4210 SD/MMC host controller model
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * 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 "sdhci.h"
+
+#define EXYNOS4_SDHC_CAPABILITIES    0x05E80080
+#define EXYNOS4_SDHC_MAX_BUFSZ       512
+
+#define EXYNOS4_SDHC_DEBUG           0
+
+#if EXYNOS4_SDHC_DEBUG == 0
+    #define DPRINT_L1(fmt, args...)       do { } while (0)
+    #define DPRINT_L2(fmt, args...)       do { } while (0)
+    #define ERRPRINT(fmt, args...)        do { } while (0)
+#elif EXYNOS4_SDHC_DEBUG == 1
+    #define DPRINT_L1(fmt, args...)       \
+        do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0)
+    #define DPRINT_L2(fmt, args...)       do { } while (0)
+    #define ERRPRINT(fmt, args...)        \
+        do {fprintf(stderr, "QEMU SDHC ERROR: "fmt, ## args); } while (0)
+#else
+    #define DPRINT_L1(fmt, args...)       \
+        do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0)
+    #define DPRINT_L2(fmt, args...)       \
+        do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0)
+    #define ERRPRINT(fmt, args...)        \
+        do {fprintf(stderr, "QEMU SDHC ERROR: "fmt, ## args); } while (0)
+#endif
+
+
+#define TYPE_EXYNOS4_SDHC            "exynos4210.sdhci"
+#define EXYNOS4_SDHCI(obj)           \
+     OBJECT_CHECK(Exynos4SDHCIState, (obj), TYPE_EXYNOS4_SDHC)
+
+/* ADMA Error Status Register */
+#define EXYNOS4_SDHC_FINAL_BLOCK     (1 << 10)
+#define EXYNOS4_SDHC_CONTINUE_REQ    (1 << 9)
+#define EXYNOS4_SDHC_IRQ_STAT        (1 << 8)
+/* Control register 2 */
+#define EXYNOS4_SDHC_CONTROL2        0x80
+#define EXYNOS4_SDHC_HWINITFIN       (1 << 0)
+#define EXYNOS4_SDHC_DISBUFRD        (1 << 6)
+#define EXYNOS4_SDHC_SDOPSIGPC       (1 << 12)
+#define EXYNOS4_SDHC_SDINPSIGPC      (1 << 3)
+/* Control register 3 */
+#define EXYNOS4_SDHC_CONTROL3        0x84
+/* Control register 4 */
+#define EXYNOS4_SDHC_CONTROL4        0x8C
+/* Clock control register */
+#define EXYNOS4_SDHC_SDCLK_STBL      (1 << 3)
+
+#define EXYNOS4_SDHC_CMD_USES_DAT(cmd)  \
+    (((cmd) & SDHC_CMD_DATA_PRESENT) || \
+    ((cmd) & SDHC_CMD_RESPONSE) == SDHC_CMD_RSP_WITH_BUSY)
+
+typedef struct Exynos4SDHCIState {
+    SDHCIState sdhci;
+
+    uint32_t admaerr;
+    uint32_t control2;
+    uint32_t control3;
+    bool stopped_adma;
+} Exynos4SDHCIState;
+
+static uint8_t sdhci_slotint(SDHCIState *s)
+{
+    return (s->norintsts & s->norintsigen) || (s->errintsts & s->errintsigen) ||
+         ((s->norintsts & SDHC_NIS_INSERT) && (s->wakcon & SDHC_WKUP_ON_INS)) ||
+         ((s->norintsts & SDHC_NIS_REMOVE) && (s->wakcon & SDHC_WKUP_ON_RMV));
+}
+
+static inline void exynos4210_sdhci_update_irq(SDHCIState *s)
+{
+    qemu_set_irq(s->irq, sdhci_slotint(s));
+}
+
+static void exynos4210_sdhci_reset(DeviceState *d)
+{
+    Exynos4SDHCIState *s = EXYNOS4_SDHCI(d);
+
+    SDHCI_GET_CLASS(d)->reset(SDHCI(d));
+    s->stopped_adma = false;
+    s->admaerr = 0;
+    s->control2 = 0;
+    s->control3 = 0x7F5F3F1F;
+}
+
+static void exynos4210_sdhci_start_adma(SDHCIState *sdhci)
+{
+    Exynos4SDHCIState *s = EXYNOS4_SDHCI(sdhci);
+    unsigned int length, n, begin;
+    target_phys_addr_t entry_addr;
+    uint32_t addr;
+    uint8_t attributes;
+    const uint16_t block_size = sdhci->blksize & 0x0fff;
+    s->admaerr &=
+            ~(EXYNOS4_SDHC_FINAL_BLOCK | SDHC_ADMAERR_LENGTH_MISMATCH);
+
+    while (1) {
+        addr = length = attributes = 0;
+        entry_addr = (target_phys_addr_t)(sdhci->admasysaddr & 0xFFFFFFFFull);
+
+        /* fetch next entry from descriptor table */
+        cpu_physical_memory_read(entry_addr + 4, (uint8_t *)(&addr), 4);
+        cpu_physical_memory_read(entry_addr + 2, (uint8_t *)(&length), 2);
+        cpu_physical_memory_read(entry_addr, (uint8_t *)(&attributes), 1);
+        DPRINT_L1("ADMA loop: addr=0x%08x, len=%d, attr=%x\n",
+                addr, length, attributes);
+
+        if ((attributes & SDHC_ADMA_ATTR_VALID) == 0) {
+            /* Indicate that error occurred in ST_FDS state */
+            s->admaerr &= ~SDHC_ADMAERR_STATE_MASK;
+            s->admaerr |= SDHC_ADMAERR_STATE_ST_FDS;
+            DPRINT_L1("ADMA not valid at addr=0x%lx\n", sdhci->admasysaddr);
+
+            if (sdhci->errintstsen & SDHC_EISEN_ADMAERR) {
+                sdhci->errintsts |= SDHC_EIS_ADMAERR;
+                sdhci->norintsts |= SDHC_NIS_ERR;
+            }
+
+            exynos4210_sdhci_update_irq(sdhci);
+            break;
+        }
+
+        if (length == 0) {
+            length = 65536;
+        }
+
+        addr &= 0xfffffffc;  /* minimum unit of addr is 4 byte */
+
+        switch (attributes & SDHC_ADMA_ATTR_ACT_MASK) {
+        case SDHC_ADMA_ATTR_ACT_TRAN:  /* data transfer */
+            if (sdhci->trnmod & SDHC_TRNS_READ) {
+                while (length) {
+                    if (sdhci->data_count == 0) {
+                        for (n = 0; n < block_size; n++) {
+                            sdhci->fifo_buffer[n] = sd_read_data(sdhci->card);
+                        }
+                    }
+                    begin = sdhci->data_count;
+                    if ((length + begin) < block_size) {
+                        sdhci->data_count = length + begin;
+                        length = 0;
+                     } else {
+                        sdhci->data_count = block_size;
+                        length -= block_size - begin;
+                    }
+                    cpu_physical_memory_write(addr, &sdhci->fifo_buffer[begin],
+                            sdhci->data_count - begin);
+                    addr += sdhci->data_count - begin;
+                    if (sdhci->data_count == block_size) {
+                        sdhci->data_count = 0;
+                        if (sdhci->trnmod & SDHC_TRNS_BLK_CNT_EN) {
+                            sdhci->blkcnt--;
+                            if (sdhci->blkcnt == 0) {
+                                break;
+                            }
+                        }
+                    }
+                }
+            } else {
+                while (length) {
+                    begin = sdhci->data_count;
+                    if ((length + begin) < block_size) {
+                        sdhci->data_count = length + begin;
+                        length = 0;
+                     } else {
+                        sdhci->data_count = block_size;
+                        length -= block_size - begin;
+                    }
+                    cpu_physical_memory_read(addr,
+                            &sdhci->fifo_buffer[begin], sdhci->data_count);
+                    addr += sdhci->data_count - begin;
+                    if (sdhci->data_count == block_size) {
+                        for (n = 0; n < block_size; n++) {
+                            sd_write_data(sdhci->card, sdhci->fifo_buffer[n]);
+                        }
+                        sdhci->data_count = 0;
+                        if (sdhci->trnmod & SDHC_TRNS_BLK_CNT_EN) {
+                            sdhci->blkcnt--;
+                            if (sdhci->blkcnt == 0) {
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+            sdhci->admasysaddr += 8;
+            break;
+        case SDHC_ADMA_ATTR_ACT_LINK:   /* link to next descriptor table */
+            sdhci->admasysaddr = addr;
+            DPRINT_L1("ADMA link: admasysaddr=0x%lx\n", sdhci->admasysaddr);
+            break;
+        default:
+            sdhci->admasysaddr += 8;
+            break;
+        }
+
+        /* ADMA transfer terminates if blkcnt == 0 or by END attribute */
+        if (((sdhci->trnmod & SDHC_TRNS_BLK_CNT_EN) && (sdhci->blkcnt == 0)) ||
+                (attributes & SDHC_ADMA_ATTR_END)) {
+            DPRINT_L2("ADMA transfer completed\n");
+            if (length || ((attributes & SDHC_ADMA_ATTR_END) &&
+               (sdhci->trnmod & SDHC_TRNS_BLK_CNT_EN) && sdhci->blkcnt != 0) ||
+               ((sdhci->trnmod & SDHC_TRNS_BLK_CNT_EN) && sdhci->blkcnt == 0 &&
+               (attributes & SDHC_ADMA_ATTR_END) == 0)) {
+                ERRPRINT("ADMA length mismatch\n");
+                s->admaerr |= SDHC_ADMAERR_LENGTH_MISMATCH |
+                        SDHC_ADMAERR_STATE_ST_TFR;
+                if (sdhci->errintstsen & SDHC_EISEN_ADMAERR) {
+                    sdhci->errintsts |= SDHC_EIS_ADMAERR;
+                    sdhci->norintsts |= SDHC_NIS_ERR;
+                }
+
+                exynos4210_sdhci_update_irq(sdhci);
+            }
+
+            s->admaerr |= EXYNOS4_SDHC_FINAL_BLOCK;
+            SDHCI_GET_CLASS(sdhci)->end_data_transfer(sdhci);
+            break;
+        }
+
+        if (attributes & SDHC_ADMA_ATTR_INT) {
+            DPRINT_L1("ADMA interrupt: addr=0x%lx\n", sdhci->admasysaddr);
+            s->admaerr |= EXYNOS4_SDHC_IRQ_STAT;
+            s->stopped_adma = true;
+            if (sdhci->norintstsen & SDHC_NISEN_DMA) {
+                sdhci->norintsts |= SDHC_NIS_DMA;
+            }
+            exynos4210_sdhci_update_irq(sdhci);
+            break;
+        }
+    }
+}
+
+static bool exynos4210_sdhci_can_issue_command(SDHCIState *sdhci)
+{
+    Exynos4SDHCIState *s = EXYNOS4_SDHCI(sdhci);
+
+    /* Check that power is supplied and clock is enabled.
+     * If SDOPSIGPC and SDINPSIGPC bits in CONTROL2 register are not set, power
+     * is supplied regardless of the PWRCON register state */
+    if (!SDHC_CLOCK_IS_ON(sdhci->clkcon) || (!(sdhci->pwrcon & SDHC_POWER_ON) &&
+        (s->control2 & (EXYNOS4_SDHC_SDOPSIGPC | EXYNOS4_SDHC_SDINPSIGPC)))) {
+        return false;
+    }
+
+    /* Controller cannot issue a command which uses data line (unless its an
+     * ABORT command) if data line is currently busy */
+    if (((sdhci->prnsts & SDHC_DATA_INHIBIT) || sdhci->stopped_state) &&
+        (EXYNOS4_SDHC_CMD_USES_DAT(sdhci->cmdreg) &&
+        SDHC_COMMAND_TYPE(sdhci->cmdreg) != SDHC_CMD_ABORT)) {
+        return false;
+    }
+
+    return true;
+}
+
+static uint64_t
+exynos4210_sdhci_readfn(void *opaque, target_phys_addr_t offset, unsigned size)
+{
+    Exynos4SDHCIState *s = (Exynos4SDHCIState *)opaque;
+    uint32_t ret;
+
+    switch (offset & ~0x3) {
+    case SDHC_BDATA:
+        /* Buffer data port read can be disabled by CONTROL2 register */
+        if (s->control2 & EXYNOS4_SDHC_DISBUFRD) {
+            ret = 0;
+        } else {
+            ret = SDHCI_GET_CLASS(s)->mem_read(SDHCI(s), offset, size);
+        }
+        break;
+    case SDHC_ADMAERR:
+        ret = (s->admaerr >> 8 * (offset - SDHC_ADMAERR)) &
+                ((1 << 8 * size) - 1);
+        break;
+    case EXYNOS4_SDHC_CONTROL2:
+        ret = (s->control2 >> 8 * (offset - EXYNOS4_SDHC_CONTROL2)) &
+                ((1 << 8 * size) - 1);
+        break;
+    case EXYNOS4_SDHC_CONTROL3:
+        ret = (s->control3 >> 8 * (offset - EXYNOS4_SDHC_CONTROL3)) &
+                ((1 << 8 * size) - 1);
+        break;
+    case EXYNOS4_SDHC_CONTROL4:
+        ret = 0;
+        break;
+    default:
+        ret = SDHCI_GET_CLASS(s)->mem_read(SDHCI(s), offset, size);
+        break;
+    }
+
+    DPRINT_L2("read %ub: addr[0x%04x] -> %u(0x%x)\n", size, offset, ret, ret);
+    return ret;
+}
+
+static void exynos4210_sdhci_writefn(void *opaque, target_phys_addr_t offset,
+        uint64_t val, unsigned size)
+{
+    Exynos4SDHCIState *s = (Exynos4SDHCIState *)opaque;
+    SDHCIState *sdhci = SDHCI(s);
+    unsigned shift;
+
+    DPRINT_L2("write %ub: addr[0x%04x] <- %u(0x%x)\n", size, (uint32_t)offset,
+            (uint32_t)val, (uint32_t)val);
+
+    switch (offset) {
+    case SDHC_CLKCON:
+        if ((val & SDHC_CLOCK_SDCLK_EN) &&
+                (sdhci->prnsts & SDHC_CARD_PRESENT)) {
+            val |= EXYNOS4_SDHC_SDCLK_STBL;
+        } else {
+            val &= ~EXYNOS4_SDHC_SDCLK_STBL;
+        }
+        /* Break out to superclass write to handle the rest of this register */
+        break;
+    case EXYNOS4_SDHC_CONTROL2 ... EXYNOS4_SDHC_CONTROL2 + 3:
+        shift = (offset - EXYNOS4_SDHC_CONTROL2) * 8;
+        s->control2 = (s->control2 & ~(((1 << 8 * size) - 1) << shift)) |
+                (val << shift);
+        return;
+    case EXYNOS4_SDHC_CONTROL3 ... EXYNOS4_SDHC_CONTROL3 + 3:
+        shift = (offset - EXYNOS4_SDHC_CONTROL2) * 8;
+        s->control3 = (s->control3 & ~(((1 << 8 * size) - 1) << shift)) |
+                (val << shift);
+        return;
+    case SDHC_ADMAERR ... SDHC_ADMAERR + 3:
+        if (size == 4 || (size == 2 && offset == SDHC_ADMAERR) ||
+                (size == 1 && offset == (SDHC_ADMAERR + 1))) {
+            uint32_t mask = 0;
+
+            if (size == 2) {
+                mask = 0xFFFF0000;
+            } else if (size == 1) {
+                mask = 0xFFFF00FF;
+                val <<= 8;
+            }
+
+            s->admaerr = (s->admaerr & (mask | EXYNOS4_SDHC_FINAL_BLOCK |
+               EXYNOS4_SDHC_IRQ_STAT)) | (val & ~(EXYNOS4_SDHC_FINAL_BLOCK |
+               EXYNOS4_SDHC_IRQ_STAT | EXYNOS4_SDHC_CONTINUE_REQ));
+            s->admaerr &= ~(val & EXYNOS4_SDHC_IRQ_STAT);
+            if ((s->stopped_adma) && (val & EXYNOS4_SDHC_CONTINUE_REQ) &&
+                (SDHC_DMA_TYPE(sdhci->hostctl) == SDHC_CTRL_ADMA2_32)) {
+                s->stopped_adma = false;
+                SDHCI_GET_CLASS(sdhci)->do_adma(sdhci);
+            }
+        } else {
+            uint32_t mask = (1 << (size * 8)) - 1;
+            shift = 8 * (offset & 0x3);
+            val <<= shift;
+            mask = ~(mask << shift);
+            s->admaerr = (s->admaerr & mask) | val;
+        }
+        return;
+    }
+
+    SDHCI_GET_CLASS(s)->mem_write(sdhci, offset, val, size);
+}
+
+static const MemoryRegionOps exynos4210_sdhci_mmio_ops = {
+    .read = exynos4210_sdhci_readfn,
+    .write = exynos4210_sdhci_writefn,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 4,
+        .unaligned = false
+    },
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const VMStateDescription exynos4210_sdhci_vmstate = {
+    .name = "exynos4210.sdhci",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_STRUCT(sdhci, Exynos4SDHCIState, 1, sdhci_vmstate, SDHCIState),
+        VMSTATE_UINT32(admaerr, Exynos4SDHCIState),
+        VMSTATE_UINT32(control2, Exynos4SDHCIState),
+        VMSTATE_UINT32(control3, Exynos4SDHCIState),
+        VMSTATE_BOOL(stopped_adma, Exynos4SDHCIState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static int exynos4210_sdhci_realize(SysBusDevice *busdev)
+{
+    SDHCIState *sdhci = SDHCI(busdev);
+
+    qdev_prop_set_uint32(DEVICE(busdev), "capareg", EXYNOS4_SDHC_CAPABILITIES);
+    sdhci->buf_maxsz = EXYNOS4_SDHC_MAX_BUFSZ;
+    sdhci->fifo_buffer = g_malloc0(sdhci->buf_maxsz);
+    sysbus_init_irq(busdev, &sdhci->irq);
+    memory_region_init_io(&sdhci->iomem, &exynos4210_sdhci_mmio_ops,
+            EXYNOS4_SDHCI(sdhci), "exynos4210.sdhci", SDHC_REGISTERS_MAP_SIZE);
+    sysbus_init_mmio(busdev, &sdhci->iomem);
+    return 0;
+}
+
+static void exynos4210_sdhci_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    SysBusDeviceClass *sbdc = SYS_BUS_DEVICE_CLASS(klass);
+    SDHCIClass *k = SDHCI_CLASS(klass);
+
+    dc->vmsd = &exynos4210_sdhci_vmstate;
+    dc->reset = exynos4210_sdhci_reset;
+    sbdc->init = exynos4210_sdhci_realize;
+
+    k->can_issue_command = exynos4210_sdhci_can_issue_command;
+    k->do_adma = exynos4210_sdhci_start_adma;
+}
+
+static const TypeInfo exynos4210_sdhci_type_info = {
+    .name = TYPE_EXYNOS4_SDHC,
+    .parent = TYPE_SDHCI,
+    .instance_size = sizeof(Exynos4SDHCIState),
+    .class_init = exynos4210_sdhci_class_init,
+};
+
+static void exynos4210_sdhci_register_types(void)
+{
+    type_register_static(&exynos4210_sdhci_type_info);
+}
+
+type_init(exynos4210_sdhci_register_types)
-- 
1.7.5.4