Patchwork [RFC,2/2] target-arm: introduce Exynos4210 SD host controller model

login
register
mail settings
Submitter Mitsyanko Igor
Date April 18, 2012, 8:43 a.m.
Message ID <1334738620-7034-3-git-send-email-i.mitsyanko@samsung.com>
Download mbox | patch
Permalink /patch/153439/
State New
Headers show

Comments

Mitsyanko Igor - April 18, 2012, 8:43 a.m.
Exynos4210 SD/MMC host controller is based on SD association standart host
controller ver. 2.00

Signed-off-by: Igor Mitsyanko <i.mitsyanko@samsung.com>
---
 Makefile.target       |    1 +
 hw/exynos4210.c       |   20 +++
 hw/exynos4210_sdhci.c |  438 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 459 insertions(+), 0 deletions(-)
 create mode 100644 hw/exynos4210_sdhci.c
Peter A. G. Crosthwaite - April 20, 2012, 6:13 a.m.
"target-arm:" is probably non the correct subject prefix for this
patch. target-arm generally means you are patching the arm CPU stuff?

Peter

On Wed, Apr 18, 2012 at 6:43 PM, Igor Mitsyanko <i.mitsyanko@samsung.com> wrote:
> Exynos4210 SD/MMC host controller is based on SD association standart host
> controller ver. 2.00
>
> Signed-off-by: Igor Mitsyanko <i.mitsyanko@samsung.com>
> ---
>  Makefile.target       |    1 +
>  hw/exynos4210.c       |   20 +++
>  hw/exynos4210_sdhci.c |  438 +++++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 459 insertions(+), 0 deletions(-)
>  create mode 100644 hw/exynos4210_sdhci.c
>
> diff --git a/Makefile.target b/Makefile.target
> index 84951a0..7cd58a1 100644
> --- a/Makefile.target
> +++ b/Makefile.target
> @@ -373,6 +373,7 @@ obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
>  obj-arm-y += exynos4210_gic.o exynos4210_combiner.o exynos4210.o
>  obj-arm-y += exynos4_boards.o exynos4210_uart.o exynos4210_pwm.o
>  obj-arm-y += exynos4210_pmu.o exynos4210_mct.o exynos4210_fimd.o
> +obj-arm-y += exynos4210_sdhci.o
>  obj-arm-y += arm_l2x0.o
>  obj-arm-y += arm_mptimer.o a15mpcore.o
>  obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
> diff --git a/hw/exynos4210.c b/hw/exynos4210.c
> index afc4bdc..4f9d91b 100644
> --- a/hw/exynos4210.c
> +++ b/hw/exynos4210.c
> @@ -56,6 +56,12 @@
>  #define EXYNOS4210_EXT_COMBINER_BASE_ADDR   0x10440000
>  #define EXYNOS4210_INT_COMBINER_BASE_ADDR   0x10448000
>
> +/* SD/MMC host controllers SFR base addresses */
> +#define EXYNOS4210_SDHC0_BASE_ADDR          0x12510000
> +#define EXYNOS4210_SDHC1_BASE_ADDR          0x12520000
> +#define EXYNOS4210_SDHC2_BASE_ADDR          0x12530000
> +#define EXYNOS4210_SDHC3_BASE_ADDR          0x12540000
> +
>  /* PMU SFR base address */
>  #define EXYNOS4210_PMU_BASE_ADDR            0x10020000
>
> @@ -289,6 +295,20 @@ Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
>                            EXYNOS4210_UART3_FIFO_SIZE, 3, NULL,
>                   s->irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 3)]);
>
> +    /*** SD/MMC host controllers ***/
> +
> +    sysbus_create_simple("exynos4210.sdhci", EXYNOS4210_SDHC0_BASE_ADDR,
> +            s->irq_table[exynos4210_get_irq(29, 0)]);
> +
> +    sysbus_create_simple("exynos4210.sdhci", EXYNOS4210_SDHC1_BASE_ADDR,
> +            s->irq_table[exynos4210_get_irq(29, 1)]);
> +
> +    sysbus_create_simple("exynos4210.sdhci", EXYNOS4210_SDHC2_BASE_ADDR,
> +            s->irq_table[exynos4210_get_irq(29, 2)]);
> +
> +    sysbus_create_simple("exynos4210.sdhci", EXYNOS4210_SDHC3_BASE_ADDR,
> +            s->irq_table[exynos4210_get_irq(29, 3)]);
> +
>     /*** Display controller (FIMD) ***/
>     sysbus_create_varargs("exynos4210.fimd", EXYNOS4210_FIMD0_BASE_ADDR,
>             s->irq_table[exynos4210_get_irq(11, 0)],
> diff --git a/hw/exynos4210_sdhci.c b/hw/exynos4210_sdhci.c
> new file mode 100644
> index 0000000..cb63279
> --- /dev/null
> +++ b/hw/exynos4210_sdhci.c
> @@ -0,0 +1,438 @@
> +/*
> + * Samsung Exynos4210 SD/MMC host controller model
> + *
> + * Copyright (c) 2012 Samsung Electronics Co., Ltd.
> + * Mitsyanko Igor <i.mitsyanko@samsung.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License as published by the
> + * Free Software Foundation; either version 2 of the License, or (at your
> + * option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
> + * See the GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "sdhci.h"
> +
> +#define EXYNOS4_SDHC_CAPABILITIES    0x05E80080
> +#define EXYNOS4_SDHC_MAX_BUFSZ       512
> +
> +#define EXYNOS4_SDHC_DEBUG           0
> +
> +#if EXYNOS4_SDHC_DEBUG == 0
> +    #define DPRINT_L1(fmt, args...)       do { } while (0)
> +    #define DPRINT_L2(fmt, args...)       do { } while (0)
> +    #define ERRPRINT(fmt, args...)        do { } while (0)
> +#elif EXYNOS4_SDHC_DEBUG == 1
> +    #define DPRINT_L1(fmt, args...)       \
> +        do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0)
> +    #define DPRINT_L2(fmt, args...)       do { } while (0)
> +    #define ERRPRINT(fmt, args...)        \
> +        do {fprintf(stderr, "QEMU SDHC ERROR: "fmt, ## args); } while (0)
> +#else
> +    #define DPRINT_L1(fmt, args...)       \
> +        do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0)
> +    #define DPRINT_L2(fmt, args...)       \
> +        do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0)
> +    #define ERRPRINT(fmt, args...)        \
> +        do {fprintf(stderr, "QEMU SDHC ERROR: "fmt, ## args); } while (0)
> +#endif
> +
> +
> +#define TYPE_EXYNOS4_SDHC            "exynos4210.sdhci"
> +#define EXYNOS4_SDHCI(obj)           \
> +     OBJECT_CHECK(Exynos4SDHCIState, (obj), TYPE_EXYNOS4_SDHC)
> +
> +/* ADMA Error Status Register */
> +#define EXYNOS4_SDHC_FINAL_BLOCK     (1 << 10)
> +#define EXYNOS4_SDHC_CONTINUE_REQ    (1 << 9)
> +#define EXYNOS4_SDHC_IRQ_STAT        (1 << 8)
> +/* Control register 2 */
> +#define EXYNOS4_SDHC_CONTROL2        0x80
> +#define EXYNOS4_SDHC_HWINITFIN       (1 << 0)
> +#define EXYNOS4_SDHC_DISBUFRD        (1 << 6)
> +#define EXYNOS4_SDHC_SDOPSIGPC       (1 << 12)
> +#define EXYNOS4_SDHC_SDINPSIGPC      (1 << 3)
> +/* Control register 3 */
> +#define EXYNOS4_SDHC_CONTROL3        0x84
> +/* Control register 4 */
> +#define EXYNOS4_SDHC_CONTROL4        0x8C
> +/* Clock control register */
> +#define EXYNOS4_SDHC_SDCLK_STBL      (1 << 3)
> +
> +typedef struct Exynos4SDHCIState {
> +    SDHCIState sdhci;
> +
> +    uint32_t admaerr;
> +    uint32_t control2;
> +    uint32_t control3;
> +    bool stoped_adma;
> +} Exynos4SDHCIState;
> +
> +static void exynos4210_sdhci_reset(DeviceState *d)
> +{
> +    Exynos4SDHCIState *s = EXYNOS4_SDHCI(d);
> +
> +    SDHCI_GET_CLASS(d)->reset(SDHCI(d));
> +    s->stoped_adma = false;
> +    s->admaerr = 0;
> +    s->control2 = 0;
> +    s->control3 = 0x7F5F3F1F;
> +}
> +
> +static void exynos4210_sdhci_start_adma(SDHCIState *sdhci)
> +{
> +    Exynos4SDHCIState *s = EXYNOS4_SDHCI(sdhci);
> +    unsigned int length, n, begin;
> +    target_phys_addr_t entry_addr;
> +    uint32_t addr;
> +    uint8_t attributes;
> +    const uint16_t block_size = sdhci->blksize & 0x0fff;
> +    s->admaerr &=
> +            ~(EXYNOS4_SDHC_FINAL_BLOCK | SDHC_ADMAERR_LENGTH_MISMATCH);
> +
> +    while (1) {
> +        addr = length = attributes = 0;
> +        entry_addr = (target_phys_addr_t)(sdhci->admasysaddr & 0xFFFFFFFFull);
> +
> +        /* fetch next entry from descriptor table */
> +        cpu_physical_memory_read(entry_addr + 4, (uint8_t *)(&addr), 4);
> +        cpu_physical_memory_read(entry_addr + 2, (uint8_t *)(&length), 2);
> +        cpu_physical_memory_read(entry_addr, (uint8_t *)(&attributes), 1);
> +        DPRINT_L1("ADMA loop: addr=0x%08x, len=%d, attr=%x\n",
> +                addr, length, attributes);
> +
> +        if ((attributes & SDHC_ADMA_ATTR_VALID) == 0) {
> +            /* Indicate that error occurred in ST_FDS state */
> +            s->admaerr &= ~SDHC_ADMAERR_STATE_MASK;
> +            s->admaerr |= SDHC_ADMAERR_STATE_ST_FDS;
> +            DPRINT_L1("ADMA not valid at addr=0x%lx\n", sdhci->admasysaddr);
> +
> +            if (sdhci->errintstsen & SDHC_EISEN_ADMAERR) {
> +                sdhci->errintsts |= SDHC_EIS_ADMAERR;
> +                sdhci->norintsts |= SDHC_NIS_ERR;
> +            }
> +
> +            if (sdhci->errintsigen & sdhci->errintsts) {
> +                sdhci->slotint = 1;
> +            }
> +
> +            qemu_set_irq(sdhci->irq, sdhci->errintsigen & sdhci->errintsts);
> +            break;
> +        }
> +
> +        if (length == 0) {
> +            length = 65536;
> +        }
> +
> +        addr &= 0xfffffffc;  /* minimum unit of addr is 4 byte */
> +
> +        switch (attributes & SDHC_ADMA_ATTR_ACT_MASK) {
> +        case SDHC_ADMA_ATTR_ACT_TRAN:  /* data transfer */
> +            if (sdhci->trnmod & SDHC_TRNS_READ) {
> +                while (length) {
> +                    if (sdhci->data_count == 0) {
> +                        for (n = 0; n < block_size; n++) {
> +                            sdhci->fifo_buffer[n] = sd_read_data(sdhci->card);
> +                        }
> +                    }
> +                    begin = sdhci->data_count;
> +                    if ((length + begin) < block_size) {
> +                        sdhci->data_count = length + begin;
> +                        length = 0;
> +                     } else {
> +                        sdhci->data_count = block_size;
> +                        length -= block_size - begin;
> +                    }
> +                    cpu_physical_memory_write(addr, &sdhci->fifo_buffer[begin],
> +                            sdhci->data_count - begin);
> +                    addr += sdhci->data_count - begin;
> +                    if (sdhci->data_count == block_size) {
> +                        sdhci->data_count = 0;
> +                        if (sdhci->trnmod & SDHC_TRNS_BLK_CNT_EN) {
> +                            sdhci->blkcnt--;
> +                            if (sdhci->blkcnt == 0) {
> +                                break;
> +                            }
> +                        }
> +                    }
> +                }
> +            } else {
> +                while (length) {
> +                    begin = sdhci->data_count;
> +                    if ((length + begin) < block_size) {
> +                        sdhci->data_count = length + begin;
> +                        length = 0;
> +                     } else {
> +                        sdhci->data_count = block_size;
> +                        length -= block_size - begin;
> +                    }
> +                    cpu_physical_memory_read(addr,
> +                            &sdhci->fifo_buffer[begin], sdhci->data_count);
> +                    addr += sdhci->data_count - begin;
> +                    if (sdhci->data_count == block_size) {
> +                        for (n = 0; n < block_size; n++) {
> +                            sd_write_data(sdhci->card, sdhci->fifo_buffer[n]);
> +                        }
> +                        sdhci->data_count = 0;
> +                        if (sdhci->trnmod & SDHC_TRNS_BLK_CNT_EN) {
> +                            sdhci->blkcnt--;
> +                            if (sdhci->blkcnt == 0) {
> +                                break;
> +                            }
> +                        }
> +                    }
> +                }
> +            }
> +            sdhci->admasysaddr += 8;
> +            break;
> +        case SDHC_ADMA_ATTR_ACT_LINK:   /* link to next descriptor table */
> +            sdhci->admasysaddr = addr;
> +            DPRINT_L1("ADMA link: admasysaddr=0x%lx\n", sdhci->admasysaddr);
> +            break;
> +        default:
> +            sdhci->admasysaddr += 8;
> +            break;
> +        }
> +
> +        /* ADMA transfer terminates if blkcnt == 0 or by END attribute */
> +        if (((sdhci->trnmod & SDHC_TRNS_BLK_CNT_EN) &&
> +             (sdhci->blkcnt == 0)) || (attributes & SDHC_ADMA_ATTR_END)) {
> +            DPRINT_L2("ADMA transfer completed\n");
> +            if (length || ((attributes & SDHC_ADMA_ATTR_END) &&
> +               (sdhci->trnmod & SDHC_TRNS_BLK_CNT_EN) && sdhci->blkcnt != 0) ||
> +               ((sdhci->trnmod & SDHC_TRNS_BLK_CNT_EN) && sdhci->blkcnt == 0 &&
> +               (attributes & SDHC_ADMA_ATTR_END) == 0)) {
> +                ERRPRINT("ADMA length mismatch\n");
> +                s->admaerr |= SDHC_ADMAERR_LENGTH_MISMATCH |
> +                        SDHC_ADMAERR_STATE_ST_TFR;
> +                if (sdhci->errintstsen & SDHC_EISEN_ADMAERR) {
> +                    sdhci->errintsts |= SDHC_EIS_ADMAERR;
> +                    sdhci->norintsts |= SDHC_NIS_ERR;
> +                }
> +
> +                if (sdhci->errintsigen & sdhci->errintsts) {
> +                    sdhci->slotint = 1;
> +                }
> +
> +                qemu_set_irq(sdhci->irq, sdhci->errintsigen & sdhci->errintsts);
> +            }
> +
> +            s->admaerr |= EXYNOS4_SDHC_FINAL_BLOCK;
> +            SDHCI_GET_CLASS(sdhci)->end_data_transfer(sdhci);
> +            break;
> +        }
> +
> +        if (attributes & SDHC_ADMA_ATTR_INT) {
> +            DPRINT_L1("ADMA interrupt: addr=0x%lx\n", sdhci->admasysaddr);
> +            s->admaerr |= EXYNOS4_SDHC_IRQ_STAT;
> +            s->stoped_adma = true;
> +            if (sdhci->norintstsen & SDHC_NISEN_DMA) {
> +                sdhci->norintsts |= SDHC_NIS_DMA;
> +            }
> +            if (sdhci->norintsts & sdhci->norintsigen) {
> +                sdhci->slotint = 1;
> +            }
> +            qemu_set_irq(sdhci->irq, sdhci->norintsigen & sdhci->norintsts);
> +            break;
> +        }
> +    }
> +}
> +
> +static bool exynos4210_sdhci_can_issue_command(SDHCIState *sdhci)
> +{
> +    Exynos4SDHCIState *s = EXYNOS4_SDHCI(sdhci);
> +
> +    if (!SDHC_CLOCK_IS_ON(sdhci->clkcon) || (!(sdhci->pwrcon & SDHC_POWER_ON) &&
> +        (s->control2 & (EXYNOS4_SDHC_SDOPSIGPC | EXYNOS4_SDHC_SDINPSIGPC))) ||
> +        (((sdhci->prnsts & SDHC_DATA_INHIBIT) || sdhci->stoped_state) &&
> +        ((sdhci->cmdreg & SDHC_CMD_DATA_PRESENT) ||
> +        ((sdhci->cmdreg & SDHC_CMD_RESPONSE) == SDHC_CMD_RSP_WITH_BUSY &&
> +        !(SDHC_COMMAND_TYPE(sdhci->cmdreg) == SDHC_CMD_ABORT))))) {
> +        return false;
> +    }
> +
> +    return true;
> +}
> +
> +static uint64_t
> +exynos4210_sdhci_readfn(void *opaque, target_phys_addr_t offset, unsigned size)
> +{
> +    Exynos4SDHCIState *s = (Exynos4SDHCIState *)opaque;
> +    uint32_t ret;
> +
> +    switch (offset & ~0x3) {
> +    case SDHC_BDATA:
> +        /* Buffer data port read can be disabled by CONTROL2 register */
> +        if (s->control2 & EXYNOS4_SDHC_DISBUFRD) {
> +            ret = 0;
> +        } else {
> +            ret = SDHCI_GET_CLASS(s)->mem_read(SDHCI(s), offset, size);
> +        }
> +        break;
> +    case SDHC_ADMAERR:
> +        ret = (s->admaerr >> 8 * (offset - SDHC_ADMAERR)) &
> +                ((1 << 8 * size) - 1);
> +        break;
> +    case EXYNOS4_SDHC_CONTROL2:
> +        ret = (s->control2 >> 8 * (offset - EXYNOS4_SDHC_CONTROL2)) &
> +                ((1 << 8 * size) - 1);
> +        break;
> +    case EXYNOS4_SDHC_CONTROL3:
> +        ret = (s->control3 >> 8 * (offset - EXYNOS4_SDHC_CONTROL3)) &
> +                ((1 << 8 * size) - 1);
> +        break;
> +    case EXYNOS4_SDHC_CONTROL4:
> +        ret = 0;
> +        break;
> +    default:
> +        ret = SDHCI_GET_CLASS(s)->mem_read(SDHCI(s), offset, size);
> +        break;
> +    }
> +
> +    DPRINT_L2("read %ub: addr[0x%04x] -> %u(0x%x)\n", size, offset, ret, ret);
> +    return ret;
> +}
> +
> +static void exynos4210_sdhci_writefn(void *opaque, target_phys_addr_t offset,
> +        uint64_t val, unsigned size)
> +{
> +    Exynos4SDHCIState *s = (Exynos4SDHCIState *)opaque;
> +    SDHCIState *sdhci = SDHCI(s);
> +    unsigned shift;
> +
> +    DPRINT_L2("write %ub: addr[0x%04x] <- %u(0x%x)\n", size, (uint32_t)offset,
> +            (uint32_t)val, (uint32_t)val);
> +
> +    switch (offset) {
> +    case SDHC_CLKCON:
> +        if ((val & SDHC_CLOCK_SDCLK_EN) &&
> +                (sdhci->prnsts & SDHC_CARD_PRESENT)) {
> +            val |= EXYNOS4_SDHC_SDCLK_STBL;
> +        } else {
> +            val &= ~EXYNOS4_SDHC_SDCLK_STBL;
> +        }
> +        break;
> +    case EXYNOS4_SDHC_CONTROL2 ... EXYNOS4_SDHC_CONTROL2 + 3:
> +        shift = (offset - EXYNOS4_SDHC_CONTROL2) * 8;
> +        s->control2 = (s->control2 & ~(((1 << 8 * size) - 1) << shift)) |
> +                (val << shift);
> +        return;
> +    case EXYNOS4_SDHC_CONTROL3 ... EXYNOS4_SDHC_CONTROL3 + 3:
> +        shift = (offset - EXYNOS4_SDHC_CONTROL2) * 8;
> +        s->control3 = (s->control3 & ~(((1 << 8 * size) - 1) << shift)) |
> +                (val << shift);
> +        return;
> +    case SDHC_ADMAERR ... SDHC_ADMAERR + 3:
> +        if (size == 4 || (size == 2 && offset == SDHC_ADMAERR) ||
> +                (size == 1 && offset == (SDHC_ADMAERR + 1))) {
> +            uint32_t mask = 0;
> +
> +            if (size == 2) {
> +                mask = 0xFFFF0000;
> +            } else if (size == 1) {
> +                mask = 0xFFFF00FF;
> +                val <<= 8;
> +            }
> +
> +            s->admaerr = (s->admaerr & (mask | EXYNOS4_SDHC_FINAL_BLOCK |
> +               EXYNOS4_SDHC_IRQ_STAT)) | (val & ~(EXYNOS4_SDHC_FINAL_BLOCK |
> +               EXYNOS4_SDHC_IRQ_STAT | EXYNOS4_SDHC_CONTINUE_REQ));
> +            s->admaerr &= ~(val & EXYNOS4_SDHC_IRQ_STAT);
> +            if ((s->stoped_adma) && (val & EXYNOS4_SDHC_CONTINUE_REQ) &&
> +                (SDHC_DMA_TYPE(sdhci->hostctl) == SDHC_CTRL_ADMA_32)) {
> +                s->stoped_adma = false;
> +                SDHCI_GET_CLASS(sdhci)->do_adma(sdhci);
> +            }
> +        } else {
> +            uint32_t mask = (1 << (size * 8)) - 1;
> +            shift = 8 * (offset & 0x3);
> +            val <<= shift;
> +            mask = ~(mask << shift);
> +            s->admaerr = (s->admaerr & mask) | val;
> +        }
> +        return;
> +    }
> +
> +    SDHCI_GET_CLASS(s)->mem_write(sdhci, offset, val, size);
> +}
> +
> +static const MemoryRegionOps exynos4210_sdhci_mmio_ops = {
> +    .read = exynos4210_sdhci_readfn,
> +    .write = exynos4210_sdhci_writefn,
> +    .valid = {
> +        .min_access_size = 1,
> +        .max_access_size = 4,
> +        .unaligned = false
> +    },
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +};
> +
> +static const VMStateDescription exynos4210_sdhci_vmstate = {
> +    .name = "exynos4210.sdhci",
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_STRUCT(sdhci, Exynos4SDHCIState, 1, sdhci_vmstate, SDHCIState),
> +        VMSTATE_UINT32(admaerr, Exynos4SDHCIState),
> +        VMSTATE_UINT32(control2, Exynos4SDHCIState),
> +        VMSTATE_UINT32(control3, Exynos4SDHCIState),
> +        VMSTATE_BOOL(stoped_adma, Exynos4SDHCIState),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static int exynos4210_sdhci_realize(SysBusDevice *busdev)
> +{
> +    SDHCIState *sdhci = SDHCI(busdev);
> +
> +    sdhci->buf_maxsz = EXYNOS4_SDHC_MAX_BUFSZ;
> +    sdhci->fifo_buffer = g_malloc0(sdhci->buf_maxsz);
> +    sysbus_init_irq(busdev, &sdhci->irq);
> +    memory_region_init_io(&sdhci->iomem, &exynos4210_sdhci_mmio_ops,
> +            EXYNOS4_SDHCI(sdhci), "exynos4210.sdhci", SDHC_REGISTERS_MAP_SIZE);
> +    sysbus_init_mmio(busdev, &sdhci->iomem);
> +    return 0;
> +}
> +
> +static Property exynos4210_sdhci_properties[] = {
> +    DEFINE_PROP_HEX32("capareg", SDHCIState, capareg,
> +            EXYNOS4_SDHC_CAPABILITIES),
> +    DEFINE_PROP_HEX32("maxcurr", SDHCIState, maxcurr, 0),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void exynos4210_sdhci_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    SysBusDeviceClass *sbdc = SYS_BUS_DEVICE_CLASS(klass);
> +    SDHCIClass *k = SDHCI_CLASS(klass);
> +
> +    dc->vmsd = &exynos4210_sdhci_vmstate;
> +    dc->reset = exynos4210_sdhci_reset;
> +    dc->props = exynos4210_sdhci_properties;
> +    sbdc->init = exynos4210_sdhci_realize;
> +
> +    k->can_issue_command = exynos4210_sdhci_can_issue_command;
> +    k->do_adma = exynos4210_sdhci_start_adma;
> +}
> +
> +static const TypeInfo exynos4210_sdhci_type_info = {
> +    .name = TYPE_EXYNOS4_SDHC,
> +    .parent = TYPE_SDHCI,
> +    .instance_size = sizeof(Exynos4SDHCIState),
> +    .class_init = exynos4210_sdhci_class_init,
> +};
> +
> +static void exynos4210_sdhci_register_types(void)
> +{
> +    type_register_static(&exynos4210_sdhci_type_info);
> +}
> +
> +type_init(exynos4210_sdhci_register_types)
> --
> 1.7.4.1
>

Patch

diff --git a/Makefile.target b/Makefile.target
index 84951a0..7cd58a1 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -373,6 +373,7 @@  obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
 obj-arm-y += exynos4210_gic.o exynos4210_combiner.o exynos4210.o
 obj-arm-y += exynos4_boards.o exynos4210_uart.o exynos4210_pwm.o
 obj-arm-y += exynos4210_pmu.o exynos4210_mct.o exynos4210_fimd.o
+obj-arm-y += exynos4210_sdhci.o
 obj-arm-y += arm_l2x0.o
 obj-arm-y += arm_mptimer.o a15mpcore.o
 obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index afc4bdc..4f9d91b 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -56,6 +56,12 @@ 
 #define EXYNOS4210_EXT_COMBINER_BASE_ADDR   0x10440000
 #define EXYNOS4210_INT_COMBINER_BASE_ADDR   0x10448000
 
+/* SD/MMC host controllers SFR base addresses */
+#define EXYNOS4210_SDHC0_BASE_ADDR          0x12510000
+#define EXYNOS4210_SDHC1_BASE_ADDR          0x12520000
+#define EXYNOS4210_SDHC2_BASE_ADDR          0x12530000
+#define EXYNOS4210_SDHC3_BASE_ADDR          0x12540000
+
 /* PMU SFR base address */
 #define EXYNOS4210_PMU_BASE_ADDR            0x10020000
 
@@ -289,6 +295,20 @@  Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
                            EXYNOS4210_UART3_FIFO_SIZE, 3, NULL,
                   s->irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 3)]);
 
