qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH V3 5/5] hw: Introduce spec. ver. 2.00 compliant SD h


From: Mitsyanko Igor
Subject: [Qemu-devel] [PATCH V3 5/5] hw: Introduce spec. ver. 2.00 compliant SD host controller
Date: Wed, 28 Dec 2011 19:32:22 +0400

This patch adds implementation of "SD specification version 2.00" compliant
SD host controller. Also it provides interface to implement SoC-specific
controllers on top of this specification-compliant one.

Signed-off-by: Mitsyanko Igor <address@hidden>
---
 Makefile.target |    1 +
 hw/sdhc_ver2.c  | 1569 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/sdhc_ver2.h  |  327 ++++++++++++
 3 files changed, 1897 insertions(+), 0 deletions(-)
 create mode 100644 hw/sdhc_ver2.c
 create mode 100644 hw/sdhc_ver2.h

diff --git a/Makefile.target b/Makefile.target
index 3261383..79c33ac 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -358,6 +358,7 @@ obj-arm-y += vexpress.o
 obj-arm-y += strongarm.o
 obj-arm-y += collie.o
 obj-arm-y += pl041.o lm4549.o
+obj-arm-y += sdhc_ver2.o
 
 obj-sh4-y = shix.o r2d.o sh7750.o sh7750_regnames.o tc58128.o
 obj-sh4-y += sh_timer.o sh_serial.o sh_intc.o sh_pci.o sm501.o
