@@ -345,7 +345,8 @@ obj-arm-y += arm_boot.o pl011.o pl031.o pl050.o pl080.o pl110.o pl181.o pl190.o
obj-arm-y += versatile_pci.o
obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
obj-arm-y += s5pc210.o s5pc210_cmu.o s5pc210_uart.o s5pc210_gic.o \
- s5pc210_combiner.o s5pc210_pwm.o s5pc210_mct.o s5pc210_fimd.o
+ s5pc210_combiner.o s5pc210_pwm.o s5pc210_mct.o s5pc210_fimd.o \
+ s5pc210_sdhc.o
obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
obj-arm-y += pl061.o
obj-arm-y += arm-semi.o
@@ -97,6 +97,12 @@
/* MCT */
#define S5PC210_MCT_BASE_ADDR 0x10050000
+/* SD/MMC host controllers SFR base addresses */
+#define S5PC210_SDHC0_BASE_ADDR 0x12510000
+#define S5PC210_SDHC1_BASE_ADDR 0x12520000
+#define S5PC210_SDHC2_BASE_ADDR 0x12530000
+#define S5PC210_SDHC3_BASE_ADDR 0x12540000
+
/* Display controllers (FIMD) */
#define S5PC210_FIMD0_BASE_ADDR 0x11C00000
#define S5PC210_FIMD1_BASE_ADDR 0x12000000
@@ -454,6 +460,20 @@ static void s5pc210_init(ram_addr_t ram_size,
s5pc210_uart_create(addr, fifo_size, channel, NULL, uart_irq);
}
+ /*** SD/MMC host controllers ***/
+
+ sysbus_create_simple("s5pc210.sdhc", S5PC210_SDHC0_BASE_ADDR,
+ irq_table[s5pc210_get_irq(29, 0)]);
+
+ sysbus_create_simple("s5pc210.sdhc", S5PC210_SDHC1_BASE_ADDR,
+ irq_table[s5pc210_get_irq(29, 1)]);
+
+ sysbus_create_simple("s5pc210.sdhc", S5PC210_SDHC2_BASE_ADDR,
+ irq_table[s5pc210_get_irq(29, 2)]);
+
+ sysbus_create_simple("s5pc210.sdhc", S5PC210_SDHC3_BASE_ADDR,
+ irq_table[s5pc210_get_irq(29, 3)]);
+
/*** LAN adapter ***/
if (board_type == BOARD_S5PC210_SMDKC210) {
new file mode 100644
@@ -0,0 +1,1693 @@
+/*
+ * Samsung s5pc210 SD/MMC host controller
+ * (SD host controller specification ver. 2.0 compliant)
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ * Contributed by Mitsyanko Igor <i.mitsyanko@samsung.com>
+ *
+ * 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, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "hw.h"
+#include "sd.h"
+#include "qemu-common.h"
+#include "block_int.h"
+#include "blockdev.h"
+#include "sysbus.h"
+#include "qemu-timer.h"
+
+/* host controller debug messages */
+#define DEBUG_SDHC 0
+
+#if DEBUG_SDHC
+#define DPRINTF(fmt, args...) \
+do { fprintf(stderr, "QEMU SDHC: " fmt , ##args); } while (0)
+#else
+#define DPRINTF(fmt, args...) do {} while (0)
+#endif
+
+#define TARGET_WORD_SIZE (TARGET_PHYS_ADDR_SPACE_BITS / 8)
+#define READ_BUFFER_DELAY 5
+#define WRITE_BUFFER_DELAY 6
+#define INSERTION_DELAY (get_ticks_per_sec())
+#define CMD_RESPONSE (3 << 0)
+
+/* Default SD/MMC host controller features information, which will be
+ * presented in host's CAPABILITIES REGISTER at reset.
+ * If not stated otherwise:
+ * 0 - not supported, 1 - supported, other - prohibited.
+ */
+#define SDHC_CAPAB_64BITBUS 0ul /* 64-bit System Bus Suport */
+#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))
+
+/****************************************************
+ * SD host controller registers offset
+ ***************************************************/
+
+/* R/W SDMA System Address register 0x0 */
+#define SDHC_SYSAD 0x00
+
+/* R/W Host DMA Buffer Boundary and Transfer Block Size Register 0x0 */
+#define SDHC_BLKSIZE 0x04
+
+/* R/W Blocks count for current transfer 0x0 */
+#define SDHC_BLKCNT 0x06
+
+/* R/W Command Argument Register 0x0 */
+#define SDHC_ARGUMENT 0x08
+
+/* R/W Transfer Mode Setting Register 0x0 */
+#define SDHC_TRNMOD 0x0C
+#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
+#define SDHC_TRNS_CEATA 0x0300
+#define SDHC_TRNS_BOOTCMD 0x1000
+#define SDHC_TRNS_BOOTACK 0x2000
+
+/* R/W Command Register 0x0 */
+#define SDHC_CMDREG 0x0E
+#define SDHC_CMD_RSP_WITH_BUSY (3 << 0)
+#define SDHC_CMD_DATA_PRESENT (1 << 5)
+#define SDHC_CMD_SUSPEND_MASK (1 << 6)
+#define SDHC_CMD_RESUME_MASK (1 << 7)
+#define SDHC_CMD_ABORT_MASK ((1 << 6)|(1 << 7))
+#define SDHC_CMD_TYPE_MASK ((1 << 6)|(1 << 7))
+
+/* 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_CMD_RESP_MASK 0x00000003
+#define SDHC_CMD_CRC 0x00000008
+#define SDHC_CMD_INDEX 0x00000010
+#define SDHC_CMD_DATA 0x00000020
+#define SDHC_CMD_RESP_NONE 0x00000000
+#define SDHC_CMD_RESP_LONG 0x00000001
+#define SDHC_CMD_RESP_SHORT 0x00000002
+#define SDHC_CMD_RESP_SHORT_BUSY 0x00000003
+#define SDHC_MAKE_CMD(c, f) (((c & 0xFF) << 8) | (f & 0xFF))
+
+/* R/W Buffer Data Register 0x0 */
+#define SDHC_BDATA 0x20
+
+/* R/ROC Present State Register 0x000A0000 */
+#define SDHC_PRNSTS 0x24
+#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
+
+/* R/W Host control Register 0x0 */
+#define SDHC_HOSTCTL 0x28
+#define SDHC_CTRL_LED 0x01
+#define SDHC_CTRL_4BITBUS 0x02
+#define SDHC_CTRL_HIGHSPEED 0x04
+#define SDHC_CTRL_1BIT 0x00
+#define SDHC_CTRL_4BIT 0x02
+#define SDHC_CTRL_8BIT 0x20
+#define SDHC_CTRL_DMA_CHECK_MASK 0x18
+#define SDHC_CTRL_SDMA 0x00
+#define SDHC_CTRL_ADMA2_32 0x10
+
+/* R/W Power Control Register 0x0 */
+#define SDHC_PWRCON 0x29
+#define SDHC_POWER_OFF 0x00
+#define SDHC_POWER_ON 0x01
+#define SDHC_POWER_180 0x0A
+#define SDHC_POWER_300 0x0C
+#define SDHC_POWER_330 0x0E
+#define SDHC_POWER_ON_ALL 0xFF
+
+/* 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_STAWAKEUP 0x08
+
+/*
+ * CLKCON
+ * SELFREQ[15:8] : base clock divied by value
+ * ENSDCLK[2] : SD Clock Enable
+ * STBLINTCLK[1] : Internal Clock Stable
+ * ENINTCLK[0] : Internal Clock Enable
+ */
+#define SDHC_CLKCON 0x2C
+#define SDHC_DIVIDER_SHIFT 0x0008
+#define SDHC_CLOCK_EXT_STABLE 0x0008
+#define SDHC_CLOCK_CARD_EN 0x0004
+#define SDHC_CLOCK_INT_STABLE 0x0002
+#define SDHC_CLOCK_INT_EN 0x0001
+#define SDHC_CLOCK_CHK_MASK 0x000F
+
+/* R/W Timeout Control Register 0x0 */
+#define SDHC_TIMEOUTCON 0x2E
+#define SDHC_TIMEOUT_MAX 0x0E
+
+/* 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_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_EIS_CMDTIMEOUT 0x0001
+#define SDHC_EIS_BLKGAP 0x0004
+#define SDHC_EIS_CMDERR 0x000E
+#define SDHC_EIS_DATATIMEOUT 0x0010
+#define SDHC_EIS_DATAERR 0x0060
+#define SDHC_EIS_CMD12ERR 0x0100
+#define SDHC_EIS_ADMAERR 0x0200
+#define SDHC_EIS_STABOOTACKERR 0x0400
+
+/* R/W Normal Interrupt Status Enable Register 0x0 */
+#define SDHC_NORINTSTSEN 0x34
+#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_EISEN_CMDTIMEOUT 0x0001
+#define SDHC_EISEN_BLKGAP 0x0004
+#define SDHC_EISEN_ADMAERR 0x0200
+#define SDHC_EISEN_STABOOTACKERR 0x0400
+
+/* R/W Normal Interrupt Signal Enable Register 0x0 */
+#define SDHC_NORINTSIGEN 0x38
+#define SDHC_NISSIG_MASK_ALL 0x00
+#define SDHC_NISSIG_RESPONSE 0x0001
+#define SDHC_NISSIG_DATA_END 0x0002
+#define SDHC_NISSIG_DMA_END 0x0008
+#define SDHC_NISSIG_SPACE_AVAIL 0x0010
+#define SDHC_NISSIG_DATA_AVAIL 0x0020
+#define SDHC_NISSIG_CARD_INSERT 0x0040
+#define SDHC_NISSIG_CARD_REMOVE 0x0080
+#define SDHC_NISSIG_CARD_CHANGE 0x00C0
+#define SDHC_NISSIG_CARD_INT 0x0100
+
+/* R/W Error Interrupt Signal Enable Register 0x0 */
+#define SDHC_ERRINTSIGEN 0x3A
+#define SDHC_EISSIG_STABOOTACKERR 0x0400
+#define SDHC_EISSIG_ADMAERR 0x0200
+
+/* ROC Auto CMD12 error status register 0x0 */
+#define SDHC_ACMD12ERRSTS 0x3C
+
+/* HWInit Capabilities Register 0x05E80080 */
+#define SDHC_CAPAREG 0x40
+#define SDHC_TIMEOUT_CLK_MASK 0x0000003F
+#define SDHC_TIMEOUT_CLK_SHIFT 0x0
+#define SDHC_TIMEOUT_CLK_UNIT 0x00000080
+#define SDHC_CLOCK_BASE_MASK 0x00003F00
+#define SDHC_CLOCK_BASE_SHIFT 0x8
+#define SDHC_MAX_BLOCK_MASK 0x00030000
+#define SDHC_MAX_BLOCK_SHIFT 0x10
+#define SDHC_CAN_DO_DMA 0x00400000
+#define SDHC_CAN_DO_ADMA2 0x00080000
+#define SDHC_CAN_VDD_330 0x01000000
+#define SDHC_CAN_VDD_300 0x02000000
+#define SDHC_CAN_VDD_180 0x04000000
+
+/* HWInit Maximum Current Capabilities Register 0x0 */
+#define SDHC_MAXCURR 0x48
+
+/* For ADMA2 */
+
+/* W Force Event Auto CMD12 Error Interrupt Register 0x0000 */
+#define SDHC_FEAER 0x50
+/* W Force Event Error Interrupt Register Error Interrupt 0x0000 */
+#define SDHC_FEERR 0x52
+
+/* R/W ADMA Error Status Register 0x00 */
+#define SDHC_ADMAERR 0x54
+#define SDHC_ADMAERR_FINAL_BLOCK (1 << 10)
+#define SDHC_ADMAERR_CONTINUE_REQUEST (1 << 9)
+#define SDHC_ADMAERR_INTRRUPT_STATUS (1 << 8)
+#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_ADMA_ATTR_MSK 0x3F
+#define SDHC_ADMA_ATTR_ACT_NOP (0 << 4)
+#define SDHC_ADMA_ATTR_ACT_RSV (1 << 4)
+#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))
+
+/* R/W Control register 2 0x0 */
+#define SDHC_CONTROL2 0x80
+#define SDHC_HWINITFIN 0x00000001
+#define SDHC_SDOPSIGPC 0x00001000
+#define SDHC_ENCLKOUTHOLD 0x00000100
+#define SDHC_SDINPSIGPC 0x00000008
+#define SDHC_DISBUFRD 0x00000040
+#define SDHC_ENCMDCNFMSK 0x40000000
+
+/* R/W FIFO Interrupt Control (Control Register 3) 0x7F5F3F1F */
+#define SDHC_CONTROL3 0x84
+
+/* R/W Control register 4 0x0 */
+#define SDHC_CONTROL4 0x8C
+
+/* Magic register which is used from kernel! */
+#define SDHC_SLOT_INT_STATUS 0xFC
+
+/* HWInit Host Controller Version Register 0x0401 */
+#define SDHC_HCVER 0xFE
+#define SDHC_VENDOR_VER_MASK 0xFF00
+#define SDHC_VENDOR_VER_SHIFT 0x8
+#define SDHC_SPEC_VER_MASK 0x00FF
+#define SDHC_SPEC_VER_SHIFT 0x0
+
+#define SDHC_REG_SIZE 0x100
+
+enum {
+ not_stoped = 0, /* normal sdhc state */
+ gap_read = 1, /* sdhc stoped at bloch gap during read operation */
+ gap_write = 2, /* sdhc stoped at bloch gap during write operation */
+ adma_intr = 3 /* sdhc stoped after ADMA interrupt occured */
+};
+
+/* SD/MMC host controller state */
+typedef struct S5pc210SDHCState {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+ 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;
+ qemu_irq irq;
+ uint8_t stoped_state;
+
+ /* sd host i/o fifo buffer */
+ uint32_t fifo_buffer[SDHC_CAPAB_MAXBLOCKLENGTH / TARGET_WORD_SIZE];
+ uint16_t data_count; /* curent element in fifo buffer */
+
+ uint32_t sdmasysad; /* SDMA System Address register */
+ uint16_t blksize; /* Host DMA Buff Boundary and Transfer BlSize 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 bdata Buffer Data Register - access point to R and W buffers */
+ 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 swrst Software Reset Register - must read as 0 */
+ 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 */
+ uint32_t capareg; /* Capabilities Register */
+ uint32_t maxcurr; /* Maximum Current Capabilities Register */
+/* uint16_t feaer Force Event Auto CMD12 Error Interrupt Reg - write only */
+/* uint16_t feerr Force Event Error Interrupt Register- write only */
+ uint32_t admaerr; /* ADMA Error Status Register */
+ uint32_t admasysaddr; /* ADMA System Address Register */
+ uint32_t control2; /* Control Register 2 */
+ uint32_t control3; /* FIFO Interrupt Control (Control Register 3) */
+/* uint32_t control4 makes no sense in emulation so always return 0 */
+/* uint16_t hcver Host Controller Ver Reg 0x2401 -sd specification v2 */
+} S5pc210SDHCState;
+
+/****************************************************
+ * Interrupt raising
+ * functions
+ ***************************************************/
+
+static void sdhc_raise_transfer_complete_irq(void *opaque)
+{
+ S5pc210SDHCState *s = (S5pc210SDHCState *)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;
+ }
+
+ qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+}
+
+/* raise comand response recieved interrupt */
+static inline void sdhc_raise_response_recieved_irq(S5pc210SDHCState *s)
+{
+ DPRINTF("raise IRQ response\n");
+
+ if (s->norintstsen & SDHC_NISEN_CMDCMP) {
+ s->norintsts |= SDHC_NIS_CMDCMP;
+ }
+
+ qemu_set_irq(s->irq, ((s->norintsts & s->norintsigen) ||
+ (s->errintsts & s->errintsigen)));
+}
+
+static void sdhc_raise_insertion_irq(void *opaque)
+{
+ S5pc210SDHCState *s = (S5pc210SDHCState *)opaque;
+ DPRINTF("raise IRQ response\n");
+
+ if (s->norintsts & SDHC_NIS_REMOVE) {
+ DPRINTF("sdhc_raise_insertion_irq: set timer!\n");
+ qemu_mod_timer(s->insert_timer,
+ qemu_get_clock_ns(vm_clock) + INSERTION_DELAY);
+ } else {
+ DPRINTF("sdhc_raise_insertion_irq: raise irq!\n");
+ if (s->norintstsen & SDHC_NIS_INSERT) {
+ s->norintsts |= SDHC_NIS_INSERT;
+ }
+ qemu_set_irq(s->irq, (s->norintsts & s->norintsigen));
+ }
+}
+
+static void sdhc_insert_eject(void *opaque, int irq, int level)
+{
+ S5pc210SDHCState *s = (S5pc210SDHCState *)opaque;
+ DPRINTF("change card state: %s!\n", level ? "insert" : "eject");
+
+ if (s->norintsts & SDHC_NIS_REMOVE) {
+ if (level) {
+ DPRINTF("change card state: timer set!\n");
+ qemu_mod_timer(s->insert_timer,
+ qemu_get_clock_ns(vm_clock) + INSERTION_DELAY);
+ }
+ } else {
+ if (level) {
+ s->prnsts = 0x1ff0000;
+ if (s->norintstsen & SDHC_NIS_INSERT) {
+ s->norintsts |= SDHC_NIS_INSERT;
+ }
+ } else {
+ s->prnsts = 0x1fa0000;
+ if (s->norintstsen & SDHC_NIS_REMOVE) {
+ s->norintsts |= SDHC_NIS_REMOVE;
+ }
+ }
+ qemu_set_irq(s->irq, (s->norintsts & s->norintsigen));
+ }
+}
+
+/****************************************************
+ * Misc control
+ * functions
+ ***************************************************/
+
+/* Reset ALL */
+static void s5pc210_sdhc_reset(DeviceState *d)
+{
+ S5pc210SDHCState *s = container_of(d, S5pc210SDHCState, busdev.qdev);
+ unsigned long begin = (unsigned long)s + offsetof(S5pc210SDHCState,
+ sdmasysad);
+ unsigned long len = ((unsigned long)s + offsetof(S5pc210SDHCState,
+ control3)) - begin;
+
+ /* Set all registers to 0 and then set appropriate values
+ * for hardware-initialize registers */
+ memset((void *)begin, 0, len);
+ (s->card) ? (s->prnsts = 0x1ff0000) : (s->prnsts = 0x1fa0000);
+ s->stoped_state = not_stoped;
+ s->data_count = 0;
+ s->capareg = SDHC_CAPAB_REG_DEFAULT;
+ s->control3 = 0x7F5F3F1F;
+}
+
+static void sdhc_send_command(S5pc210SDHCState *s)
+{
+ SDRequest request;
+ uint8_t response[16];
+ int rlen;
+ s->errintsts = 0;
+ if (!s->card) {
+ goto error;
+ }
+
+ request.cmd = s->cmdreg >> 8;
+ request.arg = s->argument;
+ DPRINTF("Command %d %08x\n", request.cmd, request.arg);
+ rlen = sd_do_command(s->card, &request, response);
+ if (rlen < 0) {
+ goto error;
+ }
+ if ((s->cmdreg & 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);
+ }
+ DPRINTF("Response received\n");
+#undef RWORD
+ } else {
+ DPRINTF("Command sent\n");
+ }
+ return;
+
+error:
+ DPRINTF("Timeout\n");
+ if (s->errintstsen & SDHC_EISEN_CMDTIMEOUT) {
+ s->errintsts |= SDHC_EIS_CMDTIMEOUT;
+ s->norintsts |= SDHC_NIS_ERR;
+ }
+}
+
+/* Do transfer complete routine */
+static void sdhc_do_transfer_complete(S5pc210SDHCState *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;
+ DPRINTF("Command Auto%d %08x\n", request.cmd, request.arg);
+ sd_do_command(s->card, &request, response);
+ }
+ /* 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
+ * functions
+ ***************************************************/
+
+/* Fill host controlerr's read buffer with BLKSIZE bytes of data from card */
+static void sdhc_read_block_from_card(void *opaque)
+{
+ S5pc210SDHCState *s = (S5pc210SDHCState *)opaque;
+ uint32_t value = 0;
+ int n = 0, index = 0;
+ uint16_t datacount = s->blksize & 0x0fff;
+
+ if ((s->trnmod & SDHC_TRNS_MULTI) &&
+ (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0)) {
+ return;
+ }
+
+ while (datacount) {
+ value |= (uint32_t)sd_read_data(s->card) << (n * 8);
+ n++;
+ if (n == 4) {
+ s->fifo_buffer[index] = value;
+ value = 0;
+ n = 0;
+ index++;
+ }
+ datacount--;
+ }
+
+ /* New data 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 == 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;
+ }
+ }
+
+ qemu_set_irq(s->irq, (s->norintsts & s->norintsigen));
+}
+
+/* Read data from host controler BUFFER DATA PORT register */
+static inline uint32_t sdhc_read_dataport(S5pc210SDHCState *s)
+{
+ /* first check if a valid data exists in host controller input buffer
+ * and that buffer read is not disabled */
+ if ((s->prnsts & SDHC_DATA_AVAILABLE) == 0 ||
+ (s->control2 & SDHC_DISBUFRD)) {
+ DPRINTF("Trying to read from empty buffer or read disabled\n");
+ return 0;
+ }
+
+ uint32_t value = s->fifo_buffer[s->data_count];
+ s->data_count++;
+ /* check if we've read all valid data (blksize) from buffer */
+ if (((s->data_count) * 4) >= (s->blksize & 0x0fff)) {
+ 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))) {
+ sdhc_do_transfer_complete(s);
+ } else if (s->stoped_state == gap_read && /* stop at gap request */
+ !(s->prnsts & SDHC_DAT_LINE_ACTIVE)) {
+ sdhc_raise_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) + READ_BUFFER_DELAY);
+ }
+ }
+ return value;
+}
+
+/* Write data from host controller fifo to card */
+static void sdhc_write_block_to_card(void *opaque)
+{
+ S5pc210SDHCState *s = (S5pc210SDHCState *)opaque;
+ uint32_t value = 0;
+ int n = 0, index = 0;
+ uint16_t datacount = s->blksize & 0x0fff;
+
+ if (s->prnsts & SDHC_SPACE_AVAILABLE) {
+ if (s->norintstsen & SDHC_NISEN_WBUFRDY) {
+ s->norintsts |= SDHC_NIS_WBUFRDY;
+ }
+ 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--;
+ }
+ }
+
+ while (datacount) {
+ if (n == 0) {
+ value = s->fifo_buffer[index];
+ n = 4;
+ index++;
+ }
+ sd_write_data(s->card, (uint8_t)(value & 0xff));
+ value >>= 8;
+ n--;
+ datacount--;
+ }
+
+ /* Next data can be writen 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))) {
+ sdhc_do_transfer_complete(s);
+ }
+
+ /* Generate Block Gap Event if requsted and if not the last block of data */
+ if (s->stoped_state == 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);
+ }
+
+ qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+}
+
+/* Write data to host controler BUFFER DATA PORT register */
+static inline void sdhc_write_dataport(S5pc210SDHCState *s, uint32_t value)
+{
+ if ((s->prnsts & SDHC_SPACE_AVAILABLE) == 0) {
+ DPRINTF("s5pc210.sdhc: Trying to write to full buffer\n");
+ return;
+ }
+
+ s->fifo_buffer[s->data_count] = value;
+ s->data_count++;
+
+ if (((s->data_count) * 4) >= (s->blksize & 0x0fff)) {
+ 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) + WRITE_BUFFER_DELAY);
+ }
+ }
+}
+
+/****************************************************
+ * Single DMA data transfer
+ * functions
+ ***************************************************/
+
+/* multi block SDMA transfer */
+static void sdhc_sdma_transfer_multi_blocks(S5pc210SDHCState *s)
+{
+ uint32_t value = 0, datacnt, saved_datacnt, pos = 0;
+ int n = 0, page_aligned = 0;
+ int is_read = (s->trnmod & SDHC_TRNS_READ) != 0;
+ uint32_t dma_buf_boundary = (s->blksize & 0xf000) >> 12;
+ uint32_t boundary_chk = 1 << (dma_buf_boundary+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.
+ * Documentation is vague on how this really behaves in hardware, so allow
+ * them to work properly */
+ if ((s->sdmasysad % boundary_chk) == 0) {
+ page_aligned = 1;
+ }
+
+ if (is_read) {
+ s->prnsts |= SDHC_DOING_READ | SDHC_DATA_INHIBIT |
+ SDHC_DAT_LINE_ACTIVE;
+ } else {
+ s->prnsts |= SDHC_DOING_WRITE | SDHC_DATA_INHIBIT |
+ SDHC_DAT_LINE_ACTIVE;
+ }
+
+ /* transfer data left from last loop when it stoped at DMA buffer boundary.
+ * Will not execute if all SDMA transfer are aligned to DMA buff boundary */
+ saved_datacnt = s->data_count;
+ while (saved_datacnt) {
+ if (is_read) {
+ value |= (uint32_t)sd_read_data(s->card) << (n * 8);
+ n++;
+ if (n == 4) {
+ cpu_physical_memory_write(s->sdmasysad + pos,
+ (uint8_t *)(&value), 4);
+ value = 0;
+ n = 0;
+ pos += 4;
+ }
+ } else {
+ if (n == 0) {
+ cpu_physical_memory_read(s->sdmasysad + pos,
+ (uint8_t *)(&value), 4);
+ n = 4;
+ pos += 4;
+ }
+ sd_write_data(s->card, value & 0xff);
+ value >>= 8;
+ n--;
+ }
+ saved_datacnt--;
+ }
+ if (s->data_count) {
+ s->blkcnt--;
+ boundary_count -= s->data_count--;
+ s->sdmasysad += s->data_count--;
+ s->data_count = 0;
+ }
+
+ n = value = 0;
+ while (s->blkcnt) {
+ if ((boundary_count < (s->blksize & 0x0fff)) && page_aligned) {
+ datacnt = boundary_count;
+ s->data_count = (s->blksize & 0x0fff) - boundary_count;
+ } else {
+ datacnt = s->blksize & 0x0fff;
+ }
+ pos = 0;
+ saved_datacnt = datacnt;
+
+ if (is_read) {
+ while (datacnt) {
+ value |= (uint32_t)sd_read_data(s->card) << (n * 8);
+ n++;
+ if (n == 4) {
+ cpu_physical_memory_write(s->sdmasysad + pos,
+ (uint8_t *)(&value), 4);
+ value = 0;
+ n = 0;
+ pos += 4;
+ }
+ datacnt--;
+ }
+ } else {
+ while (datacnt) {
+ if (n == 0) {
+ cpu_physical_memory_read(s->sdmasysad + pos,
+ (uint8_t *)(&value), 4);
+ n = 4;
+ pos += 4;
+ }
+ sd_write_data(s->card, value & 0xff);
+ value >>= 8;
+ n--;
+ datacnt--;
+ }
+ }
+
+ s->sdmasysad += saved_datacnt;
+ boundary_count -= saved_datacnt;
+ if (s->data_count == 0) {
+ s->blkcnt--;
+ }
+
+ if (boundary_count == 0) {
+ break;
+ }
+ }
+
+ if (s->blkcnt == 0) {
+ sdhc_do_transfer_complete(s);
+ } else {
+ if (s->norintstsen & SDHC_NISEN_DMA) {
+ s->norintsts |= SDHC_NIS_DMA;
+ }
+ qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+ }
+}
+
+/* single block SDMA transfer */
+static void sdhc_sdma_transfer_single_block(S5pc210SDHCState *s)
+{
+ int is_read = (s->trnmod & SDHC_TRNS_READ) != 0;
+ int n = 0;
+ uint32_t value = 0, pos = 0;
+ uint32_t datacnt = s->blksize & 0x0fff;
+
+ while (datacnt) {
+ if (is_read) {
+ value |= (uint32_t)sd_read_data(s->card) << (n * 8);
+ n++;
+ if (n == 4) {
+ cpu_physical_memory_write(s->sdmasysad + pos,
+ (uint8_t *)(&value), 4);
+ value = 0;
+ n = 0;
+ pos += 4;
+ }
+ } else {
+ if (n == 0) {
+ cpu_physical_memory_read(s->sdmasysad + pos,
+ (uint8_t *)(&value), 4);
+ n = 4;
+ pos += 4;
+ }
+ sd_write_data(s->card, value & 0xff);
+ value >>= 8;
+ n--;
+ }
+ datacnt--;
+ }
+
+ if ((s->trnmod & SDHC_TRNS_BLK_CNT_EN) != 0) {
+ s->blkcnt--;
+ }
+
+ sdhc_do_transfer_complete(s);
+}
+
+/****************************************************
+ * Advansed DMA data transfer
+ * function
+ ***************************************************/
+
+static void sdhc_run_adma(S5pc210SDHCState *s)
+{
+ uint32_t value;
+ int n, pos, length;
+ uint32_t address, datacnt;
+ uint16_t length_table;
+ uint8_t attributes;
+
+ int is_read = (s->trnmod & SDHC_TRNS_READ) != 0;
+ s->admaerr &= ~(SDHC_ADMAERR_FINAL_BLOCK | SDHC_ADMAERR_LENGTH_MISMATCH);
+
+ while (1) {
+ address = length_table = length = attributes = 0;
+ value = n = pos = 0;
+
+ /* fetch next entry from descriptor table */
+ cpu_physical_memory_read(s->admasysaddr+4, (uint8_t *)(&address), 4);
+ cpu_physical_memory_read(s->admasysaddr+2,
+ (uint8_t *)(&length_table), 2);
+ cpu_physical_memory_read(s->admasysaddr, (uint8_t *)(&attributes), 1);
+ DPRINTF("ADMA loop: addr=0x%x, len=%d, attr=%x\n", address,
+ length_table, attributes);
+
+ if ((attributes & SDHC_ADMA_ATTR_VALID) == 0) {
+ /* Indicate that error occured 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;
+ }
+ qemu_set_irq(s->irq, (s->errintsigen & s->errintsts));
+ break;
+ }
+
+ if (length_table == 0) {
+ length = 65536;
+ } else {
+ length = length_table;
+ }
+
+ address &= 0xfffffffc; /* minimum unit of address is 4 byte */
+
+ switch (attributes & SDHC_ADMA_ATTR_ACT_MASK) {
+ case (SDHC_ADMA_ATTR_ACT_TRAN): /* data transfer */
+ for (;;) {
+ if (s->data_count) {
+ datacnt = s->data_count;
+ s->data_count = 0;
+ } else {
+ datacnt = s->blksize & 0x0fff;
+ }
+
+ length -= datacnt;
+
+ if (length < 0) {
+ s->data_count = (uint16_t)(-length);
+ datacnt += length;
+ }
+
+ while (datacnt) {
+ if (is_read) {
+ value |= (uint32_t)sd_read_data(s->card) << (n * 8);
+ n++;
+ if (n == 4) {
+ cpu_physical_memory_write(address + pos,
+ (uint8_t *)(&value), 4);
+ value = 0;
+ n = 0;
+ pos += 4;
+ }
+ } else {
+ if (n == 0) {
+ cpu_physical_memory_read(address + pos,
+ (uint8_t *)(&value), 4);
+ n = 4;
+ pos += 4;
+ }
+ sd_write_data(s->card, value & 0xff);
+ value >>= 8;
+ n--;
+ }
+ datacnt--;
+ }
+
+ if ((s->trnmod & SDHC_TRNS_BLK_CNT_EN) &&
+ (!s->data_count)) {
+ s->blkcnt--;
+ if (s->blkcnt == 0) {
+ break;
+ }
+ }
+
+ if (length == 0 || s->data_count) {
+ break;
+ }
+ }
+ s->admasysaddr += 8;
+ break;
+ case (SDHC_ADMA_ATTR_ACT_LINK): /* link to next descriptor table */
+ s->admasysaddr = address;
+ DPRINTF("ADMA link: admasysaddr=0x%x\n", s->admasysaddr);
+ break;
+ default:
+ s->admasysaddr += 8;
+ 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)) {
+ DPRINTF("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)) {
+ DPRINTF("SD/MMC host ADMA length mismatch\n");
+ s->admaerr |= SDHC_ADMAERR_LENGTH_MISMATCH |
+ SDHC_ADMAERR_STATE_ST_TFR;
+ if (s->errintstsen & SDHC_EISEN_ADMAERR) {
+ DPRINTF("Set ADMA error flag\n");
+ s->errintsts |= SDHC_EIS_ADMAERR;
+ s->norintsts |= SDHC_NIS_ERR;
+ }
+ qemu_set_irq(s->irq, (s->errintsigen & s->errintsts));
+ }
+
+ s->admaerr |= SDHC_ADMAERR_FINAL_BLOCK;
+ sdhc_do_transfer_complete(s);
+ break;
+ }
+
+ if (attributes & SDHC_ADMA_ATTR_INT) {
+ DPRINTF("ADMA interrupt: admasysaddr=0x%x\n", s->admasysaddr);
+ s->admaerr |= SDHC_ADMAERR_INTRRUPT_STATUS;
+ s->stoped_state = adma_intr;
+ if (s->norintstsen & SDHC_NISEN_DMA) {
+ s->norintsts |= SDHC_NIS_DMA;
+ }
+ qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+ break;
+ }
+ }
+}
+
+/****************************************************
+ * Do nessesary transfers, specified by previosly
+ * issued command
+ ***************************************************/
+static void sdhc_process_command(S5pc210SDHCState *s)
+{
+ int is_read = (s->trnmod & SDHC_TRNS_READ) != 0;
+ if (s->trnmod & SDHC_TRNS_DMA) {
+ if (!((is_read && sd_data_ready(s->card)) ||
+ (!is_read && sd_recieve_ready(s->card)))) {
+ return;
+ }
+
+ switch (s->hostctl & SDHC_CTRL_DMA_CHECK_MASK) {
+ case SDHC_CTRL_SDMA:
+ if ((s->trnmod & SDHC_TRNS_MULTI) &&
+ ((s->trnmod & SDHC_TRNS_BLK_CNT_EN) == 0
+ || s->blkcnt == 0)) {
+ break;
+ }
+
+ if ((s->blkcnt == 1) || !(s->trnmod & SDHC_TRNS_MULTI)) {
+ sdhc_sdma_transfer_single_block(s);
+ } else {
+ s->data_count = 0; /* # of bytes from last transfer */
+ sdhc_sdma_transfer_multi_blocks(s);
+ }
+ break;
+ case SDHC_CTRL_ADMA2_32:
+ s->data_count = 0; /* # of bytes from last transfer */
+ sdhc_run_adma(s);
+ break;
+ default:
+ DPRINTF("Unsupported DMA type\n");
+ break;
+ }
+ } else {
+ if (is_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) + READ_BUFFER_DELAY);
+ } else if (!is_read && sd_recieve_ready(s->card)) {
+ 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) + WRITE_BUFFER_DELAY);
+ }
+ }
+}
+
+/****************************************************
+ * Read from and write to SD/MMC host controller
+ * registers functions
+ ***************************************************/
+
+/* Read byte from SD/MMC host controller registers */
+static uint32_t s5pc210_sdhc_read_1(S5pc210SDHCState *s,
+ target_phys_addr_t offset)
+{
+ switch (offset) {
+ 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_TIMEOUTCON:
+ return s->timeoutcon;
+ case SDHC_SWRST:
+ return 0;
+ case SDHC_RSPREG0 + 0x3:
+ return (uint8_t)(s->rspreg[0] >> 24);
+ case SDHC_RSPREG1 + 0x3:
+ return (uint8_t)(s->rspreg[1] >> 24);
+ case SDHC_RSPREG2 + 0x3:
+ return (uint8_t)(s->rspreg[2] >> 24);
+ default:
+ DPRINTF("sdhc: bad 1 byte read offset " TARGET_FMT_plx "\n", offset);
+ return 0xBAADBAAD;
+ }
+}
+
+/* Read two bytes from SD/MMC host controller registers */
+static uint32_t s5pc210_sdhc_read_2(S5pc210SDHCState *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_CLKCON:
+ return s->clkcon;
+ case SDHC_NORINTSTS:
+ qemu_set_irq(s->irq, 0);
+ return s->norintsts;
+ case SDHC_ERRINTSTS:
+ qemu_set_irq(s->irq, 0);
+ 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 0;
+ case SDHC_FEAER: case SDHC_FEERR:
+ DPRINTF("Reading from WO register\n");
+ return 0;
+ case SDHC_HCVER:
+ return 0x2401; /* SD Host Specification Version 2.0 */
+ default:
+ DPRINTF("sdhc: bad 2 byte read offset " TARGET_FMT_plx "\n", offset);
+ return 0xBAADBAAD;
+ }
+}
+
+/* MMC read 4 bytes function */
+static uint32_t s5pc210_sdhc_read_4(S5pc210SDHCState *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:
+ return sdhc_read_dataport(s);
+ case SDHC_PRNSTS:
+ return s->prnsts;
+ case SDHC_NORINTSTS:
+ qemu_set_irq(s->irq, 0);
+ return (s->errintsts << 16) | (s->norintsts);
+ case SDHC_NORINTSTSEN:
+ return (s->errintstsen << 16) | s->norintstsen;
+ case SDHC_NORINTSIGEN:
+ return (s->errintsigen << 16) | s->norintsigen;
+ case SDHC_CAPAREG:
+ return s->capareg;
+ case SDHC_MAXCURR:
+ return s->maxcurr;
+ case SDHC_ADMAERR:
+ return s->admaerr;
+ case SDHC_ADMASYSADDR:
+ return s->admasysaddr;
+ case SDHC_CONTROL2:
+ return s->control2;
+ case SDHC_CONTROL3:
+ return s->control3;
+ case SDHC_CONTROL4:
+ return 0; /* makes no sense in emulation so return 0 */
+ default:
+ DPRINTF("bad 4 byte read offset " TARGET_FMT_plx "\n", offset);
+ return 0xBAADBAAD;
+ }
+}
+
+/* MMC write (byte) function */
+static void s5pc210_sdhc_write_1(S5pc210SDHCState *s, target_phys_addr_t offset,
+ uint32_t value)
+{
+ switch (offset) {
+ case SDHC_HOSTCTL:
+ s->hostctl = value & 0x3F;
+ break;
+ case SDHC_PWRCON:
+ s->pwrcon = value & 0x0F;
+ break;
+ case SDHC_BLKGAP:
+ if (value & 0x0C) {
+ error_report("SDHC: ReadWait & IntAtBlockGap 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 == gap_read) {
+ s->prnsts |= SDHC_DAT_LINE_ACTIVE | SDHC_DOING_READ;
+ qemu_mod_timer(s->read_buffer_timer,
+ qemu_get_clock_ns(vm_clock) + 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) + READ_BUFFER_DELAY);
+ }
+ s->stoped_state = not_stoped;
+ } else if (!s->stoped_state && (value & SDHC_STOP_AT_GAP_REQ)) {
+ if (s->prnsts & SDHC_DOING_READ) {
+ s->stoped_state = gap_read;
+ } else if (s->prnsts & SDHC_DOING_WRITE) {
+ s->stoped_state = gap_write;
+ }
+ }
+ break;
+ case SDHC_WAKCON:
+ s->wakcon = value & 0x07;
+ s->wakcon &= ~(value & SDHC_STAWAKEUP);
+ break;
+ case SDHC_TIMEOUTCON:
+ s->timeoutcon = value & 0x0F;
+ break;
+ case SDHC_SWRST:
+ switch (value) {
+ case SDHC_RESET_ALL:
+ s5pc210_sdhc_reset(&s->busdev.qdev);
+ 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 = not_stoped;
+ s->norintsts &= ~(SDHC_NIS_WBUFRDY | SDHC_NIS_RBUFRDY |
+ SDHC_NIS_DMA | SDHC_NIS_TRSCMP | SDHC_NIS_BLKGAP);
+ break;
+ }
+ break;
+ case (SDHC_NORINTSTS+1):
+ if (s->norintstsen & SDHC_NISEN_CARDINT) {
+ value &= ~1;
+ }
+ s->norintsts &= (s->norintsts & 0x8000) | (~((value << 8) & 0xFF00));
+ break;
+ case (SDHC_NORINTSTS):
+ s->norintsts &= ~(((uint16_t)value) & 0x00FF);
+ break;
+ default:
+ DPRINTF("bad 1 byte write offset " TARGET_FMT_plx "\n", offset);
+ break;
+ }
+}
+
+/* MMC write 2 bytes function */
+static void s5pc210_sdhc_write_2(S5pc210SDHCState *s, target_phys_addr_t offset,
+ uint32_t value)
+{
+ switch (offset) {
+ case SDHC_BLKSIZE:
+ if (!(s->prnsts & (SDHC_DOING_READ | SDHC_DOING_WRITE))) {
+ s->blksize = value & 0x7FFF;
+ }
+ break;
+ case SDHC_BLKCNT:
+ if (!(s->prnsts & (SDHC_DOING_READ | SDHC_DOING_WRITE))) {
+ s->blkcnt = value;
+ }
+ break;
+ case SDHC_TRNMOD:
+ if (value & (SDHC_TRNS_BOOTCMD | SDHC_TRNS_BOOTACK |
+ SDHC_TRNS_CEATA)) {
+ error_report("QEMU SDHC: CEATA mode not implemented\n");
+ }
+ if ((s->capareg & SDHC_CAN_DO_DMA) == 0) {
+ value &= ~SDHC_TRNS_DMA;
+ }
+ s->trnmod = value & 0x0037;
+ break;
+ case SDHC_CMDREG: /* Command */
+ s->cmdreg = value & 0x3FFB;
+
+ if (((s->control2 & SDHC_SDOPSIGPC) || (s->control2 & SDHC_SDINPSIGPC))
+ && !(s->pwrcon & SDHC_POWER_ON)) {
+ DPRINTF("Can't issue command with power off\n");
+ break;
+ }
+
+ if ((s->clkcon & SDHC_CLOCK_CHK_MASK) != SDHC_CLOCK_CHK_MASK) {
+ DPRINTF("Can't issue command with clock off\n");
+ break;
+ }
+
+ if ((value & SDHC_CMD_TYPE_MASK) == SDHC_CMD_RESUME_MASK ||
+ (value & SDHC_CMD_TYPE_MASK) == SDHC_CMD_SUSPEND_MASK) {
+ DPRINTF("sdhc: suspend/resume commands not implemented\n");
+ break;
+ }
+
+ if ((s->stoped_state || (s->prnsts & SDHC_DATA_INHIBIT)) &&
+ ((value & SDHC_CMD_DATA_PRESENT) || ((value & SDHC_CMD_RSP_WITH_BUSY) &&
+ !(value & SDHC_CMD_ABORT_MASK)))) {
+ DPRINTF("Can't issue command which uses DAT line\n");
+ break;
+ }
+
+ if ((value & SDHC_CMD_TYPE_MASK) == SDHC_CMD_ABORT_MASK &&
+ ((s->prnsts & SDHC_DOING_READ) || (s->prnsts & SDHC_DOING_WRITE))) {
+ DPRINTF("ABORT command\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);
+ }
+
+ sdhc_send_command(s);
+ sdhc_raise_response_recieved_irq(s);
+
+ if (s->blksize == 0) {
+ break;
+ }
+ sdhc_process_command(s);
+ 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;
+ }
+ if (SDHC_CLOCK_CARD_EN & s->clkcon) {
+ s->clkcon |= SDHC_CLOCK_EXT_STABLE;
+ } else {
+ s->clkcon &= ~SDHC_CLOCK_EXT_STABLE;
+ }
+ break;
+ case SDHC_NORINTSTS:
+ if (s->norintstsen & SDHC_NISEN_CARDINT) {
+ value &= ~SDHC_NIS_CARDINT;
+ }
+ s->norintsts &= (s->norintsts & 0x8000) | (uint16_t)(~(value & 0xFFFF));
+ break;
+ case SDHC_ERRINTSTS:
+ s->errintsts &= ~((value & 0x077F) | 0xF880);
+ s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) :
+ (s->norintsts &= ~SDHC_NIS_ERR);
+ break;
+ case SDHC_NORINTSTSEN:
+ s->norintstsen = value & 0x7FFF;
+ break;
+ case SDHC_ERRINTSTSEN:
+ s->errintstsen = value & 0x07FF;
+ break;
+ case SDHC_NORINTSIGEN:
+ s->norintsigen = value & 0x7FFF;
+ break;
+ case SDHC_ERRINTSIGEN:
+ s->errintsigen = value & 0x07FF;
+ break;
+ case SDHC_ACMD12ERRSTS: case SDHC_FEAER: case SDHC_HCVER:
+ break;
+ case SDHC_FEERR:
+ s->norintsts |= (value & 0x073F) & s->norintstsen;
+ qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+ break;
+ default:
+ DPRINTF("bad 2 byte write offset " TARGET_FMT_plx "\n", offset);
+ break;
+ }
+}
+
+/* MMC write 4 bytes function */
+static void s5pc210_sdhc_write_4(S5pc210SDHCState *s, target_phys_addr_t offset,
+ uint32_t value)
+{
+ switch (offset) {
+ case SDHC_SYSAD:
+ s->sdmasysad = value;
+ if ((s->prnsts & (SDHC_DOING_READ | SDHC_DOING_WRITE))
+ && (s->blkcnt != 0) && (s->blksize != 0) &&
+ (s->hostctl & SDHC_CTRL_DMA_CHECK_MASK) == SDHC_CTRL_SDMA) {
+ sdhc_sdma_transfer_multi_blocks(s);
+ }
+ break;
+ case SDHC_ARGUMENT:
+ s->argument = value;
+ break;
+ case SDHC_BDATA:
+ sdhc_write_dataport(s, value);
+ break;
+ case SDHC_NORINTSTS:
+ if (s->norintstsen & SDHC_NISEN_CARDINT) {
+ value &= ~SDHC_NIS_CARDINT;
+ }
+ s->norintsts &= (s->norintsts & 0x8000) | (uint16_t)(~(value & 0xFFFF));
+
+ s->errintsts &= ~(((value >> 16) & 0x077F) | 0xF880);
+ s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) :
+ (s->norintsts &= ~SDHC_NIS_ERR);
+ break;
+ case SDHC_NORINTSTSEN:
+ s->norintstsen = (uint16_t)(value & 0x7FFF);
+ s->errintstsen = (uint16_t)((value >> 16) & 0x07FF);
+ break;
+ case SDHC_NORINTSIGEN:
+ s->norintsigen = (uint16_t)(value & 0x7FFF);
+ s->errintsigen = (uint16_t)((value >> 16) & 0x07FF);
+ break;
+ case SDHC_CAPAREG:
+ if (!(s->control2 & SDHC_HWINITFIN)) {
+ s->capareg = value & 0x07EB3FBF;
+ }
+ break;
+ case SDHC_MAXCURR:
+ if (!(s->control2 & SDHC_HWINITFIN)) {
+ s->maxcurr = value & 0x00FFFFFF;
+ }
+ break;
+ case SDHC_ADMAERR:
+ s->admaerr = ((s->admaerr & SDHC_ADMAERR_FINAL_BLOCK) |
+ (value & 0x00000007)) & 0x00000507;
+ s->admaerr &= ~(value & SDHC_ADMAERR_INTRRUPT_STATUS);
+ if (value & SDHC_ADMAERR_CONTINUE_REQUEST) {
+ s->stoped_state = not_stoped;
+ sdhc_run_adma(s);
+ }
+ break;
+ case SDHC_ADMASYSADDR:
+ s->admasysaddr = value;
+ break;
+ case SDHC_CONTROL2:
+ s->control2 = value & 0xDF00DFFB;
+ break;
+ case SDHC_CONTROL3:
+ s->control3 = value;
+ break;
+ case SDHC_RSPREG0: case SDHC_RSPREG1: case SDHC_RSPREG2:
+ case SDHC_RSPREG3: case SDHC_PRNSTS: case SDHC_CONTROL4:
+ /* Nothing for emulation */
+ break;
+ default:
+ DPRINTF("sdhc: bad 4 byte write offset " TARGET_FMT_plx "\n", offset);
+ break;
+ }
+}
+
+static uint64_t s5pc210_sdhc_read(void *opaque, target_phys_addr_t offset,
+ unsigned size)
+{
+ S5pc210SDHCState *s = (S5pc210SDHCState *)opaque;
+
+ switch (size) {
+ case (1):
+ return s5pc210_sdhc_read_1(s, offset);
+ case (2):
+ return s5pc210_sdhc_read_2(s, offset);
+ case (4):
+ return s5pc210_sdhc_read_4(s, offset);
+ }
+ return 0xBAADBAADull;
+}
+
+static void s5pc210_sdhc_write(void *opaque, target_phys_addr_t offset,
+ uint64_t val, unsigned size)
+{
+ S5pc210SDHCState *s = (S5pc210SDHCState *)opaque;
+
+ switch (size) {
+ case (1):
+ s5pc210_sdhc_write_1(s, offset, (uint32_t)val);
+ break;
+ case (2):
+ s5pc210_sdhc_write_2(s, offset, (uint32_t)val);
+ break;
+ case (4):
+ s5pc210_sdhc_write_4(s, offset, (uint32_t)val);
+ break;
+ }
+}
+
+static const MemoryRegionOps s5pc210_sdhc_mmio_ops = {
+ .read = s5pc210_sdhc_read,
+ .write = s5pc210_sdhc_write,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ .unaligned = false
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription s5pc210_sdhc_vmstate = {
+ .name = "s5pc210.sdhc",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(stoped_state, S5pc210SDHCState),
+ VMSTATE_UINT32_ARRAY(fifo_buffer, S5pc210SDHCState,
+ (SDHC_CAPAB_MAXBLOCKLENGTH / TARGET_WORD_SIZE)),
+ VMSTATE_UINT16(data_count, S5pc210SDHCState),
+ VMSTATE_UINT32(sdmasysad, S5pc210SDHCState),
+ VMSTATE_UINT16(blksize, S5pc210SDHCState),
+ VMSTATE_UINT16(blkcnt, S5pc210SDHCState),
+ VMSTATE_UINT32(argument, S5pc210SDHCState),
+ VMSTATE_UINT16(trnmod, S5pc210SDHCState),
+ VMSTATE_UINT16(cmdreg, S5pc210SDHCState),
+ VMSTATE_UINT32_ARRAY(rspreg, S5pc210SDHCState, 4),
+ VMSTATE_UINT32(prnsts, S5pc210SDHCState),
+ VMSTATE_UINT8(hostctl, S5pc210SDHCState),
+ VMSTATE_UINT8(pwrcon, S5pc210SDHCState),
+ VMSTATE_UINT8(blkgap, S5pc210SDHCState),
+ VMSTATE_UINT8(wakcon, S5pc210SDHCState),
+ VMSTATE_UINT16(clkcon, S5pc210SDHCState),
+ VMSTATE_UINT8(timeoutcon, S5pc210SDHCState),
+ VMSTATE_UINT16(norintsts, S5pc210SDHCState),
+ VMSTATE_UINT16(errintsts, S5pc210SDHCState),
+ VMSTATE_UINT16(norintstsen, S5pc210SDHCState),
+ VMSTATE_UINT16(errintstsen, S5pc210SDHCState),
+ VMSTATE_UINT16(norintsigen, S5pc210SDHCState),
+ VMSTATE_UINT16(errintsigen, S5pc210SDHCState),
+ VMSTATE_UINT16(acmd12errsts, S5pc210SDHCState),
+ VMSTATE_UINT32(capareg, S5pc210SDHCState),
+ VMSTATE_UINT32(maxcurr, S5pc210SDHCState),
+ VMSTATE_UINT32(admaerr, S5pc210SDHCState),
+ VMSTATE_UINT32(admasysaddr, S5pc210SDHCState),
+ VMSTATE_UINT32(control2, S5pc210SDHCState),
+ VMSTATE_UINT32(control3, S5pc210SDHCState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/****************************************************
+ * Initialization and registration
+ * functions
+ ***************************************************/
+
+static int s5pc210_sdhc_init(SysBusDevice *dev)
+{
+ S5pc210SDHCState *s = FROM_SYSBUS(S5pc210SDHCState, dev);
+ DriveInfo *bd;
+
+ sysbus_init_irq(dev, &s->irq);
+ memory_region_init_io(&s->iomem, &s5pc210_sdhc_mmio_ops, s, "s5pc210.sdhc",
+ SDHC_REG_SIZE);
+ sysbus_init_mmio_region(dev, &s->iomem);
+ bd = drive_get_next(IF_SD);
+
+ if ((bd == NULL)) {
+ s->card = NULL;
+ DPRINTF("s->card = NULL\n");
+ } else {
+ s->eject = qemu_allocate_irqs(sdhc_insert_eject, s, 1)[0];
+ DPRINTF("name = %s, sectors = %ld\n",
+ bd->bdrv->device_name, bd->bdrv->total_sectors);
+ s->card = sd_init(bd->bdrv, 0);
+ sd_set_cb(s->card, NULL, s->eject);
+ }
+
+ s->insert_timer =
+ qemu_new_timer(vm_clock, SCALE_NS, sdhc_raise_insertion_irq, s);
+
+ s->read_buffer_timer =
+ qemu_new_timer(vm_clock, SCALE_NS, sdhc_read_block_from_card, s);
+
+ s->write_buffer_timer =
+ qemu_new_timer(vm_clock, SCALE_NS, sdhc_write_block_to_card, s);
+
+ s->transfer_complete_timer =
+ qemu_new_timer(vm_clock, SCALE_NS, sdhc_raise_transfer_complete_irq, s);
+
+ return 0;
+}
+
+static SysBusDeviceInfo s5pc210_sdhc_info = {
+ .init = s5pc210_sdhc_init,
+ .qdev.name = "s5pc210.sdhc",
+ .qdev.size = sizeof(S5pc210SDHCState),
+ .qdev.vmsd = &s5pc210_sdhc_vmstate,
+ .qdev.reset = s5pc210_sdhc_reset,
+};
+
+static void sdhc_register_devices(void)
+{
+ sysbus_register_withprop(&s5pc210_sdhc_info);
+}
+
+device_init(sdhc_register_devices)