+    /*** SD/MMC host controllers ***/
+
+    sysbus_create_simple("exynos4210.sdhci", EXYNOS4210_SDHC0_BASE_ADDR,
+            s->irq_table[exynos4210_get_irq(29, 0)]);
+
+    sysbus_create_simple("exynos4210.sdhci", EXYNOS4210_SDHC1_BASE_ADDR,
+            s->irq_table[exynos4210_get_irq(29, 1)]);
+
+    sysbus_create_simple("exynos4210.sdhci", EXYNOS4210_SDHC2_BASE_ADDR,
+            s->irq_table[exynos4210_get_irq(29, 2)]);
+
+    sysbus_create_simple("exynos4210.sdhci", EXYNOS4210_SDHC3_BASE_ADDR,
+            s->irq_table[exynos4210_get_irq(29, 3)]);
+
     /*** Display controller (FIMD) ***/
     sysbus_create_varargs("exynos4210.fimd", EXYNOS4210_FIMD0_BASE_ADDR,
             s->irq_table[exynos4210_get_irq(11, 0)],
diff --git a/hw/exynos4210_sdhci.c b/hw/exynos4210_sdhci.c
new file mode 100644
index 0000000..cb63279
--- /dev/null
+++ b/hw/exynos4210_sdhci.c
@@ -0,0 +1,438 @@ 
+/*
+ * Samsung Exynos4210 SD/MMC host controller model
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * Mitsyanko Igor <i.mitsyanko@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "sdhci.h"
+
+#define EXYNOS4_SDHC_CAPABILITIES    0x05E80080
+#define EXYNOS4_SDHC_MAX_BUFSZ       512
+
+#define EXYNOS4_SDHC_DEBUG           0
+
+#if EXYNOS4_SDHC_DEBUG == 0
+    #define DPRINT_L1(fmt, args...)       do { } while (0)
+    #define DPRINT_L2(fmt, args...)       do { } while (0)
+    #define ERRPRINT(fmt, args...)        do { } while (0)
+#elif EXYNOS4_SDHC_DEBUG == 1
+    #define DPRINT_L1(fmt, args...)       \
+        do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0)
+    #define DPRINT_L2(fmt, args...)       do { } while (0)
+    #define ERRPRINT(fmt, args...)        \
+        do {fprintf(stderr, "QEMU SDHC ERROR: "fmt, ## args); } while (0)
+#else
+    #define DPRINT_L1(fmt, args...)       \
+        do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0)
+    #define DPRINT_L2(fmt, args...)       \
+        do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0)
+    #define ERRPRINT(fmt, args...)        \
+        do {fprintf(stderr, "QEMU SDHC ERROR: "fmt, ## args); } while (0)
+#endif
+
+
+#define TYPE_EXYNOS4_SDHC            "exynos4210.sdhci"
+#define EXYNOS4_SDHCI(obj)           \
+     OBJECT_CHECK(Exynos4SDHCIState, (obj), TYPE_EXYNOS4_SDHC)
+
+/* ADMA Error Status Register */
+#define EXYNOS4_SDHC_FINAL_BLOCK     (1 << 10)
+#define EXYNOS4_SDHC_CONTINUE_REQ    (1 << 9)
+#define EXYNOS4_SDHC_IRQ_STAT        (1 << 8)
+/* Control register 2 */
+#define EXYNOS4_SDHC_CONTROL2        0x80
+#define EXYNOS4_SDHC_HWINITFIN       (1 << 0)
+#define EXYNOS4_SDHC_DISBUFRD        (1 << 6)
+#define EXYNOS4_SDHC_SDOPSIGPC       (1 << 12)
+#define EXYNOS4_SDHC_SDINPSIGPC      (1 << 3)
+/* Control register 3 */
+#define EXYNOS4_SDHC_CONTROL3        0x84
+/* Control register 4 */
+#define EXYNOS4_SDHC_CONTROL4        0x8C
+/* Clock control register */
+#define EXYNOS4_SDHC_SDCLK_STBL      (1 << 3)
+
+typedef struct Exynos4SDHCIState {
+    SDHCIState sdhci;
+
+    uint32_t admaerr;
+    uint32_t control2;
+    uint32_t control3;
+    bool stoped_adma;
+} Exynos4SDHCIState;
+
+static void exynos4210_sdhci_reset(DeviceState *d)
+{
+    Exynos4SDHCIState *s = EXYNOS4_SDHCI(d);
+
+    SDHCI_GET_CLASS(d)->reset(SDHCI(d));
+    s->stoped_adma = false;
+    s->admaerr = 0;
+    s->control2 = 0;
+    s->control3 = 0x7F5F3F1F;
+}
+
+static void exynos4210_sdhci_start_adma(SDHCIState *sdhci)
+{
+    Exynos4SDHCIState *s = EXYNOS4_SDHCI(sdhci);
+    unsigned int length, n, begin;
+    target_phys_addr_t entry_addr;
+    uint32_t addr;
+    uint8_t attributes;
+    const uint16_t block_size = sdhci->blksize & 0x0fff;
+    s->admaerr &=
+            ~(EXYNOS4_SDHC_FINAL_BLOCK | SDHC_ADMAERR_LENGTH_MISMATCH);
+
+    while (1) {
+        addr = length = attributes = 0;
+        entry_addr = (target_phys_addr_t)(sdhci->admasysaddr & 0xFFFFFFFFull);
+
+        /* fetch next entry from descriptor table */
+        cpu_physical_memory_read(entry_addr + 4, (uint8_t *)(&addr), 4);
+        cpu_physical_memory_read(entry_addr + 2, (uint8_t *)(&length), 2);
+        cpu_physical_memory_read(entry_addr, (uint8_t *)(&attributes), 1);
+        DPRINT_L1("ADMA loop: addr=0x%08x, len=%d, attr=%x\n",
+                addr, length, attributes);
+
+        if ((attributes & SDHC_ADMA_ATTR_VALID) == 0) {
+            /* Indicate that error occurred in ST_FDS state */
+            s->admaerr &= ~SDHC_ADMAERR_STATE_MASK;
+            s->admaerr |= SDHC_ADMAERR_STATE_ST_FDS;
+            DPRINT_L1("ADMA not valid at addr=0x%lx\n", sdhci->admasysaddr);
+
+            if (sdhci->errintstsen & SDHC_EISEN_ADMAERR) {
+                sdhci->errintsts |= SDHC_EIS_ADMAERR;
+                sdhci->norintsts |= SDHC_NIS_ERR;
+            }
+
+            if (sdhci->errintsigen & sdhci->errintsts) {
+                sdhci->slotint = 1;
+            }
+
+            qemu_set_irq(sdhci->irq, sdhci->errintsigen & sdhci->errintsts);
+            break;
+        }
+
+        if (length == 0) {
+            length = 65536;
+        }
+
+        addr &= 0xfffffffc;  /* minimum unit of addr is 4 byte */
+
+        switch (attributes & SDHC_ADMA_ATTR_ACT_MASK) {
+        case SDHC_ADMA_ATTR_ACT_TRAN:  /* data transfer */
+            if (sdhci->trnmod & SDHC_TRNS_READ) {
+                while (length) {
+                    if (sdhci->data_count == 0) {
+                        for (n = 0; n < block_size; n++) {
+                            sdhci->fifo_buffer[n] = sd_read_data(sdhci->card);
+                        }
+                    }
+                    begin = sdhci->data_count;
+                    if ((length + begin) < block_size) {
+                        sdhci->data_count = length + begin;
+                        length = 0;
+                     } else {
+                        sdhci->data_count = block_size;
+                        length -= block_size - begin;
+                    }
+                    cpu_physical_memory_write(addr, &sdhci->fifo_buffer[begin],
+                            sdhci->data_count - begin);
+                    addr += sdhci->data_count - begin;
+                    if (sdhci->data_count == block_size) {
+                        sdhci->data_count = 0;
+                        if (sdhci->trnmod & SDHC_TRNS_BLK_CNT_EN) {
+                            sdhci->blkcnt--;
+                            if (sdhci->blkcnt == 0) {
+                                break;
+                            }
+                        }
+                    }
+                }
+            } else {
+                while (length) {
+                    begin = sdhci->data_count;
+                    if ((length + begin) < block_size) {
+                        sdhci->data_count = length + begin;
+                        length = 0;
+                     } else {
+                        sdhci->data_count = block_size;
+                        length -= block_size - begin;
+                    }
+                    cpu_physical_memory_read(addr,
+                            &sdhci->fifo_buffer[begin], sdhci->data_count);
+                    addr += sdhci->data_count - begin;
+                    if (sdhci->data_count == block_size) {
+                        for (n = 0; n < block_size; n++) {
+                            sd_write_data(sdhci->card, sdhci->fifo_buffer[n]);
+                        }
+                        sdhci->data_count = 0;
+                        if (sdhci->trnmod & SDHC_TRNS_BLK_CNT_EN) {
+                            sdhci->blkcnt--;
+                            if (sdhci->blkcnt == 0) {
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+            sdhci->admasysaddr += 8;
+            break;
+        case SDHC_ADMA_ATTR_ACT_LINK:   /* link to next descriptor table */
+            sdhci->admasysaddr = addr;
+            DPRINT_L1("ADMA link: admasysaddr=0x%lx\n", sdhci->admasysaddr);
+            break;
+        default:
+            sdhci->admasysaddr += 8;
+            break;
+        }
+
+        /* ADMA transfer terminates if blkcnt == 0 or by END attribute */
+        if (((sdhci->trnmod & SDHC_TRNS_BLK_CNT_EN) &&
+             (sdhci->blkcnt == 0)) || (attributes & SDHC_ADMA_ATTR_END)) {
+            DPRINT_L2("ADMA transfer completed\n");
+            if (length || ((attributes & SDHC_ADMA_ATTR_END) &&
+               (sdhci->trnmod & SDHC_TRNS_BLK_CNT_EN) && sdhci->blkcnt != 0) ||
+               ((sdhci->trnmod & SDHC_TRNS_BLK_CNT_EN) && sdhci->blkcnt == 0 &&
+               (attributes & SDHC_ADMA_ATTR_END) == 0)) {
+                ERRPRINT("ADMA length mismatch\n");
+                s->admaerr |= SDHC_ADMAERR_LENGTH_MISMATCH |
+                        SDHC_ADMAERR_STATE_ST_TFR;
+                if (sdhci->errintstsen & SDHC_EISEN_ADMAERR) {
+                    sdhci->errintsts |= SDHC_EIS_ADMAERR;
+                    sdhci->norintsts |= SDHC_NIS_ERR;
+                }
+
+                if (sdhci->errintsigen & sdhci->errintsts) {
+                    sdhci->slotint = 1;
+                }
+
+                qemu_set_irq(sdhci->irq, sdhci->errintsigen & sdhci->errintsts);
+            }
+
+            s->admaerr |= EXYNOS4_SDHC_FINAL_BLOCK;
+            SDHCI_GET_CLASS(sdhci)->end_data_transfer(sdhci);
+            break;
+        }
+
+        if (attributes & SDHC_ADMA_ATTR_INT) {
+            DPRINT_L1("ADMA interrupt: addr=0x%lx\n", sdhci->admasysaddr);
+            s->admaerr |= EXYNOS4_SDHC_IRQ_STAT;
+            s->stoped_adma = true;
+            if (sdhci->norintstsen & SDHC_NISEN_DMA) {
+                sdhci->norintsts |= SDHC_NIS_DMA;
+            }
+            if (sdhci->norintsts & sdhci->norintsigen) {
+                sdhci->slotint = 1;
+            }
+            qemu_set_irq(sdhci->irq, sdhci->norintsigen & sdhci->norintsts);
+            break;
+        }
+    }
+}
+
+static bool exynos4210_sdhci_can_issue_command(SDHCIState *sdhci)
+{
+    Exynos4SDHCIState *s = EXYNOS4_SDHCI(sdhci);
+
+    if (!SDHC_CLOCK_IS_ON(sdhci->clkcon) || (!(sdhci->pwrcon & SDHC_POWER_ON) &&
+        (s->control2 & (EXYNOS4_SDHC_SDOPSIGPC | EXYNOS4_SDHC_SDINPSIGPC))) ||
+        (((sdhci->prnsts & SDHC_DATA_INHIBIT) || sdhci->stoped_state) &&
+        ((sdhci->cmdreg & SDHC_CMD_DATA_PRESENT) ||
+        ((sdhci->cmdreg & SDHC_CMD_RESPONSE) == SDHC_CMD_RSP_WITH_BUSY &&
+        !(SDHC_COMMAND_TYPE(sdhci->cmdreg) == SDHC_CMD_ABORT))))) {
+        return false;
+    }
+
+    return true;
+}
+
+static uint64_t
+exynos4210_sdhci_readfn(void *opaque, target_phys_addr_t offset, unsigned size)
+{
+    Exynos4SDHCIState *s = (Exynos4SDHCIState *)opaque;
+    uint32_t ret;
+
+    switch (offset & ~0x3) {
+    case SDHC_BDATA:
+        /* Buffer data port read can be disabled by CONTROL2 register */
+        if (s->control2 & EXYNOS4_SDHC_DISBUFRD) {
+            ret = 0;
+        } else {
+            ret = SDHCI_GET_CLASS(s)->mem_read(SDHCI(s), offset, size);
+        }
+        break;
+    case SDHC_ADMAERR:
+        ret = (s->admaerr >> 8 * (offset - SDHC_ADMAERR)) &
+                ((1 << 8 * size) - 1);
+        break;
+    case EXYNOS4_SDHC_CONTROL2:
+        ret = (s->control2 >> 8 * (offset - EXYNOS4_SDHC_CONTROL2)) &
+                ((1 << 8 * size) - 1);
+        break;
+    case EXYNOS4_SDHC_CONTROL3:
+        ret = (s->control3 >> 8 * (offset - EXYNOS4_SDHC_CONTROL3)) &
+                ((1 << 8 * size) - 1);
+        break;
+    case EXYNOS4_SDHC_CONTROL4:
+        ret = 0;
+        break;
+    default:
+        ret = SDHCI_GET_CLASS(s)->mem_read(SDHCI(s), offset, size);
+        break;
+    }
+
+    DPRINT_L2("read %ub: addr[0x%04x] -> %u(0x%x)\n", size, offset, ret, ret);
+    return ret;
+}
+
+static void exynos4210_sdhci_writefn(void *opaque, target_phys_addr_t offset,
+        uint64_t val, unsigned size)
+{
+    Exynos4SDHCIState *s = (Exynos4SDHCIState *)opaque;
+    SDHCIState *sdhci = SDHCI(s);
+    unsigned shift;
+
+    DPRINT_L2("write %ub: addr[0x%04x] <- %u(0x%x)\n", size, (uint32_t)offset,
+            (uint32_t)val, (uint32_t)val);
+
+    switch (offset) {
+    case SDHC_CLKCON:
+        if ((val & SDHC_CLOCK_SDCLK_EN) &&
+                (sdhci->prnsts & SDHC_CARD_PRESENT)) {
+            val |= EXYNOS4_SDHC_SDCLK_STBL;
+        } else {
+            val &= ~EXYNOS4_SDHC_SDCLK_STBL;
+        }
+        break;
+    case EXYNOS4_SDHC_CONTROL2 ... EXYNOS4_SDHC_CONTROL2 + 3:
+        shift = (offset - EXYNOS4_SDHC_CONTROL2) * 8;
+        s->control2 = (s->control2 & ~(((1 << 8 * size) - 1) << shift)) |
+                (val << shift);
+        return;
+    case EXYNOS4_SDHC_CONTROL3 ... EXYNOS4_SDHC_CONTROL3 + 3:
+        shift = (offset - EXYNOS4_SDHC_CONTROL2) * 8;
+        s->control3 = (s->control3 & ~(((1 << 8 * size) - 1) << shift)) |
+                (val << shift);
+        return;
+    case SDHC_ADMAERR ... SDHC_ADMAERR + 3:
+        if (size == 4 || (size == 2 && offset == SDHC_ADMAERR) ||
+                (size == 1 && offset == (SDHC_ADMAERR + 1))) {
+            uint32_t mask = 0;
+
+            if (size == 2) {
+                mask = 0xFFFF0000;
+            } else if (size == 1) {
+                mask = 0xFFFF00FF;
+                val <<= 8;
+            }
+
+            s->admaerr = (s->admaerr & (mask | EXYNOS4_SDHC_FINAL_BLOCK |
+               EXYNOS4_SDHC_IRQ_STAT)) | (val & ~(EXYNOS4_SDHC_FINAL_BLOCK |
+               EXYNOS4_SDHC_IRQ_STAT | EXYNOS4_SDHC_CONTINUE_REQ));
+            s->admaerr &= ~(val & EXYNOS4_SDHC_IRQ_STAT);
+            if ((s->stoped_adma) && (val & EXYNOS4_SDHC_CONTINUE_REQ) &&
+                (SDHC_DMA_TYPE(sdhci->hostctl) == SDHC_CTRL_ADMA_32)) {
+                s->stoped_adma = false;
+                SDHCI_GET_CLASS(sdhci)->do_adma(sdhci);
+            }
+        } else {
+            uint32_t mask = (1 << (size * 8)) - 1;
+            shift = 8 * (offset & 0x3);
+            val <<= shift;
+            mask = ~(mask << shift);
+            s->admaerr = (s->admaerr & mask) | val;
+        }
+        return;
+    }
+
+    SDHCI_GET_CLASS(s)->mem_write(sdhci, offset, val, size);
+}
+
+static const MemoryRegionOps exynos4210_sdhci_mmio_ops = {
+    .read = exynos4210_sdhci_readfn,
+    .write = exynos4210_sdhci_writefn,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 4,
+        .unaligned = false
+    },
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const VMStateDescription exynos4210_sdhci_vmstate = {
+    .name = "exynos4210.sdhci",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_STRUCT(sdhci, Exynos4SDHCIState, 1, sdhci_vmstate, SDHCIState),
+        VMSTATE_UINT32(admaerr, Exynos4SDHCIState),
+        VMSTATE_UINT32(control2, Exynos4SDHCIState),
+        VMSTATE_UINT32(control3, Exynos4SDHCIState),
+        VMSTATE_BOOL(stoped_adma, Exynos4SDHCIState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static int exynos4210_sdhci_realize(SysBusDevice *busdev)
+{
+    SDHCIState *sdhci = SDHCI(busdev);
+
+    sdhci->buf_maxsz = EXYNOS4_SDHC_MAX_BUFSZ;
+    sdhci->fifo_buffer = g_malloc0(sdhci->buf_maxsz);
+    sysbus_init_irq(busdev, &sdhci->irq);
+    memory_region_init_io(&sdhci->iomem, &exynos4210_sdhci_mmio_ops,
+            EXYNOS4_SDHCI(sdhci), "exynos4210.sdhci", SDHC_REGISTERS_MAP_SIZE);
+    sysbus_init_mmio(busdev, &sdhci->iomem);
+    return 0;
+}
+
+static Property exynos4210_sdhci_properties[] = {
+    DEFINE_PROP_HEX32("capareg", SDHCIState, capareg,
+            EXYNOS4_SDHC_CAPABILITIES),
+    DEFINE_PROP_HEX32("maxcurr", SDHCIState, maxcurr, 0),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void exynos4210_sdhci_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    SysBusDeviceClass *sbdc = SYS_BUS_DEVICE_CLASS(klass);
+    SDHCIClass *k = SDHCI_CLASS(klass);
+
+    dc->vmsd = &exynos4210_sdhci_vmstate;
+    dc->reset = exynos4210_sdhci_reset;
+    dc->props = exynos4210_sdhci_properties;
+    sbdc->init = exynos4210_sdhci_realize;
+
+    k->can_issue_command = exynos4210_sdhci_can_issue_command;
+    k->do_adma = exynos4210_sdhci_start_adma;
+}
+
+static const TypeInfo exynos4210_sdhci_type_info = {
+    .name = TYPE_EXYNOS4_SDHC,
+    .parent = TYPE_SDHCI,
+    .instance_size = sizeof(Exynos4SDHCIState),
+    .class_init = exynos4210_sdhci_class_init,
+};
+
+static void exynos4210_sdhci_register_types(void)
+{
+    type_register_static(&exynos4210_sdhci_type_info);
+}
+
+type_init(exynos4210_sdhci_register_types)