From patchwork Wed Apr 18 08:43:40 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mitsyanko Igor X-Patchwork-Id: 153439 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id F0CF6B6EEC for ; Wed, 18 Apr 2012 18:44:25 +1000 (EST) Received: from localhost ([::1]:59118 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SKQUx-0001zL-TI for incoming@patchwork.ozlabs.org; Wed, 18 Apr 2012 04:44:23 -0400 Received: from eggs.gnu.org ([208.118.235.92]:42689) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SKQUf-0001of-71 for qemu-devel@nongnu.org; Wed, 18 Apr 2012 04:44:12 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1SKQUV-0002L7-KA for qemu-devel@nongnu.org; Wed, 18 Apr 2012 04:44:04 -0400 Received: from mailout3.w1.samsung.com ([210.118.77.13]:58902) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SKQUV-0002KU-8k for qemu-devel@nongnu.org; Wed, 18 Apr 2012 04:43:55 -0400 MIME-version: 1.0 Content-transfer-encoding: 7BIT Content-type: TEXT/PLAIN Received: from euspt1 ([210.118.77.13]) by mailout3.w1.samsung.com (Sun Java(tm) System Messaging Server 6.3-8.04 (built Jul 29 2009; 32bit)) with ESMTP id <0M2O005PE2WDO680@mailout3.w1.samsung.com> for qemu-devel@nongnu.org; Wed, 18 Apr 2012 09:43:25 +0100 (BST) Received: from dodo.rnd.samsung.ru ([106.109.8.162]) by spt1.w1.samsung.com (iPlanet Messaging Server 5.2 Patch 2 (built Jul 14 2004)) with ESMTPA id <0M2O00HSF2WTTX@spt1.w1.samsung.com> for qemu-devel@nongnu.org; Wed, 18 Apr 2012 09:43:51 +0100 (BST) Date: Wed, 18 Apr 2012 12:43:40 +0400 From: Igor Mitsyanko In-reply-to: <1334738620-7034-1-git-send-email-i.mitsyanko@samsung.com> To: qemu-devel@nongnu.org Message-id: <1334738620-7034-3-git-send-email-i.mitsyanko@samsung.com> X-Mailer: git-send-email 1.7.4.1 References: <1334738620-7034-1-git-send-email-i.mitsyanko@samsung.com> X-detected-operating-system: by eggs.gnu.org: Solaris 9.1 X-Received-From: 210.118.77.13 Cc: peter.maydell@linaro.org, Igor Mitsyanko , e.voevodin@samsung.com, peter.crosthwaite@petalogix.com, kyungmin.park@samsung.com, d.solodkiy@samsung.com, m.kozlov@samsung.com Subject: [Qemu-devel] [RFC PATCH 2/2] target-arm: introduce Exynos4210 SD host controller model X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Exynos4210 SD/MMC host controller is based on SD association standart host controller ver. 2.00 Signed-off-by: Igor Mitsyanko --- 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 + * + * 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 . + */ + +#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)