/*
* SD Association Host Standard Specification v2.0 controller emulation
*
+ * Datasheet: PartA2_SD_Host_Controller_Simplified_Specification_Ver2.00.pdf
+ *
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* Mitsyanko Igor <i.mitsyanko@samsung.com>
* Peter A.G. Crosthwaite <peter.crosthwaite@petalogix.com>
#include "qemu/log.h"
#include "qemu/module.h"
#include "trace.h"
+#include "qom/object.h"
#define TYPE_SDHCI_BUS "sdhci-bus"
-#define SDHCI_BUS(obj) OBJECT_CHECK(SDBus, (obj), TYPE_SDHCI_BUS)
+/* This is reusing the SDBus typedef from SD_BUS */
+DECLARE_INSTANCE_CHECKER(SDBus, SDHCI_BUS,
+ TYPE_SDHCI_BUS)
#define MASKED_WRITE(reg, mask, val) (reg = (reg & (mask)) | (val))
((s->norintsts & SDHC_NIS_REMOVE) && (s->wakcon & SDHC_WKUP_ON_RMV));
}
-static inline void sdhci_update_irq(SDHCIState *s)
+/* Return true if IRQ was pending and delivered */
+static bool sdhci_update_irq(SDHCIState *s)
{
- qemu_set_irq(s->irq, sdhci_slotint(s));
+ bool pending = sdhci_slotint(s);
+
+ qemu_set_irq(s->irq, pending);
+
+ return pending;
}
static void sdhci_raise_insertion_irq(void *opaque)
SDRequest request;
uint8_t response[16];
int rlen;
+ bool timeout = false;
s->errintsts = 0;
s->acmd12errsts = 0;
trace_sdhci_response16(s->rspreg[3], s->rspreg[2],
s->rspreg[1], s->rspreg[0]);
} else {
+ timeout = true;
trace_sdhci_error("timeout waiting for command response");
if (s->errintstsen & SDHC_EISEN_CMDTIMEOUT) {
s->errintsts |= SDHC_EIS_CMDTIMEOUT;
sdhci_update_irq(s);
- if (s->blksize && (s->cmdreg & SDHC_CMD_DATA_PRESENT)) {
+ if (!timeout && s->blksize && (s->cmdreg & SDHC_CMD_DATA_PRESENT)) {
s->data_count = 0;
sdhci_data_transfer(s);
}
/* Fill host controller's read buffer with BLKSIZE bytes of data from card */
static void sdhci_read_block_from_card(SDHCIState *s)
{
- int index = 0;
- uint8_t data;
const uint16_t blk_size = s->blksize & BLOCK_SIZE_MASK;
if ((s->trnmod & SDHC_TRNS_MULTI) &&
return;
}
- for (index = 0; index < blk_size; index++) {
- data = sdbus_read_data(&s->sdbus);
- if (!FIELD_EX32(s->hostctl2, SDHC_HOSTCTL2, EXECUTE_TUNING)) {
- /* Device is not in tuning */
- s->fifo_buffer[index] = data;
- }
+ if (!FIELD_EX32(s->hostctl2, SDHC_HOSTCTL2, EXECUTE_TUNING)) {
+ /* Device is not in tuning */
+ sdbus_read_data(&s->sdbus, s->fifo_buffer, blk_size);
}
if (FIELD_EX32(s->hostctl2, SDHC_HOSTCTL2, EXECUTE_TUNING)) {
/* Write data from host controller FIFO to card */
static void sdhci_write_block_to_card(SDHCIState *s)
{
- int index = 0;
-
if (s->prnsts & SDHC_SPACE_AVAILABLE) {
if (s->norintstsen & SDHC_NISEN_WBUFRDY) {
s->norintsts |= SDHC_NIS_WBUFRDY;
}
}
- for (index = 0; index < (s->blksize & BLOCK_SIZE_MASK); index++) {
- sdbus_write_data(&s->sdbus, s->fifo_buffer[index]);
- }
+ sdbus_write_data(&s->sdbus, s->fifo_buffer, s->blksize & BLOCK_SIZE_MASK);
/* Next data can be written through BUFFER DATORT register */
s->prnsts |= SDHC_SPACE_AVAILABLE;
static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s)
{
bool page_aligned = false;
- unsigned int n, begin;
+ unsigned int begin;
const uint16_t block_size = s->blksize & BLOCK_SIZE_MASK;
uint32_t boundary_chk = 1 << (((s->blksize & ~BLOCK_SIZE_MASK) >> 12) + 12);
uint32_t boundary_count = boundary_chk - (s->sdmasysad % boundary_chk);
page_aligned = true;
}
+ s->prnsts |= SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE;
if (s->trnmod & SDHC_TRNS_READ) {
- s->prnsts |= SDHC_DOING_READ | SDHC_DATA_INHIBIT |
- SDHC_DAT_LINE_ACTIVE;
+ s->prnsts |= SDHC_DOING_READ;
while (s->blkcnt) {
if (s->data_count == 0) {
- for (n = 0; n < block_size; n++) {
- s->fifo_buffer[n] = sdbus_read_data(&s->sdbus);
- }
+ sdbus_read_data(&s->sdbus, s->fifo_buffer, block_size);
}
begin = s->data_count;
if (((boundary_count + begin) < block_size) && page_aligned) {
s->blkcnt--;
}
}
- dma_memory_write(s->dma_as, s->sdmasysad,
- &s->fifo_buffer[begin], s->data_count - begin);
+ dma_memory_write(s->dma_as, s->sdmasysad, &s->fifo_buffer[begin],
+ s->data_count - begin, MEMTXATTRS_UNSPECIFIED);
s->sdmasysad += s->data_count - begin;
if (s->data_count == block_size) {
s->data_count = 0;
}
}
} else {
- s->prnsts |= SDHC_DOING_WRITE | SDHC_DATA_INHIBIT |
- SDHC_DAT_LINE_ACTIVE;
+ s->prnsts |= SDHC_DOING_WRITE;
while (s->blkcnt) {
begin = s->data_count;
if (((boundary_count + begin) < block_size) && page_aligned) {
s->data_count = block_size;
boundary_count -= block_size - begin;
}
- dma_memory_read(s->dma_as, s->sdmasysad,
- &s->fifo_buffer[begin], s->data_count - begin);
+ dma_memory_read(s->dma_as, s->sdmasysad, &s->fifo_buffer[begin],
+ s->data_count - begin, MEMTXATTRS_UNSPECIFIED);
s->sdmasysad += s->data_count - begin;
if (s->data_count == block_size) {
- for (n = 0; n < block_size; n++) {
- sdbus_write_data(&s->sdbus, s->fifo_buffer[n]);
- }
+ sdbus_write_data(&s->sdbus, s->fifo_buffer, block_size);
s->data_count = 0;
if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
s->blkcnt--;
/* single block SDMA transfer */
static void sdhci_sdma_transfer_single_block(SDHCIState *s)
{
- int n;
uint32_t datacnt = s->blksize & BLOCK_SIZE_MASK;
if (s->trnmod & SDHC_TRNS_READ) {
- for (n = 0; n < datacnt; n++) {
- s->fifo_buffer[n] = sdbus_read_data(&s->sdbus);
- }
- dma_memory_write(s->dma_as, s->sdmasysad, s->fifo_buffer, datacnt);
+ sdbus_read_data(&s->sdbus, s->fifo_buffer, datacnt);
+ dma_memory_write(s->dma_as, s->sdmasysad, s->fifo_buffer, datacnt,
+ MEMTXATTRS_UNSPECIFIED);
} else {
- dma_memory_read(s->dma_as, s->sdmasysad, s->fifo_buffer, datacnt);
- for (n = 0; n < datacnt; n++) {
- sdbus_write_data(&s->sdbus, s->fifo_buffer[n]);
- }
+ dma_memory_read(s->dma_as, s->sdmasysad, s->fifo_buffer, datacnt,
+ MEMTXATTRS_UNSPECIFIED);
+ sdbus_write_data(&s->sdbus, s->fifo_buffer, datacnt);
}
s->blkcnt--;
hwaddr entry_addr = (hwaddr)s->admasysaddr;
switch (SDHC_DMA_TYPE(s->hostctl1)) {
case SDHC_CTRL_ADMA2_32:
- dma_memory_read(s->dma_as, entry_addr, (uint8_t *)&adma2,
- sizeof(adma2));
+ dma_memory_read(s->dma_as, entry_addr, &adma2, sizeof(adma2),
+ MEMTXATTRS_UNSPECIFIED);
adma2 = le64_to_cpu(adma2);
/* The spec does not specify endianness of descriptor table.
* We currently assume that it is LE.
dscr->incr = 8;
break;
case SDHC_CTRL_ADMA1_32:
- dma_memory_read(s->dma_as, entry_addr, (uint8_t *)&adma1,
- sizeof(adma1));
+ dma_memory_read(s->dma_as, entry_addr, &adma1, sizeof(adma1),
+ MEMTXATTRS_UNSPECIFIED);
adma1 = le32_to_cpu(adma1);
dscr->addr = (hwaddr)(adma1 & 0xFFFFF000);
dscr->attr = (uint8_t)extract32(adma1, 0, 7);
}
break;
case SDHC_CTRL_ADMA2_64:
- dma_memory_read(s->dma_as, entry_addr,
- (uint8_t *)(&dscr->attr), 1);
- dma_memory_read(s->dma_as, entry_addr + 2,
- (uint8_t *)(&dscr->length), 2);
+ dma_memory_read(s->dma_as, entry_addr, &dscr->attr, 1,
+ MEMTXATTRS_UNSPECIFIED);
+ dma_memory_read(s->dma_as, entry_addr + 2, &dscr->length, 2,
+ MEMTXATTRS_UNSPECIFIED);
dscr->length = le16_to_cpu(dscr->length);
- dma_memory_read(s->dma_as, entry_addr + 4,
- (uint8_t *)(&dscr->addr), 8);
+ dma_memory_read(s->dma_as, entry_addr + 4, &dscr->addr, 8,
+ MEMTXATTRS_UNSPECIFIED);
dscr->addr = le64_to_cpu(dscr->addr);
dscr->attr &= (uint8_t) ~0xC0;
dscr->incr = 12;
static void sdhci_do_adma(SDHCIState *s)
{
- unsigned int n, begin, length;
+ unsigned int begin, length;
const uint16_t block_size = s->blksize & BLOCK_SIZE_MASK;
+ const MemTxAttrs attrs = { .memory = true };
ADMADescr dscr = {};
+ MemTxResult res;
int i;
+ if (s->trnmod & SDHC_TRNS_BLK_CNT_EN && !s->blkcnt) {
+ /* Stop Multiple Transfer */
+ sdhci_end_transfer(s);
+ return;
+ }
+
for (i = 0; i < SDHC_ADMA_DESCS_PER_DELAY; ++i) {
s->admaerr &= ~SDHC_ADMAERR_LENGTH_MISMATCH;
switch (dscr.attr & SDHC_ADMA_ATTR_ACT_MASK) {
case SDHC_ADMA_ATTR_ACT_TRAN: /* data transfer */
-
+ s->prnsts |= SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE;
if (s->trnmod & SDHC_TRNS_READ) {
+ s->prnsts |= SDHC_DOING_READ;
while (length) {
if (s->data_count == 0) {
- for (n = 0; n < block_size; n++) {
- s->fifo_buffer[n] = sdbus_read_data(&s->sdbus);
- }
+ sdbus_read_data(&s->sdbus, s->fifo_buffer, block_size);
}
begin = s->data_count;
if ((length + begin) < block_size) {
s->data_count = block_size;
length -= block_size - begin;
}
- dma_memory_write(s->dma_as, dscr.addr,
- &s->fifo_buffer[begin],
- s->data_count - begin);
+ res = dma_memory_write(s->dma_as, dscr.addr,
+ &s->fifo_buffer[begin],
+ s->data_count - begin,
+ attrs);
+ if (res != MEMTX_OK) {
+ break;
+ }
dscr.addr += s->data_count - begin;
if (s->data_count == block_size) {
s->data_count = 0;
}
}
} else {
+ s->prnsts |= SDHC_DOING_WRITE;
while (length) {
begin = s->data_count;
if ((length + begin) < block_size) {
s->data_count = block_size;
length -= block_size - begin;
}
- dma_memory_read(s->dma_as, dscr.addr,
- &s->fifo_buffer[begin],
- s->data_count - begin);
+ res = dma_memory_read(s->dma_as, dscr.addr,
+ &s->fifo_buffer[begin],
+ s->data_count - begin,
+ attrs);
+ if (res != MEMTX_OK) {
+ break;
+ }
dscr.addr += s->data_count - begin;
if (s->data_count == block_size) {
- for (n = 0; n < block_size; n++) {
- sdbus_write_data(&s->sdbus, s->fifo_buffer[n]);
- }
+ sdbus_write_data(&s->sdbus, s->fifo_buffer, block_size);
s->data_count = 0;
if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
s->blkcnt--;
}
}
}
- s->admasysaddr += dscr.incr;
+ if (res != MEMTX_OK) {
+ if (s->errintstsen & SDHC_EISEN_ADMAERR) {
+ trace_sdhci_error("Set ADMA error flag");
+ s->errintsts |= SDHC_EIS_ADMAERR;
+ s->norintsts |= SDHC_NIS_ERR;
+ }
+ sdhci_update_irq(s);
+ } else {
+ s->admasysaddr += dscr.incr;
+ }
break;
case SDHC_ADMA_ATTR_ACT_LINK: /* link to next descriptor table */
s->admasysaddr = dscr.addr;
s->norintsts |= SDHC_NIS_DMA;
}
- sdhci_update_irq(s);
+ if (sdhci_update_irq(s) && !(dscr.attr & SDHC_ADMA_ATTR_END)) {
+ /* IRQ delivered, reschedule current transfer */
+ break;
+ }
}
/* ADMA transfer terminates if blkcnt == 0 or by END attribute */
return true;
}
+static void sdhci_resume_pending_transfer(SDHCIState *s)
+{
+ timer_del(s->transfer_timer);
+ sdhci_data_transfer(s);
+}
+
static uint64_t sdhci_read(void *opaque, hwaddr offset, unsigned size)
{
SDHCIState *s = (SDHCIState *)opaque;
uint32_t ret = 0;
+ if (timer_pending(s->transfer_timer)) {
+ sdhci_resume_pending_transfer(s);
+ }
+
switch (offset & ~0x3) {
case SDHC_SYSAD:
ret = s->sdmasysad;
uint32_t value = val;
value <<= shift;
+ if (timer_pending(s->transfer_timer)) {
+ sdhci_resume_pending_transfer(s);
+ }
+
switch (offset & ~0x3) {
case SDHC_SYSAD:
- s->sdmasysad = (s->sdmasysad & mask) | value;
- MASKED_WRITE(s->sdmasysad, mask, value);
- /* Writing to last byte of sdmasysad might trigger transfer */
- if (!(mask & 0xFF000000) && TRANSFERRING_DATA(s->prnsts) && s->blkcnt &&
- s->blksize && SDHC_DMA_TYPE(s->hostctl1) == SDHC_CTRL_SDMA) {
- if (s->trnmod & SDHC_TRNS_MULTI) {
- sdhci_sdma_transfer_multi_blocks(s);
- } else {
- sdhci_sdma_transfer_single_block(s);
+ if (!TRANSFERRING_DATA(s->prnsts)) {
+ s->sdmasysad = (s->sdmasysad & mask) | value;
+ MASKED_WRITE(s->sdmasysad, mask, value);
+ /* Writing to last byte of sdmasysad might trigger transfer */
+ if (!(mask & 0xFF000000) && s->blkcnt && s->blksize &&
+ SDHC_DMA_TYPE(s->hostctl1) == SDHC_CTRL_SDMA) {
+ if (s->trnmod & SDHC_TRNS_MULTI) {
+ sdhci_sdma_transfer_multi_blocks(s);
+ } else {
+ sdhci_sdma_transfer_single_block(s);
+ }
}
}
break;
case SDHC_BLKSIZE:
if (!TRANSFERRING_DATA(s->prnsts)) {
- MASKED_WRITE(s->blksize, mask, value);
+ uint16_t blksize = s->blksize;
+
+ MASKED_WRITE(s->blksize, mask, extract32(value, 0, 12));
MASKED_WRITE(s->blkcnt, mask >> 16, value >> 16);
- }
- /* Limit block size to the maximum buffer size */
- if (extract32(s->blksize, 0, 12) > s->buf_maxsz) {
- qemu_log_mask(LOG_GUEST_ERROR, "%s: Size 0x%x is larger than " \
- "the maximum buffer 0x%x", __func__, s->blksize,
- s->buf_maxsz);
+ /* Limit block size to the maximum buffer size */
+ if (extract32(s->blksize, 0, 12) > s->buf_maxsz) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Size 0x%x is larger than "
+ "the maximum buffer 0x%x\n", __func__, s->blksize,
+ s->buf_maxsz);
+
+ s->blksize = deposit32(s->blksize, 0, 12, s->buf_maxsz);
+ }
- s->blksize = deposit32(s->blksize, 0, 12, s->buf_maxsz);
+ /*
+ * If the block size is programmed to a different value from
+ * the previous one, reset the data pointer of s->fifo_buffer[]
+ * so that s->fifo_buffer[] can be filled in using the new block
+ * size in the next transfer.
+ */
+ if (blksize != s->blksize) {
+ s->data_count = 0;
+ }
}
break;
static void sdhci_init_readonly_registers(SDHCIState *s, Error **errp)
{
- Error *local_err = NULL;
+ ERRP_GUARD();
switch (s->sd_spec_version) {
case 2 ... 3:
}
s->version = (SDHC_HCVER_VENDOR << 8) | (s->sd_spec_version - 1);
- sdhci_check_capareg(s, &local_err);
- if (local_err) {
- error_propagate(errp, local_err);
+ sdhci_check_capareg(s, errp);
+ if (*errp) {
return;
}
}
void sdhci_initfn(SDHCIState *s)
{
- qbus_create_inplace(&s->sdbus, sizeof(s->sdbus),
- TYPE_SDHCI_BUS, DEVICE(s), "sd-bus");
+ qbus_init(&s->sdbus, sizeof(s->sdbus), TYPE_SDHCI_BUS, DEVICE(s), "sd-bus");
s->insert_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, sdhci_raise_insertion_irq, s);
s->transfer_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, sdhci_data_transfer, s);
void sdhci_uninitfn(SDHCIState *s)
{
- timer_del(s->insert_timer);
timer_free(s->insert_timer);
- timer_del(s->transfer_timer);
timer_free(s->transfer_timer);
g_free(s->fifo_buffer);
void sdhci_common_realize(SDHCIState *s, Error **errp)
{
- Error *local_err = NULL;
+ ERRP_GUARD();
- sdhci_init_readonly_registers(s, &local_err);
- if (local_err) {
- error_propagate(errp, local_err);
+ sdhci_init_readonly_registers(s, errp);
+ if (*errp) {
return;
}
s->buf_maxsz = sdhci_get_fifolen(s);
SDHC_REGISTERS_MAP_SIZE);
}
-void sdhci_common_unrealize(SDHCIState *s, Error **errp)
+void sdhci_common_unrealize(SDHCIState *s)
{
/* This function is expected to be called only once for each class:
* - SysBus: via DeviceClass->unrealize(),
static void sdhci_sysbus_realize(DeviceState *dev, Error **errp)
{
+ ERRP_GUARD();
SDHCIState *s = SYSBUS_SDHCI(dev);
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
- Error *local_err = NULL;
- sdhci_common_realize(s, &local_err);
- if (local_err) {
- error_propagate(errp, local_err);
+ sdhci_common_realize(s, errp);
+ if (*errp) {
return;
}
sysbus_init_mmio(sbd, &s->iomem);
}
-static void sdhci_sysbus_unrealize(DeviceState *dev, Error **errp)
+static void sdhci_sysbus_unrealize(DeviceState *dev)
{
SDHCIState *s = SYSBUS_SDHCI(dev);
- sdhci_common_unrealize(s, &error_abort);
+ sdhci_common_unrealize(s);
if (s->dma_mr) {
address_space_destroy(s->dma_as);
{
DeviceClass *dc = DEVICE_CLASS(klass);
- dc->props = sdhci_sysbus_properties;
+ device_class_set_props(dc, sdhci_sysbus_properties);
dc->realize = sdhci_sysbus_realize;
dc->unrealize = sdhci_sysbus_unrealize;
}
break;
+ case ESDHC_VENDOR_SPEC:
+ ret = s->vendor_spec;
+ break;
case ESDHC_DLL_CTRL:
case ESDHC_TUNE_CTRL_STATUS:
case ESDHC_UNDOCUMENTED_REG27:
case ESDHC_TUNING_CTRL:
- case ESDHC_VENDOR_SPEC:
case ESDHC_MIX_CTRL:
case ESDHC_WTMK_LVL:
ret = 0;
case ESDHC_UNDOCUMENTED_REG27:
case ESDHC_TUNING_CTRL:
case ESDHC_WTMK_LVL:
+ break;
+
case ESDHC_VENDOR_SPEC:
+ s->vendor_spec = value;
+ switch (s->vendor) {
+ case SDHCI_VENDOR_IMX:
+ if (value & ESDHC_IMX_FRC_SDCLK_ON) {
+ s->prnsts &= ~SDHC_IMX_CLOCK_GATE_OFF;
+ } else {
+ s->prnsts |= SDHC_IMX_CLOCK_GATE_OFF;
+ }
+ break;
+ default:
+ break;
+ }
break;
case SDHC_HOSTCTL: