diff mbox

[14/15] ARM: exynos4210: added SD/MMC host controller

Message ID 1323437682-28792-15-git-send-email-e.voevodin@samsung.com
State New
Headers show

Commit Message

Evgeny Voevodin Dec. 9, 2011, 1:34 p.m. UTC
From: Mitsyanko Igor <i.mitsyanko@samsung.com>


Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 Makefile.target      |    3 +-
 hw/exynos4210.c      |   20 +
 hw/exynos4210_sdhc.c | 1666 ++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1688 insertions(+), 1 deletions(-)
 create mode 100644 hw/exynos4210_sdhc.c
diff mbox

Patch

diff --git a/Makefile.target b/Makefile.target
index 691582b..a5d67c9 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -345,7 +345,8 @@  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.o exynos4210_cmu.o exynos4210_uart.o exynos4210_gic.o \
-             exynos4210_combiner.o exynos4210_pwm.o exynos4210_mct.o
+             exynos4210_combiner.o exynos4210_pwm.o exynos4210_mct.o \
+             exynos4210_sdhc.o
 obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
 obj-arm-y += pl061.o
 obj-arm-y += arm-semi.o
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index 5b18b68..62c90e9 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -95,6 +95,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
+
 /* Secondary CPU polling address to get loader start from */
 #define EXYNOS4210_SECOND_CPU_BOOTREG       0x10020814
 
@@ -448,6 +454,20 @@  static void exynos4210_init(ram_addr_t ram_size,
         exynos4210_uart_create(addr, fifo_size, channel, NULL, uart_irq);
     }
 
