* [Qemu-devel] [PATCH 4/7] sd: add eMMC support
2011-07-25 23:19 [Qemu-devel] sd: add SDHCI and eMMC support Vincent Palatin
` (2 preceding siblings ...)
2011-07-25 23:19 ` [Qemu-devel] [PATCH 3/7] block: add eMMC block device type Vincent Palatin
@ 2011-07-25 23:19 ` Vincent Palatin
2011-07-25 23:19 ` [Qemu-devel] [PATCH 5/7] sd: add PCI ids for SDHCI controller Vincent Palatin
` (3 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Vincent Palatin @ 2011-07-25 23:19 UTC (permalink / raw)
To: Qemu devel; +Cc: Vincent Palatin
The parameters mimick a real 4GB eMMC, but it can be set to various
sizes.
Signed-off-by: Vincent Palatin <vpalatin@chromium.org>
---
hw/sd.c | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
1 files changed, 136 insertions(+), 19 deletions(-)
diff --git a/hw/sd.c b/hw/sd.c
index de477fe..0db8d78 100644
--- a/hw/sd.c
+++ b/hw/sd.c
@@ -91,11 +91,13 @@ struct SDState {
int function_group[6];
int spi;
+ int emmc;
int current_cmd;
int blk_written;
uint64_t data_start;
uint32_t data_offset;
uint8_t data[512];
+ uint8_t ext_csd[512];
qemu_irq readonly_cb;
qemu_irq inserted_cb;
BlockDriverState *bdrv;
@@ -196,7 +198,7 @@ static uint16_t sd_crc16(void *message, size_t width)
static void sd_set_ocr(SDState *sd)
{
/* All voltages OK, card power-up OK, Standard Capacity SD Memory Card */
- sd->ocr = 0x80ffff00;
+ sd->ocr = 0x80ffff80;
}
static void sd_set_scr(SDState *sd)
@@ -250,13 +252,85 @@ static const uint8_t sd_csd_rw_mask[16] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfe,
};
+static void mmc_set_ext_csd(SDState *sd, uint64_t size)
+{
+ uint32_t sectcount = size >> HWBLOCK_SHIFT;
+
+ memset(sd->ext_csd, 0, 512);
+ sd->ext_csd[504] = 0x1; /* supported command sets */
+ sd->ext_csd[503] = 0x1; /* HPI features */
+ sd->ext_csd[502] = 0x1; /* Background operations support */
+ sd->ext_csd[241] = 0xA; /* 1st initialization time after partitioning */
+ sd->ext_csd[232] = 0x1; /* Trim multiplier */
+ sd->ext_csd[231] = 0x15; /* Secure feature support */
+ sd->ext_csd[230] = 0x96; /* Secure erase support */
+ sd->ext_csd[229] = 0x96; /* Secure TRIM multiplier */
+ sd->ext_csd[228] = 0x7; /* Boot information */
+ sd->ext_csd[226] = 0x8; /* Boot partition size */
+ sd->ext_csd[225] = 0x6; /* Access size */
+ sd->ext_csd[224] = 0x4; /* HC Erase unit size */
+ sd->ext_csd[223] = 0x1; /* HC erase timeout */
+ sd->ext_csd[222] = 0x1; /* Reliable write sector count */
+ sd->ext_csd[221] = 0x4; /* HC write protect group size */
+ sd->ext_csd[220] = 0x8; /* Sleep current VCC */
+ sd->ext_csd[219] = 0x7; /* Sleep current VCCQ */
+ sd->ext_csd[217] = 0x11; /* Sleep/Awake timeout */
+ sd->ext_csd[215] = (sectcount >> 24) & 0xff; /* Sector count */
+ sd->ext_csd[214] = (sectcount >> 16) & 0xff; /* ... */
+ sd->ext_csd[213] = (sectcount >> 8) & 0xff; /* ... */
+ sd->ext_csd[212] = (sectcount & 0xff); /* ... */
+ sd->ext_csd[210] = 0xa; /* Min write perf for 8bit@52Mhz */
+ sd->ext_csd[209] = 0xa; /* Min read perf for 8bit@52Mhz */
+ sd->ext_csd[208] = 0xa; /* Min write perf for 4bit@52Mhz */
+ sd->ext_csd[207] = 0xa; /* Min read perf for 4bit@52Mhz */
+ sd->ext_csd[206] = 0xa; /* Min write perf for 4bit@26Mhz */
+ sd->ext_csd[205] = 0xa; /* Min read perf for 4bit@26Mhz */
+ sd->ext_csd[199] = 0x1; /* Partition switching timing */
+ sd->ext_csd[198] = 0x1; /* Out-of-interrupt busy timing */
+ sd->ext_csd[196] = 0x7; /* Card type */
+ sd->ext_csd[194] = 0x2; /* CSD Structure version */
+ sd->ext_csd[192] = 0x5; /* Extended CSD revision */
+ sd->ext_csd[168] = 0x1; /* RPMB size */
+ sd->ext_csd[160] = 0x3; /* Partinioning support */
+ sd->ext_csd[159] = 0x00; /* Max enhanced area size */
+ sd->ext_csd[158] = 0x00; /* ... */
+ sd->ext_csd[157] = 0xEC; /* ... */
+}
+
static void sd_set_csd(SDState *sd, uint64_t size)
{
uint32_t csize = (size >> (CMULT_SHIFT + HWBLOCK_SHIFT)) - 1;
uint32_t sectsize = (1 << (SECTOR_SHIFT + 1)) - 1;
uint32_t wpsize = (1 << (WPGROUP_SHIFT + 1)) - 1;
- if (size <= 0x40000000) { /* Standard Capacity SD */
+ if (sd->emmc) { /* eMMC */
+ sd->csd[0] = 0xd0;
+ sd->csd[1] = 0x0f;
+ sd->csd[2] = 0x00;
+ sd->csd[3] = 0x32;
+ sd->csd[4] = 0x0f;
+ if (size <= 0x80000000ULL) {
+ /* use 1k blocks */
+ uint32_t csize1k = (size >> (CMULT_SHIFT + 10)) - 1;
+ sd->csd[5] = 0x5a;
+ sd->csd[6] = 0x80 | ((csize1k >> 10) & 0xf);
+ sd->csd[7] = (csize1k >> 2) & 0xff;
+ } else { /* >= 2GB : size stored in ext CSD, block addressing */
+ sd->csd[5] = 0x59;
+ sd->csd[6] = 0x8f;
+ sd->csd[7] = 0xff;
+ sd->ocr |= 1 << 30;
+ }
+ sd->csd[8] = 0xff;
+ sd->csd[9] = 0xff;
+ sd->csd[10] = 0xf7;
+ sd->csd[11] = 0xfe;
+ sd->csd[12] = 0x49;
+ sd->csd[13] = 0x10;
+ sd->csd[14] = 0x00;
+ sd->csd[15] = (sd_crc7(sd->csd, 15) << 1) | 1;
+ mmc_set_ext_csd(sd, size);
+ } else if (size <= 0x40000000) { /* Standard Capacity SD */
sd->csd[0] = 0x00; /* CSD structure */
sd->csd[1] = 0x26; /* Data read access-time-1 */
sd->csd[2] = 0x00; /* Data read access-time-2 */
@@ -305,9 +379,13 @@ static void sd_set_csd(SDState *sd, uint64_t size)
}
}
-static void sd_set_rca(SDState *sd)
+static void sd_set_rca(SDState *sd, uint16_t value)
{
- sd->rca += 0x4567;
+ if (sd->emmc) {
+ sd->rca = value;
+ } else {
+ sd->rca += 0x4567;
+ }
}
#define CARD_STATUS_A 0x02004100
@@ -343,6 +421,8 @@ static void sd_response_r1_make(SDState *sd,
uint32_t status;
status = (sd->card_status & ~mask) | (last_status & mask);
+ status &= ~CURRENT_STATE;
+ status |= sd->state << 9;
sd->card_status &= ~CARD_STATUS_C | APP_CMD;
response[0] = (status >> 24) & 0xff;
@@ -446,6 +526,7 @@ SDState *sd_init(BlockDriverState *bs, int is_spi)
sd = (SDState *) qemu_mallocz(sizeof(SDState));
sd->buf = qemu_blockalign(bs, 512);
sd->spi = is_spi;
+ sd->emmc = !bdrv_is_removable(bs);
sd->enable = 1;
sd_reset(sd, bs);
if (sd->bdrv) {
@@ -627,6 +708,11 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
break;
case 1: /* CMD1: SEND_OP_CMD */
+ if (sd->emmc) {
+ sd->state = sd_ready_state;
+ return sd_r3;
+ }
+
if (!sd->spi)
goto bad_cmd;
@@ -653,8 +739,8 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
case sd_identification_state:
case sd_standby_state:
sd->state = sd_standby_state;
- sd_set_rca(sd);
- return sd_r6;
+ sd_set_rca(sd, req.arg >> 16);
+ return sd->emmc ? sd_r1 : sd_r6;
default:
break;
@@ -678,6 +764,9 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
return sd_r0;
case 6: /* CMD6: SWITCH_FUNCTION */
+ if (sd->emmc) {
+ return sd_r1b;
+ } else {
if (sd->spi)
goto bad_cmd;
switch (sd->mode) {
@@ -691,6 +780,7 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
default:
break;
}
+ }
break;
case 7: /* CMD7: SELECT/DESELECT_CARD */
@@ -731,22 +821,36 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
}
break;
- case 8: /* CMD8: SEND_IF_COND */
- /* Physical Layer Specification Version 2.00 command */
- switch (sd->state) {
- case sd_idle_state:
- sd->vhs = 0;
+ case 8: /* CMD8: SEND_IF_COND / SEND_EXT_CSD */
+ if (sd->emmc) {
+ switch (sd->state) {
+ case sd_transfer_state:
+ /* MMC : Sends the EXT_CSD register as a Block of data */
+ sd->state = sd_sendingdata_state;
+ memcpy(sd->data, sd->ext_csd, 512);
+ sd->data_start = addr;
+ sd->data_offset = 0;
+ return sd_r1;
+ default:
+ break;
+ }
+ } else {
+ /* SD Physical Layer Specification Version 2.00 command */
+ switch (sd->state) {
+ case sd_idle_state:
+ sd->vhs = 0;
- /* No response if not exactly one VHS bit is set. */
- if (!(req.arg >> 8) || (req.arg >> ffs(req.arg & ~0xff)))
- return sd->spi ? sd_r7 : sd_r0;
+ /* No response if not exactly one VHS bit is set. */
+ if (!(req.arg >> 8) || (req.arg >> ffs(req.arg & ~0xff)))
+ return sd->spi ? sd_r7 : sd_r0;
- /* Accept. */
- sd->vhs = req.arg;
- return sd_r7;
+ /* Accept. */
+ sd->vhs = req.arg;
+ return sd_r7;
- default:
- break;
+ default:
+ break;
+ }
}
break;
@@ -1115,6 +1219,11 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
/* Application specific commands (Class 8) */
case 55: /* CMD55: APP_CMD */
+ /* Not supported by MMC */
+ if (sd->emmc) {
+ return sd_r0;
+ }
+
if (sd->rca != rca)
return sd_r0;
@@ -1566,6 +1675,14 @@ uint8_t sd_read_data(SDState *sd)
sd->state = sd_transfer_state;
break;
+ case 8: /* CMD8: SEND_EXT_CSD on MMC */
+ ret = sd->data[sd->data_offset++];
+
+ if (sd->data_offset >= 512) {
+ sd->state = sd_transfer_state;
+ }
+ break;
+
case 9: /* CMD9: SEND_CSD */
case 10: /* CMD10: SEND_CID */
ret = sd->data[sd->data_offset ++];
--
1.7.3.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [Qemu-devel] [PATCH 6/7] sd: add SD Host Controller (SDHCI) emulation
2011-07-25 23:19 [Qemu-devel] sd: add SDHCI and eMMC support Vincent Palatin
` (4 preceding siblings ...)
2011-07-25 23:19 ` [Qemu-devel] [PATCH 5/7] sd: add PCI ids for SDHCI controller Vincent Palatin
@ 2011-07-25 23:19 ` Vincent Palatin
2011-07-25 23:19 ` [Qemu-devel] [PATCH 7/7] sd: compile SDHCI on PCI platforms Vincent Palatin
2011-12-13 16:38 ` [Qemu-devel] sd: add SDHCI and eMMC support Stefan Hajnoczi
7 siblings, 0 replies; 10+ messages in thread
From: Vincent Palatin @ 2011-07-25 23:19 UTC (permalink / raw)
To: Qemu devel; +Cc: Vincent Palatin
Try to be compliant with "SD Specifications Part A2 SD Host Controller
Simplified Specification Version 3.00", but not every feature is
implemented.
Signed-off-by: Vincent Palatin <vpalatin@chromium.org>
---
hw/sdhci.c | 670 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 670 insertions(+), 0 deletions(-)
create mode 100644 hw/sdhci.c
diff --git a/hw/sdhci.c b/hw/sdhci.c
new file mode 100644
index 0000000..d097f0f
--- /dev/null
+++ b/hw/sdhci.c
@@ -0,0 +1,670 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * 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.
+ *
+ *
+ * SDHCI (SD Host Controler Interface) emulation
+ */
+
+#include "blockdev.h"
+#include "sysbus.h"
+#include "pci.h"
+#include "sd.h"
+
+/* from Linux sources : drivers/mmc/host/sdhci.h */
+/*
+ * Controller registers
+ */
+
+#define SDHCI_DMA_ADDRESS 0x00
+
+#define SDHCI_BLOCK_SIZE 0x04
+
+#define SDHCI_BLOCK_COUNT 0x06
+
+#define SDHCI_ARGUMENT 0x08
+
+#define SDHCI_TRANSFER_MODE 0x0C
+#define SDHCI_TRNS_DMA 0x01
+#define SDHCI_TRNS_BLK_CNT_EN 0x02
+#define SDHCI_TRNS_ACMD12 0x04
+#define SDHCI_TRNS_READ 0x10
+#define SDHCI_TRNS_MULTI 0x20
+
+#define SDHCI_COMMAND 0x0E
+#define SDHCI_CMD_RESP_MASK 0x03
+#define SDHCI_CMD_CRC 0x08
+#define SDHCI_CMD_INDEX 0x10
+#define SDHCI_CMD_DATA 0x20
+
+#define SDHCI_CMD_RESP_NONE 0x00
+#define SDHCI_CMD_RESP_LONG 0x01
+#define SDHCI_CMD_RESP_SHORT 0x02
+#define SDHCI_CMD_RESP_SHORT_BUSY 0x03
+
+#define SDHCI_RESPONSE 0x10
+
+#define SDHCI_BUFFER 0x20
+
+#define SDHCI_PRESENT_STATE 0x24
+#define SDHCI_CMD_INHIBIT 0x00000001
+#define SDHCI_DATA_INHIBIT 0x00000002
+#define SDHCI_DOING_WRITE 0x00000100
+#define SDHCI_DOING_READ 0x00000200
+#define SDHCI_SPACE_AVAILABLE 0x00000400
+#define SDHCI_DATA_AVAILABLE 0x00000800
+#define SDHCI_CARD_PRESENT 0x00010000
+#define SDHCI_WRITE_PROTECT 0x00080000
+
+#define SDHCI_HOST_CONTROL 0x28
+#define SDHCI_CTRL_LED 0x01
+#define SDHCI_CTRL_4BITBUS 0x02
+#define SDHCI_CTRL_HISPD 0x04
+#define SDHCI_CTRL_DMA_MASK 0x18
+#define SDHCI_CTRL_SDMA 0x00
+#define SDHCI_CTRL_ADMA1 0x08
+#define SDHCI_CTRL_ADMA32 0x10
+#define SDHCI_CTRL_ADMA64 0x18
+#define SDHCI_CTRL_8BITBUS 0x20
+
+#define SDHCI_POWER_CONTROL 0x29
+#define SDHCI_POWER_ON 0x01
+#define SDHCI_POWER_180 0x0A
+#define SDHCI_POWER_300 0x0C
+#define SDHCI_POWER_330 0x0E
+
+#define SDHCI_BLOCK_GAP_CONTROL 0x2A
+
+#define SDHCI_WAKE_UP_CONTROL 0x2B
+#define SDHCI_WAKE_ON_INT 0x01
+#define SDHCI_WAKE_ON_INSERT 0x02
+#define SDHCI_WAKE_ON_REMOVE 0x04
+
+#define SDHCI_CLOCK_CONTROL 0x2C
+#define SDHCI_DIVIDER_SHIFT 8
+#define SDHCI_DIVIDER_HI_SHIFT 6
+#define SDHCI_DIV_MASK 0xFF
+#define SDHCI_DIV_MASK_LEN 8
+#define SDHCI_DIV_HI_MASK 0x300
+#define SDHCI_CLOCK_CARD_EN 0x0004
+#define SDHCI_CLOCK_INT_STABLE 0x0002
+#define SDHCI_CLOCK_INT_EN 0x0001
+
+#define SDHCI_TIMEOUT_CONTROL 0x2E
+
+#define SDHCI_SOFTWARE_RESET 0x2F
+#define SDHCI_RESET_ALL 0x01
+#define SDHCI_RESET_CMD 0x02
+#define SDHCI_RESET_DATA 0x04
+
+#define SDHCI_INT_STATUS 0x30
+#define SDHCI_INT_ENABLE 0x34
+#define SDHCI_SIGNAL_ENABLE 0x38
+#define SDHCI_INT_RESPONSE 0x00000001
+#define SDHCI_INT_DATA_END 0x00000002
+#define SDHCI_INT_DMA_END 0x00000008
+#define SDHCI_INT_SPACE_AVAIL 0x00000010
+#define SDHCI_INT_DATA_AVAIL 0x00000020
+#define SDHCI_INT_CARD_INSERT 0x00000040
+#define SDHCI_INT_CARD_REMOVE 0x00000080
+#define SDHCI_INT_CARD_INT 0x00000100
+#define SDHCI_INT_ERROR 0x00008000
+#define SDHCI_INT_TIMEOUT 0x00010000
+#define SDHCI_INT_CRC 0x00020000
+#define SDHCI_INT_END_BIT 0x00040000
+#define SDHCI_INT_INDEX 0x00080000
+#define SDHCI_INT_DATA_TIMEOUT 0x00100000
+#define SDHCI_INT_DATA_CRC 0x00200000
+#define SDHCI_INT_DATA_END_BIT 0x00400000
+#define SDHCI_INT_BUS_POWER 0x00800000
+#define SDHCI_INT_ACMD12ERR 0x01000000
+#define SDHCI_INT_ADMA_ERROR 0x02000000
+
+#define SDHCI_INT_NORMAL_MASK 0x00007FFF
+#define SDHCI_INT_ERROR_MASK 0xFFFF8000
+
+#define SDHCI_ACMD12_ERR 0x3C
+
+/* 3E-3F reserved */
+
+#define SDHCI_CAPABILITIES 0x40
+#define SDHCI_TIMEOUT_CLK_MASK 0x0000003F
+#define SDHCI_TIMEOUT_CLK_SHIFT 0
+#define SDHCI_TIMEOUT_CLK_UNIT 0x00000080
+#define SDHCI_CLOCK_BASE_MASK 0x00003F00
+#define SDHCI_CLOCK_V3_BASE_MASK 0x0000FF00
+#define SDHCI_CLOCK_BASE_SHIFT 8
+#define SDHCI_MAX_BLOCK_MASK 0x00030000
+#define SDHCI_MAX_BLOCK_SHIFT 16
+#define SDHCI_CAN_DO_8BIT 0x00040000
+#define SDHCI_CAN_DO_ADMA2 0x00080000
+#define SDHCI_CAN_DO_ADMA1 0x00100000
+#define SDHCI_CAN_DO_HISPD 0x00200000
+#define SDHCI_CAN_DO_SDMA 0x00400000
+#define SDHCI_CAN_VDD_330 0x01000000
+#define SDHCI_CAN_VDD_300 0x02000000
+#define SDHCI_CAN_VDD_180 0x04000000
+#define SDHCI_CAN_64BIT 0x10000000
+#define SDHCI_SLOT_TYPE_EMBED 0x40000000
+
+#define SDHCI_CAPABILITIES_1 0x44
+
+/* 44-47 reserved for more caps */
+
+#define SDHCI_MAX_CURRENT 0x48
+
+/* 4C-4F reserved for more max current */
+
+#define SDHCI_SET_ACMD12_ERROR 0x50
+#define SDHCI_SET_INT_ERROR 0x52
+
+#define SDHCI_ADMA_ERROR 0x54
+
+/* 55-57 reserved */
+
+#define SDHCI_ADMA_ADDRESS 0x58
+
+/* 60-FB reserved */
+
+#define SDHCI_SLOT_INT_STATUS 0xFC
+
+#define SDHCI_HOST_VERSION 0xFE
+#define SDHCI_VENDOR_VER_MASK 0xFF00
+#define SDHCI_VENDOR_VER_SHIFT 8
+#define SDHCI_SPEC_VER_MASK 0x00FF
+#define SDHCI_SPEC_VER_SHIFT 0
+#define SDHCI_SPEC_100 0
+#define SDHCI_SPEC_200 1
+#define SDHCI_SPEC_300 2
+
+/* ADMA descriptor flags */
+#define ADMA_VALID (1<<0)
+#define ADMA_END (1<<1)
+#define ADMA_INT (1<<2)
+
+#define ADMA_OP_MASK (3<<4)
+#define ADMA_OP_NOP (0<<4)
+#define ADMA_OP_RSV (1<<4)
+#define ADMA_OP_TRAN (2<<4)
+#define ADMA_OP_LINK (3<<4)
+
+/* re-write a part of a variable using a mask
+ * to emulate the "byte-enable" behaviour in hardware
+ */
+#define MASKED_WRITE(var, val, mask) do {\
+ var = (var & ~(mask)) | ((val) & (mask));\
+ } while (0)
+
+typedef struct {
+ union {
+ SysBusDevice busdev;
+ PCIDevice pcidev;
+ };
+ BlockDriverState *bs;
+ uint32_t sdma_address;
+ uint16_t block_size;
+ uint16_t block_count;
+ uint32_t arg;
+ uint16_t transfer_mode;
+ uint32_t response[4];
+ uint32_t clock;
+ uint32_t host_control;
+ uint32_t int_enable;
+ uint32_t int_status;
+ uint32_t adma_address;
+ uint8_t adma_error;
+ SDState *sd;
+ qemu_irq irq;
+} sdhci_state;
+
+static void sdhci_reset(DeviceState *d);
+
+static void sdhci_set_irq(sdhci_state *s)
+{
+ qemu_set_irq(s->irq, !!(s->int_status & s->int_enable));
+}
+
+static void sdhci_dma_transfer(sdhci_state *s)
+{
+ int b;
+ struct adma_desc {
+ uint16_t flags;
+ uint16_t size;
+ uint32_t addr;
+ } desc;
+ uint16_t xfer_size;
+ uint32_t total_size, remaining_size;
+
+ s->adma_error = 0;
+
+ if (s->host_control & SDHCI_CTRL_ADMA32) {
+ cpu_physical_memory_read(s->adma_address, (void *)&desc, sizeof(desc));
+ } else { /* use SDMA */
+ /* Generate hardcoded descriptor to emulate ADMA */
+ desc.addr = s->sdma_address;
+ desc.size = s->block_count * (s->block_size & 0xfff);
+ desc.flags = ADMA_VALID | ADMA_END | ADMA_OP_TRAN;
+ /* TODO: manage SDMA buffer boundary */
+ }
+ /* 0 in the size field means 65536 bytes */
+ remaining_size = desc.size ? desc.size : 65536;
+
+ s->int_status |= SDHCI_INT_DATA_END;
+
+ total_size = s->block_count*(s->block_size & 0xfff);
+ while (total_size) {
+ while (!(desc.flags & ADMA_VALID) || !remaining_size ||
+ ((desc.flags & ADMA_OP_MASK) != ADMA_OP_TRAN)) {
+ if ((desc.flags & ADMA_END) || !(desc.flags & ADMA_VALID)) {
+ /* Abort ADMA transfer */
+ s->int_status |= SDHCI_INT_ADMA_ERROR;
+ s->adma_error = 1 /* ST_FDS */;
+ s->block_count = total_size / s->block_size;
+ return;
+ }
+ if ((desc.flags & ADMA_OP_MASK) == ADMA_OP_LINK) {
+ s->adma_address = desc.addr;
+ } else {
+ s->adma_address += sizeof(struct adma_desc);
+ }
+ cpu_physical_memory_read(s->adma_address, (void *)&desc,
+ sizeof(desc));
+ /* 0 in the size field means 65536 bytes */
+ remaining_size = desc.size ? desc.size : 65536;
+ /* ADMA_INT flag not implemented : it seems to be debug only */
+ }
+
+ xfer_size = MIN((s->block_size & 0xfff), remaining_size);
+ for (b = 0; b < xfer_size; b++, desc.addr++) {
+ if (s->transfer_mode & SDHCI_TRNS_READ) {
+ uint8_t data = sd_read_data(s->sd);
+ cpu_physical_memory_write(desc.addr, &data, 1);
+ } else {
+ uint8_t data;
+ cpu_physical_memory_read(desc.addr, &data, 1);
+ sd_write_data(s->sd, data);
+ }
+ }
+ remaining_size -= xfer_size;
+ total_size -= xfer_size;
+ }
+ s->block_count = 0;
+}
+
+static void sdhci_command(sdhci_state *s, uint32_t cmd)
+{
+ SDRequest request;
+ int len;
+ uint8_t r[16];
+
+ if (!s->sd) { /* nothing beyond the controller */
+ s->int_status |= SDHCI_INT_TIMEOUT;
+ sdhci_set_irq(s);
+ return;
+ }
+
+ request.cmd = (cmd>>8) & 0xff;
+ request.arg = s->arg;
+ len = sd_do_command(s->sd, &request, r);
+ if (len == 0) {
+ if (cmd & SDHCI_CMD_RESP_MASK) {
+ /* no response expected */
+ s->int_status |= SDHCI_INT_INDEX;
+ } else {
+ /* error */
+ s->int_status |= SDHCI_INT_RESPONSE;
+ }
+ } else {
+ if (len == 4) {
+ s->response[0] = (r[0]<<24) | (r[1]<<16) | (r[2]<<8) | r[3];
+ } else if (len == 16) {
+ s->response[0] = (r[11]<<24) | (r[12]<<16) | (r[13]<<8) | r[14];
+ s->response[1] = (r[7]<<24) | (r[8]<<16) | (r[9]<<8) | r[10];
+ s->response[2] = (r[3]<<24) | (r[4]<<16) | (r[5]<<8) | r[6];
+ s->response[3] = (0xcc<<24) | (r[0]<<16) | (r[1]<<8) | r[2];
+ }
+ s->int_status |= SDHCI_INT_RESPONSE;
+ if ((cmd & 3) == SDHCI_CMD_RESP_SHORT_BUSY) {
+ /* the command will trigger the busy (DAT[0]) line ON then OFF
+ * this will raise the Data END interrupt when done.
+ */
+ s->int_status |= SDHCI_INT_DATA_END;
+ }
+ if ((s->transfer_mode & SDHCI_TRNS_DMA) && (cmd & SDHCI_CMD_DATA)) {
+ sdhci_dma_transfer(s);
+ }
+ }
+ sdhci_set_irq(s);
+}
+
+static uint32_t sdhci_read32(void *opaque, target_phys_addr_t offset)
+{
+ sdhci_state *s = opaque;
+
+ if ((offset >= 0x100) && (offset < 0x200)) {
+ fprintf(stderr, "sdhci: unsupported vendor read @" TARGET_FMT_plx "\n",
+ offset);
+ return 0;
+ }
+
+ switch (offset) {
+ case SDHCI_DMA_ADDRESS:
+ return s->sdma_address;
+ case SDHCI_BLOCK_SIZE /* +SDHCI_BLOCK_COUNT */:
+ return s->block_size | (s->block_count << 16);
+ case SDHCI_ARGUMENT:
+ return s->arg;
+ case SDHCI_TRANSFER_MODE /* +SDHCI_COMMAND */:
+ return s->transfer_mode;
+ case SDHCI_RESPONSE:
+ case SDHCI_RESPONSE+4:
+ case SDHCI_RESPONSE+8:
+ case SDHCI_RESPONSE+12:
+ return s->response[(offset-SDHCI_RESPONSE)/sizeof(uint32_t)];
+ case SDHCI_BUFFER:
+ /* TODO: implement PIO mode */
+ return 0;
+ case SDHCI_PRESENT_STATE:
+ return (s->sd ? 0x00170000 : 0) |
+ (s->bs && bdrv_is_read_only(s->bs) ? 0 : 0x00080000);
+ case SDHCI_HOST_CONTROL:
+ /*+SDHCI_POWER_CONTROL +SDHCI_BLOCK_GAP_CONTROL +SDHCI_WAKE_UP_CONTROL*/
+ return s->host_control;
+ case SDHCI_CLOCK_CONTROL /* +SDHCI_TIMEOUT_CONTROL +SDHCI_SOFTWARE_RESET */:
+ return s->clock | (0<<24 /* reset done */);
+ case SDHCI_INT_STATUS:
+ return s->int_status;
+ case SDHCI_INT_ENABLE:
+ return s->int_enable;
+ case SDHCI_SIGNAL_ENABLE:
+ return 0;
+ case SDHCI_ACMD12_ERR:
+ return 0;
+ case SDHCI_CAPABILITIES:
+ return SDHCI_SLOT_TYPE_EMBED | SDHCI_CAN_VDD_330 |
+ SDHCI_CAN_DO_ADMA1 | SDHCI_CAN_DO_HISPD | SDHCI_CAN_DO_SDMA |
+ SDHCI_CAN_DO_8BIT | SDHCI_CAN_DO_ADMA2 |
+ (52 << SDHCI_CLOCK_BASE_SHIFT) |
+ SDHCI_TIMEOUT_CLK_UNIT /* MHz */ | (52<<SDHCI_TIMEOUT_CLK_SHIFT);
+ case SDHCI_CAPABILITIES_1:
+ return 0;
+ case SDHCI_MAX_CURRENT:
+ return 0;
+ case SDHCI_SET_ACMD12_ERROR /* +SDHCI_SET_INT_ERROR */:
+ hw_error("sdhci_read: read only register at " TARGET_FMT_plx "\n",
+ offset);
+ case SDHCI_ADMA_ERROR:
+ return s->adma_error;
+ case SDHCI_ADMA_ADDRESS:
+ return s->adma_address;
+ case SDHCI_SLOT_INT_STATUS /* +SDHCI_HOST_VERSION */:
+ return 0 | (SDHCI_SPEC_200 << 16);
+ default:
+ hw_error("sdhci_read: Bad offset " TARGET_FMT_plx "\n", offset);
+ }
+
+ return 0;
+}
+
+static uint32_t sdhci_read16(void *opaque, target_phys_addr_t offset)
+{
+ uint32_t v = sdhci_read32(opaque, offset & ~0x3);
+
+ return (v >> (8 * (offset & 0x3))) & 0xffff;
+}
+
+static uint32_t sdhci_read8(void *opaque, target_phys_addr_t offset)
+{
+ uint32_t v = sdhci_read32(opaque, offset & ~0x3);
+
+ return (v >> (8 * (offset & 0x3))) & 0xff;
+}
+
+static void sdhci_write_masked(void *opaque, target_phys_addr_t offset,
+ uint32_t value, uint32_t mask)
+{
+ sdhci_state *s = opaque;
+
+ if ((offset >= 0x100) && (offset < 0x200)) {
+ fprintf(stderr, "sdhci: unsupported vendor write at "TARGET_FMT_plx"\n",
+ offset);
+ return;
+ }
+
+ switch (offset) {
+ case SDHCI_DMA_ADDRESS:
+ MASKED_WRITE(s->sdma_address, value, mask);
+ break;
+ case SDHCI_BLOCK_SIZE /* +SDHCI_BLOCK_COUNT */:
+ MASKED_WRITE(s->block_size, value & 0x7fff, mask);
+ MASKED_WRITE(s->block_count, value >> 16, mask >> 16);
+ break;
+ case SDHCI_ARGUMENT:
+ MASKED_WRITE(s->arg, value, mask);
+ break;
+ case SDHCI_TRANSFER_MODE /* +SDHCI_COMMAND */:
+ MASKED_WRITE(s->transfer_mode, value & 0x3f, mask);
+ if (mask & 0xffff0000) {
+ sdhci_command(s, value >> 16);
+ }
+ break;
+ case SDHCI_BUFFER:
+ /* TODO: implement PIO mode */
+ break;
+ case SDHCI_HOST_CONTROL:
+ /*+SDHCI_POWER_CONTROL +SDHCI_BLOCK_GAP_CONTROL +SDHCI_WAKE_UP_CONTROL*/
+ MASKED_WRITE(s->host_control, value, mask);
+ if ((mask >> 8) & SDHCI_POWER_ON) {
+ if (s->sd) {
+ sd_enable(s->sd, ((value & mask) >> 8) & SDHCI_POWER_ON);
+ }
+ }
+ break;
+ case SDHCI_CLOCK_CONTROL /* +SDHCI_TIMEOUT_CONTROL +SDHCI_SOFTWARE_RESET */:
+ if (((value & mask) >> 24) & SDHCI_RESET_ALL) {
+ sdhci_reset(opaque);
+ }
+ if (((value & mask) >> 24) & SDHCI_RESET_CMD) {
+ s->int_status &= ~SDHCI_INT_RESPONSE;
+ sdhci_set_irq(s);
+ }
+ if (((value & mask) >> 24) & SDHCI_RESET_DATA) {
+ s->adma_error = 0;
+ s->int_status &= ~(SDHCI_INT_DATA_END | SDHCI_INT_DMA_END);
+ sdhci_set_irq(s);
+ }
+ MASKED_WRITE(s->clock, (value & 0xfffff) | SDHCI_CLOCK_INT_STABLE,
+ mask);
+ break;
+ case SDHCI_INT_STATUS:
+ s->int_status &= ~(value & mask);
+ sdhci_set_irq(s);
+ break;
+ case SDHCI_INT_ENABLE:
+ MASKED_WRITE(s->int_enable, value, mask);
+ sdhci_set_irq(s);
+ break;
+ case SDHCI_SIGNAL_ENABLE:
+ break;
+ case SDHCI_SET_ACMD12_ERROR /* +SDHCI_SET_INT_ERROR */:
+ break;
+ case SDHCI_ADMA_ADDRESS:
+ MASKED_WRITE(s->adma_address, value, mask);
+ break;
+ case SDHCI_SLOT_INT_STATUS /* +SDHCI_HOST_VERSION */:
+ break;
+ /* Read only registers */
+ case SDHCI_RESPONSE:
+ case SDHCI_RESPONSE+4:
+ case SDHCI_RESPONSE+8:
+ case SDHCI_RESPONSE+12:
+ case SDHCI_PRESENT_STATE:
+ case SDHCI_ACMD12_ERR:
+ case SDHCI_CAPABILITIES:
+ case SDHCI_CAPABILITIES_1:
+ case SDHCI_MAX_CURRENT:
+ case SDHCI_ADMA_ERROR:
+ hw_error("sdhci_write: Read only register at " TARGET_FMT_plx "\n",
+ offset);
+ default:
+ hw_error("sdhci_write: Bad offset " TARGET_FMT_plx "\n", offset);
+ }
+}
+
+static void sdhci_write32(void *opaque, target_phys_addr_t offset,
+ uint32_t value)
+{
+ sdhci_write_masked(opaque, offset, value, 0xffffffff);
+}
+
+static void sdhci_write16(void *opaque, target_phys_addr_t offset,
+ uint32_t value)
+{
+ int shift = 8 * (offset & 0x3);
+
+ sdhci_write_masked(opaque, offset & ~3, value << shift, 0xffff << shift);
+}
+
+static void sdhci_write8(void *opaque, target_phys_addr_t offset,
+ uint32_t value)
+{
+ int shift = 8 * (offset & 0x3);
+
+ sdhci_write_masked(opaque, offset & ~3, value << shift, 0xff << shift);
+}
+
+static CPUReadMemoryFunc * const sdhci_readfn[] = {
+ sdhci_read8,
+ sdhci_read16,
+ sdhci_read32
+};
+
+static CPUWriteMemoryFunc * const sdhci_writefn[] = {
+ sdhci_write8,
+ sdhci_write16,
+ sdhci_write32
+};
+
+static void sdhci_reset(DeviceState *d)
+{
+ sdhci_state *s = container_of(d, sdhci_state, busdev.qdev);
+
+ if (!s->bs) {
+ DriveInfo *inf = drive_get_next(IF_SD);
+ s->bs = inf ? inf->bdrv : NULL;
+ }
+ if (!s->sd && s->bs) {
+ s->sd = sd_init(s->bs, 0);
+ }
+
+ s->sdma_address = 0;
+ s->block_size = 0;
+ s->block_count = 0;
+ s->arg = 0;
+ s->transfer_mode = 0;
+ s->clock = SDHCI_CLOCK_INT_STABLE;
+ s->int_enable = 0;
+ s->int_status = 0;
+ s->adma_address = 0;
+ s->adma_error = 0;
+}
+
+static const VMStateDescription sdhci_vmstate = {
+ .name = "sdhci",
+ .version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(sdma_address, sdhci_state),
+ VMSTATE_UINT16(block_size, sdhci_state),
+ VMSTATE_UINT16(block_count, sdhci_state),
+ VMSTATE_UINT32(arg, sdhci_state),
+ VMSTATE_UINT16(transfer_mode, sdhci_state),
+ VMSTATE_UINT32_ARRAY(response, sdhci_state, 4),
+ VMSTATE_UINT32(clock, sdhci_state),
+ VMSTATE_UINT32(int_enable, sdhci_state),
+ VMSTATE_UINT32(int_status, sdhci_state),
+ VMSTATE_UINT32(adma_address, sdhci_state),
+ VMSTATE_UINT8(adma_error, sdhci_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int sdhci_mm_init(SysBusDevice *dev)
+{
+ int iomemtype;
+ sdhci_state *s = FROM_SYSBUS(sdhci_state, dev);
+
+ iomemtype = cpu_register_io_memory(sdhci_readfn,
+ sdhci_writefn, s,
+ DEVICE_NATIVE_ENDIAN);
+ sysbus_init_mmio(dev, 0x200, iomemtype);
+ sysbus_init_irq(dev, &s->irq);
+
+ return 0;
+}
+
+static SysBusDeviceInfo sdhci_mm_info = {
+ .init = sdhci_mm_init,
+ .qdev.name = "sdhci",
+ .qdev.size = sizeof(sdhci_state),
+ .qdev.vmsd = &sdhci_vmstate,
+ .qdev.reset = sdhci_reset,
+ .qdev.props = (Property[]) {
+ DEFINE_PROP_DRIVE("block", sdhci_state, bs),
+ DEFINE_PROP_END_OF_LIST(),
+ }
+};
+
+static int sdhci_pci_init(PCIDevice *dev)
+{
+ sdhci_state *s = DO_UPCAST(sdhci_state, pcidev, dev);
+ uint8_t *pci_conf = s->pcidev.config;
+ int iomemtype;
+
+ pci_conf[PCI_CLASS_PROG] = 0x01; /* Standard Host supported DMA */
+ pci_conf[PCI_INTERRUPT_PIN] = 1; /* interrupt pin 0 */
+ pci_conf[0x40] = 0x00; /* 1 slot at BAR0 */
+
+ iomemtype = cpu_register_io_memory(sdhci_readfn,
+ sdhci_writefn, s,
+ DEVICE_NATIVE_ENDIAN);
+ pci_register_bar_simple(&s->pcidev, 0, 0x100, 0, iomemtype);
+ s->irq = s->pcidev.irq[0];
+ return 0;
+}
+
+static PCIDeviceInfo sdhci_pci_info = {
+ .qdev.name = "sdhci_pci",
+ .qdev.size = sizeof(sdhci_state),
+ .qdev.vmsd = &sdhci_vmstate,
+ .qdev.reset = sdhci_reset,
+ .qdev.props = (Property[]) {
+ DEFINE_PROP_DRIVE("block", sdhci_state, bs),
+ DEFINE_PROP_END_OF_LIST(),
+ },
+ .init = sdhci_pci_init,
+ .vendor_id = PCI_VENDOR_ID_QEMU,
+ .device_id = PCI_DEVICE_ID_SDHCI,
+ .class_id = PCI_CLASS_SYSTEM_SDHCI,
+};
+
+static void sdhci_register(void)
+{
+ sysbus_register_withprop(&sdhci_mm_info);
+ pci_qdev_register(&sdhci_pci_info);
+}
+
+device_init(sdhci_register)
--
1.7.3.1
^ permalink raw reply related [flat|nested] 10+ messages in thread