diff --git a/hw/sdhc_ver2.c b/hw/sdhc_ver2.c
new file mode 100644
index 0000000..1803ae6
--- /dev/null
+++ b/hw/sdhc_ver2.c
@@ -0,0 +1,1569 @@
+/*
+ * SD host controller (SD Host Controller Simplified
+ * Specification Version 2.00 compliant) emulation
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ * Contributed by Mitsyanko Igor <address@hidden>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw.h"
+#include "block_int.h"
+#include "blockdev.h"
+#include "qemu-timer.h"
+#include "sdhc_ver2.h"
+
+/* host controller debug messages */
+#define SDHC_DEBUG                        0
+
+#if SDHC_DEBUG == 0
+    #define DPRINT_L1(fmt, args...)       do { } while (0)
+    #define DPRINT_L2(fmt, args...)       do { } while (0)
+    #define DPRINT_ERROR(fmt, args...)    do { } while (0)
+#elif 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 DPRINT_ERROR(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 DPRINT_ERROR(fmt, args...)  \
+        do {fprintf(stderr, "QEMU SDHC ERROR: "fmt, ## args); } while (0)
+#endif
+
+/* Default SD/MMC host controller features information, which will be
+ * presented in CAPABILITIES register of generic SD host controller 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))
+
+static void sdhcv2_transfer_complete_irq(void *opaque)
+{
+    SDHCv2State *s = (SDHCv2State *)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;
+    }
+    s->slotint |= (s->norintsigen & s->norintsts) ? 1 : 0;
+    qemu_set_irq(s->irq, s->norintsigen & s->norintsts);
+}
+
+/* raise command response received interrupt */
+void sdhcv2_raise_response_recieved_irq(SDHCv2State *s)
+{
+    DPRINT_L2("raise IRQ response\n");
+
+    if (s->norintstsen & SDHC_NISEN_CMDCMP) {
+        s->norintsts |= SDHC_NIS_CMDCMP;
+    }
+
+    DPRINT_L2("Interrupt request %s\n", (s->norintsts & s->norintsigen) ||
+            (s->errintsts & s->errintsigen) ? "raised" : "lowered");
+    s->slotint |= ((s->norintsts & s->norintsigen) ||
+            (s->errintsts & s->errintsigen)) ? 1 : 0;
+    qemu_set_irq(s->irq, (s->norintsts & s->norintsigen) ||
+            (s->errintsts & s->errintsigen));
+}
+
+static void sdhcv2_raise_insertion_irq(void *opaque)
+{
+    SDHCv2State *s = (SDHCv2State *)opaque;
+    bool int_raised = false;
+
+    if (s->norintsts & SDHC_NIS_REMOVE) {
+        qemu_mod_timer(s->insert_timer,
+                       qemu_get_clock_ns(vm_clock) + SDHC_INSERTION_DELAY);
+    } else {
+        s->prnsts = 0x1ff0000;
+        if (s->norintstsen & SDHC_NISEN_INSERT) {
+            s->norintsts |= SDHC_NIS_INSERT;
+            int_raised = (s->norintsigen & SDHC_NORINTSIG_INSERT) ||
+                    (s->wakcon & SDHC_WKUP_ON_INSERT);
+            s->slotint |= (int_raised ? 1 : 0);
+        }
+        qemu_set_irq(s->irq, int_raised);
+    }
+}
+
+static void sdhcv2_insert_eject_cb(void *opaque, int irq, int level)
+{
+    SDHCv2State *s = (SDHCv2State *)opaque;
+    DPRINT_L1("Card state changed: %s!\n", level ? "insert" : "eject");
+    bool int_raised = false;
+
+    if (s->norintsts & SDHC_NIS_REMOVE) {
+        if (level) {
+            DPRINT_L2("Change card state: timer set!\n");
+            qemu_mod_timer(s->insert_timer,
+                           qemu_get_clock_ns(vm_clock) + SDHC_INSERTION_DELAY);
+        }
+    } else {
+        if (level) {
+            s->prnsts = 0x1ff0000;
+            if (s->norintstsen & SDHC_NISEN_INSERT) {
+                s->norintsts |= SDHC_NIS_INSERT;
+                int_raised = (s->norintsigen & SDHC_NORINTSIG_INSERT) ||
+                        (s->wakcon & SDHC_WKUP_ON_INSERT);
+                s->slotint |= (int_raised ? 1 : 0);
+            }
+        } else {
+            s->prnsts = 0x1fa0000;
+            s->pwrcon &= ~SDHC_POWER_ON;
+            s->clkcon &= ~SDHC_CLOCK_SDCLK_EN;
+            if (s->norintstsen & SDHC_NISEN_REMOVE) {
+                s->norintsts |= SDHC_NIS_REMOVE;
+                int_raised = (s->norintsigen & SDHC_NORINTSIG_REMOVE) ||
+                        (s->wakcon & SDHC_WKUP_ON_REMOVE);
+                s->slotint |= (int_raised ? 1 : 0);
+            }
+        }
+        qemu_set_irq(s->irq, int_raised);
+    }
+}
+
+static void sdhcv2_card_readonly_cb(void *opaque, int irq, int level)
+{
+    SDHCv2State *s = (SDHCv2State *)opaque;
+    level ? (s->prnsts &= ~SDHC_WRITE_PROTECT) :
+            (s->prnsts |= SDHC_WRITE_PROTECT);
+}
+
+void sdhcv2_reset(SDHCv2State *s)
+{
+    /* Set all registers to 0. Capabilities registers are not cleared
+     * and assumed to always preserve their value, given to them during
+     * initialization */
+    memset(&s->sdmasysad, 0, (uintptr_t)&s->capareg - 
(uintptr_t)&s->sdmasysad);
+
+    s->card ? (s->prnsts = 0x1ff0000) : (s->prnsts = 0x1fa0000);
+    s->data_count = 0;
+    s->stoped_state = sdhc_not_stoped;
+}
+
+void sdhcv2_send_command(SDHCv2State *s)
+{
+    SDRequest request;
+    uint8_t response[16];
+    int rlen;
+    s->errintsts = 0;
+    s->acmd12errsts = 0;
+    if (!s->card) {
+        goto error;
+    }
+
+    request.cmd = s->cmdreg >> 8;
+    request.arg = s->argument;
+    DPRINT_L1("Sending command %u with argument %08x\n",
+            request.cmd, request.arg);
+    rlen = sd_do_command(s->card, &request, response);
+    if (rlen < 0) {
+        goto error;
+    }
+    if ((s->cmdreg & SDHC_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);
+        }
+#undef RWORD
+        DPRINT_L1("Response received:\n RSPREG[127..96]=0x%08x, 
RSPREG[95..64]="
+                "0x%08x,\n RSPREG[63..32]=0x%08x, RSPREG[31..0]=0x%08x\n",
+                s->rspreg[3], s->rspreg[2], s->rspreg[1], s->rspreg[0]);
+    }
+    return;
+
+error:
+    DPRINT_ERROR("Timeout waiting for command response\n");
+    if (s->errintstsen & SDHC_EISEN_CMDTIMEOUT) {
+        s->errintsts |= SDHC_EIS_CMDTIMEOUT;
+        s->norintsts |= SDHC_NIS_ERR;
+    }
+}
+
+void sdhcv2_do_transfer_complete(SDHCv2State *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;
+        DPRINT_L1("Automatically issue CMD%d %08x\n", request.cmd, 
request.arg);
+        sd_do_command(s->card, &request, response);
+        /* Auto CMD12 response goes to the upper Response register */
+        s->rspreg[3] = (response[0] << 24) | (response[1] << 16) |
+                (response[2] << 8) | response[3];
+    }
+    /* 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 controller's read buffer with BLKSIZE bytes of data from card */
+static void sdhcv2_read_block_from_card(void *opaque)
+{
+    SDHCv2State *s = (SDHCv2State *)opaque;
+    int index = 0;
+
+    if ((s->trnmod & SDHC_TRNS_MULTI) &&
+            (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0)) {
+        return;
+    }
+
+    for (index = 0; index < (s->blksize & 0x0fff); index++) {
+        s->fifo_buffer[index] = sd_read_data(s->card);
+    }
+
+    /* 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 == sdhc_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;
+        }
+    }
+
+    s->slotint |= (s->norintsts & s->norintsigen) ? 1 : 0;
+    qemu_set_irq(s->irq, s->norintsts & s->norintsigen);
+}
+
+/* Read @size byte of data from host controller @s BUFFER DATA PORT register */
+uint32_t sdhcv2_read_dataport(SDHCv2State *s, unsigned size)
+{
+    uint32_t value = 0;
+    int i;
+
+    /* first check that a valid data exists in host controller input buffer */
+    if ((s->prnsts & SDHC_DATA_AVAILABLE) == 0) {
+        DPRINT_ERROR("Trying to read from empty buffer\n");
+        return 0;
+    }
+
+    for (i = 0; i < size; i++) {
+        value |= s->fifo_buffer[s->data_count] << i * 8;
+        s->data_count++;
+        /* check if we've read all valid data (blksize bytes) from buffer */
+        if ((s->data_count) >= (s->blksize & 0x0fff)) {
+            DPRINT_L2("All %u bytes of data have been read from input 
buffer\n",
+                    s->data_count);
+            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))) {
+                sdhcv2_do_transfer_complete(s);
+            } else if (s->stoped_state == sdhc_gap_read &&
+                    !(s->prnsts & SDHC_DAT_LINE_ACTIVE)) {
+                /* stop at gap request */
+                sdhcv2_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) + SDHC_READ_BUFFER_DELAY);
+            }
+            break;
+        }
+    }
+
+    return value;
+}
+
+/* Write data from host controller FIFO to card */
+static void sdhcv2_write_block_to_card(void *opaque)
+{
+    SDHCv2State *s = (SDHCv2State *)opaque;
+    int index = 0;
+
+    if (s->prnsts & SDHC_SPACE_AVAILABLE) {
+        if (s->norintstsen & SDHC_NISEN_WBUFRDY) {
+            s->norintsts |= SDHC_NIS_WBUFRDY;
+        }
+        s->slotint |= (s->norintsts & s->norintsigen) ? 1 : 0;
+        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--;
+        }
+    }
+
+    for (index = 0; index < (s->blksize & 0x0fff); index++) {
+        sd_write_data(s->card, s->fifo_buffer[index]);
+    }
+
+    /* 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))) {
+        sdhcv2_do_transfer_complete(s);
+    }
+
+    /* Generate Block Gap Event if requested and if not the last block */
+    if (s->stoped_state == sdhc_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);
+    }
+
+    s->slotint |= (s->norintsts & s->norintsigen) ? 1 : 0;
+    qemu_set_irq(s->irq, s->norintsigen & s->norintsts);
+}
+
+/* Write @size bytes of @value data to host controller @s Buffer Data Port
+ * register */
+void sdhcv2_write_dataport(SDHCv2State *s, uint32_t value, unsigned size)
+{
+    unsigned i;
+
+    /* Check that there is free space left in a buffer */
+    if (!(s->prnsts & SDHC_SPACE_AVAILABLE)) {
+        DPRINT_ERROR("Can't write to data buffer: buffer full\n");
+        return;
+    }
+
+    for (i = 0; i < size; i++) {
+        s->fifo_buffer[s->data_count] = value & 0xFF;
+        s->data_count++;
+        value >>= 8;
+        if (s->data_count >= (s->blksize & 0x0fff)) {
+            DPRINT_L2("write buffer filled with %u bytes of data\n",
+                    s->data_count);
+            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) + SDHC_WRITE_BUFFER_DELAY);
+            }
+        }
+    }
+}
+
+/*
+ * Single DMA data transfer
+ */
+
+/* Multi block SDMA transfer */
+void sdhcv2_sdma_transfer_multi_blocks(SDHCv2State *s)
+{
+    bool page_aligned = false;
+    unsigned int n, begin;
+    const uint16_t block_size = s->blksize & 0x0fff;
+    uint32_t boundary_chk = 1 << (((s->blksize & 0xf000) >> 12) + 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,
+     * allow them to work properly */
+    if ((s->sdmasysad % boundary_chk) == 0) {
+        page_aligned = true;
+    }
+
+    if (s->trnmod & SDHC_TRNS_READ) {
+        s->prnsts |= SDHC_DOING_READ | SDHC_DATA_INHIBIT |
+                SDHC_DAT_LINE_ACTIVE;
+        while (s->blkcnt) {
+            if (s->data_count == 0) {
+                for (n = 0; n < block_size; n++) {
+                    s->fifo_buffer[n] = sd_read_data(s->card);
+                }
+            }
+            begin = s->data_count;
+            if (((boundary_count + begin) < block_size) && page_aligned) {
+                s->data_count = boundary_count + begin;
+                boundary_count = 0;
+             } else {
+                s->data_count = block_size;
+                boundary_count -= block_size - begin;
+                s->blkcnt--;
+            }
+            cpu_physical_memory_write(s->sdmasysad, &s->fifo_buffer[begin],
+                    s->data_count - begin);
+            s->sdmasysad += s->data_count - begin;
+            if (s->data_count == block_size) {
+                s->data_count = 0;
+            }
+            if (page_aligned && boundary_count == 0) {
+                break;
+            }
+        }
+    } else {
+        s->prnsts |= SDHC_DOING_WRITE | SDHC_DATA_INHIBIT |
+                SDHC_DAT_LINE_ACTIVE;
+        while (s->blkcnt) {
+            begin = s->data_count;
+            if (((boundary_count + begin) < block_size) && page_aligned) {
+                s->data_count = boundary_count + begin;
+                boundary_count = 0;
+             } else {
+                s->data_count = block_size;
+                boundary_count -= block_size - begin;
+            }
+            cpu_physical_memory_read(s->sdmasysad,
+                    &s->fifo_buffer[begin], s->data_count);
+            s->sdmasysad += s->data_count - begin;
+            if (s->data_count == block_size) {
+                for (n = 0; n < block_size; n++) {
+                    sd_write_data(s->card, s->fifo_buffer[n]);
+                }
+                s->data_count = 0;
+                s->blkcnt--;
+            }
+            if (page_aligned && boundary_count == 0) {
+                break;
+            }
+        }
+    }
+
+    if (s->blkcnt == 0) {
+        sdhcv2_do_transfer_complete(s);
+    } else {
+        if (s->norintstsen & SDHC_NISEN_DMA) {
+            s->norintsts |= SDHC_NIS_DMA;
+        }
+        s->slotint |= (s->norintsts & s->norintsigen) ? 1 : 0;
+        qemu_set_irq(s->irq, s->norintsigen & s->norintsts);
+    }
+}
+
+/* single block SDMA transfer */
+void sdhcv2_sdma_transfer_single_block(SDHCv2State *s)
+{
+    int n;
+    uint32_t datacnt = s->blksize & 0x0fff;
+
+    if (s->trnmod & SDHC_TRNS_READ) {
+        for (n = 0; n < datacnt; n++) {
+            s->fifo_buffer[n] = sd_read_data(s->card);
+        }
+        cpu_physical_memory_write(s->sdmasysad, s->fifo_buffer, datacnt);
+    } else {
+        cpu_physical_memory_read(s->sdmasysad, s->fifo_buffer, datacnt);
+        for (n = 0; n < datacnt; n++) {
+            sd_write_data(s->card, s->fifo_buffer[n]);
+        }
+    }
+
+    if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
+        s->blkcnt--;
+    }
+
+    sdhcv2_do_transfer_complete(s);
+}
+
+/* Advanced DMA data transfer */
+void sdhcv2_start_adma(SDHCv2State *s)
+{
+    unsigned int n, length, begin;
+    uint8_t attributes;
+    target_phys_addr_t entry_addr, address;
+    const bool is_32bit_adma = SDHC_DMA_TYPE(s->hostctl) == SDHC_CTRL_ADMA_32;
+    const uint16_t block_size = s->blksize & 0x0fff;
+    s->admaerr &= ~SDHC_ADMAERR_LENGTH_MISMATCH;
+
+    while (1) {
+        address = length = attributes = 0;
+
+        entry_addr = is_32bit_adma ?
+                (s->admasysaddr & 0xFFFFFFFFull) : s->admasysaddr;
+        /* fetch next entry from descriptor table */
+        cpu_physical_memory_read(entry_addr + 4, (uint8_t *)(&address),
+                is_32bit_adma ? 4 : 8);
+        cpu_physical_memory_read(entry_addr + 2, (uint8_t *)(&length), 2);
+        cpu_physical_memory_read(entry_addr, (uint8_t *)(&attributes), 1);
+        DPRINT_L2("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;
+            }
+            s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0;
+            qemu_set_irq(s->irq, s->errintsigen & s->errintsts);
+            break;
+        }
+
+        if (length == 0) {
+            length = 65536;
+        }
+        /* Address must be aligned */
+        address &= (is_32bit_adma ? 0xfffffffc : 0xfffffff8);
+
+        switch (attributes & SDHC_ADMA_ATTR_ACT_MASK) {
+        case SDHC_ADMA_ATTR_ACT_TRAN:  /* data transfer */
+            if (s->trnmod & SDHC_TRNS_READ) {
+                while (length) {
+                    if (s->data_count == 0) {
+                        for (n = 0; n < block_size; n++) {
+                            s->fifo_buffer[n] = sd_read_data(s->card);
+                        }
+                    }
+                    begin = s->data_count;
+                    if ((length + begin) < block_size) {
+                        s->data_count = length + begin;
+                        length = 0;
+                     } else {
+                        s->data_count = block_size;
+                        length -= block_size - begin;
+                    }
+                    cpu_physical_memory_write(address, &s->fifo_buffer[begin],
+                            s->data_count - begin);
+                    address += s->data_count - begin;
+                    if (s->data_count == block_size) {
+                        s->data_count = 0;
+                        if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
+                            s->blkcnt--;
+                            if (s->blkcnt == 0) {
+                                break;
+                            }
+                        }
+                    }
+                }
+            } else {
+                while (length) {
+                    begin = s->data_count;
+                    if ((length + begin) < block_size) {
+                        s->data_count = length + begin;
+                        length = 0;
+                     } else {
+                        s->data_count = block_size;
+                        length -= block_size - begin;
+                    }
+                    cpu_physical_memory_read(address,
+                            &s->fifo_buffer[begin], s->data_count);
+                    address += s->data_count - begin;
+                    if (s->data_count == block_size) {
+                        for (n = 0; n < block_size; n++) {
+                            sd_write_data(s->card, s->fifo_buffer[n]);
+                        }
+                        s->data_count = 0;
+                        if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
+                            s->blkcnt--;
+                            if (s->blkcnt == 0) {
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+            s->admasysaddr += (is_32bit_adma ? 8 : 12);
+            break;
+        case SDHC_ADMA_ATTR_ACT_LINK:   /* link to next descriptor table */
+            s->admasysaddr = address;
+            DPRINT_L1("ADMA link: admasysaddr=0x%lx\n", s->admasysaddr);
+            break;
+        default:
+            s->admasysaddr += (is_32bit_adma ? 8 : 12);
+            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)) {
+            DPRINT_L2("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)) {
+                DPRINT_ERROR("SD/MMC host ADMA length mismatch\n");
+                s->admaerr |= SDHC_ADMAERR_LENGTH_MISMATCH |
+                        SDHC_ADMAERR_STATE_ST_TFR;
+                if (s->errintstsen & SDHC_EISEN_ADMAERR) {
+                    DPRINT_ERROR("Set ADMA error flag\n");
+                    s->errintsts |= SDHC_EIS_ADMAERR;
+                    s->norintsts |= SDHC_NIS_ERR;
+                }
+                s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0;
+                qemu_set_irq(s->irq, s->errintsigen & s->errintsts);
+            }
+            sdhcv2_do_transfer_complete(s);
+            break;
+        }
+
+        if (attributes & SDHC_ADMA_ATTR_INT) {
+            DPRINT_L1("ADMA interrupt: admasysaddr=0x%lx\n", s->admasysaddr);
+            if (s->norintstsen & SDHC_NISEN_DMA) {
+                s->norintsts |= SDHC_NIS_DMA;
+            }
+            s->slotint |= (s->norintsts & s->norintsigen) ? 1 : 0;
+            qemu_set_irq(s->irq, s->norintsigen & s->norintsts);
+            break;
+        }
+    }
+}
+
+/* Perform data transfer according to controller configuration */
+void sdhcv2_transfer_data(SDHCv2State *s)
+{
+    if (s->trnmod & SDHC_TRNS_DMA) {
+        switch (SDHC_DMA_TYPE(s->hostctl)) {
+        case SDHC_CTRL_SDMA:
+            if ((s->trnmod & SDHC_TRNS_MULTI) &&
+                    (!(s->trnmod & SDHC_TRNS_BLK_CNT_EN) || s->blkcnt == 0)) {
+                break;
+            }
+
+            if ((s->blkcnt == 1) || !(s->trnmod & SDHC_TRNS_MULTI)) {
+                sdhcv2_sdma_transfer_single_block(s);
+            } else {
+                s->data_count = 0;
+                sdhcv2_sdma_transfer_multi_blocks(s);
+            }
+            break;
+        case SDHC_CTRL_ADMA_32:
+            if (!(s->capareg & SDHC_CAN_DO_ADMA)) {
+                DPRINT_ERROR("ADMA32 transfer aborted: ADMA not supported as"
+                        "states by Capabilities register\n");
+                break;
+            }
+            s->data_count = 0;
+            sdhcv2_start_adma(s);
+            break;
+        case SDHC_CTRL_ADMA_64:
+            if (!(s->capareg & SDHC_CAN_DO_ADMA) ||
+                    !(s->capareg & SDHC_64_BIT_BUS_SUPPORT)) {
+                DPRINT_ERROR("ADMA64 transfer aborted: 64 bit ADMA not "
+                        "supported as states by Capabilities register\n");
+                break;
+            }
+            s->data_count = 0;
+            sdhcv2_start_adma(s);
+            break;
+        default:
+            DPRINT_ERROR("Unsupported DMA type in HOSTCTL register\n");
+            break;
+        }
+    } else {
+        if ((s->trnmod & SDHC_TRNS_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) + SDHC_READ_BUFFER_DELAY);
+        } else {
+            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) + SDHC_WRITE_BUFFER_DELAY);
+        }
+    }
+}
+
+static inline bool sdhcv2_sdclk_is_active(SDHCv2State *s)
+{
+    if (SDHC_CLOCK_IS_ON(s->clkcon) && (s->pwrcon & SDHC_POWER_ON)) {
+        return true;
+    }
+    return false;
+}
+
+void sdhcv2_trigger_command_generation(SDHCv2State *s)
+{
+    if (!sdhcv2_sdclk_is_active(s)) {
+        DPRINT_ERROR("Command not issued: SDCLK not active\n");
+        return;
+    }
+    if (SDHC_COMMAND_TYPE(s->cmdreg) == SDHC_CMD_RESUME ||
+            SDHC_COMMAND_TYPE(s->cmdreg) == SDHC_CMD_SUSPEND) {
+        DPRINT_ERROR("Command not issued: suspend/resume commands not"
+                "implemented\n");
+        return;
+    }
+    if ((s->stoped_state || (s->prnsts & SDHC_DATA_INHIBIT)) &&
+            ((s->cmdreg & SDHC_CMD_DATA_PRESENT) ||
+            ((s->cmdreg & SDHC_CMD_RSP_WITH_BUSY) &&
+                    !(SDHC_COMMAND_TYPE(s->cmdreg) == SDHC_CMD_ABORT)))) {
+        DPRINT_ERROR("Command not issued: DAT line is busy\n");
+        return;
+    }
+
+    if (SDHC_COMMAND_TYPE(s->cmdreg) == SDHC_CMD_ABORT &&
+        TRANSFERRING_DATA(s->prnsts)) {
+        DPRINT_L2("ABORT command issued: data transfer stopped\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);
+    }
+
+    sdhcv2_send_command(s);
+    sdhcv2_raise_response_recieved_irq(s);
+
+    if (s->blksize == 0 || !(s->cmdreg & SDHC_CMD_DATA_PRESENT)) {
+        return;
+    }
+    sdhcv2_transfer_data(s);
+}
+
+/* The Buffer Data Port register must be accessed by sequential and
+ * continuous manner.
+ * @byte_num - byte number [0..3] of BDATAPORT register to access */
+static inline bool
+sdhcv2_buff_access_is_sequential(SDHCv2State *s, unsigned byte_num)
+{
+    if ((s->data_count & 0x3) != byte_num) {
+        DPRINT_ERROR("Non-sequential access to Buffer Data Port register"
+                "is prohibited\n");
+        return false;
+    }
+    return true;
+}
+
+/* Read byte from SD host controller registers map */
+uint8_t sdhcv2_read_1byte(SDHCv2State *s, target_phys_addr_t offset)
+{
+    switch (offset) {
+    case SDHC_SYSAD ... SDHC_SYSAD_END:
+        return (s->sdmasysad >> 8 * (offset - SDHC_SYSAD)) & 0xFF;
+    case SDHC_BLKSIZE ... SDHC_BLKSIZE_END:
+        return (s->blksize >> 8 * (offset - SDHC_BLKSIZE)) & 0xFF;
+    case SDHC_BLKCNT ... SDHC_BLKCNT_END:
+        return (s->blkcnt >> 8 * (offset - SDHC_BLKCNT)) & 0xFF;
+    case SDHC_ARGUMENT ... SDHC_ARGUMENT_END:
+        return (s->argument >> 8 * (offset - SDHC_ARGUMENT)) & 0xFF;
+    case SDHC_TRNMOD ... SDHC_TRNMOD_END:
+        return (s->trnmod >> 8 * (offset - SDHC_TRNMOD)) & 0xFF;
+    case SDHC_CMDREG ... SDHC_CMDREG_END:
+        return (s->cmdreg >> 8 * (offset - SDHC_CMDREG)) & 0xFF;
+    case SDHC_RSPREG0 ... SDHC_RSPREG3_END:
+    {
+        int i = (offset - SDHC_RSPREG0) >> 2;
+        return (s->rspreg[i] >> 8 * (offset - SDHC_RSPREG0 - i * 4)) & 0xFF;
+    }
+    case  SDHC_BDATA ... SDHC_BDATA_END:
+        if (sdhcv2_buff_access_is_sequential(s, offset - SDHC_BDATA)) {
+            return sdhcv2_read_dataport(s, 1);
+        }
+        return 0;
+    case SDHC_PRNSTS ... SDHC_PRNSTS_END:
+        return (s->prnsts >> 8 * (offset - SDHC_PRNSTS)) & 0xFF;
+    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_CLKCON ... SDHC_CLKCON_END:
+        return (s->clkcon >> 8 * (offset - SDHC_CLKCON)) & 0xFF;
+    case SDHC_TIMEOUTCON:
+        return s->timeoutcon;
+    case SDHC_SWRST:
+        return 0;
+    case SDHC_SLOT_INT_STATUS ... SDHC_SLOT_INT_STATUS_END:
+        return (s->slotint >> 8 * (offset - SDHC_SLOT_INT_STATUS)) & 0xFF;
+    case SDHC_NORINTSTS ... SDHC_NORINTSTS_END:
+        return (s->norintsts >> 8 * (offset - SDHC_NORINTSTS)) & 0xFF;
+    case SDHC_ERRINTSTS ... SDHC_ERRINTSTS_END:
+        return (s->errintsts >> 8 * (offset - SDHC_ERRINTSTS)) & 0xFF;
+    case SDHC_NORINTSTSEN ... SDHC_NORINTSTSEN_END:
+        return (s->norintstsen >> 8 * (offset - SDHC_NORINTSTSEN)) & 0xFF;
+    case SDHC_ERRINTSTSEN ... SDHC_ERRINTSTSEN_END:
+        return (s->errintstsen >> 8 * (offset - SDHC_ERRINTSTSEN)) & 0xFF;
+    case SDHC_NORINTSIGEN ... SDHC_NORINTSIGEN_END:
+        return (s->norintsigen >> 8 * (offset - SDHC_NORINTSIGEN)) & 0xFF;
+    case SDHC_ERRINTSIGEN ... SDHC_ERRINTSIGEN_END:
+        return (s->errintsigen >> 8 * (offset - SDHC_ERRINTSIGEN)) & 0xFF;
+    case SDHC_ACMD12ERRSTS ... SDHC_ACMD12ERRSTS_END:
+        return (s->acmd12errsts >> 8 * (offset - SDHC_ACMD12ERRSTS)) & 0xFF;
+    case SDHC_CAPAREG ... SDHC_CAPAREG_END:
+        return (s->capareg >> 8 * (offset - SDHC_CAPAREG)) & 0xFF;
+    case SDHC_MAXCURR ... SDHC_MAXCURR_END:
+        return (s->maxcurr >> 8 * (offset - SDHC_MAXCURR)) & 0xFF;
+    case SDHC_ADMAERR:
+        return s->admaerr;
+    case SDHC_ADMASYSADDR ... SDHC_ADMASYSADDR_END:
+        return (s->admasysaddr >> 8 * (offset - SDHC_ADMASYSADDR)) & 0xFF;
+    case SDHC_HCVER ... SDHC_HCVER_END:
+        return (SD_HOST_SPECv2_VERS >> 8 * (offset - SDHC_HCVER)) & 0xFF;
+    default:
+        DPRINT_ERROR("bad byte read offset " TARGET_FMT_plx "\n", offset);
+        return 0;
+    }
+}
+
+/* Read two bytes from SD host controller registers map */
+uint16_t sdhcv2_read_2byte(SDHCv2State *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_BDATA ... SDHC_BDATA_END:
+        if (sdhcv2_buff_access_is_sequential(s, offset - SDHC_BDATA)) {
+            return sdhcv2_read_dataport(s, 2);
+        }
+        return 0;
+    case SDHC_CLKCON:
+        return s->clkcon;
+    case SDHC_NORINTSTS:
+        return s->norintsts;
+    case SDHC_ERRINTSTS:
+        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 s->slotint;
+    case SDHC_HCVER:
+        return SD_HOST_SPECv2_VERS;
+    }
+    /* Try to read 2 bytes one by one */
+    return (sdhcv2_read_1byte(s, offset + 1) << 8) |
+            sdhcv2_read_1byte(s, offset);
+}
+
+/* Read four bytes from SD host controller registers map */
+uint32_t sdhcv2_read_4byte(SDHCv2State *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:
+        if (sdhcv2_buff_access_is_sequential(s, 0)) {
+            return sdhcv2_read_dataport(s, 4);
+        }
+        return 0;
+    case SDHC_PRNSTS:
+        return s->prnsts;
+    case SDHC_CAPAREG:
+        return s->capareg;
+    case SDHC_MAXCURR:
+        return s->maxcurr;
+    case SDHC_ADMASYSADDR:
+        return s->admasysaddr;
+    }
+    /* Try to split one 4-bytes read into two 2-bytes reads  */
+    return (sdhcv2_read_2byte(s, offset + 2) << 16) |
+            sdhcv2_read_2byte(s, offset);
+}
+
+static void sdhcv2_update_slotint(SDHCv2State *s)
+{
+    if (!(s->norintsts & s->norintsigen) &&
+        !(s->errintsts & s->errintsigen) &&
+        !((s->norintsts & SDHC_NIS_INSERT) &&
+                (s->wakcon & SDHC_WKUP_ON_INSERT)) &&
+                !((s->norintsts & SDHC_NIS_REMOVE) &&
+                                    (s->wakcon & SDHC_WKUP_ON_REMOVE))) {
+        s->slotint = 0;
+    } else {
+        s->slotint = 1;
+    }
+}
+
+/* Write byte to SD host controller registers map */
+void sdhcv2_write_1byte(SDHCv2State *s, target_phys_addr_t offset,
+        uint32_t value)
+{
+    int off;
+
+    switch (offset) {
+    case SDHC_SYSAD ... (SDHC_SYSAD_END - 1):
+        off = 8 * (offset - SDHC_SYSAD);
+        s->sdmasysad = (s->sdmasysad & ~(0xFF << off)) | (value << off);
+        break;
+    case SDHC_SYSAD_END:
+        /* Writing to last byte of sdmasysad register might trigger transfer */
+        s->sdmasysad = (s->sdmasysad & ~0xFF000000) | (value << 24);
+        if (TRANSFERRING_DATA(s->prnsts) && (s->blkcnt != 0) &&
+            sdhcv2_sdclk_is_active(s) &&  (s->blksize != 0) &&
+            SDHC_DMA_TYPE(s->hostctl) == SDHC_CTRL_SDMA) {
+            sdhcv2_sdma_transfer_multi_blocks(s);
+        }
+        break;
+    case SDHC_BLKSIZE ... SDHC_BLKSIZE_END:
+        off = 8 * (offset - SDHC_BLKSIZE);
+        if (!TRANSFERRING_DATA(s->prnsts)) {
+            s->blksize = (s->blksize & ~(0xFF << off)) | (value << off);
+        }
+        break;
+    case SDHC_BLKCNT ... SDHC_BLKCNT_END:
+        off = 8 * (offset - SDHC_BLKCNT);
+        if (!TRANSFERRING_DATA(s->prnsts)) {
+            s->blkcnt = (s->blkcnt & ~(0xFF << off)) | (value << off);
+        }
+        break;
+    case SDHC_ARGUMENT ... SDHC_ARGUMENT_END:
+        off = 8 * (offset - SDHC_ARGUMENT);
+        s->argument = (s->argument & ~(0xFF << off)) | (value << off);
+        break;
+    case SDHC_TRNMOD ... SDHC_TRNMOD_END:
+        off = 8 * (offset - SDHC_TRNMOD);
+        s->trnmod = (s->trnmod & ~(0xFF << off)) | (value << off);
+        /* DMA can be enabled only if it's supported as indicated by
+         * capabilities register */
+        if (!(s->capareg & SDHC_CAN_DO_DMA)) {
+            s->trnmod &= ~SDHC_TRNS_DMA;
+        }
+        break;
+    case SDHC_CMDREG:
+        s->cmdreg = (s->cmdreg & ~(0xFF)) | value;
+        break;
+    case SDHC_CMDREG_END:
+        /* Writing to the upper byte of CMDREG triggers SD command generation 
*/
+        s->cmdreg = (s->cmdreg & ~(0xFF00)) | (value << 8);
+        sdhcv2_trigger_command_generation(s);
+        break;
+    case SDHC_BDATA ... SDHC_BDATA_END:
+        if (sdhcv2_buff_access_is_sequential(s, offset - SDHC_BDATA)) {
+            sdhcv2_write_dataport(s, value, 1);
+        }
+        break;
+    case SDHC_HOSTCTL:
+        s->hostctl = value;
+        break;
+    case SDHC_PWRCON:
+        /* Check that voltage is supported */
+        off = (value >> 1) & 0x7;
+        if ((off < 5) || !(s->capareg & (1 << (31 - off)))) {
+            DPRINT_ERROR("Bus voltage is not supported\n");
+            value &= SDHC_POWER_ON;
+        }
+        if (!(s->prnsts & SDHC_CARD_PRESENT)) {
+            value &= SDHC_POWER_ON;
+        }
+        s->pwrcon = value;
+        break;
+    case SDHC_BLKGAP:
+        if (value & 0x0C) {
+            DPRINT_ERROR("Read-Wait and interrupt at block gap functions"
+                    " 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 == sdhc_gap_read) {
+                s->prnsts |= SDHC_DAT_LINE_ACTIVE | SDHC_DOING_READ;
+                qemu_mod_timer(s->read_buffer_timer,
+                    qemu_get_clock_ns(vm_clock) + SDHC_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) + SDHC_READ_BUFFER_DELAY);
+            }
+            s->stoped_state = sdhc_not_stoped;
+        } else if (!s->stoped_state && (value & SDHC_STOP_AT_GAP_REQ)) {
+            if (s->prnsts & SDHC_DOING_READ) {
+                s->stoped_state = sdhc_gap_read;
+            } else if (s->prnsts & SDHC_DOING_WRITE) {
+                s->stoped_state = sdhc_gap_write;
+            }
+        }
+        break;
+    case SDHC_WAKCON:
+        s->wakcon = value;
+        break;
+    case SDHC_CLKCON ... SDHC_CLKCON_END:
+        s->clkcon = (s->clkcon & ~(0xFF)) | value;
+        if (SDHC_CLOCK_INT_EN & s->clkcon) {
+            s->clkcon |= SDHC_CLOCK_INT_STABLE;
+        } else {
+            s->clkcon &= ~SDHC_CLOCK_INT_STABLE;
+        }
+        if (!(s->prnsts & SDHC_CARD_PRESENT)) {
+            s->clkcon &= SDHC_CLOCK_SDCLK_EN;
+        }
+        break;
+    case SDHC_TIMEOUTCON:
+        s->timeoutcon = value & 0x0F;
+        break;
+    case SDHC_SWRST:
+        switch (value) {
+        case SDHC_RESET_ALL:
+            sdhcv2_reset(s);
+            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 = sdhc_not_stoped;
+            s->norintsts &= ~(SDHC_NIS_WBUFRDY | SDHC_NIS_RBUFRDY |
+                    SDHC_NIS_DMA | SDHC_NIS_TRSCMP | SDHC_NIS_BLKGAP);
+            break;
+        }
+        break;
+    case SDHC_NORINTSTS:
+        s->norintsts &= ~value;
+        sdhcv2_update_slotint(s);
+        qemu_set_irq(s->irq, s->slotint);
+        break;
+    case SDHC_NORINTSTS_END:
+        if (s->norintstsen & SDHC_NISEN_CARDINT) {
+            value &= ~(SDHC_NIS_CARDINT >> 8);
+        }
+        s->norintsts &= (s->norintsts & SDHC_NIS_ERR) | ~(value << 8);
+        sdhcv2_update_slotint(s);
+        qemu_set_irq(s->irq, s->slotint);
+        break;
+    case SDHC_ERRINTSTS ... SDHC_ERRINTSTS_END:
+        s->errintsts &= ~(value << 8 * (offset - SDHC_ERRINTSTS));
+        if (!(s->errintsts & SDHC_EIS_CMD12ERR)) {
+            s->acmd12errsts = 0;
+        }
+        s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) :
+                (s->norintsts &= ~SDHC_NIS_ERR);
+        sdhcv2_update_slotint(s);
+        qemu_set_irq(s->irq, s->slotint);
+        break;
+    case SDHC_NORINTSTSEN ... SDHC_NORINTSTSEN_END:
+        off = 8 * (offset - SDHC_NORINTSTSEN);
+        s->norintstsen = (s->norintstsen & ~(0xFF << off)) | (value << off);
+        s->norintsts &= s->norintstsen | SDHC_NIS_ERR;
+        sdhcv2_update_slotint(s);
+        qemu_set_irq(s->irq, s->slotint);
+        break;
+    case SDHC_ERRINTSTSEN ... SDHC_ERRINTSTSEN_END:
+        off = 8 * (offset - SDHC_ERRINTSTSEN);
+        s->errintstsen = (s->errintstsen & ~(0xFF << off)) | (value << off);
+        s->errintsts &= s->errintstsen;
+        sdhcv2_update_slotint(s);
+        qemu_set_irq(s->irq, s->slotint);
+        break;
+    case SDHC_NORINTSIGEN ... SDHC_NORINTSIGEN_END:
+        off = 8 * (offset - SDHC_NORINTSIGEN);
+        s->norintsigen = (s->norintsigen & ~(0xFF << off)) | (value << off);
+        break;
+    case SDHC_ERRINTSIGEN ... SDHC_ERRINTSIGEN_END:
+        off = 8 * (offset - SDHC_ERRINTSIGEN);
+        s->errintsigen = (s->errintsigen & ~(0xFF << off)) | (value << off);
+        break;
+    case SDHC_FEERR ... SDHC_FEERR_END:
+        s->errintsts |= (value << 8 * (offset - SDHC_FEERR)) & s->errintstsen;
+        s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) :
+            (s->norintsts &= ~SDHC_NIS_ERR);
+        s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0;
+        qemu_set_irq(s->irq, s->errintsigen & s->errintsts);
+        break;
+    case SDHC_FEAER ... SDHC_FEAER_END:
+        s->acmd12errsts |= (value << 8 * (offset - SDHC_FEAER));
+        s->acmd12errsts ? (s->errintsts |= SDHC_EIS_CMD12ERR),
+            (s->norintsts |= SDHC_NIS_ERR) :
+            (s->errintsts &= ~SDHC_EIS_CMD12ERR);
+        s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0;
+        qemu_set_irq(s->irq, s->errintsigen & s->errintsts);
+        break;
+    case SDHC_ADMAERR:
+        s->admaerr = value;
+        break;
+    case SDHC_ADMASYSADDR ... SDHC_ADMASYSADDR_END:
+        off = 8 * (offset - SDHC_ADMASYSADDR);
+        s->admasysaddr =
+                (s->admasysaddr & ~(0xFFull << off)) | ((uint64_t)value << 
off);
+        break;
+    default:
+        DPRINT_ERROR("bad byte write offset " TARGET_FMT_plx ", value="
+                "%u(0x%x)\n", offset, value, value);
+        break;
+    }
+}
+
+/* Write 2 bytes to SD host controller registers map */
+void sdhcv2_write_2byte(SDHCv2State *s, target_phys_addr_t offset,
+        uint16_t value)
+{
+    switch (offset) {
+    case SDHC_BLKSIZE:
+        if (!TRANSFERRING_DATA(s->prnsts)) {
+            s->blksize = value;
+        }
+        break;
+    case SDHC_BLKCNT:
+        if (!TRANSFERRING_DATA(s->prnsts)) {
+            s->blkcnt = value;
+        }
+        break;
+    case SDHC_TRNMOD:
+        /* DMA can be enabled only if it's supported as indicated by
+         * capabilities register */
+        if (!(s->capareg & SDHC_CAN_DO_DMA)) {
+            value &= ~SDHC_TRNS_DMA;
+        }
+        s->trnmod = value;
+        break;
+    case SDHC_CMDREG:
+        s->cmdreg = value;
+        sdhcv2_trigger_command_generation(s);
+        break;
+    case SDHC_BDATA ... SDHC_BDATA_END:
+        if (sdhcv2_buff_access_is_sequential(s, offset - SDHC_BDATA)) {
+            sdhcv2_write_dataport(s, value, 2);
+        }
+        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;
+        }
+        break;
+    case SDHC_NORINTSTS:
+        if (s->norintstsen & SDHC_NISEN_CARDINT) {
+            value &= ~SDHC_NIS_CARDINT;
+        }
+        s->norintsts &= (s->norintsts & SDHC_NIS_ERR) | ~value;
+        sdhcv2_update_slotint(s);
+        qemu_set_irq(s->irq, s->slotint);
+        break;
+    case SDHC_ERRINTSTS:
+        s->errintsts &= ~value;
+        s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) :
+                (s->norintsts &= ~SDHC_NIS_ERR);
+        sdhcv2_update_slotint(s);
+        qemu_set_irq(s->irq, s->slotint);
+        break;
+    case SDHC_NORINTSTSEN:
+        s->norintstsen = value;
+        s->norintsts &= s->norintstsen | SDHC_NIS_ERR;
+        sdhcv2_update_slotint(s);
+        qemu_set_irq(s->irq, s->slotint);
+        break;
+    case SDHC_ERRINTSTSEN:
+        s->errintstsen = value;
+        s->errintsts &= s->errintstsen;
+        sdhcv2_update_slotint(s);
+        qemu_set_irq(s->irq, s->slotint);
+        break;
+    case SDHC_NORINTSIGEN:
+        s->norintsigen = value;
+        break;
+    case SDHC_ERRINTSIGEN:
+        s->errintsigen = value;
+        break;
+    case SDHC_FEAER:
+        s->acmd12errsts |= value;
+        s->acmd12errsts ? (s->errintsts |= SDHC_EIS_CMD12ERR),
+            (s->norintsts |= SDHC_NIS_ERR) :
+            (s->errintsts &= ~SDHC_EIS_CMD12ERR);
+        s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0;
+        qemu_set_irq(s->irq, s->errintsigen & s->errintsts);
+        break;
+    case SDHC_FEERR:
+        s->errintsts |= value & s->errintstsen;
+        s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) :
+            (s->norintsts &= ~SDHC_NIS_ERR);
+        s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0;
+        qemu_set_irq(s->irq, s->errintsigen & s->errintsts);
+        break;
+    default:
+        /* Try to split 2-bytes write into two 1-byte writes */
+        sdhcv2_write_1byte(s, offset, value & 0xFF);
+        sdhcv2_write_1byte(s, offset + 1, (value >> 8) & 0xFF);
+        break;
+    }
+}
+
+/* Write 4 bytes to SD host controller registers map */
+void sdhcv2_write_4byte(SDHCv2State *s, target_phys_addr_t offset,
+        uint32_t value)
+{
+    switch (offset) {
+    case SDHC_SYSAD:
+        s->sdmasysad = value;
+        if (TRANSFERRING_DATA(s->prnsts) && (s->blkcnt != 0) &&
+            sdhcv2_sdclk_is_active(s) && (s->blksize != 0) &&
+            SDHC_DMA_TYPE(s->hostctl) == SDHC_CTRL_SDMA) {
+            sdhcv2_sdma_transfer_multi_blocks(s);
+        }
+        break;
+    case SDHC_ARGUMENT:
+        s->argument = value;
+        break;
+    case SDHC_BDATA:
+        if (sdhcv2_buff_access_is_sequential(s, 0)) {
+            sdhcv2_write_dataport(s, value, 4);
+        }
+        break;
+    case SDHC_ADMASYSADDR ... SDHC_ADMASYSADDR_END:
+    {
+        int off = 8 * (offset - SDHC_ADMASYSADDR);
+        s->admasysaddr = (s->admasysaddr & ~(0xFFFFFFFFull << off)) |
+            ((uint64_t)value << off);
+        break;
+    }
+    default:
+        /* Try to split 4-bytes write into two 2-byte writes */
+        sdhcv2_write_2byte(s, offset, value & 0xFFFF);
+        sdhcv2_write_2byte(s, offset + 2, (value >> 16) & 0xFFFF);
+        break;
+    }
+}
+
+void sdhcv2_initialize(SDHCv2State *s)
+{
+    DriveInfo *bd;
+    size_t fifo_len;
+
+    switch (SDHC_CAPAB_BLOCKSIZE(s->capareg)) {
+    case 0:
+        fifo_len = 512;
+        break;
+    case 1:
+        fifo_len = 1024;
+        break;
+    case 2:
+        fifo_len = 2048;
+        break;
+    default:
+        hw_error("SDHC: unsupported value for maximum block size\n");
+        break;
+    }
+    s->fifo_buffer = g_malloc0(fifo_len);
+
+    sysbus_init_irq(&s->busdev, &s->irq);
+    bd = drive_get_next(IF_SD);
+
+    if (bd) {
+        DPRINT_L1("SD card inserted: name = %s, sectors = %ld\n",
+                bd->bdrv->device_name, bd->bdrv->total_sectors);
+        s->card = sd_init(bd->bdrv, 0);
+    } else {
+        DPRINT_L1("No SD card\n");
+        s->card = sd_init(NULL, 0);
+    }
+    s->eject_cb = qemu_allocate_irqs(sdhcv2_insert_eject_cb, s, 1)[0];
+    s->ro_cb = qemu_allocate_irqs(sdhcv2_card_readonly_cb, s, 1)[0];
+    sd_set_cb(s->card, s->ro_cb, s->eject_cb);
+
+    s->insert_timer =
+        qemu_new_timer(vm_clock, SCALE_NS, sdhcv2_raise_insertion_irq, s);
+
+    s->read_buffer_timer =
+        qemu_new_timer(vm_clock, SCALE_NS, sdhcv2_read_block_from_card, s);
+
+    s->write_buffer_timer =
+        qemu_new_timer(vm_clock, SCALE_NS, sdhcv2_write_block_to_card, s);
+
+    s->transfer_complete_timer =
+        qemu_new_timer(vm_clock, SCALE_NS, sdhcv2_transfer_complete_irq, s);
+}
+
+typedef struct SDHCv2GenericState {
+    SDHCv2State hc;
+    MemoryRegion iomem;
+} SDHCv2GenericState;
+
+static void sdhcv2_generic_reset(DeviceState *d)
+{
+    sdhcv2_reset(container_of(d, SDHCv2State, busdev.qdev));
+}
+
+static uint64_t
+sdhcv2_generic_read(void *opaque, target_phys_addr_t offset, unsigned size)
+{
+    SDHCv2State *s = (SDHCv2State *)opaque;
+    DPRINT_L2("read %u byte: offset " TARGET_FMT_plx "\n",
+            size, offset);
+
+    switch (size) {
+    case 1:
+        return sdhcv2_read_1byte(s, offset);
+    case 2:
+        return sdhcv2_read_2byte(s, offset);
+    case 4:
+        return sdhcv2_read_4byte(s, offset);
+    }
+    return 0;
+}
+
+static void
+sdhcv2_generic_write(void *opaque, target_phys_addr_t offset, uint64_t val,
+        unsigned size)
+{
+    SDHCv2State *s = (SDHCv2State *)opaque;
+    DPRINT_L2("write %u byte: offset " TARGET_FMT_plx " = %lu(0x%lx)\n",
+            size, offset, val, val);
+
+    switch (size) {
+    case 1:
+        sdhcv2_write_1byte(s, offset, val);
+        break;
+    case 2:
+        sdhcv2_write_2byte(s, offset, val);
+        break;
+    case 4:
+        sdhcv2_write_4byte(s, offset, val);
+        break;
+    }
+}
+
+static const MemoryRegionOps sdhcv2_generic_mmio_ops = {
+    .read = sdhcv2_generic_read,
+    .write = sdhcv2_generic_write,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 4,
+        .unaligned = false
+    },
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static int sdhcv2_get_fifolen(void *opaque, int version_id)
+{
+    SDHCv2State *sd = (SDHCv2State *)opaque;
+    switch (SDHC_CAPAB_BLOCKSIZE(sd->capareg)) {
+    case 0:
+        return 512;
+    case 1:
+        return 1024;
+    case 2:
+        return 2048;
+    default:
+        hw_error("SDHC: unsupported value for maximum block size\n");
+    }
+}
+
+const VMStateDescription sdhcv2_vmstate = {
+    .name = "sdhcv2",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields      = (VMStateField[]) {
+        VMSTATE_UINT32(sdmasysad, SDHCv2State),
+        VMSTATE_UINT16(blksize, SDHCv2State),
+        VMSTATE_UINT16(blkcnt, SDHCv2State),
+        VMSTATE_UINT32(argument, SDHCv2State),
+        VMSTATE_UINT16(trnmod, SDHCv2State),
+        VMSTATE_UINT16(cmdreg, SDHCv2State),
+        VMSTATE_UINT32_ARRAY(rspreg, SDHCv2State, 4),
+        VMSTATE_UINT32(prnsts, SDHCv2State),
+        VMSTATE_UINT8(hostctl, SDHCv2State),
+        VMSTATE_UINT8(pwrcon, SDHCv2State),
+        VMSTATE_UINT8(blkgap, SDHCv2State),
+        VMSTATE_UINT8(wakcon, SDHCv2State),
+        VMSTATE_UINT16(clkcon, SDHCv2State),
+        VMSTATE_UINT8(timeoutcon, SDHCv2State),
+        VMSTATE_UINT8(admaerr, SDHCv2State),
+        VMSTATE_UINT16(norintsts, SDHCv2State),
+        VMSTATE_UINT16(errintsts, SDHCv2State),
+        VMSTATE_UINT16(norintstsen, SDHCv2State),
+        VMSTATE_UINT16(errintstsen, SDHCv2State),
+        VMSTATE_UINT16(norintsigen, SDHCv2State),
+        VMSTATE_UINT16(errintsigen, SDHCv2State),
+        VMSTATE_UINT16(acmd12errsts, SDHCv2State),
+        VMSTATE_UINT16(data_count, SDHCv2State),
+        VMSTATE_UINT16(slotint, SDHCv2State),
+        VMSTATE_UINT64(admasysaddr, SDHCv2State),
+        VMSTATE_UINT8(stoped_state, SDHCv2State),
+        VMSTATE_VBUFFER(fifo_buffer, SDHCv2State, 1, NULL, 0,
+                sdhcv2_get_fifolen),
+        VMSTATE_TIMER(insert_timer, SDHCv2State),
+        VMSTATE_TIMER(read_buffer_timer, SDHCv2State),
+        VMSTATE_TIMER(write_buffer_timer, SDHCv2State),
+        VMSTATE_TIMER(transfer_complete_timer, SDHCv2State),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static const VMStateDescription sdhcv2_generic_vmstate = {
+    .name = "sdhcv2_generic",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields      = (VMStateField[]) {
+        VMSTATE_STRUCT(hc, SDHCv2GenericState, 1, sdhcv2_vmstate, SDHCv2State),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static int sdhcv2_generic_init(SysBusDevice *dev)
+{
+    SDHCv2State *s = FROM_SYSBUS(SDHCv2State, dev);
+    SDHCv2GenericState *hc_gen = DO_UPCAST(SDHCv2GenericState, hc, s);
+
+    sdhcv2_initialize(s);
+    memory_region_init_io(&hc_gen->iomem, &sdhcv2_generic_mmio_ops, s,
+            "sdhcv2", SDHC_REGISTERS_MAP_SIZE);
+    sysbus_init_mmio(dev, &hc_gen->iomem);
+
+    return 0;
+}
+
+static SysBusDeviceInfo sdhcv2_generic_info = {
+    .init = sdhcv2_generic_init,
+    .qdev.name  = "sdhcv2",
+    .qdev.size  = sizeof(SDHCv2GenericState),
+    .qdev.vmsd  = &sdhcv2_generic_vmstate,
+    .qdev.reset = sdhcv2_generic_reset,
+    .qdev.props = (Property[]) {
+        DEFINE_PROP_HEX32("capareg", SDHCv2GenericState, hc.capareg,
+                SDHC_CAPAB_REG_DEFAULT),
+        DEFINE_PROP_HEX32("maxcurr", SDHCv2GenericState, hc.maxcurr, 0),
+        DEFINE_PROP_END_OF_LIST(),
+    }
+};
+
+static void sdhcv2_register_devices(void)
+{
+    sysbus_register_withprop(&sdhcv2_generic_info);
+}
+
+device_init(sdhcv2_register_devices)
diff --git a/hw/sdhc_ver2.h b/hw/sdhc_ver2.h
new file mode 100644
index 0000000..2b13e16
--- /dev/null
+++ b/hw/sdhc_ver2.h
@@ -0,0 +1,327 @@
+/*
+ * SD host controller (SD Host Controller Simplified
+ * Specification Version 2.00 compliant) emulation
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ * Contributed by Mitsyanko Igor <address@hidden>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SDHC_VER2_H_
+#define SDHC_VER2_H_
+
+#include "qemu-common.h"
+#include "sysbus.h"
+#include "sd.h"
+
+/* R/W SDMA System Address register 0x0 */
+#define SDHC_SYSAD                     0x00
+#define SDHC_SYSAD_END                 0x03
+
+/* R/W Host DMA Buffer Boundary and Transfer Block Size Register 0x0 */
+#define SDHC_BLKSIZE                   0x04
+#define SDHC_BLKSIZE_END               0x05
+
+/* R/W Blocks count for current transfer 0x0 */
+#define SDHC_BLKCNT                    0x06
+#define SDHC_BLKCNT_END                0x07
+
+/* R/W Command Argument Register 0x0 */
+#define SDHC_ARGUMENT                  0x08
+#define SDHC_ARGUMENT_END              0x0B
+
+/* R/W Transfer Mode Setting Register 0x0 */
+#define SDHC_TRNMOD                    0x0C
+#define SDHC_TRNMOD_END                0x0D
+#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
+
+/* R/W Command Register 0x0 */
+#define SDHC_CMDREG                    0x0E
+#define SDHC_CMDREG_END                0x0F
+#define SDHC_CMD_RSP_WITH_BUSY         (3 << 0)
+#define SDHC_CMD_DATA_PRESENT          (1 << 5)
+#define SDHC_CMD_SUSPEND               (1 << 6)
+#define SDHC_CMD_RESUME                (1 << 7)
+#define SDHC_CMD_ABORT                 ((1 << 6)|(1 << 7))
+#define SDHC_CMD_TYPE_MASK             ((1 << 6)|(1 << 7))
+#define SDHC_COMMAND_TYPE(x)           ((x) & SDHC_CMD_TYPE_MASK)
+
+/* 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_RSPREG3_END               0x1F
+
+/* R/W Buffer Data Register 0x0 */
+#define SDHC_BDATA                     0x20
+#define SDHC_BDATA_END                 0x23
+#define SDHC_BUFFER_END                (-1)
+
+/* R/ROC Present State Register 0x000A0000 */
+#define SDHC_PRNSTS                    0x24
+#define SDHC_PRNSTS_END                0x27
+#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
+#define TRANSFERRING_DATA(x)           \
+    ((x) & (SDHC_DOING_READ | SDHC_DOING_WRITE))
+
+/* R/W Host control Register 0x0 */
+#define SDHC_HOSTCTL                   0x28
+#define SDHC_CTRL_DMA_CHECK_MASK       0x18
+#define SDHC_CTRL_SDMA                 0x00
+#define SDHC_CTRL_ADMA_32              0x10
+#define SDHC_CTRL_ADMA_64              0x18
+#define SDHC_DMA_TYPE(x)               ((x) & SDHC_CTRL_DMA_CHECK_MASK)
+
+/* R/W Power Control Register 0x0 */
+#define SDHC_PWRCON                    0x29
+#define SDHC_POWER_ON                  (1 << 0)
+
+/* 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_WKUP_ON_INSERT            (1 << 1)
+#define SDHC_WKUP_ON_REMOVE            (1 << 2)
+
+/* CLKCON */
+#define SDHC_CLKCON                    0x2C
+#define SDHC_CLKCON_END                0x2D
+#define SDHC_CLOCK_INT_STABLE          0x0002
+#define SDHC_CLOCK_INT_EN              0x0001
+#define SDHC_CLOCK_SDCLK_EN            (1 << 2)
+#define SDHC_CLOCK_CHK_MASK            0x0007
+#define SDHC_CLOCK_IS_ON(x)            \
+    (((x) & SDHC_CLOCK_CHK_MASK) == SDHC_CLOCK_CHK_MASK)
+
+/* R/W Timeout Control Register 0x0 */
+#define SDHC_TIMEOUTCON                0x2E
+
+/* 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_NORINTSTS_END             0x31
+#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_ERRINTSTS_END             0x33
+#define SDHC_EIS_CMDTIMEOUT            0x0001
+#define SDHC_EIS_BLKGAP                0x0004
+#define SDHC_EIS_CMD12ERR              0x0100
+#define SDHC_EIS_ADMAERR               0x0200
+
+/* R/W Normal Interrupt Status Enable Register 0x0 */
+#define SDHC_NORINTSTSEN               0x34
+#define SDHC_NORINTSTSEN_END           0x35
+#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_ERRINTSTSEN_END           0x37
+#define SDHC_EISEN_CMDTIMEOUT          0x0001
+#define SDHC_EISEN_BLKGAP              0x0004
+#define SDHC_EISEN_ADMAERR             0x0200
+
+/* R/W Normal Interrupt Signal Enable Register 0x0 */
+#define SDHC_NORINTSIGEN               0x38
+#define SDHC_NORINTSIGEN_END           0x39
+#define SDHC_NORINTSIG_INSERT          (1 << 6)
+#define SDHC_NORINTSIG_REMOVE          (1 << 7)
+
+/* R/W Error Interrupt Signal Enable Register 0x0 */
+#define SDHC_ERRINTSIGEN               0x3A
+#define SDHC_ERRINTSIGEN_END           0x3B
+
+/* ROC Auto CMD12 error status register 0x0 */
+#define SDHC_ACMD12ERRSTS              0x3C
+#define SDHC_ACMD12ERRSTS_END          0x3D
+
+/* HWInit Capabilities Register 0x05E80080 */
+#define SDHC_CAPAREG                   0x40
+#define SDHC_CAPAREG_END               0x43
+#define SDHC_CAN_DO_DMA                0x00400000
+#define SDHC_CAN_DO_ADMA               0x00080000
+#define SDHC_64_BIT_BUS_SUPPORT        (1 << 28)
+#define SDHC_CAPAB_BLOCKSIZE(x)        (((x) >> 16) & 0x3)
+
+/* HWInit Maximum Current Capabilities Register 0x0 */
+#define SDHC_MAXCURR                   0x48
+#define SDHC_MAXCURR_END               0x4B
+
+/* W Force Event Auto CMD12 Error Interrupt Register 0x0000 */
+#define SDHC_FEAER                     0x50
+#define SDHC_FEAER_END                 0x51
+/* W Force Event Error Interrupt Register Error Interrupt 0x0000 */
+#define SDHC_FEERR                     0x52
+#define SDHC_FEERR_END                 0x53
+
+/* R/W ADMA Error Status Register 0x00 */
+#define SDHC_ADMAERR                   0x54
+#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_ADMASYSADDR_END           0x5F
+#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))
+
+/* Slot interrupt status */
+#define SDHC_SLOT_INT_STATUS            0xFC
+#define SDHC_SLOT_INT_STATUS_END        0xFD
+
+/* HWInit Host Controller Version Register 0x0401 */
+#define SDHC_HCVER                      0xFE
+#define SDHC_HCVER_END                  0xFF
+#define SD_HOST_SPECv2_VERS             0x2401
+
+#define SDHC_REGISTERS_MAP_SIZE         0x100
+#define SDHC_READ_BUFFER_DELAY          1
+#define SDHC_WRITE_BUFFER_DELAY         2
+#define SDHC_INSERTION_DELAY            (get_ticks_per_sec())
+#define SDHC_CMD_RESPONSE               (3 << 0)
+
+enum {
+    sdhc_not_stoped = 0,  /* normal SDHC state */
+    sdhc_gap_read   = 1,  /* SDHC stopped at block gap during read operation */
+    sdhc_gap_write  = 2  /* SDHC stopped at block gap during write operation */
+};
+
+/* SD/MMC host controller state */
+typedef struct SDHCv2State {
+    SysBusDevice busdev;
+    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_cb;
+    qemu_irq ro_cb;
+    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 */
+    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  admaerr;      /* ADMA Error Status Register */
+    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 */
+    uint16_t slotint;      /* Slot interrupt status register */
+    uint64_t admasysaddr;  /* ADMA System Address Register */
+
+    uint32_t capareg;      /* Capabilities Register */
+    uint32_t maxcurr;      /* Maximum Current Capabilities Register */
+    uint8_t  *fifo_buffer; /* SD host i/o FIFO buffer */
+    uint16_t data_count;   /* current element in FIFO buffer */
+    uint8_t  stoped_state; /* Current SDHC state */
+    /* Buffer Data Port Register - virtual access point to R and W buffers */
+    /* Software Reset Register - always reads as 0 */
+    /* Force Event Auto CMD12 Error Interrupt Reg - write only */
+    /* Force Event Error Interrupt Register- write only */
+    /* RO Host Controller Version Register always reads as 0x2401 */
+} SDHCv2State;
+
+extern const VMStateDescription sdhcv2_vmstate;
+
+void sdhcv2_initialize(SDHCv2State *s);
+void sdhcv2_reset(SDHCv2State *s);
+uint8_t sdhcv2_read_1byte(SDHCv2State *s, target_phys_addr_t offset);
+uint16_t sdhcv2_read_2byte(SDHCv2State *s, target_phys_addr_t offset);
+uint32_t sdhcv2_read_4byte(SDHCv2State *s, target_phys_addr_t offset);
+void sdhcv2_write_1byte(SDHCv2State *s, target_phys_addr_t offset,
+        uint32_t value);
+void sdhcv2_write_2byte(SDHCv2State *s, target_phys_addr_t offset,
+        uint16_t value);
+void sdhcv2_write_4byte(SDHCv2State *s, target_phys_addr_t offset,
+        uint32_t value);
+void sdhcv2_send_command(SDHCv2State *s);
+void sdhcv2_raise_response_recieved_irq(SDHCv2State *s);
+void sdhcv2_trigger_command_generation(SDHCv2State *s);
+void sdhcv2_transfer_data(SDHCv2State *s);
+void sdhcv2_sdma_transfer_single_block(SDHCv2State *s);
+void sdhcv2_sdma_transfer_multi_blocks(SDHCv2State *s);
+void sdhcv2_start_adma(SDHCv2State *s);
+void sdhcv2_do_transfer_complete(SDHCv2State *s);
+uint32_t sdhcv2_read_dataport(SDHCv2State *s, unsigned size);
+void sdhcv2_write_dataport(SDHCv2State *s, uint32_t value, unsigned size);
+
+#endif /* SDHC_VER2_H_ */
-- 
1.7.4.1




reply via email to

[Prev in Thread] Current Thread [Next in Thread]