+    /*** SD/MMC host controllers ***/
+
+    sysbus_create_simple("exynos4210.sdhc", EXYNOS4210_SDHC0_BASE_ADDR,
+            irq_table[exynos4210_get_irq(29, 0)]);
+
+    sysbus_create_simple("exynos4210.sdhc", EXYNOS4210_SDHC1_BASE_ADDR,
+            irq_table[exynos4210_get_irq(29, 1)]);
+
+    sysbus_create_simple("exynos4210.sdhc", EXYNOS4210_SDHC2_BASE_ADDR,
+            irq_table[exynos4210_get_irq(29, 2)]);
+
+    sysbus_create_simple("exynos4210.sdhc", EXYNOS4210_SDHC3_BASE_ADDR,
+            irq_table[exynos4210_get_irq(29, 3)]);
+
     /*** LAN adapter: this should be a 9215 but the 9118 is close enough ***/
     if (board_type == BOARD_EXYNOS4210_SMDKC210) {
         for (n = 0; n < nb_nics; n++) {
diff --git a/hw/exynos4210_sdhc.c b/hw/exynos4210_sdhc.c
new file mode 100644
index 0000000..05dd05c
--- /dev/null
+++ b/hw/exynos4210_sdhc.c
@@ -0,0 +1,1666 @@ 
+/*
+ * Samsung exynos4210 SD/MMC host controller
+ * (SD host controller specification ver. 2.0 compliant)
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ * Contributed by Mitsyanko Igor <i.mitsyanko@samsung.com>
+ *
+ * Based on MMC controller for Samsung S5PC1xx-based board emulation
+ * by Alexey Merkulov and Vladimir Monakhov.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "hw.h"
+#include "sd.h"
+#include "qemu-common.h"
+#include "block_int.h"
+#include "blockdev.h"
+#include "sysbus.h"
+#include "qemu-timer.h"
+
+/* host controller debug messages */
+#define DEBUG_SDHC 0
+
+#if DEBUG_SDHC
+#define DPRINTF(fmt, args...) \
+do { fprintf(stderr, "QEMU SDHC: " fmt , ##args); } while (0)
+#else
+#define DPRINTF(fmt, args...) do {} while (0)
+#endif
+
+#define TARGET_WORD_SIZE         (TARGET_PHYS_ADDR_SPACE_BITS / 8)
+#define READ_BUFFER_DELAY        5
+#define WRITE_BUFFER_DELAY       6
+#define INSERTION_DELAY          (get_ticks_per_sec())
+#define CMD_RESPONSE             (3 << 0)
+
+/* Default SD/MMC host controller features information, which will be
+ * presented in host's CAPABILITIES REGISTER at reset.
+ * If not stated otherwise:
+ * 0 - not supported, 1 - supported, other - prohibited.
+ */
+#define SDHC_CAPAB_64BITBUS       0ul        /* 64-bit System Bus Support */
+#define SDHC_CAPAB_18V            1ul        /* Voltage support 1.8v */
+#define SDHC_CAPAB_30V            0ul        /* Voltage support 3.0v */
+#define SDHC_CAPAB_33V            1ul        /* Voltage support 3.3v */
+#define SDHC_CAPAB_SUSPRESUME     0ul        /* Suspend/resume support */
+#define SDHC_CAPAB_SDMA           1ul        /* SDMA support */
+#define SDHC_CAPAB_HIGHSPEED      1ul        /* High speed support */
+#define SDHC_CAPAB_ADMA           1ul        /* ADMA2 support */
+/* Maximum host controller R/W buffers size
+ * Possible values: 512, 1024, 2048 bytes */
+#define SDHC_CAPAB_MAXBLOCKLENGTH 512ul
+/* Maximum clock frequency for SDclock in MHz
+ * value in range 10-63 MHz, 0 - not defined */
+#define SDHC_CAPAB_BASECLKFREQ    0ul
+#define SDHC_CAPAB_TOUNIT         1ul  /* Timeout clock unit 0 - kHz, 1 - MHz */
+/* Timeout clock frequency 1-63, 0 - not defined */
+#define SDHC_CAPAB_TOCLKFREQ      0ul
+
+/* Now check all parameters and calculate CAPABILITIES REGISTER value */
+#if SDHC_CAPAB_64BITBUS > 1 || SDHC_CAPAB_18V > 1 || SDHC_CAPAB_30V > 1 ||\
+    SDHC_CAPAB_33V > 1 || SDHC_CAPAB_SUSPRESUME > 1 || SDHC_CAPAB_SDMA > 1 ||\
+    SDHC_CAPAB_HIGHSPEED > 1 || SDHC_CAPAB_ADMA > 1 || SDHC_CAPAB_TOUNIT > 1
+#error Capabilities features SDHC_CAPAB_x must have value 0 or 1!
+#endif
+
+#if SDHC_CAPAB_MAXBLOCKLENGTH == 512
+#define MAX_BLOCK_LENGTH 0ul
+#elif SDHC_CAPAB_MAXBLOCKLENGTH == 1024
+#define MAX_BLOCK_LENGTH 1ul
+#elif SDHC_CAPAB_MAXBLOCKLENGTH == 2048
+#define MAX_BLOCK_LENGTH 2ul
+#else
+#error Max host controller block size can have value 512, 1024 or 2048 only!
+#endif
+
+#if (SDHC_CAPAB_BASECLKFREQ > 0 && SDHC_CAPAB_BASECLKFREQ < 10) || \
+    SDHC_CAPAB_BASECLKFREQ > 63
+#error SDclock frequency can have value in range 0, 10-63 only!
+#endif
+
+#if SDHC_CAPAB_TOCLKFREQ > 63
+#error Timeout clock frequency can have value in range 0-63 only!
+#endif
+
+#define SDHC_CAPAB_REG_DEFAULT ((SDHC_CAPAB_64BITBUS<<28)|(SDHC_CAPAB_18V<<26)|\
+   (SDHC_CAPAB_30V<<25)|(SDHC_CAPAB_33V<<24)|(SDHC_CAPAB_SUSPRESUME<<23)|\
+   (SDHC_CAPAB_SDMA<<22)|(SDHC_CAPAB_HIGHSPEED<<21)|(SDHC_CAPAB_ADMA<<19)|\
+   (MAX_BLOCK_LENGTH<<16)|(SDHC_CAPAB_BASECLKFREQ<<8)|(SDHC_CAPAB_TOUNIT<<7)|\
+   (SDHC_CAPAB_TOCLKFREQ))
+
+/****************************************************
+ * SD host controller registers offsets
+ ***************************************************/
+
+/* R/W SDMA System Address register 0x0 */
+#define SDHC_SYSAD                     0x00
+
+/* R/W Host DMA Buffer Boundary and Transfer Block Size Register 0x0 */
+#define SDHC_BLKSIZE                   0x04
+
+/* R/W Blocks count for current transfer 0x0 */
+#define SDHC_BLKCNT                    0x06
+
+/* R/W Command Argument Register 0x0 */
+#define SDHC_ARGUMENT                  0x08
+
+/* R/W Transfer Mode Setting Register 0x0 */
+#define SDHC_TRNMOD                    0x0C
+#define SDHC_TRNS_DMA                  0x0001
+#define SDHC_TRNS_BLK_CNT_EN           0x0002
+#define SDHC_TRNS_ACMD12               0x0004
+#define SDHC_TRNS_READ                 0x0010
+#define SDHC_TRNS_MULTI                0x0020
+#define SDHC_TRNS_CEATA                0x0300
+#define SDHC_TRNS_BOOTCMD              0x1000
+#define SDHC_TRNS_BOOTACK              0x2000
+
+/* R/W Command Register 0x0 */
+#define SDHC_CMDREG                    0x0E
+#define SDHC_CMD_RSP_WITH_BUSY         (3 << 0)
+#define SDHC_CMD_DATA_PRESENT          (1 << 5)
+#define SDHC_CMD_SUSPEND_MASK          (1 << 6)
+#define SDHC_CMD_RESUME_MASK           (1 << 7)
+#define SDHC_CMD_ABORT_MASK            ((1 << 6)|(1 << 7))
+#define SDHC_CMD_TYPE_MASK             ((1 << 6)|(1 << 7))
+
+/* ROC Response Register 0 0x0 */
+#define SDHC_RSPREG0                   0x10
+/* ROC Response Register 1 0x0 */
+#define SDHC_RSPREG1                   0x14
+/* ROC Response Register 2 0x0 */
+#define SDHC_RSPREG2                   0x18
+/* ROC Response Register 3 0x0 */
+#define SDHC_RSPREG3                   0x1C
+#define SDHC_CMD_RESP_MASK             0x00000003
+#define SDHC_CMD_CRC                   0x00000008
+#define SDHC_CMD_INDEX                 0x00000010
+#define SDHC_CMD_DATA                  0x00000020
+#define SDHC_CMD_RESP_NONE             0x00000000
+#define SDHC_CMD_RESP_LONG             0x00000001
+#define SDHC_CMD_RESP_SHORT            0x00000002
+#define SDHC_CMD_RESP_SHORT_BUSY       0x00000003
+#define SDHC_MAKE_CMD(c, f)            (((c & 0xFF) << 8) | (f & 0xFF))
+
+/* R/W Buffer Data Register 0x0 */
+#define SDHC_BDATA                     0x20
+
+/* R/ROC Present State Register 0x000A0000 */
+#define SDHC_PRNSTS                    0x24
+#define SDHC_CMD_INHIBIT               0x00000001
+#define SDHC_DATA_INHIBIT              0x00000002
+#define SDHC_DAT_LINE_ACTIVE           0x00000004
+#define SDHC_DOING_WRITE               0x00000100
+#define SDHC_DOING_READ                0x00000200
+#define SDHC_SPACE_AVAILABLE           0x00000400
+#define SDHC_DATA_AVAILABLE            0x00000800
+#define SDHC_CARD_PRESENT              0x00010000
+#define SDHC_WRITE_PROTECT             0x00080000
+
+/* R/W Host control Register 0x0 */
+#define SDHC_HOSTCTL                   0x28
+#define SDHC_CTRL_LED                  0x01
+#define SDHC_CTRL_4BITBUS              0x02
+#define SDHC_CTRL_HIGHSPEED            0x04
+#define SDHC_CTRL_1BIT                 0x00
+#define SDHC_CTRL_4BIT                 0x02
+#define SDHC_CTRL_8BIT                 0x20
+#define SDHC_CTRL_DMA_CHECK_MASK       0x18
+#define SDHC_CTRL_SDMA                 0x00
+#define SDHC_CTRL_ADMA2_32             0x10
+
+/* R/W Power Control Register 0x0 */
+#define SDHC_PWRCON                    0x29
+#define SDHC_POWER_OFF                 0x00
+#define SDHC_POWER_ON                  0x01
+#define SDHC_POWER_180                 0x0A
+#define SDHC_POWER_300                 0x0C
+#define SDHC_POWER_330                 0x0E
+#define SDHC_POWER_ON_ALL              0xFF
+
+/* R/W Block Gap Control Register 0x0 */
+#define SDHC_BLKGAP                    0x2A
+#define SDHC_STOP_AT_GAP_REQ           0x01
+#define SDHC_CONTINUE_REQ              0x02
+
+/* R/W WakeUp Control Register 0x0 */
+#define SDHC_WAKCON                    0x2B
+#define SDHC_STAWAKEUP                 0x08
+
+/*
+ * CLKCON
+ * SELFREQ[15:8]    : base clock divided by value
+ * ENSDCLK[2]        : SD Clock Enable
+ * STBLINTCLK[1]    : Internal Clock Stable
+ * ENINTCLK[0]        : Internal Clock Enable
+ */
+#define SDHC_CLKCON                    0x2C
+#define SDHC_DIVIDER_SHIFT             0x0008
+#define SDHC_CLOCK_EXT_STABLE          0x0008
+#define SDHC_CLOCK_CARD_EN             0x0004
+#define SDHC_CLOCK_INT_STABLE          0x0002
+#define SDHC_CLOCK_INT_EN              0x0001
+#define SDHC_CLOCK_CHK_MASK            0x000F
+
+/* R/W Timeout Control Register 0x0 */
+#define SDHC_TIMEOUTCON                0x2E
+#define SDHC_TIMEOUT_MAX               0x0E
+
+/* R/W Software Reset Register 0x0 */
+#define SDHC_SWRST                     0x2F
+#define SDHC_RESET_ALL                 0x01
+#define SDHC_RESET_CMD                 0x02
+#define SDHC_RESET_DATA                0x04
+
+/* ROC/RW1C Normal Interrupt Status Register 0x0 */
+#define SDHC_NORINTSTS                 0x30
+#define SDHC_NIS_ERR                   0x8000
+#define SDHC_NIS_CMDCMP                0x0001
+#define SDHC_NIS_TRSCMP                0x0002
+#define SDHC_NIS_BLKGAP                0x0004
+#define SDHC_NIS_DMA                   0x0008
+#define SDHC_NIS_WBUFRDY               0x0010
+#define SDHC_NIS_RBUFRDY               0x0020
+#define SDHC_NIS_INSERT                0x0040
+#define SDHC_NIS_REMOVE                0x0080
+#define SDHC_NIS_CARDINT               0x0100
+
+/* ROC/RW1C Error Interrupt Status Register 0x0 */
+#define SDHC_ERRINTSTS                 0x32
+#define SDHC_EIS_CMDTIMEOUT            0x0001
+#define SDHC_EIS_BLKGAP                0x0004
+#define SDHC_EIS_CMDERR                0x000E
+#define SDHC_EIS_DATATIMEOUT           0x0010
+#define SDHC_EIS_DATAERR               0x0060
+#define SDHC_EIS_CMD12ERR              0x0100
+#define SDHC_EIS_ADMAERR               0x0200
+#define SDHC_EIS_STABOOTACKERR         0x0400
+
+/* R/W Normal Interrupt Status Enable Register 0x0 */
+#define SDHC_NORINTSTSEN               0x34
+#define SDHC_NISEN_CMDCMP              0x0001
+#define SDHC_NISEN_TRSCMP              0x0002
+#define SDHC_NISEN_DMA                 0x0008
+#define SDHC_NISEN_WBUFRDY             0x0010
+#define SDHC_NISEN_RBUFRDY             0x0020
+#define SDHC_NISEN_INSERT              0x0040
+#define SDHC_NISEN_REMOVE              0x0080
+#define SDHC_NISEN_CARDINT             0x0100
+
+/* R/W Error Interrupt Status Enable Register 0x0 */
+#define SDHC_ERRINTSTSEN               0x36
+#define SDHC_EISEN_CMDTIMEOUT          0x0001
+#define SDHC_EISEN_BLKGAP              0x0004
+#define SDHC_EISEN_ADMAERR             0x0200
+#define SDHC_EISEN_STABOOTACKERR       0x0400
+
+/* R/W Normal Interrupt Signal Enable Register 0x0 */
+#define SDHC_NORINTSIGEN               0x38
+#define SDHC_NISSIG_MASK_ALL           0x00
+#define SDHC_NISSIG_RESPONSE           0x0001
+#define SDHC_NISSIG_DATA_END           0x0002
+#define SDHC_NISSIG_DMA_END            0x0008
+#define SDHC_NISSIG_SPACE_AVAIL        0x0010
+#define SDHC_NISSIG_DATA_AVAIL         0x0020
+#define SDHC_NISSIG_CARD_INSERT        0x0040
+#define SDHC_NISSIG_CARD_REMOVE        0x0080
+#define SDHC_NISSIG_CARD_CHANGE        0x00C0
+#define SDHC_NISSIG_CARD_INT           0x0100
+
+/* R/W Error Interrupt Signal Enable Register 0x0 */
+#define SDHC_ERRINTSIGEN               0x3A
+#define SDHC_EISSIG_STABOOTACKERR      0x0400
+#define SDHC_EISSIG_ADMAERR            0x0200
+
+/* ROC Auto CMD12 error status register 0x0 */
+#define SDHC_ACMD12ERRSTS              0x3C
+
+/* HWInit Capabilities Register 0x05E80080 */
+#define SDHC_CAPAREG                   0x40
+#define SDHC_TIMEOUT_CLK_MASK          0x0000003F
+#define SDHC_TIMEOUT_CLK_SHIFT         0x0
+#define SDHC_TIMEOUT_CLK_UNIT          0x00000080
+#define SDHC_CLOCK_BASE_MASK           0x00003F00
+#define SDHC_CLOCK_BASE_SHIFT          0x8
+#define SDHC_MAX_BLOCK_MASK            0x00030000
+#define SDHC_MAX_BLOCK_SHIFT           0x10
+#define SDHC_CAN_DO_DMA                0x00400000
+#define SDHC_CAN_DO_ADMA2              0x00080000
+#define SDHC_CAN_VDD_330               0x01000000
+#define SDHC_CAN_VDD_300               0x02000000
+#define SDHC_CAN_VDD_180               0x04000000
+
+/* HWInit Maximum Current Capabilities Register 0x0 */
+#define SDHC_MAXCURR                   0x48
+
+/* For ADMA2 */
+
+/* W Force Event Auto CMD12 Error Interrupt Register 0x0000 */
+#define SDHC_FEAER                     0x50
+/* W Force Event Error Interrupt Register Error Interrupt 0x0000 */
+#define SDHC_FEERR                     0x52
+
+/* R/W ADMA Error Status Register 0x00 */
+#define SDHC_ADMAERR                   0x54
+#define SDHC_ADMAERR_FINAL_BLOCK       (1 << 10)
+#define SDHC_ADMAERR_CONTINUE_REQUEST  (1 << 9)
+#define SDHC_ADMAERR_INTRRUPT_STATUS   (1 << 8)
+#define SDHC_ADMAERR_LENGTH_MISMATCH   (1 << 2)
+#define SDHC_ADMAERR_STATE_ST_STOP     (0 << 0)
+#define SDHC_ADMAERR_STATE_ST_FDS      (1 << 0)
+#define SDHC_ADMAERR_STATE_ST_TFR      (3 << 0)
+#define SDHC_ADMAERR_STATE_MASK        (3 << 0)
+
+/* R/W ADMA System Address Register 0x00 */
+#define SDHC_ADMASYSADDR               0x58
+#define SDHC_ADMA_ATTR_MSK             0x3F
+#define SDHC_ADMA_ATTR_ACT_NOP         (0 << 4)
+#define SDHC_ADMA_ATTR_ACT_RSV         (1 << 4)
+#define SDHC_ADMA_ATTR_ACT_TRAN        (1 << 5)
+#define SDHC_ADMA_ATTR_ACT_LINK        (3 << 4)
+#define SDHC_ADMA_ATTR_INT             (1 << 2)
+#define SDHC_ADMA_ATTR_END             (1 << 1)
+#define SDHC_ADMA_ATTR_VALID           (1 << 0)
+#define SDHC_ADMA_ATTR_ACT_MASK        ((1 << 4)|(1 << 5))
+
+/* R/W Control register 2 0x0 */
+#define SDHC_CONTROL2                   0x80
+#define SDHC_HWINITFIN                  0x00000001
+#define SDHC_SDOPSIGPC                  0x00001000
+#define SDHC_ENCLKOUTHOLD               0x00000100
+#define SDHC_SDINPSIGPC                 0x00000008
+#define SDHC_DISBUFRD                   0x00000040
+#define SDHC_ENCMDCNFMSK                0x40000000
+
+/* R/W FIFO Interrupt Control (Control Register 3) 0x7F5F3F1F */
+#define SDHC_CONTROL3                   0x84
+
+/* R/W Control register 4 0x0 */
+#define SDHC_CONTROL4                   0x8C
+
+/* Magic register which is used from kernel! */
+#define SDHC_SLOT_INT_STATUS            0xFC
+
+/* HWInit Host Controller Version Register 0x0401 */
+#define SDHC_HCVER                      0xFE
+#define SDHC_VENDOR_VER_MASK            0xFF00
+#define SDHC_VENDOR_VER_SHIFT           0x8
+#define SDHC_SPEC_VER_MASK              0x00FF
+#define SDHC_SPEC_VER_SHIFT             0x0
+
+#define SDHC_REG_SIZE                   0x100
+
+enum {
+    not_stoped = 0,       /* normal SDHC state */
+    gap_read = 1,         /* SDHC stopped at block gap during read operation */
+    gap_write = 2,        /* SDHC stopped at block gap during write operation */
+    adma_intr = 3         /* SDHC stopped after ADMA interrupt occurred */
+};
+
+/* SD/MMC host controller state */
+typedef struct Exynos4210SDHCState {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    SDState *card;
+
+    QEMUTimer *insert_timer; /* timer for 'changing' sd card. */
+    QEMUTimer *read_buffer_timer; /* read block of data to controller FIFO */
+    QEMUTimer *write_buffer_timer; /* write block of data to card from FIFO */
+    QEMUTimer *transfer_complete_timer; /* raise transfer complete irq */
+    qemu_irq eject;
+    qemu_irq irq;
+
+    uint32_t sdmasysad;    /* SDMA System Address register */
+    uint16_t blksize;      /* Host DMA Buff Boundary and Transfer BlkSize Reg */
+    uint16_t blkcnt;       /* Blocks count for current transfer */
+    uint32_t argument;     /* Command Argument Register */
+    uint16_t trnmod;       /* Transfer Mode Setting Register */
+    uint16_t cmdreg;       /* Command Register */
+    uint32_t rspreg[4];    /* Response Registers 0-3 */
+/*  32 bit bdata Buffer Data Register - access point to R and W buffers */
+    uint32_t prnsts;       /* Present State Register */
+    uint8_t hostctl;       /* Present State Register */
+    uint8_t pwrcon;        /* Present State Register */
+    uint8_t blkgap;        /* Block Gap Control Register */
+    uint8_t wakcon;        /* WakeUp Control Register */
+    uint16_t clkcon;       /* Command Register */
+    uint8_t timeoutcon;    /* Timeout Control Register */
+    uint8_t stoped_state;  /* Current SDHC state */
+    /* SD host i/o FIFO buffer */
+    uint32_t fifo_buffer[SDHC_CAPAB_MAXBLOCKLENGTH / TARGET_WORD_SIZE];
+    uint16_t data_count;   /* current element in FIFO buffer */
+/*  8 bit swrst Software Reset Register - read as 0 */
+    uint16_t norintsts;    /* Normal Interrupt Status Register */
+    uint16_t errintsts;    /* Error Interrupt Status Register */
+    uint16_t norintstsen;  /* Normal Interrupt Status Enable Register */
+    uint16_t errintstsen;  /* Error Interrupt Status Enable Register */
+    uint16_t norintsigen;  /* Normal Interrupt Signal Enable Register */
+    uint16_t errintsigen;  /* Error Interrupt Signal Enable Register */
+    uint16_t acmd12errsts;   /* Auto CMD12 error status register */
+    uint32_t capareg;      /* Capabilities Register */
+    uint32_t maxcurr;      /* Maximum Current Capabilities Register */
+/*  16 bit feaer Force Event Auto CMD12 Error Interrupt Reg - write only */
+/*  16 bit feerr Force Event Error Interrupt Register- write only */
+    uint32_t admaerr;      /* ADMA Error Status Register */
+    uint32_t admasysaddr;  /* ADMA System Address Register */
+    uint32_t control2;     /* Control Register 2 */
+    uint32_t control3;     /* FIFO Interrupt Control (Control Register 3) */
+/*  32 bit control4 makes no sense in emulation so always return 0 */
+/*  32 bit hcver Host Controller Ver Reg 0x2401 -SD specification v2 */
+} Exynos4210SDHCState;
+
+static void sdhc_raise_transfer_complete_irq(void *opaque)
+{
+    Exynos4210SDHCState *s = (Exynos4210SDHCState *)opaque;
+
+    /* free data transfer line */
+    s->prnsts &= ~(SDHC_DOING_READ | SDHC_DOING_WRITE |
+            SDHC_DAT_LINE_ACTIVE | SDHC_DATA_INHIBIT |
+            SDHC_SPACE_AVAILABLE | SDHC_DATA_AVAILABLE);
+
+    if (s->norintstsen & SDHC_NISEN_TRSCMP) {
+        s->norintsts |= SDHC_NIS_TRSCMP;
+    }
+
+    qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+}
+
+/* raise command response received interrupt */
+static inline void sdhc_raise_response_recieved_irq(Exynos4210SDHCState *s)
+{
+    DPRINTF("raise IRQ response\n");
+
+    if (s->norintstsen & SDHC_NISEN_CMDCMP) {
+        s->norintsts |= SDHC_NIS_CMDCMP;
+    }
+
+    qemu_set_irq(s->irq, ((s->norintsts & s->norintsigen) ||
+            (s->errintsts & s->errintsigen)));
+}
+
+static void sdhc_raise_insertion_irq(void *opaque)
+{
+    Exynos4210SDHCState *s = (Exynos4210SDHCState *)opaque;
+    DPRINTF("raise IRQ response\n");
+
+    if (s->norintsts & SDHC_NIS_REMOVE) {
+        DPRINTF("sdhc_raise_insertion_irq: set timer!\n");
+        qemu_mod_timer(s->insert_timer,
+                       qemu_get_clock_ns(vm_clock) + INSERTION_DELAY);
+    } else {
+        DPRINTF("sdhc_raise_insertion_irq: raise irq!\n");
+        if (s->norintstsen & SDHC_NIS_INSERT) {
+            s->norintsts |= SDHC_NIS_INSERT;
+        }
+        qemu_set_irq(s->irq, (s->norintsts & s->norintsigen));
+    }
+}
+
+static void sdhc_insert_eject(void *opaque, int irq, int level)
+{
+    Exynos4210SDHCState *s = (Exynos4210SDHCState *)opaque;
+    DPRINTF("change card state: %s!\n", level ? "insert" : "eject");
+
+    if (s->norintsts & SDHC_NIS_REMOVE) {
+        if (level) {
+            DPRINTF("change card state: timer set!\n");
+            qemu_mod_timer(s->insert_timer,
+                           qemu_get_clock_ns(vm_clock) + INSERTION_DELAY);
+        }
+    } else {
+        if (level) {
+            s->prnsts = 0x1ff0000;
+            if (s->norintstsen & SDHC_NIS_INSERT) {
+                s->norintsts |= SDHC_NIS_INSERT;
+            }
+        } else {
+            s->prnsts = 0x1fa0000;
+            if (s->norintstsen & SDHC_NIS_REMOVE) {
+                s->norintsts |= SDHC_NIS_REMOVE;
+            }
+        }
+        qemu_set_irq(s->irq, (s->norintsts & s->norintsigen));
+    }
+}
+
+static void exynos4210_sdhc_reset(DeviceState *d)
+{
+    Exynos4210SDHCState *s = container_of(d, Exynos4210SDHCState, busdev.qdev);
+    unsigned long begin = (unsigned long)s + offsetof(Exynos4210SDHCState,
+            sdmasysad);
+    unsigned long len = ((unsigned long)s + offsetof(Exynos4210SDHCState,
+            control3)) - begin;
+
+    /* Set all registers to 0 and then set appropriate values
+     * for hardware-initialize registers */
+    memset((void *)begin, 0, len);
+    (s->card) ? (s->prnsts = 0x1ff0000) : (s->prnsts = 0x1fa0000);
+    s->stoped_state = not_stoped;
+    s->data_count = 0;
+    s->capareg = SDHC_CAPAB_REG_DEFAULT;
+    s->control3 = 0x7F5F3F1F;
+}
+
+static void sdhc_send_command(Exynos4210SDHCState *s)
+{
+    SDRequest request;
+    uint8_t response[16];
+    int rlen;
+    s->errintsts = 0;
+    if (!s->card) {
+        goto error;
+    }
+
+    request.cmd = s->cmdreg >> 8;
+    request.arg = s->argument;
+    DPRINTF("Command %d %08x\n", request.cmd, request.arg);
+    rlen = sd_do_command(s->card, &request, response);
+    if (rlen < 0) {
+        goto error;
+    }
+    if ((s->cmdreg & CMD_RESPONSE) != 0) {
+#define RWORD(n) ((n >= 0 ? (response[n] << 24) : 0) \
+                  | (response[n + 1] << 16) \
+                  | (response[n + 2] << 8) \
+                  |  response[n + 3])
+
+        if ((rlen == 0) || (rlen != 4 && rlen != 16)) {
+            goto error;
+        }
+
+        s->rspreg[0] = RWORD(0);
+        if (rlen == 4) {
+            s->rspreg[1] = s->rspreg[2] = s->rspreg[3] = 0;
+        } else {
+            s->rspreg[0] = RWORD(11);
+            s->rspreg[1] = RWORD(7);
+            s->rspreg[2] = RWORD(3);
+            s->rspreg[3] = RWORD(-1);
+        }
+        DPRINTF("Response received\n");
+#undef RWORD
+    } else {
+        DPRINTF("Command sent\n");
+    }
+    return;
+
+error:
+    DPRINTF("Timeout\n");
+    if (s->errintstsen & SDHC_EISEN_CMDTIMEOUT) {
+        s->errintsts |= SDHC_EIS_CMDTIMEOUT;
+        s->norintsts |= SDHC_NIS_ERR;
+    }
+}
+
+static void sdhc_do_transfer_complete(Exynos4210SDHCState *s)
+{
+    /* Automatically send CMD12 to stop transfer if AutoCMD12 enabled */
+    if ((s->trnmod & SDHC_TRNS_ACMD12) != 0) {
+        SDRequest request;
+        uint8_t response[16];
+
+        request.cmd = 0x0C;
+        request.arg = 0;
+        DPRINTF("Command Auto%d %08x\n", request.cmd, request.arg);
+        sd_do_command(s->card, &request, response);
+    }
+    /* pend a timer which will raise a transfer complete irq */
+    qemu_mod_timer(s->transfer_complete_timer,
+        qemu_get_clock_ns(vm_clock) + 1);
+}
+
+/*
+ * Programmed i/o data transfer
+ */
+
+/* Fill host controlerr's read buffer with BLKSIZE bytes of data from card */
+static void sdhc_read_block_from_card(void *opaque)
+{
+    Exynos4210SDHCState *s = (Exynos4210SDHCState *)opaque;
+    uint32_t value = 0;
+    int n = 0, index = 0;
+    uint16_t datacount = s->blksize & 0x0fff;
+
+    if ((s->trnmod & SDHC_TRNS_MULTI) &&
+            (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0)) {
+        return;
+    }
+
+    while (datacount) {
+        value |= (uint32_t)sd_read_data(s->card) << (n * 8);
+        n++;
+        if (n == 4) {
+            s->fifo_buffer[index] = value;
+            value = 0;
+            n = 0;
+            index++;
+        }
+        datacount--;
+    }
+
+    /* New data now available for READ through Buffer Port Register */
+    s->prnsts |= SDHC_DATA_AVAILABLE;
+    if (s->norintstsen & SDHC_NISEN_RBUFRDY) {
+        s->norintsts |= SDHC_NIS_RBUFRDY;
+    }
+
+    /* Clear DAT line active status if that was the last block */
+    if ((s->trnmod & SDHC_TRNS_MULTI) == 0 ||
+            ((s->trnmod & SDHC_TRNS_MULTI) && s->blkcnt == 1)) {
+        s->prnsts &= ~SDHC_DAT_LINE_ACTIVE;
+    }
+
+    /* If stop at block gap request was set and it's not the last block of
+     * data - generate Block Event interrupt */
+    if (s->stoped_state == gap_read && (s->trnmod & SDHC_TRNS_MULTI) &&
+            s->blkcnt != 1)    {
+        s->prnsts &= ~SDHC_DAT_LINE_ACTIVE;
+        if (s->norintstsen & SDHC_EISEN_BLKGAP) {
+            s->norintsts |= SDHC_EIS_BLKGAP;
+        }
+    }
+
+    qemu_set_irq(s->irq, (s->norintsts & s->norintsigen));
+}
+
+/* Read data from host controller BUFFER DATA PORT register */
+static inline uint32_t sdhc_read_dataport(Exynos4210SDHCState *s)
+{
+    /* first check if a valid data exists in host controller input buffer
+     * and that buffer read is not disabled */
+    if ((s->prnsts & SDHC_DATA_AVAILABLE) == 0 ||
+            (s->control2 & SDHC_DISBUFRD)) {
+        DPRINTF("Trying to read from empty buffer or read disabled\n");
+        return 0;
+    }
+
+    uint32_t value = s->fifo_buffer[s->data_count];
+    s->data_count++;
+    /* check if we've read all valid data (blksize bytes) from buffer */
+    if (((s->data_count) * 4) >= (s->blksize & 0x0fff)) {
+        s->prnsts &= ~SDHC_DATA_AVAILABLE; /* no more data in a buffer */
+        s->data_count = 0;  /* next buff read must start at position [0] */
+
+        if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
+            s->blkcnt--;
+        }
+
+        /* if that was the last block of data */
+        if ((s->trnmod & SDHC_TRNS_MULTI) == 0 ||
+                ((s->trnmod & SDHC_TRNS_MULTI) &&
+                (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0))) {
+            sdhc_do_transfer_complete(s);
+        } else if (s->stoped_state == gap_read &&  /* stop at gap request */
+                !(s->prnsts & SDHC_DAT_LINE_ACTIVE)) {
+            sdhc_raise_transfer_complete_irq(s);
+        } else { /* if there are more data, read next block from card */
+            qemu_mod_timer(s->read_buffer_timer,
+                qemu_get_clock_ns(vm_clock) + READ_BUFFER_DELAY);
+        }
+    }
+    return value;
+}
+
+/* Write data from host controller FIFO to card */
+static void sdhc_write_block_to_card(void *opaque)
+{
+    Exynos4210SDHCState *s = (Exynos4210SDHCState *)opaque;
+    uint32_t value = 0;
+    int n = 0, index = 0;
+    uint16_t datacount = s->blksize & 0x0fff;
+
+    if (s->prnsts & SDHC_SPACE_AVAILABLE) {
+        if (s->norintstsen & SDHC_NISEN_WBUFRDY) {
+            s->norintsts |= SDHC_NIS_WBUFRDY;
+        }
+        qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+        return;
+    }
+
+    if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
+        if (s->blkcnt == 0) {
+            return;
+        } else {
+            s->blkcnt--;
+        }
+    }
+
+    while (datacount) {
+        if (n == 0) {
+            value = s->fifo_buffer[index];
+            n = 4;
+            index++;
+        }
+        sd_write_data(s->card, (uint8_t)(value & 0xff));
+        value >>= 8;
+        n--;
+        datacount--;
+    }
+
+    /* Next data can be written through BUFFER DATORT register */
+    s->prnsts |= SDHC_SPACE_AVAILABLE;
+    if (s->norintstsen & SDHC_NISEN_WBUFRDY) {
+        s->norintsts |= SDHC_NIS_WBUFRDY;
+    }
+
+    /* Finish transfer if that was the last block of data */
+    if ((s->trnmod & SDHC_TRNS_MULTI) == 0 ||
+            ((s->trnmod & SDHC_TRNS_MULTI) &&
+            (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0))) {
+        sdhc_do_transfer_complete(s);
+    }
+
+    /* Generate Block Gap Event if requested and if not the last block */
+    if (s->stoped_state == gap_write && (s->trnmod & SDHC_TRNS_MULTI) &&
+            s->blkcnt > 0) {
+        s->prnsts &= ~SDHC_DOING_WRITE;
+        if (s->norintstsen & SDHC_EISEN_BLKGAP) {
+            s->norintsts |= SDHC_EIS_BLKGAP;
+        }
+        qemu_mod_timer(s->transfer_complete_timer,
+            qemu_get_clock_ns(vm_clock)+1);
+    }
+
+    qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+}
+
+/* Write data to host controller BUFFER DATA PORT register */
+static inline void sdhc_write_dataport(Exynos4210SDHCState *s, uint32_t value)
+{
+    if ((s->prnsts & SDHC_SPACE_AVAILABLE) == 0) {
+        DPRINTF("exynos4210.sdhc: Trying to write to full buffer\n");
+        return;
+    }
+
+    s->fifo_buffer[s->data_count] = value;
+    s->data_count++;
+
+    if (((s->data_count) * 4) >= (s->blksize & 0x0fff)) {
+        s->data_count = 0;
+        s->prnsts &= ~SDHC_SPACE_AVAILABLE;
+        if (s->prnsts & SDHC_DOING_WRITE) {
+            qemu_mod_timer(s->write_buffer_timer,
+                qemu_get_clock_ns(vm_clock) + WRITE_BUFFER_DELAY);
+        }
+    }
+}
+
+/*
+ * Single DMA data transfer
+ */
+
+/* Multi block SDMA transfer */
+static void sdhc_sdma_transfer_multi_blocks(Exynos4210SDHCState *s)
+{
+    uint32_t value = 0, datacnt, saved_datacnt, pos = 0;
+    int n = 0, page_aligned = 0;
+    int is_read = (s->trnmod & SDHC_TRNS_READ) != 0;
+    uint32_t dma_buf_boundary = (s->blksize & 0xf000) >> 12;
+    uint32_t boundary_chk = 1 << (dma_buf_boundary+12);
+    uint32_t boundary_count = boundary_chk - (s->sdmasysad % boundary_chk);
+
+    /* XXX: Some sd/mmc drivers (for example, u-boot-slp) do not account for
+     * possible stop at page boundary if initial address is not page aligned.
+     * Documentation is vague on how this really behaves in hardware, so allow
+     * them to work properly */
+    if ((s->sdmasysad % boundary_chk) == 0) {
+        page_aligned = 1;
+    }
+
+    if (is_read) {
+        s->prnsts |= SDHC_DOING_READ | SDHC_DATA_INHIBIT |
+                SDHC_DAT_LINE_ACTIVE;
+    } else {
+        s->prnsts |= SDHC_DOING_WRITE | SDHC_DATA_INHIBIT |
+                SDHC_DAT_LINE_ACTIVE;
+    }
+
+    /* Transfer data left from last SDMA transaction loop, after controller
+     * stopped at DMA buffer boundary. This is required only if transfers
+     * are not aligned to DMA buffer boundary */
+    saved_datacnt = s->data_count;
+    while (saved_datacnt) {
+        if (is_read) {
+            value |= (uint32_t)sd_read_data(s->card) << (n * 8);
+            n++;
+            if (n == 4) {
+                cpu_physical_memory_write(s->sdmasysad + pos,
+                        (uint8_t *)(&value), 4);
+                value = 0;
+                n = 0;
+                pos += 4;
+            }
+        } else {
+            if (n == 0) {
+                cpu_physical_memory_read(s->sdmasysad + pos,
+                        (uint8_t *)(&value), 4);
+                n = 4;
+                pos += 4;
+            }
+            sd_write_data(s->card, value & 0xff);
+            value >>= 8;
+            n--;
+        }
+        saved_datacnt--;
+    }
+    if (s->data_count) {
+        s->blkcnt--;
+        boundary_count -= s->data_count--;
+        s->sdmasysad += s->data_count--;
+        s->data_count = 0;
+    }
+
+    n = value = 0;
+    while (s->blkcnt) {
+        if ((boundary_count < (s->blksize & 0x0fff)) && page_aligned) {
+            datacnt = boundary_count;
+            s->data_count = (s->blksize & 0x0fff) - boundary_count;
+        } else {
+            datacnt = s->blksize & 0x0fff;
+        }
+        pos = 0;
+        saved_datacnt = datacnt;
+
+        if (is_read) {
+            while (datacnt) {
+                value |= (uint32_t)sd_read_data(s->card) << (n * 8);
+                n++;
+                if (n == 4) {
+                    cpu_physical_memory_write(s->sdmasysad + pos,
+                            (uint8_t *)(&value), 4);
+                    value = 0;
+                    n = 0;
+                    pos += 4;
+                }
+                datacnt--;
+            }
+        } else {
+            while (datacnt) {
+                if (n == 0) {
+                    cpu_physical_memory_read(s->sdmasysad + pos,
+                            (uint8_t *)(&value), 4);
+                    n = 4;
+                    pos += 4;
+                }
+                sd_write_data(s->card, value & 0xff);
+                value >>= 8;
+                n--;
+                datacnt--;
+            }
+        }
+
+        s->sdmasysad += saved_datacnt;
+        boundary_count -= saved_datacnt;
+        if (s->data_count == 0) {
+            s->blkcnt--;
+        }
+
+        if (boundary_count == 0) {
+            break;
+        }
+    }
+
+    if (s->blkcnt == 0) {
+        sdhc_do_transfer_complete(s);
+    } else {
+        if (s->norintstsen & SDHC_NISEN_DMA) {
+            s->norintsts |= SDHC_NIS_DMA;
+        }
+        qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+    }
+}
+
+/* single block SDMA transfer */
+static void sdhc_sdma_transfer_single_block(Exynos4210SDHCState *s)
+{
+    int is_read = (s->trnmod & SDHC_TRNS_READ) != 0;
+    int n = 0;
+    uint32_t value = 0, pos = 0;
+    uint32_t datacnt = s->blksize & 0x0fff;
+
+    while (datacnt) {
+        if (is_read) {
+            value |= (uint32_t)sd_read_data(s->card) << (n * 8);
+            n++;
+            if (n == 4) {
+                cpu_physical_memory_write(s->sdmasysad + pos,
+                        (uint8_t *)(&value), 4);
+                value = 0;
+                n = 0;
+                pos += 4;
+            }
+        } else {
+            if (n == 0) {
+                cpu_physical_memory_read(s->sdmasysad + pos,
+                        (uint8_t *)(&value), 4);
+                n = 4;
+                pos += 4;
+            }
+            sd_write_data(s->card, value & 0xff);
+            value >>= 8;
+            n--;
+        }
+        datacnt--;
+    }
+
+    if ((s->trnmod & SDHC_TRNS_BLK_CNT_EN) != 0) {
+        s->blkcnt--;
+    }
+
+    sdhc_do_transfer_complete(s);
+}
+
+/* Advanced DMA data transfer */
+static void sdhc_run_adma(Exynos4210SDHCState *s)
+{
+    uint32_t value;
+    int n, pos, length;
+    uint32_t address, datacnt;
+    uint16_t length_table;
+    uint8_t attributes;
+
+    int is_read = (s->trnmod & SDHC_TRNS_READ) != 0;
+    s->admaerr &= ~(SDHC_ADMAERR_FINAL_BLOCK | SDHC_ADMAERR_LENGTH_MISMATCH);
+
+    while (1) {
+        address = length_table = length = attributes = 0;
+        value = n = pos  = 0;
+
+        /* fetch next entry from descriptor table */
+        cpu_physical_memory_read(s->admasysaddr+4, (uint8_t *)(&address), 4);
+        cpu_physical_memory_read(s->admasysaddr+2,
+                (uint8_t *)(&length_table), 2);
+        cpu_physical_memory_read(s->admasysaddr, (uint8_t *)(&attributes), 1);
+        DPRINTF("ADMA loop: addr=0x%x, len=%d, attr=%x\n", address,
+                length_table, 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;
+
+            /* Generate ADMA error interrupt */
+            if (s->errintstsen & SDHC_EISEN_ADMAERR) {
+                s->errintsts |= SDHC_EIS_ADMAERR;
+                s->norintsts |= SDHC_NIS_ERR;
+            }
+            qemu_set_irq(s->irq, (s->errintsigen & s->errintsts));
+            break;
+        }
+
+        if (length_table == 0) {
+            length = 65536;
+        } else {
+            length = length_table;
+        }
+
+        address &= 0xfffffffc;  /* minimum unit of address is 4 byte */
+
+        switch (attributes & SDHC_ADMA_ATTR_ACT_MASK) {
+        case (SDHC_ADMA_ATTR_ACT_TRAN):  /* data transfer */
+            for (;;) {
+                if (s->data_count) {
+                    datacnt = s->data_count;
+                    s->data_count = 0;
+                } else {
+                    datacnt = s->blksize & 0x0fff;
+                }
+
+                length -= datacnt;
+
+                if (length < 0) {
+                    s->data_count = (uint16_t)(-length);
+                    datacnt += length;
+                }
+
+                while (datacnt) {
+                    if (is_read) {
+                        value |= (uint32_t)sd_read_data(s->card) << (n * 8);
+                        n++;
+                        if (n == 4) {
+                            cpu_physical_memory_write(address + pos,
+                                    (uint8_t *)(&value), 4);
+                            value = 0;
+                            n = 0;
+                            pos += 4;
+                        }
+                    } else {
+                        if (n == 0) {
+                            cpu_physical_memory_read(address + pos,
+                                    (uint8_t *)(&value), 4);
+                            n = 4;
+                            pos += 4;
+                        }
+                        sd_write_data(s->card, value & 0xff);
+                        value >>= 8;
+                        n--;
+                    }
+                    datacnt--;
+                }
+
+                if ((s->trnmod & SDHC_TRNS_BLK_CNT_EN) &&
+                        (!s->data_count)) {
+                    s->blkcnt--;
+                    if (s->blkcnt == 0) {
+                        break;
+                    }
+                }
+
+                if (length == 0 || s->data_count) {
+                    break;
+                }
+            }
+            s->admasysaddr += 8;
+            break;
+        case (SDHC_ADMA_ATTR_ACT_LINK):   /* link to next descriptor table */
+            s->admasysaddr = address;
+            DPRINTF("ADMA link: admasysaddr=0x%x\n", s->admasysaddr);
+            break;
+        default:
+            s->admasysaddr += 8;
+            break;
+        }
+
+        /* ADMA transfer terminates if blkcnt == 0 or by END attribute */
+        if (((s->trnmod & SDHC_TRNS_BLK_CNT_EN) &&
+                    (s->blkcnt == 0)) || (attributes & SDHC_ADMA_ATTR_END)) {
+            DPRINTF("ADMA transfer completed\n");
+            if (length || ((attributes & SDHC_ADMA_ATTR_END) &&
+                (s->trnmod & SDHC_TRNS_BLK_CNT_EN) &&
+                s->blkcnt != 0) || ((s->trnmod & SDHC_TRNS_BLK_CNT_EN) &&
+                s->blkcnt == 0 && (attributes & SDHC_ADMA_ATTR_END) == 0)) {
+                DPRINTF("SD/MMC host ADMA length mismatch\n");
+                s->admaerr |= SDHC_ADMAERR_LENGTH_MISMATCH |
+                        SDHC_ADMAERR_STATE_ST_TFR;
+                if (s->errintstsen & SDHC_EISEN_ADMAERR) {
+                    DPRINTF("Set ADMA error flag\n");
+                    s->errintsts |= SDHC_EIS_ADMAERR;
+                    s->norintsts |= SDHC_NIS_ERR;
+                }
+                qemu_set_irq(s->irq, (s->errintsigen & s->errintsts));
+            }
+
+            s->admaerr |= SDHC_ADMAERR_FINAL_BLOCK;
+            sdhc_do_transfer_complete(s);
+            break;
+        }
+
+        if (attributes & SDHC_ADMA_ATTR_INT) {
+            DPRINTF("ADMA interrupt: admasysaddr=0x%x\n", s->admasysaddr);
+            s->admaerr |= SDHC_ADMAERR_INTRRUPT_STATUS;
+            s->stoped_state = adma_intr;
+            if (s->norintstsen & SDHC_NISEN_DMA) {
+                s->norintsts |= SDHC_NIS_DMA;
+            }
+            qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+            break;
+        }
+    }
+}
+
+/* Perform data transfer according to controller configuration */
+static void sdhc_process_command(Exynos4210SDHCState *s)
+{
+    int is_read = (s->trnmod & SDHC_TRNS_READ) != 0;
+    if (s->trnmod & SDHC_TRNS_DMA) {
+        if (!((is_read && sd_data_ready(s->card)) ||
+                (!is_read && sd_receive_ready(s->card)))) {
+            return;
+        }
+
+        switch (s->hostctl & SDHC_CTRL_DMA_CHECK_MASK) {
+        case SDHC_CTRL_SDMA:
+            if ((s->trnmod & SDHC_TRNS_MULTI) &&
+                    ((s->trnmod & SDHC_TRNS_BLK_CNT_EN) == 0
+                            || s->blkcnt == 0)) {
+                break;
+            }
+
+            if ((s->blkcnt == 1) || !(s->trnmod & SDHC_TRNS_MULTI)) {
+                sdhc_sdma_transfer_single_block(s);
+            } else {
+                s->data_count = 0;  /* # of bytes from last transfer */
+                sdhc_sdma_transfer_multi_blocks(s);
+            }
+            break;
+        case SDHC_CTRL_ADMA2_32:
+            s->data_count = 0;  /* # of bytes from last transfer */
+            sdhc_run_adma(s);
+            break;
+        default:
+            DPRINTF("Unsupported DMA type\n");
+            break;
+        }
+    } else {
+        if (is_read && sd_data_ready(s->card)) {
+                s->prnsts |= SDHC_DOING_READ | SDHC_DATA_INHIBIT |
+                        SDHC_DAT_LINE_ACTIVE;
+                s->data_count = 0;
+                qemu_mod_timer(s->read_buffer_timer,
+                    qemu_get_clock_ns(vm_clock) + READ_BUFFER_DELAY);
+        } else if (!is_read && sd_receive_ready(s->card)) {
+                s->prnsts |= SDHC_DOING_WRITE | SDHC_DAT_LINE_ACTIVE |
+                        SDHC_SPACE_AVAILABLE | SDHC_DATA_INHIBIT;
+                s->data_count = 0;
+                qemu_mod_timer(s->write_buffer_timer,
+                    qemu_get_clock_ns(vm_clock) + WRITE_BUFFER_DELAY);
+        }
+    }
+}
+
+/* Read byte from SD/MMC host controller registers */
+static uint32_t exynos4210_sdhc_read_1(Exynos4210SDHCState *s,
+        target_phys_addr_t offset)
+{
+    switch (offset) {
+    case SDHC_HOSTCTL:
+        return s->hostctl;
+    case SDHC_PWRCON:
+        return s->pwrcon;
+    case SDHC_BLKGAP:
+        return s->blkgap;
+    case SDHC_WAKCON:
+        return s->wakcon;
+    case SDHC_TIMEOUTCON:
+        return s->timeoutcon;
+    case SDHC_SWRST:
+        return 0;
+    case SDHC_RSPREG0 + 0x3:
+        return (uint8_t)(s->rspreg[0] >> 24);
+    case SDHC_RSPREG1 + 0x3:
+        return (uint8_t)(s->rspreg[1] >> 24);
+    case SDHC_RSPREG2 + 0x3:
+        return (uint8_t)(s->rspreg[2] >> 24);
+    default:
+        DPRINTF("bad 1 byte read offset " TARGET_FMT_plx "\n", offset);
+        return 0xBAADBAAD;
+    }
+}
+
+/* Read two bytes from SD/MMC host controller registers */
+static uint32_t exynos4210_sdhc_read_2(Exynos4210SDHCState *s,
+        target_phys_addr_t offset)
+{
+    switch (offset) {
+    case SDHC_BLKSIZE:
+        return s->blksize;
+    case SDHC_BLKCNT:
+        return s->blkcnt;
+    case SDHC_TRNMOD:
+        return s->trnmod;
+    case SDHC_CMDREG:
+        return s->cmdreg;
+    case SDHC_CLKCON:
+        return s->clkcon;
+    case SDHC_NORINTSTS:
+        qemu_set_irq(s->irq, 0);
+        return s->norintsts;
+    case SDHC_ERRINTSTS:
+        qemu_set_irq(s->irq, 0);
+        return s->errintsts;
+    case SDHC_NORINTSTSEN:
+        return s->norintstsen;
+    case SDHC_ERRINTSTSEN:
+        return s->errintstsen;
+    case SDHC_NORINTSIGEN:
+        return s->norintsigen;
+    case SDHC_ERRINTSIGEN:
+        return s->errintsigen;
+    case SDHC_ACMD12ERRSTS:
+        return s->acmd12errsts;
+    case SDHC_SLOT_INT_STATUS:
+        return 0;
+    case SDHC_FEAER: case SDHC_FEERR:
+        DPRINTF("Reading from WO register\n");
+        return 0;
+    case SDHC_HCVER:
+        return 0x2401;  /* SD Host Specification Version 2.0 */
+    default:
+        DPRINTF("bad 2 byte read offset " TARGET_FMT_plx "\n", offset);
+        return 0xBAADBAAD;
+    }
+}
+
+/* MMC read 4 bytes function */
+static uint32_t exynos4210_sdhc_read_4(Exynos4210SDHCState *s,
+        target_phys_addr_t offset)
+{
+    switch (offset) {
+    case SDHC_SYSAD:
+        return s->sdmasysad;
+    case SDHC_ARGUMENT:
+        return s->argument;
+    case SDHC_RSPREG0:
+        return s->rspreg[0];
+    case SDHC_RSPREG1:
+        return s->rspreg[1];
+    case SDHC_RSPREG2:
+        return s->rspreg[2];
+    case SDHC_RSPREG3:
+        return s->rspreg[3];
+    case SDHC_BDATA:
+        return sdhc_read_dataport(s);
+    case SDHC_PRNSTS:
+        return s->prnsts;
+    case SDHC_NORINTSTS:
+        qemu_set_irq(s->irq, 0);
+        return (s->errintsts << 16) | (s->norintsts);
+    case SDHC_NORINTSTSEN:
+        return (s->errintstsen << 16) | s->norintstsen;
+    case SDHC_NORINTSIGEN:
+        return (s->errintsigen << 16) | s->norintsigen;
+    case SDHC_CAPAREG:
+        return s->capareg;
+    case SDHC_MAXCURR:
+        return s->maxcurr;
+    case SDHC_ADMAERR:
+        return s->admaerr;
+    case SDHC_ADMASYSADDR:
+        return s->admasysaddr;
+    case SDHC_CONTROL2:
+        return s->control2;
+    case SDHC_CONTROL3:
+        return s->control3;
+    case SDHC_CONTROL4:
+        return 0;  /* makes no sense in emulation so return 0 */
+    default:
+        DPRINTF("bad 4 byte read offset " TARGET_FMT_plx "\n", offset);
+        return 0xBAADBAAD;
+    }
+}
+
+/* MMC write (byte) function */
+static void
+exynos4210_sdhc_write_1(Exynos4210SDHCState *s, target_phys_addr_t offset,
+        uint32_t value)
+{
+    switch (offset) {
+    case SDHC_HOSTCTL:
+        s->hostctl = value & 0x3F;
+        break;
+    case SDHC_PWRCON:
+        s->pwrcon = value & 0x0F;
+        break;
+    case SDHC_BLKGAP:
+        if (value & 0x0C) {
+            error_report("SDHC: ReadWait & IntAtBlockGap not implemented\n");
+        }
+
+        if ((value & SDHC_STOP_AT_GAP_REQ) &&
+                (s->blkgap & SDHC_STOP_AT_GAP_REQ)) {
+            break;
+        }
+        s->blkgap = value & (SDHC_STOP_AT_GAP_REQ);
+
+        if ((value & SDHC_CONTINUE_REQ) && s->stoped_state &&
+                (s->blkgap & SDHC_STOP_AT_GAP_REQ) == 0) {
+            if (s->stoped_state == gap_read) {
+                s->prnsts |= SDHC_DAT_LINE_ACTIVE | SDHC_DOING_READ;
+                qemu_mod_timer(s->read_buffer_timer,
+                    qemu_get_clock_ns(vm_clock) + READ_BUFFER_DELAY);
+            } else {
+            s->prnsts |= SDHC_DAT_LINE_ACTIVE | SDHC_DOING_WRITE;
+            qemu_mod_timer(s->write_buffer_timer,
+                qemu_get_clock_ns(vm_clock) + READ_BUFFER_DELAY);
+            }
+            s->stoped_state = not_stoped;
+        } else if (!s->stoped_state && (value & SDHC_STOP_AT_GAP_REQ)) {
+            if (s->prnsts & SDHC_DOING_READ) {
+                s->stoped_state = gap_read;
+            } else if (s->prnsts & SDHC_DOING_WRITE) {
+                s->stoped_state = gap_write;
+            }
+        }
+        break;
+    case SDHC_WAKCON:
+        s->wakcon = value & 0x07;
+        s->wakcon &= ~(value & SDHC_STAWAKEUP);
+        break;
+    case SDHC_TIMEOUTCON:
+        s->timeoutcon = value & 0x0F;
+        break;
+    case SDHC_SWRST:
+        switch (value) {
+        case SDHC_RESET_ALL:
+            exynos4210_sdhc_reset(&s->busdev.qdev);
+            break;
+        case SDHC_RESET_CMD:
+            s->prnsts &= ~SDHC_CMD_INHIBIT;
+            s->norintsts &= ~SDHC_NIS_CMDCMP;
+            break;
+        case SDHC_RESET_DATA:
+            s->data_count = 0;
+            qemu_del_timer(s->read_buffer_timer);
+            qemu_del_timer(s->write_buffer_timer);
+            s->prnsts &= ~(SDHC_SPACE_AVAILABLE | SDHC_DATA_AVAILABLE |
+                    SDHC_DOING_READ | SDHC_DOING_WRITE |
+                    SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE);
+            s->blkgap &= ~(SDHC_STOP_AT_GAP_REQ | SDHC_CONTINUE_REQ);
+            s->stoped_state = not_stoped;
+            s->norintsts &= ~(SDHC_NIS_WBUFRDY | SDHC_NIS_RBUFRDY |
+                    SDHC_NIS_DMA | SDHC_NIS_TRSCMP | SDHC_NIS_BLKGAP);
+            break;
+        }
+        break;
+    case (SDHC_NORINTSTS+1):
+        if (s->norintstsen & SDHC_NISEN_CARDINT) {
+            value &= ~1;
+        }
+        s->norintsts &= (s->norintsts & 0x8000) | (~((value << 8) & 0xFF00));
+        break;
+    case (SDHC_NORINTSTS):
+        s->norintsts &= ~(((uint16_t)value) & 0x00FF);
+        break;
+    default:
+        DPRINTF("bad 1 byte write offset " TARGET_FMT_plx "\n", offset);
+        break;
+    }
+}
+
+/* MMC write 2 bytes function */
+static void
+exynos4210_sdhc_write_2(Exynos4210SDHCState *s, target_phys_addr_t offset,
+        uint32_t value)
+{
+    switch (offset) {
+    case SDHC_BLKSIZE:
+        if (!(s->prnsts & (SDHC_DOING_READ | SDHC_DOING_WRITE))) {
+            s->blksize = value & 0x7FFF;
+        }
+        break;
+    case SDHC_BLKCNT:
+        if (!(s->prnsts & (SDHC_DOING_READ | SDHC_DOING_WRITE))) {
+            s->blkcnt = value;
+        }
+        break;
+    case SDHC_TRNMOD:
+        if (value & (SDHC_TRNS_BOOTCMD | SDHC_TRNS_BOOTACK |
+                SDHC_TRNS_CEATA)) {
+            error_report("QEMU SDHC: CEATA mode not implemented\n");
+        }
+        if ((s->capareg & SDHC_CAN_DO_DMA) ==  0) {
+            value &= ~SDHC_TRNS_DMA;
+        }
+        s->trnmod = value & 0x0037;
+        break;
+    case SDHC_CMDREG: /* Command */
+        s->cmdreg = value & 0x3FFB;
+
+        if (((s->control2 & SDHC_SDOPSIGPC) || (s->control2 & SDHC_SDINPSIGPC))
+                && !(s->pwrcon & SDHC_POWER_ON)) {
+            DPRINTF("Can't issue command with power off\n");
+            break;
+        }
+
+        if ((s->clkcon & SDHC_CLOCK_CHK_MASK) != SDHC_CLOCK_CHK_MASK) {
+            DPRINTF("Can't issue command with clock off\n");
+            break;
+        }
+
+        if ((value & SDHC_CMD_TYPE_MASK) == SDHC_CMD_RESUME_MASK ||
+                (value & SDHC_CMD_TYPE_MASK) == SDHC_CMD_SUSPEND_MASK) {
+            DPRINTF("sdhc: suspend/resume commands not implemented\n");
+            break;
+        }
+
+        if ((s->stoped_state || (s->prnsts & SDHC_DATA_INHIBIT)) &&
+        ((value & SDHC_CMD_DATA_PRESENT) || ((value & SDHC_CMD_RSP_WITH_BUSY) &&
+                    !(value & SDHC_CMD_ABORT_MASK)))) {
+            DPRINTF("Can't issue command which uses DAT line\n");
+            break;
+        }
+
+        if ((value & SDHC_CMD_TYPE_MASK) == SDHC_CMD_ABORT_MASK &&
+            ((s->prnsts & SDHC_DOING_READ) || (s->prnsts & SDHC_DOING_WRITE))) {
+            DPRINTF("ABORT command\n");
+            qemu_del_timer(s->read_buffer_timer);   /* stop reading data */
+            qemu_del_timer(s->write_buffer_timer);  /* stop writing data */
+            qemu_mod_timer(s->transfer_complete_timer,
+                    qemu_get_clock_ns(vm_clock) + 1);
+        }
+
+        sdhc_send_command(s);
+        sdhc_raise_response_recieved_irq(s);
+
+        if (s->blksize == 0) {
+            break;
+        }
+        sdhc_process_command(s);
+        break;
+    case SDHC_CLKCON:
+        s->clkcon = value;
+        if (SDHC_CLOCK_INT_EN & s->clkcon) {
+            s->clkcon |= SDHC_CLOCK_INT_STABLE;
+        } else {
+            s->clkcon &= ~SDHC_CLOCK_INT_STABLE;
+        }
+        if (SDHC_CLOCK_CARD_EN & s->clkcon) {
+            s->clkcon |= SDHC_CLOCK_EXT_STABLE;
+        } else {
+            s->clkcon &= ~SDHC_CLOCK_EXT_STABLE;
+        }
+        break;
+    case SDHC_NORINTSTS:
+        if (s->norintstsen & SDHC_NISEN_CARDINT) {
+            value &= ~SDHC_NIS_CARDINT;
+        }
+        s->norintsts &= (s->norintsts & 0x8000) | (uint16_t)(~(value & 0xFFFF));
+        break;
+    case SDHC_ERRINTSTS:
+        s->errintsts &= ~((value & 0x077F) | 0xF880);
+        s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) :
+                (s->norintsts &= ~SDHC_NIS_ERR);
+        break;
+    case SDHC_NORINTSTSEN:
+        s->norintstsen = value & 0x7FFF;
+        break;
+    case SDHC_ERRINTSTSEN:
+        s->errintstsen = value & 0x07FF;
+        break;
+    case SDHC_NORINTSIGEN:
+        s->norintsigen = value & 0x7FFF;
+        break;
+    case SDHC_ERRINTSIGEN:
+        s->errintsigen = value & 0x07FF;
+        break;
+    case SDHC_ACMD12ERRSTS: case SDHC_FEAER: case SDHC_HCVER:
+        break;
+    case SDHC_FEERR:
+        s->norintsts |= (value & 0x073F) & s->norintstsen;
+        qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+        break;
+    default:
+        DPRINTF("bad 2 byte write offset " TARGET_FMT_plx "\n", offset);
+        break;
+    }
+}
+
+/* MMC write 4 bytes function */
+static void
+exynos4210_sdhc_write_4(Exynos4210SDHCState *s, target_phys_addr_t offset,
+                                 uint32_t value)
+{
+    switch (offset) {
+    case SDHC_SYSAD:
+        s->sdmasysad = value;
+        if ((s->prnsts & (SDHC_DOING_READ | SDHC_DOING_WRITE))
+                && (s->blkcnt != 0) && (s->blksize != 0) &&
+                (s->hostctl & SDHC_CTRL_DMA_CHECK_MASK) == SDHC_CTRL_SDMA) {
+            sdhc_sdma_transfer_multi_blocks(s);
+        }
+        break;
+    case SDHC_ARGUMENT:
+        s->argument = value;
+        break;
+    case SDHC_BDATA:
+        sdhc_write_dataport(s, value);
+        break;
+    case SDHC_NORINTSTS:
+        if (s->norintstsen & SDHC_NISEN_CARDINT) {
+            value &= ~SDHC_NIS_CARDINT;
+        }
+        s->norintsts &= (s->norintsts & 0x8000) | (uint16_t)(~(value & 0xFFFF));
+
+        s->errintsts &= ~(((value >> 16) & 0x077F) | 0xF880);
+        s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) :
+                (s->norintsts &= ~SDHC_NIS_ERR);
+        break;
+    case SDHC_NORINTSTSEN:
+        s->norintstsen = (uint16_t)(value & 0x7FFF);
+        s->errintstsen = (uint16_t)((value >> 16) & 0x07FF);
+        break;
+    case SDHC_NORINTSIGEN:
+        s->norintsigen = (uint16_t)(value & 0x7FFF);
+        s->errintsigen = (uint16_t)((value >> 16) & 0x07FF);
+        break;
+    case SDHC_CAPAREG:
+        if (!(s->control2 & SDHC_HWINITFIN)) {
+            s->capareg = value & 0x07EB3FBF;
+        }
+        break;
+    case SDHC_MAXCURR:
+        if (!(s->control2 & SDHC_HWINITFIN)) {
+            s->maxcurr = value & 0x00FFFFFF;
+        }
+        break;
+    case SDHC_ADMAERR:
+        s->admaerr = ((s->admaerr & SDHC_ADMAERR_FINAL_BLOCK) |
+                (value & 0x00000007)) & 0x00000507;
+        s->admaerr &= ~(value & SDHC_ADMAERR_INTRRUPT_STATUS);
+        if (value & SDHC_ADMAERR_CONTINUE_REQUEST) {
+            s->stoped_state = not_stoped;
+            sdhc_run_adma(s);
+        }
+        break;
+    case SDHC_ADMASYSADDR:
+        s->admasysaddr = value;
+        break;
+    case SDHC_CONTROL2:
+        s->control2 = value & 0xDF00DFFB;
+        break;
+    case SDHC_CONTROL3:
+        s->control3 = value;
+        break;
+    case SDHC_RSPREG0: case SDHC_RSPREG1: case SDHC_RSPREG2:
+    case SDHC_RSPREG3: case SDHC_PRNSTS: case SDHC_CONTROL4:
+        /* Nothing for emulation */
+        break;
+    default:
+        DPRINTF("bad 4 byte write offset " TARGET_FMT_plx "\n", offset);
+        break;
+    }
+}
+
+static uint64_t
+exynos4210_sdhc_read(void *opaque, target_phys_addr_t offset, unsigned size)
+{
+    Exynos4210SDHCState *s = (Exynos4210SDHCState *)opaque;
+
+    switch (size) {
+    case (1):
+        return exynos4210_sdhc_read_1(s, offset);
+    case (2):
+        return exynos4210_sdhc_read_2(s, offset);
+    case (4):
+        return exynos4210_sdhc_read_4(s, offset);
+    }
+    return 0xBAADBAADull;
+}
+
+static void
+exynos4210_sdhc_write(void *opaque, target_phys_addr_t offset, uint64_t val,
+        unsigned size)
+{
+    Exynos4210SDHCState *s = (Exynos4210SDHCState *)opaque;
+
+    switch (size) {
+    case (1):
+        exynos4210_sdhc_write_1(s, offset, (uint32_t)val);
+        break;
+    case (2):
+        exynos4210_sdhc_write_2(s, offset, (uint32_t)val);
+        break;
+    case (4):
+        exynos4210_sdhc_write_4(s, offset, (uint32_t)val);
+        break;
+    }
+}
+
+static const MemoryRegionOps exynos4210_sdhc_mmio_ops = {
+    .read = exynos4210_sdhc_read,
+    .write = exynos4210_sdhc_write,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 4,
+        .unaligned = false
+    },
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription exynos4210_sdhc_vmstate = {
+    .name = "exynos4210.sdhc",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields      = (VMStateField[]) {
+        VMSTATE_UINT8(stoped_state, Exynos4210SDHCState),
+        VMSTATE_UINT32_ARRAY(fifo_buffer, Exynos4210SDHCState,
+                (SDHC_CAPAB_MAXBLOCKLENGTH / TARGET_WORD_SIZE)),
+        VMSTATE_UINT16(data_count, Exynos4210SDHCState),
+        VMSTATE_UINT32(sdmasysad, Exynos4210SDHCState),
+        VMSTATE_UINT16(blksize, Exynos4210SDHCState),
+        VMSTATE_UINT16(blkcnt, Exynos4210SDHCState),
+        VMSTATE_UINT32(argument, Exynos4210SDHCState),
+        VMSTATE_UINT16(trnmod, Exynos4210SDHCState),
+        VMSTATE_UINT16(cmdreg, Exynos4210SDHCState),
+        VMSTATE_UINT32_ARRAY(rspreg, Exynos4210SDHCState, 4),
+        VMSTATE_UINT32(prnsts, Exynos4210SDHCState),
+        VMSTATE_UINT8(hostctl, Exynos4210SDHCState),
+        VMSTATE_UINT8(pwrcon, Exynos4210SDHCState),
+        VMSTATE_UINT8(blkgap, Exynos4210SDHCState),
+        VMSTATE_UINT8(wakcon, Exynos4210SDHCState),
+        VMSTATE_UINT16(clkcon, Exynos4210SDHCState),
+        VMSTATE_UINT8(timeoutcon, Exynos4210SDHCState),
+        VMSTATE_UINT16(norintsts, Exynos4210SDHCState),
+        VMSTATE_UINT16(errintsts, Exynos4210SDHCState),
+        VMSTATE_UINT16(norintstsen, Exynos4210SDHCState),
+        VMSTATE_UINT16(errintstsen, Exynos4210SDHCState),
+        VMSTATE_UINT16(norintsigen, Exynos4210SDHCState),
+        VMSTATE_UINT16(errintsigen, Exynos4210SDHCState),
+        VMSTATE_UINT16(acmd12errsts, Exynos4210SDHCState),
+        VMSTATE_UINT32(capareg, Exynos4210SDHCState),
+        VMSTATE_UINT32(maxcurr, Exynos4210SDHCState),
+        VMSTATE_UINT32(admaerr, Exynos4210SDHCState),
+        VMSTATE_UINT32(admasysaddr, Exynos4210SDHCState),
+        VMSTATE_UINT32(control2, Exynos4210SDHCState),
+        VMSTATE_UINT32(control3, Exynos4210SDHCState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static int exynos4210_sdhc_init(SysBusDevice *dev)
+{
+    Exynos4210SDHCState *s = FROM_SYSBUS(Exynos4210SDHCState, dev);
+    DriveInfo *bd;
+
+    sysbus_init_irq(dev, &s->irq);
+    memory_region_init_io(&s->iomem, &exynos4210_sdhc_mmio_ops, s,
+            "exynos4210.sdhc", SDHC_REG_SIZE);
+    sysbus_init_mmio(dev, &s->iomem);
+    bd = drive_get_next(IF_SD);
+
+    if ((bd == NULL)) {
+        s->card = NULL;
+        DPRINTF("s->card = NULL\n");
+    } else {
+        s->eject = qemu_allocate_irqs(sdhc_insert_eject, s, 1)[0];
+        DPRINTF("name = %s, sectors = %ld\n",
+                bd->bdrv->device_name, bd->bdrv->total_sectors);
+        s->card = sd_init(bd->bdrv, 0);
+        sd_set_cb(s->card, NULL, s->eject);
+    }
+
+    s->insert_timer =
+        qemu_new_timer(vm_clock, SCALE_NS, sdhc_raise_insertion_irq, s);
+
+    s->read_buffer_timer =
+        qemu_new_timer(vm_clock, SCALE_NS, sdhc_read_block_from_card, s);
+
+    s->write_buffer_timer =
+        qemu_new_timer(vm_clock, SCALE_NS, sdhc_write_block_to_card, s);
+
+    s->transfer_complete_timer =
+        qemu_new_timer(vm_clock, SCALE_NS, sdhc_raise_transfer_complete_irq, s);
+
+    return 0;
+}
+
+static SysBusDeviceInfo exynos4210_sdhc_info = {
+    .init = exynos4210_sdhc_init,
+    .qdev.name  = "exynos4210.sdhc",
+    .qdev.size  = sizeof(Exynos4210SDHCState),
+    .qdev.vmsd  = &exynos4210_sdhc_vmstate,
+    .qdev.reset = exynos4210_sdhc_reset,
+};
+
+static void sdhc_register_devices(void)
+{
+    sysbus_register_withprop(&exynos4210_sdhc_info);
+}
+
+device_init(sdhc_register_devices)