[Top][All Lists]
[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
- [Qemu-devel] [PATCH V2 2/3] hw/sd.c: add SD card save/load support, (continued)
[Qemu-devel] [PATCH V2 3/3] hw: Introduce spec. ver. 2.00 compliant SD host controller, Mitsyanko Igor, 2011/12/28
[Qemu-devel] [PATCH V3 0/5] Improve SD controllers emulation, Mitsyanko Igor, 2011/12/28
- [Qemu-devel] [PATCH V3 1/5] vmstate: introduce get_bufsize entry in VMStateField, Mitsyanko Igor, 2011/12/28
- [Qemu-devel] [PATCH V3 2/5] hw/sd.c: add SD card save/load support, Mitsyanko Igor, 2011/12/28
- [Qemu-devel] [PATCH V3 4/5] hw/sd.c: convert wp_switch and spi to bool, Mitsyanko Igor, 2011/12/28
- [Qemu-devel] [PATCH V3 3/5] hw/sd.c: convert wp_groups, expecting_acmd and enable to bool, Mitsyanko Igor, 2011/12/28
- [Qemu-devel] [PATCH V3 5/5] hw: Introduce spec. ver. 2.00 compliant SD host controller,
Mitsyanko Igor <=