From patchwork Thu Jul 5 04:04:00 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Peter A. G. Crosthwaite" X-Patchwork-Id: 169066 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 46AC72C0089 for ; Thu, 5 Jul 2012 14:05:50 +1000 (EST) Received: from localhost ([::1]:48239 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SmdK8-0008IH-1e for incoming@patchwork.ozlabs.org; Thu, 05 Jul 2012 00:05:48 -0400 Received: from eggs.gnu.org ([208.118.235.92]:36562) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SmdJv-0008DU-GC for qemu-devel@nongnu.org; Thu, 05 Jul 2012 00:05:37 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1SmdJr-00068A-Nj for qemu-devel@nongnu.org; Thu, 05 Jul 2012 00:05:34 -0400 Received: from mail-gg0-f173.google.com ([209.85.161.173]:35091) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SmdJr-00065g-IU for qemu-devel@nongnu.org; Thu, 05 Jul 2012 00:05:31 -0400 Received: by ggnp1 with SMTP id p1so7525472ggn.4 for ; Wed, 04 Jul 2012 21:05:29 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references :in-reply-to:references:x-gm-message-state; bh=apYt2Bpc7q7Y8XZWw47flLw/zKoNoTto8ekEK8iJRu0=; b=LKH2DxwoYZAx+JkoxNI6QUtdkM2sYJEu/1YR3TKSqfX0IV92QpJrZkJTxCQZ6qsapF M02lwMtih+bNY6HrERCvmFqfPL+hczbtiyKfHbA4He1eALqYvhBTnHgQ6gS320DYCbIC l0mh0504eHCwIAakpUrn/P9TS/gDAr2U7aQjBiHWGLo7Dz1dbB9Jov1kMaqlrG56Qne0 ozQIq7PbmLMCIeynsInBzVwXGTDnCs1fFr7FCYr2dQOKtRnw/CfPDRncVl8ZCH9oO0Kd dzPKLp4DCrCCA07B12+2Dho8XVOZdP1x7lWmo5IVdDirUSsQMOufCXr+ZwL9KTn1PXxZ NPIA== Received: by 10.50.140.4 with SMTP id rc4mr13214962igb.68.1341461129315; Wed, 04 Jul 2012 21:05:29 -0700 (PDT) Received: from localhost ([124.148.20.9]) by mx.google.com with ESMTPS id gs4sm33029550igc.1.2012.07.04.21.05.24 (version=TLSv1/SSLv3 cipher=OTHER); Wed, 04 Jul 2012 21:05:28 -0700 (PDT) From: "Peter A. G. Crosthwaite" To: qemu-devel@nongnu.org, i.mitsyanko@samsung.com Date: Thu, 5 Jul 2012 14:04:00 +1000 Message-Id: <0020234e7fc3001f66ef67a1d3e19a01b6e498a5.1341457220.git.peter.crosthwaite@petalogix.com> X-Mailer: git-send-email 1.7.3.2 In-Reply-To: References: In-Reply-To: References: X-Gm-Message-State: ALoCoQkCdGl9OhedE5A8xAPOafD9VS4imO3NGaBsuU53FOuGy2TwS0WdG6sfSReL45SkLuReKi5z X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 209.85.161.173 Cc: peter.maydell@linaro.org, e.voevodin@samsung.com, peter.crosthwaite@petalogix.com, kyungmin.park@samsung.com, d.solodkiy@samsung.com, edgar.iglesias@gmail.com, m.kozlov@samsung.com, john.williams@petalogix.com Subject: [Qemu-devel] [PATCH v5 2/4] exynos4210: Added 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 From: Igor Mitsyanko Custom Exynos4210 SD/MMC host controller, based on SD association standard host controller ver. 2.00. Signed-off-by: Igor Mitsyanko --- changed from v4 (Igor): set irq on SLOTINT status instead of interrupt registers status; instead; conditional in exynos4210_sdhci_can_issue_command() made more readable and documented; add a comment to exynos4210_sdhci_writefn()'s SDHC_CLKCON case statement; do not override superclass property in exynos4210.sdhci class, set properties to required value in realize function changed from v3: rebased for new Makefile system fixed commit msg typo (Andreas review) hw/arm/Makefile.objs | 1 + hw/exynos4210.c | 20 +++ hw/exynos4210_sdhci.c | 443 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 464 insertions(+), 0 deletions(-) create mode 100644 hw/exynos4210_sdhci.c diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs index 88ff47d..c3afb6e 100644 --- a/hw/arm/Makefile.objs +++ b/hw/arm/Makefile.objs @@ -11,6 +11,7 @@ obj-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o obj-y += exynos4210_gic.o exynos4210_combiner.o exynos4210.o obj-y += exynos4_boards.o exynos4210_uart.o exynos4210_pwm.o obj-y += exynos4210_pmu.o exynos4210_mct.o exynos4210_fimd.o +obj-y += exynos4210_sdhci.o obj-y += arm_l2x0.o obj-y += arm_mptimer.o a15mpcore.o obj-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o diff --git a/hw/exynos4210.c b/hw/exynos4210.c index 9c20b3f..1a2e3d9 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 @@ -292,6 +298,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..4e87c0c --- /dev/null +++ b/hw/exynos4210_sdhci.c @@ -0,0 +1,443 @@ +/* + * 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) + +#define EXYNOS4_SDHC_CMD_USES_DAT(cmd) \ + (((cmd) & SDHC_CMD_DATA_PRESENT) || \ + ((cmd) & SDHC_CMD_RESPONSE) == SDHC_CMD_RSP_WITH_BUSY) + +typedef struct Exynos4SDHCIState { + SDHCIState sdhci; + + uint32_t admaerr; + uint32_t control2; + uint32_t control3; + bool stopped_adma; +} Exynos4SDHCIState; + +static void exynos4210_sdhci_reset(DeviceState *d) +{ + Exynos4SDHCIState *s = EXYNOS4_SDHCI(d); + + SDHCI_GET_CLASS(d)->reset(SDHCI(d)); + s->stopped_adma = false; + s->admaerr = 0; + s->control2 = 0; + s->control3 = 0x7F5F3F1F; +} + +static void exynos4210_sdhci_start_adma(SDHCIState *sdhci) +{ + Exynos4SDHCIState *s = EXYNOS4_SDHCI(sdhci); + unsigned int length, n, begin; + target_phys_addr_t entry_addr; + uint32_t addr; + uint8_t attributes; + const uint16_t block_size = sdhci->blksize & 0x0fff; + s->admaerr &= + ~(EXYNOS4_SDHC_FINAL_BLOCK | SDHC_ADMAERR_LENGTH_MISMATCH); + + while (1) { + addr = length = attributes = 0; + entry_addr = (target_phys_addr_t)(sdhci->admasysaddr & 0xFFFFFFFFull); + + /* fetch next entry from descriptor table */ + cpu_physical_memory_read(entry_addr + 4, (uint8_t *)(&addr), 4); + cpu_physical_memory_read(entry_addr + 2, (uint8_t *)(&length), 2); + cpu_physical_memory_read(entry_addr, (uint8_t *)(&attributes), 1); + DPRINT_L1("ADMA loop: addr=0x%08x, len=%d, attr=%x\n", + addr, length, attributes); + + if ((attributes & SDHC_ADMA_ATTR_VALID) == 0) { + /* Indicate that error occurred in ST_FDS state */ + s->admaerr &= ~SDHC_ADMAERR_STATE_MASK; + s->admaerr |= SDHC_ADMAERR_STATE_ST_FDS; + DPRINT_L1("ADMA not valid at addr=0x%lx\n", sdhci->admasysaddr); + + if (sdhci->errintstsen & SDHC_EISEN_ADMAERR) { + sdhci->errintsts |= SDHC_EIS_ADMAERR; + sdhci->norintsts |= SDHC_NIS_ERR; + } + + if (sdhci->errintsigen & sdhci->errintsts) { + sdhci->slotint = 1; + } + + qemu_set_irq(sdhci->irq, sdhci->slotint); + 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->slotint); + } + + s->admaerr |= EXYNOS4_SDHC_FINAL_BLOCK; + SDHCI_GET_CLASS(sdhci)->end_data_transfer(sdhci); + break; + } + + if (attributes & SDHC_ADMA_ATTR_INT) { + DPRINT_L1("ADMA interrupt: addr=0x%lx\n", sdhci->admasysaddr); + s->admaerr |= EXYNOS4_SDHC_IRQ_STAT; + s->stopped_adma = true; + if (sdhci->norintstsen & SDHC_NISEN_DMA) { + sdhci->norintsts |= SDHC_NIS_DMA; + } + if (sdhci->norintsts & sdhci->norintsigen) { + sdhci->slotint = 1; + } + qemu_set_irq(sdhci->irq, sdhci->slotint); + break; + } + } +} + +static bool exynos4210_sdhci_can_issue_command(SDHCIState *sdhci) +{ + Exynos4SDHCIState *s = EXYNOS4_SDHCI(sdhci); + + /* Check that power is supplied and clock is enabled. + * If SDOPSIGPC and SDINPSIGPC bits in CONTROL2 register are not set, power + * is supplied regardless of the PWRCON register state */ + if (!SDHC_CLOCK_IS_ON(sdhci->clkcon) || (!(sdhci->pwrcon & SDHC_POWER_ON) && + (s->control2 & (EXYNOS4_SDHC_SDOPSIGPC | EXYNOS4_SDHC_SDINPSIGPC)))) { + return false; + } + + /* Controller cannot issue a command which uses data line (unless its an + * ABORT command) if data line is currently busy */ + if (((sdhci->prnsts & SDHC_DATA_INHIBIT) || sdhci->stopped_state) && + (EXYNOS4_SDHC_CMD_USES_DAT(sdhci->cmdreg) && + SDHC_COMMAND_TYPE(sdhci->cmdreg) != SDHC_CMD_ABORT)) { + return false; + } + + return true; +} + +static uint64_t +exynos4210_sdhci_readfn(void *opaque, target_phys_addr_t offset, unsigned size) +{ + Exynos4SDHCIState *s = (Exynos4SDHCIState *)opaque; + uint32_t ret; + + switch (offset & ~0x3) { + case SDHC_BDATA: + /* Buffer data port read can be disabled by CONTROL2 register */ + if (s->control2 & EXYNOS4_SDHC_DISBUFRD) { + ret = 0; + } else { + ret = SDHCI_GET_CLASS(s)->mem_read(SDHCI(s), offset, size); + } + break; + case SDHC_ADMAERR: + ret = (s->admaerr >> 8 * (offset - SDHC_ADMAERR)) & + ((1 << 8 * size) - 1); + break; + case EXYNOS4_SDHC_CONTROL2: + ret = (s->control2 >> 8 * (offset - EXYNOS4_SDHC_CONTROL2)) & + ((1 << 8 * size) - 1); + break; + case EXYNOS4_SDHC_CONTROL3: + ret = (s->control3 >> 8 * (offset - EXYNOS4_SDHC_CONTROL3)) & + ((1 << 8 * size) - 1); + break; + case EXYNOS4_SDHC_CONTROL4: + ret = 0; + break; + default: + ret = SDHCI_GET_CLASS(s)->mem_read(SDHCI(s), offset, size); + break; + } + + DPRINT_L2("read %ub: addr[0x%04x] -> %u(0x%x)\n", size, offset, ret, ret); + return ret; +} + +static void exynos4210_sdhci_writefn(void *opaque, target_phys_addr_t offset, + uint64_t val, unsigned size) +{ + Exynos4SDHCIState *s = (Exynos4SDHCIState *)opaque; + SDHCIState *sdhci = SDHCI(s); + unsigned shift; + + DPRINT_L2("write %ub: addr[0x%04x] <- %u(0x%x)\n", size, (uint32_t)offset, + (uint32_t)val, (uint32_t)val); + + switch (offset) { + case SDHC_CLKCON: + if ((val & SDHC_CLOCK_SDCLK_EN) && + (sdhci->prnsts & SDHC_CARD_PRESENT)) { + val |= EXYNOS4_SDHC_SDCLK_STBL; + } else { + val &= ~EXYNOS4_SDHC_SDCLK_STBL; + } + /* Break out to superclass write to handle the rest of this register */ + break; + case EXYNOS4_SDHC_CONTROL2 ... EXYNOS4_SDHC_CONTROL2 + 3: + shift = (offset - EXYNOS4_SDHC_CONTROL2) * 8; + s->control2 = (s->control2 & ~(((1 << 8 * size) - 1) << shift)) | + (val << shift); + return; + case EXYNOS4_SDHC_CONTROL3 ... EXYNOS4_SDHC_CONTROL3 + 3: + shift = (offset - EXYNOS4_SDHC_CONTROL2) * 8; + s->control3 = (s->control3 & ~(((1 << 8 * size) - 1) << shift)) | + (val << shift); + return; + case SDHC_ADMAERR ... SDHC_ADMAERR + 3: + if (size == 4 || (size == 2 && offset == SDHC_ADMAERR) || + (size == 1 && offset == (SDHC_ADMAERR + 1))) { + uint32_t mask = 0; + + if (size == 2) { + mask = 0xFFFF0000; + } else if (size == 1) { + mask = 0xFFFF00FF; + val <<= 8; + } + + s->admaerr = (s->admaerr & (mask | EXYNOS4_SDHC_FINAL_BLOCK | + EXYNOS4_SDHC_IRQ_STAT)) | (val & ~(EXYNOS4_SDHC_FINAL_BLOCK | + EXYNOS4_SDHC_IRQ_STAT | EXYNOS4_SDHC_CONTINUE_REQ)); + s->admaerr &= ~(val & EXYNOS4_SDHC_IRQ_STAT); + if ((s->stopped_adma) && (val & EXYNOS4_SDHC_CONTINUE_REQ) && + (SDHC_DMA_TYPE(sdhci->hostctl) == SDHC_CTRL_ADMA2_32)) { + s->stopped_adma = false; + SDHCI_GET_CLASS(sdhci)->do_adma(sdhci); + } + } else { + uint32_t mask = (1 << (size * 8)) - 1; + shift = 8 * (offset & 0x3); + val <<= shift; + mask = ~(mask << shift); + s->admaerr = (s->admaerr & mask) | val; + } + return; + } + + SDHCI_GET_CLASS(s)->mem_write(sdhci, offset, val, size); +} + +static const MemoryRegionOps exynos4210_sdhci_mmio_ops = { + .read = exynos4210_sdhci_readfn, + .write = exynos4210_sdhci_writefn, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = false + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static const VMStateDescription exynos4210_sdhci_vmstate = { + .name = "exynos4210.sdhci", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(sdhci, Exynos4SDHCIState, 1, sdhci_vmstate, SDHCIState), + VMSTATE_UINT32(admaerr, Exynos4SDHCIState), + VMSTATE_UINT32(control2, Exynos4SDHCIState), + VMSTATE_UINT32(control3, Exynos4SDHCIState), + VMSTATE_BOOL(stopped_adma, Exynos4SDHCIState), + VMSTATE_END_OF_LIST() + } +}; + +static int exynos4210_sdhci_realize(SysBusDevice *busdev) +{ + SDHCIState *sdhci = SDHCI(busdev); + + qdev_prop_set_uint32(DEVICE(busdev), "capareg", EXYNOS4_SDHC_CAPABILITIES); + sdhci->buf_maxsz = EXYNOS4_SDHC_MAX_BUFSZ; + sdhci->fifo_buffer = g_malloc0(sdhci->buf_maxsz); + sysbus_init_irq(busdev, &sdhci->irq); + memory_region_init_io(&sdhci->iomem, &exynos4210_sdhci_mmio_ops, + EXYNOS4_SDHCI(sdhci), "exynos4210.sdhci", SDHC_REGISTERS_MAP_SIZE); + sysbus_init_mmio(busdev, &sdhci->iomem); + return 0; +} + +static void exynos4210_sdhci_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *sbdc = SYS_BUS_DEVICE_CLASS(klass); + SDHCIClass *k = SDHCI_CLASS(klass); + + dc->vmsd = &exynos4210_sdhci_vmstate; + dc->reset = exynos4210_sdhci_reset; + sbdc->init = exynos4210_sdhci_realize; + + k->can_issue_command = exynos4210_sdhci_can_issue_command; + k->do_adma = exynos4210_sdhci_start_adma; +} + +static const TypeInfo exynos4210_sdhci_type_info = { + .name = TYPE_EXYNOS4_SDHC, + .parent = TYPE_SDHCI, + .instance_size = sizeof(Exynos4SDHCIState), + .class_init = exynos4210_sdhci_class_init, +}; + +static void exynos4210_sdhci_register_types(void) +{ + type_register_static(&exynos4210_sdhci_type_info); +} + +type_init(exynos4210_sdhci_register_types)