diff options
author | Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com> | 2023-10-10 11:40:56 +0000 |
---|---|---|
committer | Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com> | 2023-10-10 11:40:56 +0000 |
commit | e02cda008591317b1625707ff8e115a4841aa889 (patch) | |
tree | aee302e3cf8b59ec2d32ec481be3d1afddfc8968 /hw/sd | |
parent | cc668e6b7e0ffd8c9d130513d12053cf5eda1d3b (diff) |
Introduce Virtio-loopback epsilon release:
Epsilon release introduces a new compatibility layer which make virtio-loopback
design to work with QEMU and rust-vmm vhost-user backend without require any
changes.
Signed-off-by: Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>
Change-Id: I52e57563e08a7d0bdc002f8e928ee61ba0c53dd9
Diffstat (limited to 'hw/sd')
-rw-r--r-- | hw/sd/Kconfig | 25 | ||||
-rw-r--r-- | hw/sd/allwinner-sdhost.c | 873 | ||||
-rw-r--r-- | hw/sd/aspeed_sdhci.c | 213 | ||||
-rw-r--r-- | hw/sd/bcm2835_sdhost.c | 459 | ||||
-rw-r--r-- | hw/sd/cadence_sdhci.c | 191 | ||||
-rw-r--r-- | hw/sd/core.c | 274 | ||||
-rw-r--r-- | hw/sd/meson.build | 13 | ||||
-rw-r--r-- | hw/sd/npcm7xx_sdhci.c | 182 | ||||
-rw-r--r-- | hw/sd/omap_mmc.c | 665 | ||||
-rw-r--r-- | hw/sd/pl181.c | 551 | ||||
-rw-r--r-- | hw/sd/pxa2xx_mmci.c | 604 | ||||
-rw-r--r-- | hw/sd/sd.c | 2227 | ||||
-rw-r--r-- | hw/sd/sdhci-internal.h | 346 | ||||
-rw-r--r-- | hw/sd/sdhci-pci.c | 87 | ||||
-rw-r--r-- | hw/sd/sdhci.c | 1869 | ||||
-rw-r--r-- | hw/sd/sdmmc-internal.c | 72 | ||||
-rw-r--r-- | hw/sd/sdmmc-internal.h | 40 | ||||
-rw-r--r-- | hw/sd/ssi-sd.c | 445 | ||||
-rw-r--r-- | hw/sd/trace-events | 74 | ||||
-rw-r--r-- | hw/sd/trace.h | 1 |
20 files changed, 9211 insertions, 0 deletions
diff --git a/hw/sd/Kconfig b/hw/sd/Kconfig new file mode 100644 index 000000000..633b9afec --- /dev/null +++ b/hw/sd/Kconfig @@ -0,0 +1,25 @@ +config PL181 + bool + select SD + +config SSI_SD + bool + depends on SSI + select SD + +config SD + bool + +config SDHCI + bool + select SD + +config SDHCI_PCI + bool + default y if PCI_DEVICES + depends on PCI + select SDHCI + +config CADENCE_SDHCI + bool + select SDHCI diff --git a/hw/sd/allwinner-sdhost.c b/hw/sd/allwinner-sdhost.c new file mode 100644 index 000000000..9166d6638 --- /dev/null +++ b/hw/sd/allwinner-sdhost.c @@ -0,0 +1,873 @@ +/* + * Allwinner (sun4i and above) SD Host Controller emulation + * + * Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/units.h" +#include "qapi/error.h" +#include "sysemu/blockdev.h" +#include "sysemu/dma.h" +#include "hw/qdev-properties.h" +#include "hw/irq.h" +#include "hw/sd/allwinner-sdhost.h" +#include "migration/vmstate.h" +#include "trace.h" +#include "qom/object.h" + +#define TYPE_AW_SDHOST_BUS "allwinner-sdhost-bus" +/* This is reusing the SDBus typedef from SD_BUS */ +DECLARE_INSTANCE_CHECKER(SDBus, AW_SDHOST_BUS, + TYPE_AW_SDHOST_BUS) + +/* SD Host register offsets */ +enum { + REG_SD_GCTL = 0x00, /* Global Control */ + REG_SD_CKCR = 0x04, /* Clock Control */ + REG_SD_TMOR = 0x08, /* Timeout */ + REG_SD_BWDR = 0x0C, /* Bus Width */ + REG_SD_BKSR = 0x10, /* Block Size */ + REG_SD_BYCR = 0x14, /* Byte Count */ + REG_SD_CMDR = 0x18, /* Command */ + REG_SD_CAGR = 0x1C, /* Command Argument */ + REG_SD_RESP0 = 0x20, /* Response Zero */ + REG_SD_RESP1 = 0x24, /* Response One */ + REG_SD_RESP2 = 0x28, /* Response Two */ + REG_SD_RESP3 = 0x2C, /* Response Three */ + REG_SD_IMKR = 0x30, /* Interrupt Mask */ + REG_SD_MISR = 0x34, /* Masked Interrupt Status */ + REG_SD_RISR = 0x38, /* Raw Interrupt Status */ + REG_SD_STAR = 0x3C, /* Status */ + REG_SD_FWLR = 0x40, /* FIFO Water Level */ + REG_SD_FUNS = 0x44, /* FIFO Function Select */ + REG_SD_DBGC = 0x50, /* Debug Enable */ + REG_SD_A12A = 0x58, /* Auto command 12 argument */ + REG_SD_NTSR = 0x5C, /* SD NewTiming Set */ + REG_SD_SDBG = 0x60, /* SD newTiming Set Debug */ + REG_SD_HWRST = 0x78, /* Hardware Reset Register */ + REG_SD_DMAC = 0x80, /* Internal DMA Controller Control */ + REG_SD_DLBA = 0x84, /* Descriptor List Base Address */ + REG_SD_IDST = 0x88, /* Internal DMA Controller Status */ + REG_SD_IDIE = 0x8C, /* Internal DMA Controller IRQ Enable */ + REG_SD_THLDC = 0x100, /* Card Threshold Control */ + REG_SD_DSBD = 0x10C, /* eMMC DDR Start Bit Detection Control */ + REG_SD_RES_CRC = 0x110, /* Response CRC from card/eMMC */ + REG_SD_DATA7_CRC = 0x114, /* CRC Data 7 from card/eMMC */ + REG_SD_DATA6_CRC = 0x118, /* CRC Data 6 from card/eMMC */ + REG_SD_DATA5_CRC = 0x11C, /* CRC Data 5 from card/eMMC */ + REG_SD_DATA4_CRC = 0x120, /* CRC Data 4 from card/eMMC */ + REG_SD_DATA3_CRC = 0x124, /* CRC Data 3 from card/eMMC */ + REG_SD_DATA2_CRC = 0x128, /* CRC Data 2 from card/eMMC */ + REG_SD_DATA1_CRC = 0x12C, /* CRC Data 1 from card/eMMC */ + REG_SD_DATA0_CRC = 0x130, /* CRC Data 0 from card/eMMC */ + REG_SD_CRC_STA = 0x134, /* CRC status from card/eMMC during write */ + REG_SD_FIFO = 0x200, /* Read/Write FIFO */ +}; + +/* SD Host register flags */ +enum { + SD_GCTL_FIFO_AC_MOD = (1 << 31), + SD_GCTL_DDR_MOD_SEL = (1 << 10), + SD_GCTL_CD_DBC_ENB = (1 << 8), + SD_GCTL_DMA_ENB = (1 << 5), + SD_GCTL_INT_ENB = (1 << 4), + SD_GCTL_DMA_RST = (1 << 2), + SD_GCTL_FIFO_RST = (1 << 1), + SD_GCTL_SOFT_RST = (1 << 0), +}; + +enum { + SD_CMDR_LOAD = (1 << 31), + SD_CMDR_CLKCHANGE = (1 << 21), + SD_CMDR_WRITE = (1 << 10), + SD_CMDR_AUTOSTOP = (1 << 12), + SD_CMDR_DATA = (1 << 9), + SD_CMDR_RESPONSE_LONG = (1 << 7), + SD_CMDR_RESPONSE = (1 << 6), + SD_CMDR_CMDID_MASK = (0x3f), +}; + +enum { + SD_RISR_CARD_REMOVE = (1 << 31), + SD_RISR_CARD_INSERT = (1 << 30), + SD_RISR_SDIO_INTR = (1 << 16), + SD_RISR_AUTOCMD_DONE = (1 << 14), + SD_RISR_DATA_COMPLETE = (1 << 3), + SD_RISR_CMD_COMPLETE = (1 << 2), + SD_RISR_NO_RESPONSE = (1 << 1), +}; + +enum { + SD_STAR_CARD_PRESENT = (1 << 8), +}; + +enum { + SD_IDST_INT_SUMMARY = (1 << 8), + SD_IDST_RECEIVE_IRQ = (1 << 1), + SD_IDST_TRANSMIT_IRQ = (1 << 0), + SD_IDST_IRQ_MASK = (1 << 1) | (1 << 0) | (1 << 8), + SD_IDST_WR_MASK = (0x3ff), +}; + +/* SD Host register reset values */ +enum { + REG_SD_GCTL_RST = 0x00000300, + REG_SD_CKCR_RST = 0x0, + REG_SD_TMOR_RST = 0xFFFFFF40, + REG_SD_BWDR_RST = 0x0, + REG_SD_BKSR_RST = 0x00000200, + REG_SD_BYCR_RST = 0x00000200, + REG_SD_CMDR_RST = 0x0, + REG_SD_CAGR_RST = 0x0, + REG_SD_RESP_RST = 0x0, + REG_SD_IMKR_RST = 0x0, + REG_SD_MISR_RST = 0x0, + REG_SD_RISR_RST = 0x0, + REG_SD_STAR_RST = 0x00000100, + REG_SD_FWLR_RST = 0x000F0000, + REG_SD_FUNS_RST = 0x0, + REG_SD_DBGC_RST = 0x0, + REG_SD_A12A_RST = 0x0000FFFF, + REG_SD_NTSR_RST = 0x00000001, + REG_SD_SDBG_RST = 0x0, + REG_SD_HWRST_RST = 0x00000001, + REG_SD_DMAC_RST = 0x0, + REG_SD_DLBA_RST = 0x0, + REG_SD_IDST_RST = 0x0, + REG_SD_IDIE_RST = 0x0, + REG_SD_THLDC_RST = 0x0, + REG_SD_DSBD_RST = 0x0, + REG_SD_RES_CRC_RST = 0x0, + REG_SD_DATA_CRC_RST = 0x0, + REG_SD_CRC_STA_RST = 0x0, + REG_SD_FIFO_RST = 0x0, +}; + +/* Data transfer descriptor for DMA */ +typedef struct TransferDescriptor { + uint32_t status; /* Status flags */ + uint32_t size; /* Data buffer size */ + uint32_t addr; /* Data buffer address */ + uint32_t next; /* Physical address of next descriptor */ +} TransferDescriptor; + +/* Data transfer descriptor flags */ +enum { + DESC_STATUS_HOLD = (1 << 31), /* Set when descriptor is in use by DMA */ + DESC_STATUS_ERROR = (1 << 30), /* Set when DMA transfer error occurred */ + DESC_STATUS_CHAIN = (1 << 4), /* Indicates chained descriptor. */ + DESC_STATUS_FIRST = (1 << 3), /* Set on the first descriptor */ + DESC_STATUS_LAST = (1 << 2), /* Set on the last descriptor */ + DESC_STATUS_NOIRQ = (1 << 1), /* Skip raising interrupt after transfer */ + DESC_SIZE_MASK = (0xfffffffc) +}; + +static void allwinner_sdhost_update_irq(AwSdHostState *s) +{ + uint32_t irq; + + if (s->global_ctl & SD_GCTL_INT_ENB) { + irq = s->irq_status & s->irq_mask; + } else { + irq = 0; + } + + trace_allwinner_sdhost_update_irq(irq); + qemu_set_irq(s->irq, irq); +} + +static void allwinner_sdhost_update_transfer_cnt(AwSdHostState *s, + uint32_t bytes) +{ + if (s->transfer_cnt > bytes) { + s->transfer_cnt -= bytes; + } else { + s->transfer_cnt = 0; + } + + if (!s->transfer_cnt) { + s->irq_status |= SD_RISR_DATA_COMPLETE; + } +} + +static void allwinner_sdhost_set_inserted(DeviceState *dev, bool inserted) +{ + AwSdHostState *s = AW_SDHOST(dev); + + trace_allwinner_sdhost_set_inserted(inserted); + + if (inserted) { + s->irq_status |= SD_RISR_CARD_INSERT; + s->irq_status &= ~SD_RISR_CARD_REMOVE; + s->status |= SD_STAR_CARD_PRESENT; + } else { + s->irq_status &= ~SD_RISR_CARD_INSERT; + s->irq_status |= SD_RISR_CARD_REMOVE; + s->status &= ~SD_STAR_CARD_PRESENT; + } + + allwinner_sdhost_update_irq(s); +} + +static void allwinner_sdhost_send_command(AwSdHostState *s) +{ + SDRequest request; + uint8_t resp[16]; + int rlen; + + /* Auto clear load flag */ + s->command &= ~SD_CMDR_LOAD; + + /* Clock change does not actually interact with the SD bus */ + if (!(s->command & SD_CMDR_CLKCHANGE)) { + + /* Prepare request */ + request.cmd = s->command & SD_CMDR_CMDID_MASK; + request.arg = s->command_arg; + + /* Send request to SD bus */ + rlen = sdbus_do_command(&s->sdbus, &request, resp); + if (rlen < 0) { + goto error; + } + + /* If the command has a response, store it in the response registers */ + if ((s->command & SD_CMDR_RESPONSE)) { + if (rlen == 4 && !(s->command & SD_CMDR_RESPONSE_LONG)) { + s->response[0] = ldl_be_p(&resp[0]); + s->response[1] = s->response[2] = s->response[3] = 0; + + } else if (rlen == 16 && (s->command & SD_CMDR_RESPONSE_LONG)) { + s->response[0] = ldl_be_p(&resp[12]); + s->response[1] = ldl_be_p(&resp[8]); + s->response[2] = ldl_be_p(&resp[4]); + s->response[3] = ldl_be_p(&resp[0]); + } else { + goto error; + } + } + } + + /* Set interrupt status bits */ + s->irq_status |= SD_RISR_CMD_COMPLETE; + return; + +error: + s->irq_status |= SD_RISR_NO_RESPONSE; +} + +static void allwinner_sdhost_auto_stop(AwSdHostState *s) +{ + /* + * The stop command (CMD12) ensures the SD bus + * returns to the transfer state. + */ + if ((s->command & SD_CMDR_AUTOSTOP) && (s->transfer_cnt == 0)) { + /* First save current command registers */ + uint32_t saved_cmd = s->command; + uint32_t saved_arg = s->command_arg; + + /* Prepare stop command (CMD12) */ + s->command &= ~SD_CMDR_CMDID_MASK; + s->command |= 12; /* CMD12 */ + s->command_arg = 0; + + /* Put the command on SD bus */ + allwinner_sdhost_send_command(s); + + /* Restore command values */ + s->command = saved_cmd; + s->command_arg = saved_arg; + + /* Set IRQ status bit for automatic stop done */ + s->irq_status |= SD_RISR_AUTOCMD_DONE; + } +} + +static uint32_t allwinner_sdhost_process_desc(AwSdHostState *s, + hwaddr desc_addr, + TransferDescriptor *desc, + bool is_write, uint32_t max_bytes) +{ + AwSdHostClass *klass = AW_SDHOST_GET_CLASS(s); + uint32_t num_done = 0; + uint32_t num_bytes = max_bytes; + uint8_t buf[1024]; + + /* Read descriptor */ + dma_memory_read(&s->dma_as, desc_addr, desc, sizeof(*desc)); + if (desc->size == 0) { + desc->size = klass->max_desc_size; + } else if (desc->size > klass->max_desc_size) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: DMA descriptor buffer size " + " is out-of-bounds: %" PRIu32 " > %zu", + __func__, desc->size, klass->max_desc_size); + desc->size = klass->max_desc_size; + } + if (desc->size < num_bytes) { + num_bytes = desc->size; + } + + trace_allwinner_sdhost_process_desc(desc_addr, desc->size, + is_write, max_bytes); + + while (num_done < num_bytes) { + /* Try to completely fill the local buffer */ + uint32_t buf_bytes = num_bytes - num_done; + if (buf_bytes > sizeof(buf)) { + buf_bytes = sizeof(buf); + } + + /* Write to SD bus */ + if (is_write) { + dma_memory_read(&s->dma_as, + (desc->addr & DESC_SIZE_MASK) + num_done, + buf, buf_bytes); + sdbus_write_data(&s->sdbus, buf, buf_bytes); + + /* Read from SD bus */ + } else { + sdbus_read_data(&s->sdbus, buf, buf_bytes); + dma_memory_write(&s->dma_as, + (desc->addr & DESC_SIZE_MASK) + num_done, + buf, buf_bytes); + } + num_done += buf_bytes; + } + + /* Clear hold flag and flush descriptor */ + desc->status &= ~DESC_STATUS_HOLD; + dma_memory_write(&s->dma_as, desc_addr, desc, sizeof(*desc)); + + return num_done; +} + +static void allwinner_sdhost_dma(AwSdHostState *s) +{ + TransferDescriptor desc; + hwaddr desc_addr = s->desc_base; + bool is_write = (s->command & SD_CMDR_WRITE); + uint32_t bytes_done = 0; + + /* Check if DMA can be performed */ + if (s->byte_count == 0 || s->block_size == 0 || + !(s->global_ctl & SD_GCTL_DMA_ENB)) { + return; + } + + /* + * For read operations, data must be available on the SD bus + * If not, it is an error and we should not act at all + */ + if (!is_write && !sdbus_data_ready(&s->sdbus)) { + return; + } + + /* Process the DMA descriptors until all data is copied */ + while (s->byte_count > 0) { + bytes_done = allwinner_sdhost_process_desc(s, desc_addr, &desc, + is_write, s->byte_count); + allwinner_sdhost_update_transfer_cnt(s, bytes_done); + + if (bytes_done <= s->byte_count) { + s->byte_count -= bytes_done; + } else { + s->byte_count = 0; + } + + if (desc.status & DESC_STATUS_LAST) { + break; + } else { + desc_addr = desc.next; + } + } + + /* Raise IRQ to signal DMA is completed */ + s->irq_status |= SD_RISR_DATA_COMPLETE | SD_RISR_SDIO_INTR; + + /* Update DMAC bits */ + s->dmac_status |= SD_IDST_INT_SUMMARY; + + if (is_write) { + s->dmac_status |= SD_IDST_TRANSMIT_IRQ; + } else { + s->dmac_status |= SD_IDST_RECEIVE_IRQ; + } +} + +static uint64_t allwinner_sdhost_read(void *opaque, hwaddr offset, + unsigned size) +{ + AwSdHostState *s = AW_SDHOST(opaque); + uint32_t res = 0; + + switch (offset) { + case REG_SD_GCTL: /* Global Control */ + res = s->global_ctl; + break; + case REG_SD_CKCR: /* Clock Control */ + res = s->clock_ctl; + break; + case REG_SD_TMOR: /* Timeout */ + res = s->timeout; + break; + case REG_SD_BWDR: /* Bus Width */ + res = s->bus_width; + break; + case REG_SD_BKSR: /* Block Size */ + res = s->block_size; + break; + case REG_SD_BYCR: /* Byte Count */ + res = s->byte_count; + break; + case REG_SD_CMDR: /* Command */ + res = s->command; + break; + case REG_SD_CAGR: /* Command Argument */ + res = s->command_arg; + break; + case REG_SD_RESP0: /* Response Zero */ + res = s->response[0]; + break; + case REG_SD_RESP1: /* Response One */ + res = s->response[1]; + break; + case REG_SD_RESP2: /* Response Two */ + res = s->response[2]; + break; + case REG_SD_RESP3: /* Response Three */ + res = s->response[3]; + break; + case REG_SD_IMKR: /* Interrupt Mask */ + res = s->irq_mask; + break; + case REG_SD_MISR: /* Masked Interrupt Status */ + res = s->irq_status & s->irq_mask; + break; + case REG_SD_RISR: /* Raw Interrupt Status */ + res = s->irq_status; + break; + case REG_SD_STAR: /* Status */ + res = s->status; + break; + case REG_SD_FWLR: /* FIFO Water Level */ + res = s->fifo_wlevel; + break; + case REG_SD_FUNS: /* FIFO Function Select */ + res = s->fifo_func_sel; + break; + case REG_SD_DBGC: /* Debug Enable */ + res = s->debug_enable; + break; + case REG_SD_A12A: /* Auto command 12 argument */ + res = s->auto12_arg; + break; + case REG_SD_NTSR: /* SD NewTiming Set */ + res = s->newtiming_set; + break; + case REG_SD_SDBG: /* SD newTiming Set Debug */ + res = s->newtiming_debug; + break; + case REG_SD_HWRST: /* Hardware Reset Register */ + res = s->hardware_rst; + break; + case REG_SD_DMAC: /* Internal DMA Controller Control */ + res = s->dmac; + break; + case REG_SD_DLBA: /* Descriptor List Base Address */ + res = s->desc_base; + break; + case REG_SD_IDST: /* Internal DMA Controller Status */ + res = s->dmac_status; + break; + case REG_SD_IDIE: /* Internal DMA Controller Interrupt Enable */ + res = s->dmac_irq; + break; + case REG_SD_THLDC: /* Card Threshold Control */ + res = s->card_threshold; + break; + case REG_SD_DSBD: /* eMMC DDR Start Bit Detection Control */ + res = s->startbit_detect; + break; + case REG_SD_RES_CRC: /* Response CRC from card/eMMC */ + res = s->response_crc; + break; + case REG_SD_DATA7_CRC: /* CRC Data 7 from card/eMMC */ + case REG_SD_DATA6_CRC: /* CRC Data 6 from card/eMMC */ + case REG_SD_DATA5_CRC: /* CRC Data 5 from card/eMMC */ + case REG_SD_DATA4_CRC: /* CRC Data 4 from card/eMMC */ + case REG_SD_DATA3_CRC: /* CRC Data 3 from card/eMMC */ + case REG_SD_DATA2_CRC: /* CRC Data 2 from card/eMMC */ + case REG_SD_DATA1_CRC: /* CRC Data 1 from card/eMMC */ + case REG_SD_DATA0_CRC: /* CRC Data 0 from card/eMMC */ + res = s->data_crc[((offset - REG_SD_DATA7_CRC) / sizeof(uint32_t))]; + break; + case REG_SD_CRC_STA: /* CRC status from card/eMMC in write operation */ + res = s->status_crc; + break; + case REG_SD_FIFO: /* Read/Write FIFO */ + if (sdbus_data_ready(&s->sdbus)) { + sdbus_read_data(&s->sdbus, &res, sizeof(uint32_t)); + le32_to_cpus(&res); + allwinner_sdhost_update_transfer_cnt(s, sizeof(uint32_t)); + allwinner_sdhost_auto_stop(s); + allwinner_sdhost_update_irq(s); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "%s: no data ready on SD bus\n", + __func__); + } + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset %" + HWADDR_PRIx"\n", __func__, offset); + res = 0; + break; + } + + trace_allwinner_sdhost_read(offset, res, size); + return res; +} + +static void allwinner_sdhost_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + AwSdHostState *s = AW_SDHOST(opaque); + uint32_t u32; + + trace_allwinner_sdhost_write(offset, value, size); + + switch (offset) { + case REG_SD_GCTL: /* Global Control */ + s->global_ctl = value; + s->global_ctl &= ~(SD_GCTL_DMA_RST | SD_GCTL_FIFO_RST | + SD_GCTL_SOFT_RST); + allwinner_sdhost_update_irq(s); + break; + case REG_SD_CKCR: /* Clock Control */ + s->clock_ctl = value; + break; + case REG_SD_TMOR: /* Timeout */ + s->timeout = value; + break; + case REG_SD_BWDR: /* Bus Width */ + s->bus_width = value; + break; + case REG_SD_BKSR: /* Block Size */ + s->block_size = value; + break; + case REG_SD_BYCR: /* Byte Count */ + s->byte_count = value; + s->transfer_cnt = value; + break; + case REG_SD_CMDR: /* Command */ + s->command = value; + if (value & SD_CMDR_LOAD) { + allwinner_sdhost_send_command(s); + allwinner_sdhost_dma(s); + allwinner_sdhost_auto_stop(s); + } + allwinner_sdhost_update_irq(s); + break; + case REG_SD_CAGR: /* Command Argument */ + s->command_arg = value; + break; + case REG_SD_RESP0: /* Response Zero */ + s->response[0] = value; + break; + case REG_SD_RESP1: /* Response One */ + s->response[1] = value; + break; + case REG_SD_RESP2: /* Response Two */ + s->response[2] = value; + break; + case REG_SD_RESP3: /* Response Three */ + s->response[3] = value; + break; + case REG_SD_IMKR: /* Interrupt Mask */ + s->irq_mask = value; + allwinner_sdhost_update_irq(s); + break; + case REG_SD_MISR: /* Masked Interrupt Status */ + case REG_SD_RISR: /* Raw Interrupt Status */ + s->irq_status &= ~value; + allwinner_sdhost_update_irq(s); + break; + case REG_SD_STAR: /* Status */ + s->status &= ~value; + allwinner_sdhost_update_irq(s); + break; + case REG_SD_FWLR: /* FIFO Water Level */ + s->fifo_wlevel = value; + break; + case REG_SD_FUNS: /* FIFO Function Select */ + s->fifo_func_sel = value; + break; + case REG_SD_DBGC: /* Debug Enable */ + s->debug_enable = value; + break; + case REG_SD_A12A: /* Auto command 12 argument */ + s->auto12_arg = value; + break; + case REG_SD_NTSR: /* SD NewTiming Set */ + s->newtiming_set = value; + break; + case REG_SD_SDBG: /* SD newTiming Set Debug */ + s->newtiming_debug = value; + break; + case REG_SD_HWRST: /* Hardware Reset Register */ + s->hardware_rst = value; + break; + case REG_SD_DMAC: /* Internal DMA Controller Control */ + s->dmac = value; + allwinner_sdhost_update_irq(s); + break; + case REG_SD_DLBA: /* Descriptor List Base Address */ + s->desc_base = value; + break; + case REG_SD_IDST: /* Internal DMA Controller Status */ + s->dmac_status &= (~SD_IDST_WR_MASK) | (~value & SD_IDST_WR_MASK); + allwinner_sdhost_update_irq(s); + break; + case REG_SD_IDIE: /* Internal DMA Controller Interrupt Enable */ + s->dmac_irq = value; + allwinner_sdhost_update_irq(s); + break; + case REG_SD_THLDC: /* Card Threshold Control */ + s->card_threshold = value; + break; + case REG_SD_DSBD: /* eMMC DDR Start Bit Detection Control */ + s->startbit_detect = value; + break; + case REG_SD_FIFO: /* Read/Write FIFO */ + u32 = cpu_to_le32(value); + sdbus_write_data(&s->sdbus, &u32, sizeof(u32)); + allwinner_sdhost_update_transfer_cnt(s, sizeof(u32)); + allwinner_sdhost_auto_stop(s); + allwinner_sdhost_update_irq(s); + break; + case REG_SD_RES_CRC: /* Response CRC from card/eMMC */ + case REG_SD_DATA7_CRC: /* CRC Data 7 from card/eMMC */ + case REG_SD_DATA6_CRC: /* CRC Data 6 from card/eMMC */ + case REG_SD_DATA5_CRC: /* CRC Data 5 from card/eMMC */ + case REG_SD_DATA4_CRC: /* CRC Data 4 from card/eMMC */ + case REG_SD_DATA3_CRC: /* CRC Data 3 from card/eMMC */ + case REG_SD_DATA2_CRC: /* CRC Data 2 from card/eMMC */ + case REG_SD_DATA1_CRC: /* CRC Data 1 from card/eMMC */ + case REG_SD_DATA0_CRC: /* CRC Data 0 from card/eMMC */ + case REG_SD_CRC_STA: /* CRC status from card/eMMC in write operation */ + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset %" + HWADDR_PRIx"\n", __func__, offset); + break; + } +} + +static const MemoryRegionOps allwinner_sdhost_ops = { + .read = allwinner_sdhost_read, + .write = allwinner_sdhost_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .impl.min_access_size = 4, +}; + +static const VMStateDescription vmstate_allwinner_sdhost = { + .name = "allwinner-sdhost", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(global_ctl, AwSdHostState), + VMSTATE_UINT32(clock_ctl, AwSdHostState), + VMSTATE_UINT32(timeout, AwSdHostState), + VMSTATE_UINT32(bus_width, AwSdHostState), + VMSTATE_UINT32(block_size, AwSdHostState), + VMSTATE_UINT32(byte_count, AwSdHostState), + VMSTATE_UINT32(transfer_cnt, AwSdHostState), + VMSTATE_UINT32(command, AwSdHostState), + VMSTATE_UINT32(command_arg, AwSdHostState), + VMSTATE_UINT32_ARRAY(response, AwSdHostState, 4), + VMSTATE_UINT32(irq_mask, AwSdHostState), + VMSTATE_UINT32(irq_status, AwSdHostState), + VMSTATE_UINT32(status, AwSdHostState), + VMSTATE_UINT32(fifo_wlevel, AwSdHostState), + VMSTATE_UINT32(fifo_func_sel, AwSdHostState), + VMSTATE_UINT32(debug_enable, AwSdHostState), + VMSTATE_UINT32(auto12_arg, AwSdHostState), + VMSTATE_UINT32(newtiming_set, AwSdHostState), + VMSTATE_UINT32(newtiming_debug, AwSdHostState), + VMSTATE_UINT32(hardware_rst, AwSdHostState), + VMSTATE_UINT32(dmac, AwSdHostState), + VMSTATE_UINT32(desc_base, AwSdHostState), + VMSTATE_UINT32(dmac_status, AwSdHostState), + VMSTATE_UINT32(dmac_irq, AwSdHostState), + VMSTATE_UINT32(card_threshold, AwSdHostState), + VMSTATE_UINT32(startbit_detect, AwSdHostState), + VMSTATE_UINT32(response_crc, AwSdHostState), + VMSTATE_UINT32_ARRAY(data_crc, AwSdHostState, 8), + VMSTATE_UINT32(status_crc, AwSdHostState), + VMSTATE_END_OF_LIST() + } +}; + +static Property allwinner_sdhost_properties[] = { + DEFINE_PROP_LINK("dma-memory", AwSdHostState, dma_mr, + TYPE_MEMORY_REGION, MemoryRegion *), + DEFINE_PROP_END_OF_LIST(), +}; + +static void allwinner_sdhost_init(Object *obj) +{ + AwSdHostState *s = AW_SDHOST(obj); + + qbus_init(&s->sdbus, sizeof(s->sdbus), + TYPE_AW_SDHOST_BUS, DEVICE(s), "sd-bus"); + + memory_region_init_io(&s->iomem, obj, &allwinner_sdhost_ops, s, + TYPE_AW_SDHOST, 4 * KiB); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); + sysbus_init_irq(SYS_BUS_DEVICE(s), &s->irq); +} + +static void allwinner_sdhost_realize(DeviceState *dev, Error **errp) +{ + AwSdHostState *s = AW_SDHOST(dev); + + if (!s->dma_mr) { + error_setg(errp, TYPE_AW_SDHOST " 'dma-memory' link not set"); + return; + } + + address_space_init(&s->dma_as, s->dma_mr, "sdhost-dma"); +} + +static void allwinner_sdhost_reset(DeviceState *dev) +{ + AwSdHostState *s = AW_SDHOST(dev); + + s->global_ctl = REG_SD_GCTL_RST; + s->clock_ctl = REG_SD_CKCR_RST; + s->timeout = REG_SD_TMOR_RST; + s->bus_width = REG_SD_BWDR_RST; + s->block_size = REG_SD_BKSR_RST; + s->byte_count = REG_SD_BYCR_RST; + s->transfer_cnt = 0; + + s->command = REG_SD_CMDR_RST; + s->command_arg = REG_SD_CAGR_RST; + + for (int i = 0; i < ARRAY_SIZE(s->response); i++) { + s->response[i] = REG_SD_RESP_RST; + } + + s->irq_mask = REG_SD_IMKR_RST; + s->irq_status = REG_SD_RISR_RST; + s->status = REG_SD_STAR_RST; + + s->fifo_wlevel = REG_SD_FWLR_RST; + s->fifo_func_sel = REG_SD_FUNS_RST; + s->debug_enable = REG_SD_DBGC_RST; + s->auto12_arg = REG_SD_A12A_RST; + s->newtiming_set = REG_SD_NTSR_RST; + s->newtiming_debug = REG_SD_SDBG_RST; + s->hardware_rst = REG_SD_HWRST_RST; + s->dmac = REG_SD_DMAC_RST; + s->desc_base = REG_SD_DLBA_RST; + s->dmac_status = REG_SD_IDST_RST; + s->dmac_irq = REG_SD_IDIE_RST; + s->card_threshold = REG_SD_THLDC_RST; + s->startbit_detect = REG_SD_DSBD_RST; + s->response_crc = REG_SD_RES_CRC_RST; + + for (int i = 0; i < ARRAY_SIZE(s->data_crc); i++) { + s->data_crc[i] = REG_SD_DATA_CRC_RST; + } + + s->status_crc = REG_SD_CRC_STA_RST; +} + +static void allwinner_sdhost_bus_class_init(ObjectClass *klass, void *data) +{ + SDBusClass *sbc = SD_BUS_CLASS(klass); + + sbc->set_inserted = allwinner_sdhost_set_inserted; +} + +static void allwinner_sdhost_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = allwinner_sdhost_reset; + dc->vmsd = &vmstate_allwinner_sdhost; + dc->realize = allwinner_sdhost_realize; + device_class_set_props(dc, allwinner_sdhost_properties); +} + +static void allwinner_sdhost_sun4i_class_init(ObjectClass *klass, void *data) +{ + AwSdHostClass *sc = AW_SDHOST_CLASS(klass); + sc->max_desc_size = 8 * KiB; +} + +static void allwinner_sdhost_sun5i_class_init(ObjectClass *klass, void *data) +{ + AwSdHostClass *sc = AW_SDHOST_CLASS(klass); + sc->max_desc_size = 64 * KiB; +} + +static TypeInfo allwinner_sdhost_info = { + .name = TYPE_AW_SDHOST, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_init = allwinner_sdhost_init, + .instance_size = sizeof(AwSdHostState), + .class_init = allwinner_sdhost_class_init, + .class_size = sizeof(AwSdHostClass), + .abstract = true, +}; + +static const TypeInfo allwinner_sdhost_sun4i_info = { + .name = TYPE_AW_SDHOST_SUN4I, + .parent = TYPE_AW_SDHOST, + .class_init = allwinner_sdhost_sun4i_class_init, +}; + +static const TypeInfo allwinner_sdhost_sun5i_info = { + .name = TYPE_AW_SDHOST_SUN5I, + .parent = TYPE_AW_SDHOST, + .class_init = allwinner_sdhost_sun5i_class_init, +}; + +static const TypeInfo allwinner_sdhost_bus_info = { + .name = TYPE_AW_SDHOST_BUS, + .parent = TYPE_SD_BUS, + .instance_size = sizeof(SDBus), + .class_init = allwinner_sdhost_bus_class_init, +}; + +static void allwinner_sdhost_register_types(void) +{ + type_register_static(&allwinner_sdhost_info); + type_register_static(&allwinner_sdhost_sun4i_info); + type_register_static(&allwinner_sdhost_sun5i_info); + type_register_static(&allwinner_sdhost_bus_info); +} + +type_init(allwinner_sdhost_register_types) diff --git a/hw/sd/aspeed_sdhci.c b/hw/sd/aspeed_sdhci.c new file mode 100644 index 000000000..df1bdf1fa --- /dev/null +++ b/hw/sd/aspeed_sdhci.c @@ -0,0 +1,213 @@ +/* + * Aspeed SD Host Controller + * Eddie James <eajames@linux.ibm.com> + * + * Copyright (C) 2019 IBM Corp + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/error-report.h" +#include "hw/sd/aspeed_sdhci.h" +#include "qapi/error.h" +#include "hw/irq.h" +#include "migration/vmstate.h" +#include "hw/qdev-properties.h" +#include "trace.h" + +#define ASPEED_SDHCI_INFO 0x00 +#define ASPEED_SDHCI_INFO_SLOT1 (1 << 17) +#define ASPEED_SDHCI_INFO_SLOT0 (1 << 16) +#define ASPEED_SDHCI_INFO_RESET (1 << 0) +#define ASPEED_SDHCI_DEBOUNCE 0x04 +#define ASPEED_SDHCI_DEBOUNCE_RESET 0x00000005 +#define ASPEED_SDHCI_BUS 0x08 +#define ASPEED_SDHCI_SDIO_140 0x10 +#define ASPEED_SDHCI_SDIO_148 0x18 +#define ASPEED_SDHCI_SDIO_240 0x20 +#define ASPEED_SDHCI_SDIO_248 0x28 +#define ASPEED_SDHCI_WP_POL 0xec +#define ASPEED_SDHCI_CARD_DET 0xf0 +#define ASPEED_SDHCI_IRQ_STAT 0xfc + +#define TO_REG(addr) ((addr) / sizeof(uint32_t)) + +static uint64_t aspeed_sdhci_read(void *opaque, hwaddr addr, unsigned int size) +{ + uint32_t val = 0; + AspeedSDHCIState *sdhci = opaque; + + switch (addr) { + case ASPEED_SDHCI_SDIO_140: + val = (uint32_t)sdhci->slots[0].capareg; + break; + case ASPEED_SDHCI_SDIO_148: + val = (uint32_t)sdhci->slots[0].maxcurr; + break; + case ASPEED_SDHCI_SDIO_240: + val = (uint32_t)sdhci->slots[1].capareg; + break; + case ASPEED_SDHCI_SDIO_248: + val = (uint32_t)sdhci->slots[1].maxcurr; + break; + default: + if (addr < ASPEED_SDHCI_REG_SIZE) { + val = sdhci->regs[TO_REG(addr)]; + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Out-of-bounds read at 0x%" HWADDR_PRIx "\n", + __func__, addr); + } + } + + trace_aspeed_sdhci_read(addr, size, (uint64_t) val); + + return (uint64_t)val; +} + +static void aspeed_sdhci_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + AspeedSDHCIState *sdhci = opaque; + + trace_aspeed_sdhci_write(addr, size, val); + + switch (addr) { + case ASPEED_SDHCI_INFO: + /* The RESET bit automatically clears. */ + sdhci->regs[TO_REG(addr)] = (uint32_t)val & ~ASPEED_SDHCI_INFO_RESET; + break; + case ASPEED_SDHCI_SDIO_140: + sdhci->slots[0].capareg = (uint64_t)(uint32_t)val; + break; + case ASPEED_SDHCI_SDIO_148: + sdhci->slots[0].maxcurr = (uint64_t)(uint32_t)val; + break; + case ASPEED_SDHCI_SDIO_240: + sdhci->slots[1].capareg = (uint64_t)(uint32_t)val; + break; + case ASPEED_SDHCI_SDIO_248: + sdhci->slots[1].maxcurr = (uint64_t)(uint32_t)val; + break; + default: + if (addr < ASPEED_SDHCI_REG_SIZE) { + sdhci->regs[TO_REG(addr)] = (uint32_t)val; + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Out-of-bounds write at 0x%" HWADDR_PRIx "\n", + __func__, addr); + } + } +} + +static const MemoryRegionOps aspeed_sdhci_ops = { + .read = aspeed_sdhci_read, + .write = aspeed_sdhci_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, +}; + +static void aspeed_sdhci_set_irq(void *opaque, int n, int level) +{ + AspeedSDHCIState *sdhci = opaque; + + if (level) { + sdhci->regs[TO_REG(ASPEED_SDHCI_IRQ_STAT)] |= BIT(n); + + qemu_irq_raise(sdhci->irq); + } else { + sdhci->regs[TO_REG(ASPEED_SDHCI_IRQ_STAT)] &= ~BIT(n); + + qemu_irq_lower(sdhci->irq); + } +} + +static void aspeed_sdhci_realize(DeviceState *dev, Error **errp) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + AspeedSDHCIState *sdhci = ASPEED_SDHCI(dev); + + /* Create input irqs for the slots */ + qdev_init_gpio_in_named_with_opaque(DEVICE(sbd), aspeed_sdhci_set_irq, + sdhci, NULL, sdhci->num_slots); + + sysbus_init_irq(sbd, &sdhci->irq); + memory_region_init_io(&sdhci->iomem, OBJECT(sdhci), &aspeed_sdhci_ops, + sdhci, TYPE_ASPEED_SDHCI, 0x1000); + sysbus_init_mmio(sbd, &sdhci->iomem); + + for (int i = 0; i < sdhci->num_slots; ++i) { + Object *sdhci_slot = OBJECT(&sdhci->slots[i]); + SysBusDevice *sbd_slot = SYS_BUS_DEVICE(&sdhci->slots[i]); + + if (!object_property_set_int(sdhci_slot, "sd-spec-version", 2, errp)) { + return; + } + + if (!object_property_set_uint(sdhci_slot, "capareg", + ASPEED_SDHCI_CAPABILITIES, errp)) { + return; + } + + if (!sysbus_realize(sbd_slot, errp)) { + return; + } + + sysbus_connect_irq(sbd_slot, 0, qdev_get_gpio_in(DEVICE(sbd), i)); + memory_region_add_subregion(&sdhci->iomem, (i + 1) * 0x100, + &sdhci->slots[i].iomem); + } +} + +static void aspeed_sdhci_reset(DeviceState *dev) +{ + AspeedSDHCIState *sdhci = ASPEED_SDHCI(dev); + + memset(sdhci->regs, 0, ASPEED_SDHCI_REG_SIZE); + + sdhci->regs[TO_REG(ASPEED_SDHCI_INFO)] = ASPEED_SDHCI_INFO_SLOT0; + if (sdhci->num_slots == 2) { + sdhci->regs[TO_REG(ASPEED_SDHCI_INFO)] |= ASPEED_SDHCI_INFO_SLOT1; + } + sdhci->regs[TO_REG(ASPEED_SDHCI_DEBOUNCE)] = ASPEED_SDHCI_DEBOUNCE_RESET; +} + +static const VMStateDescription vmstate_aspeed_sdhci = { + .name = TYPE_ASPEED_SDHCI, + .version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, AspeedSDHCIState, ASPEED_SDHCI_NUM_REGS), + VMSTATE_END_OF_LIST(), + }, +}; + +static Property aspeed_sdhci_properties[] = { + DEFINE_PROP_UINT8("num-slots", AspeedSDHCIState, num_slots, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void aspeed_sdhci_class_init(ObjectClass *classp, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(classp); + + dc->realize = aspeed_sdhci_realize; + dc->reset = aspeed_sdhci_reset; + dc->vmsd = &vmstate_aspeed_sdhci; + device_class_set_props(dc, aspeed_sdhci_properties); +} + +static TypeInfo aspeed_sdhci_info = { + .name = TYPE_ASPEED_SDHCI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AspeedSDHCIState), + .class_init = aspeed_sdhci_class_init, +}; + +static void aspeed_sdhci_register_types(void) +{ + type_register_static(&aspeed_sdhci_info); +} + +type_init(aspeed_sdhci_register_types) diff --git a/hw/sd/bcm2835_sdhost.c b/hw/sd/bcm2835_sdhost.c new file mode 100644 index 000000000..088a7ac6e --- /dev/null +++ b/hw/sd/bcm2835_sdhost.c @@ -0,0 +1,459 @@ +/* + * Raspberry Pi (BCM2835) SD Host Controller + * + * Copyright (c) 2017 Antfield SAS + * + * Authors: + * Clement Deschamps <clement.deschamps@antfield.fr> + * Luc Michel <luc.michel@antfield.fr> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "sysemu/blockdev.h" +#include "hw/irq.h" +#include "hw/sd/bcm2835_sdhost.h" +#include "migration/vmstate.h" +#include "trace.h" +#include "qom/object.h" + +#define TYPE_BCM2835_SDHOST_BUS "bcm2835-sdhost-bus" +/* This is reusing the SDBus typedef from SD_BUS */ +DECLARE_INSTANCE_CHECKER(SDBus, BCM2835_SDHOST_BUS, + TYPE_BCM2835_SDHOST_BUS) + +#define SDCMD 0x00 /* Command to SD card - 16 R/W */ +#define SDARG 0x04 /* Argument to SD card - 32 R/W */ +#define SDTOUT 0x08 /* Start value for timeout counter - 32 R/W */ +#define SDCDIV 0x0c /* Start value for clock divider - 11 R/W */ +#define SDRSP0 0x10 /* SD card rsp (31:0) - 32 R */ +#define SDRSP1 0x14 /* SD card rsp (63:32) - 32 R */ +#define SDRSP2 0x18 /* SD card rsp (95:64) - 32 R */ +#define SDRSP3 0x1c /* SD card rsp (127:96) - 32 R */ +#define SDHSTS 0x20 /* SD host status - 11 R */ +#define SDVDD 0x30 /* SD card power control - 1 R/W */ +#define SDEDM 0x34 /* Emergency Debug Mode - 13 R/W */ +#define SDHCFG 0x38 /* Host configuration - 2 R/W */ +#define SDHBCT 0x3c /* Host byte count (debug) - 32 R/W */ +#define SDDATA 0x40 /* Data to/from SD card - 32 R/W */ +#define SDHBLC 0x50 /* Host block count (SDIO/SDHC) - 9 R/W */ + +#define SDCMD_NEW_FLAG 0x8000 +#define SDCMD_FAIL_FLAG 0x4000 +#define SDCMD_BUSYWAIT 0x800 +#define SDCMD_NO_RESPONSE 0x400 +#define SDCMD_LONG_RESPONSE 0x200 +#define SDCMD_WRITE_CMD 0x80 +#define SDCMD_READ_CMD 0x40 +#define SDCMD_CMD_MASK 0x3f + +#define SDCDIV_MAX_CDIV 0x7ff + +#define SDHSTS_BUSY_IRPT 0x400 +#define SDHSTS_BLOCK_IRPT 0x200 +#define SDHSTS_SDIO_IRPT 0x100 +#define SDHSTS_REW_TIME_OUT 0x80 +#define SDHSTS_CMD_TIME_OUT 0x40 +#define SDHSTS_CRC16_ERROR 0x20 +#define SDHSTS_CRC7_ERROR 0x10 +#define SDHSTS_FIFO_ERROR 0x08 +/* Reserved */ +/* Reserved */ +#define SDHSTS_DATA_FLAG 0x01 + +#define SDHCFG_BUSY_IRPT_EN (1 << 10) +#define SDHCFG_BLOCK_IRPT_EN (1 << 8) +#define SDHCFG_SDIO_IRPT_EN (1 << 5) +#define SDHCFG_DATA_IRPT_EN (1 << 4) +#define SDHCFG_SLOW_CARD (1 << 3) +#define SDHCFG_WIDE_EXT_BUS (1 << 2) +#define SDHCFG_WIDE_INT_BUS (1 << 1) +#define SDHCFG_REL_CMD_LINE (1 << 0) + +#define SDEDM_FORCE_DATA_MODE (1 << 19) +#define SDEDM_CLOCK_PULSE (1 << 20) +#define SDEDM_BYPASS (1 << 21) + +#define SDEDM_WRITE_THRESHOLD_SHIFT 9 +#define SDEDM_READ_THRESHOLD_SHIFT 14 +#define SDEDM_THRESHOLD_MASK 0x1f + +#define SDEDM_FSM_MASK 0xf +#define SDEDM_FSM_IDENTMODE 0x0 +#define SDEDM_FSM_DATAMODE 0x1 +#define SDEDM_FSM_READDATA 0x2 +#define SDEDM_FSM_WRITEDATA 0x3 +#define SDEDM_FSM_READWAIT 0x4 +#define SDEDM_FSM_READCRC 0x5 +#define SDEDM_FSM_WRITECRC 0x6 +#define SDEDM_FSM_WRITEWAIT1 0x7 +#define SDEDM_FSM_POWERDOWN 0x8 +#define SDEDM_FSM_POWERUP 0x9 +#define SDEDM_FSM_WRITESTART1 0xa +#define SDEDM_FSM_WRITESTART2 0xb +#define SDEDM_FSM_GENPULSES 0xc +#define SDEDM_FSM_WRITEWAIT2 0xd +#define SDEDM_FSM_STARTPOWDOWN 0xf + +#define SDDATA_FIFO_WORDS 16 + +static void bcm2835_sdhost_update_irq(BCM2835SDHostState *s) +{ + uint32_t irq = s->status & + (SDHSTS_BUSY_IRPT | SDHSTS_BLOCK_IRPT | SDHSTS_SDIO_IRPT); + trace_bcm2835_sdhost_update_irq(irq); + qemu_set_irq(s->irq, !!irq); +} + +static void bcm2835_sdhost_send_command(BCM2835SDHostState *s) +{ + SDRequest request; + uint8_t rsp[16]; + int rlen; + + request.cmd = s->cmd & SDCMD_CMD_MASK; + request.arg = s->cmdarg; + + rlen = sdbus_do_command(&s->sdbus, &request, rsp); + if (rlen < 0) { + goto error; + } + if (!(s->cmd & SDCMD_NO_RESPONSE)) { + if (rlen == 0 || (rlen == 4 && (s->cmd & SDCMD_LONG_RESPONSE))) { + goto error; + } + if (rlen != 4 && rlen != 16) { + goto error; + } + if (rlen == 4) { + s->rsp[0] = ldl_be_p(&rsp[0]); + s->rsp[1] = s->rsp[2] = s->rsp[3] = 0; + } else { + s->rsp[0] = ldl_be_p(&rsp[12]); + s->rsp[1] = ldl_be_p(&rsp[8]); + s->rsp[2] = ldl_be_p(&rsp[4]); + s->rsp[3] = ldl_be_p(&rsp[0]); + } + } + /* We never really delay commands, so if this was a 'busywait' command + * then we've completed it now and can raise the interrupt. + */ + if ((s->cmd & SDCMD_BUSYWAIT) && (s->config & SDHCFG_BUSY_IRPT_EN)) { + s->status |= SDHSTS_BUSY_IRPT; + } + return; + +error: + s->cmd |= SDCMD_FAIL_FLAG; + s->status |= SDHSTS_CMD_TIME_OUT; +} + +static void bcm2835_sdhost_fifo_push(BCM2835SDHostState *s, uint32_t value) +{ + int n; + + if (s->fifo_len == BCM2835_SDHOST_FIFO_LEN) { + /* FIFO overflow */ + return; + } + n = (s->fifo_pos + s->fifo_len) & (BCM2835_SDHOST_FIFO_LEN - 1); + s->fifo_len++; + s->fifo[n] = value; +} + +static uint32_t bcm2835_sdhost_fifo_pop(BCM2835SDHostState *s) +{ + uint32_t value; + + if (s->fifo_len == 0) { + /* FIFO underflow */ + return 0; + } + value = s->fifo[s->fifo_pos]; + s->fifo_len--; + s->fifo_pos = (s->fifo_pos + 1) & (BCM2835_SDHOST_FIFO_LEN - 1); + return value; +} + +static void bcm2835_sdhost_fifo_run(BCM2835SDHostState *s) +{ + uint32_t value = 0; + int n; + int is_read; + int is_write; + + is_read = (s->cmd & SDCMD_READ_CMD) != 0; + is_write = (s->cmd & SDCMD_WRITE_CMD) != 0; + if (s->datacnt != 0 && (is_write || sdbus_data_ready(&s->sdbus))) { + if (is_read) { + n = 0; + while (s->datacnt && s->fifo_len < BCM2835_SDHOST_FIFO_LEN) { + value |= (uint32_t)sdbus_read_byte(&s->sdbus) << (n * 8); + s->datacnt--; + n++; + if (n == 4) { + bcm2835_sdhost_fifo_push(s, value); + s->status |= SDHSTS_DATA_FLAG; + if (s->config & SDHCFG_DATA_IRPT_EN) { + s->status |= SDHSTS_SDIO_IRPT; + } + n = 0; + value = 0; + } + } + if (n != 0) { + bcm2835_sdhost_fifo_push(s, value); + s->status |= SDHSTS_DATA_FLAG; + if (s->config & SDHCFG_DATA_IRPT_EN) { + s->status |= SDHSTS_SDIO_IRPT; + } + } + } else if (is_write) { /* write */ + n = 0; + while (s->datacnt > 0 && (s->fifo_len > 0 || n > 0)) { + if (n == 0) { + value = bcm2835_sdhost_fifo_pop(s); + s->status |= SDHSTS_DATA_FLAG; + if (s->config & SDHCFG_DATA_IRPT_EN) { + s->status |= SDHSTS_SDIO_IRPT; + } + n = 4; + } + n--; + s->datacnt--; + sdbus_write_byte(&s->sdbus, value & 0xff); + value >>= 8; + } + } + if (s->datacnt == 0) { + s->edm &= ~SDEDM_FSM_MASK; + s->edm |= SDEDM_FSM_DATAMODE; + trace_bcm2835_sdhost_edm_change("datacnt 0", s->edm); + } + if (is_write) { + /* set block interrupt at end of each block transfer */ + if (s->hbct && s->datacnt % s->hbct == 0 && + (s->config & SDHCFG_BLOCK_IRPT_EN)) { + s->status |= SDHSTS_BLOCK_IRPT; + } + /* set data interrupt after each transfer */ + s->status |= SDHSTS_DATA_FLAG; + if (s->config & SDHCFG_DATA_IRPT_EN) { + s->status |= SDHSTS_SDIO_IRPT; + } + } + } + + bcm2835_sdhost_update_irq(s); + + s->edm &= ~(0x1f << 4); + s->edm |= ((s->fifo_len & 0x1f) << 4); + trace_bcm2835_sdhost_edm_change("fifo run", s->edm); +} + +static uint64_t bcm2835_sdhost_read(void *opaque, hwaddr offset, + unsigned size) +{ + BCM2835SDHostState *s = (BCM2835SDHostState *)opaque; + uint32_t res = 0; + + switch (offset) { + case SDCMD: + res = s->cmd; + break; + case SDHSTS: + res = s->status; + break; + case SDRSP0: + res = s->rsp[0]; + break; + case SDRSP1: + res = s->rsp[1]; + break; + case SDRSP2: + res = s->rsp[2]; + break; + case SDRSP3: + res = s->rsp[3]; + break; + case SDEDM: + res = s->edm; + break; + case SDVDD: + res = s->vdd; + break; + case SDDATA: + res = bcm2835_sdhost_fifo_pop(s); + bcm2835_sdhost_fifo_run(s); + break; + case SDHBCT: + res = s->hbct; + break; + case SDHBLC: + res = s->hblc; + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %"HWADDR_PRIx"\n", + __func__, offset); + res = 0; + break; + } + + trace_bcm2835_sdhost_read(offset, res, size); + + return res; +} + +static void bcm2835_sdhost_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + BCM2835SDHostState *s = (BCM2835SDHostState *)opaque; + + trace_bcm2835_sdhost_write(offset, value, size); + + switch (offset) { + case SDCMD: + s->cmd = value; + if (value & SDCMD_NEW_FLAG) { + bcm2835_sdhost_send_command(s); + bcm2835_sdhost_fifo_run(s); + s->cmd &= ~SDCMD_NEW_FLAG; + } + break; + case SDTOUT: + break; + case SDCDIV: + break; + case SDHSTS: + s->status &= ~value; + bcm2835_sdhost_update_irq(s); + break; + case SDARG: + s->cmdarg = value; + break; + case SDEDM: + if ((value & 0xf) == 0xf) { + /* power down */ + value &= ~0xf; + } + s->edm = value; + trace_bcm2835_sdhost_edm_change("guest register write", s->edm); + break; + case SDHCFG: + s->config = value; + bcm2835_sdhost_fifo_run(s); + break; + case SDVDD: + s->vdd = value; + break; + case SDDATA: + bcm2835_sdhost_fifo_push(s, value); + bcm2835_sdhost_fifo_run(s); + break; + case SDHBCT: + s->hbct = value; + break; + case SDHBLC: + s->hblc = value; + s->datacnt = s->hblc * s->hbct; + bcm2835_sdhost_fifo_run(s); + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %"HWADDR_PRIx"\n", + __func__, offset); + break; + } +} + +static const MemoryRegionOps bcm2835_sdhost_ops = { + .read = bcm2835_sdhost_read, + .write = bcm2835_sdhost_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const VMStateDescription vmstate_bcm2835_sdhost = { + .name = TYPE_BCM2835_SDHOST, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(cmd, BCM2835SDHostState), + VMSTATE_UINT32(cmdarg, BCM2835SDHostState), + VMSTATE_UINT32(status, BCM2835SDHostState), + VMSTATE_UINT32_ARRAY(rsp, BCM2835SDHostState, 4), + VMSTATE_UINT32(config, BCM2835SDHostState), + VMSTATE_UINT32(edm, BCM2835SDHostState), + VMSTATE_UINT32(vdd, BCM2835SDHostState), + VMSTATE_UINT32(hbct, BCM2835SDHostState), + VMSTATE_UINT32(hblc, BCM2835SDHostState), + VMSTATE_INT32(fifo_pos, BCM2835SDHostState), + VMSTATE_INT32(fifo_len, BCM2835SDHostState), + VMSTATE_UINT32_ARRAY(fifo, BCM2835SDHostState, BCM2835_SDHOST_FIFO_LEN), + VMSTATE_UINT32(datacnt, BCM2835SDHostState), + VMSTATE_END_OF_LIST() + } +}; + +static void bcm2835_sdhost_init(Object *obj) +{ + BCM2835SDHostState *s = BCM2835_SDHOST(obj); + + qbus_init(&s->sdbus, sizeof(s->sdbus), + TYPE_BCM2835_SDHOST_BUS, DEVICE(s), "sd-bus"); + + memory_region_init_io(&s->iomem, obj, &bcm2835_sdhost_ops, s, + TYPE_BCM2835_SDHOST, 0x1000); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); + sysbus_init_irq(SYS_BUS_DEVICE(s), &s->irq); +} + +static void bcm2835_sdhost_reset(DeviceState *dev) +{ + BCM2835SDHostState *s = BCM2835_SDHOST(dev); + + s->cmd = 0; + s->cmdarg = 0; + s->edm = 0x0000c60f; + trace_bcm2835_sdhost_edm_change("device reset", s->edm); + s->config = 0; + s->hbct = 0; + s->hblc = 0; + s->datacnt = 0; + s->fifo_pos = 0; + s->fifo_len = 0; +} + +static void bcm2835_sdhost_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = bcm2835_sdhost_reset; + dc->vmsd = &vmstate_bcm2835_sdhost; +} + +static TypeInfo bcm2835_sdhost_info = { + .name = TYPE_BCM2835_SDHOST, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(BCM2835SDHostState), + .class_init = bcm2835_sdhost_class_init, + .instance_init = bcm2835_sdhost_init, +}; + +static const TypeInfo bcm2835_sdhost_bus_info = { + .name = TYPE_BCM2835_SDHOST_BUS, + .parent = TYPE_SD_BUS, + .instance_size = sizeof(SDBus), +}; + +static void bcm2835_sdhost_register_types(void) +{ + type_register_static(&bcm2835_sdhost_info); + type_register_static(&bcm2835_sdhost_bus_info); +} + +type_init(bcm2835_sdhost_register_types) diff --git a/hw/sd/cadence_sdhci.c b/hw/sd/cadence_sdhci.c new file mode 100644 index 000000000..56b8bae1c --- /dev/null +++ b/hw/sd/cadence_sdhci.c @@ -0,0 +1,191 @@ +/* + * Cadence SDHCI emulation + * + * Copyright (c) 2020 Wind River Systems, Inc. + * + * Author: + * Bin Meng <bin.meng@windriver.com> + * + * 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 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/error-report.h" +#include "qapi/error.h" +#include "migration/vmstate.h" +#include "hw/sd/cadence_sdhci.h" +#include "sdhci-internal.h" + +/* HRS - Host Register Set (specific to Cadence) */ + +#define CADENCE_SDHCI_HRS00 0x00 /* general information */ +#define CADENCE_SDHCI_HRS00_SWR BIT(0) +#define CADENCE_SDHCI_HRS00_POR_VAL 0x00010000 + +#define CADENCE_SDHCI_HRS04 0x10 /* PHY access port */ +#define CADENCE_SDHCI_HRS04_WR BIT(24) +#define CADENCE_SDHCI_HRS04_RD BIT(25) +#define CADENCE_SDHCI_HRS04_ACK BIT(26) + +#define CADENCE_SDHCI_HRS06 0x18 /* eMMC control */ +#define CADENCE_SDHCI_HRS06_TUNE_UP BIT(15) + +/* SRS - Slot Register Set (SDHCI-compatible) */ + +#define CADENCE_SDHCI_SRS_BASE 0x200 + +#define TO_REG(addr) ((addr) / sizeof(uint32_t)) + +static void cadence_sdhci_instance_init(Object *obj) +{ + CadenceSDHCIState *s = CADENCE_SDHCI(obj); + + object_initialize_child(OBJECT(s), "generic-sdhci", + &s->sdhci, TYPE_SYSBUS_SDHCI); +} + +static void cadence_sdhci_reset(DeviceState *dev) +{ + CadenceSDHCIState *s = CADENCE_SDHCI(dev); + + memset(s->regs, 0, CADENCE_SDHCI_REG_SIZE); + s->regs[TO_REG(CADENCE_SDHCI_HRS00)] = CADENCE_SDHCI_HRS00_POR_VAL; + + device_cold_reset(DEVICE(&s->sdhci)); +} + +static uint64_t cadence_sdhci_read(void *opaque, hwaddr addr, unsigned int size) +{ + CadenceSDHCIState *s = opaque; + uint32_t val; + + val = s->regs[TO_REG(addr)]; + + return (uint64_t)val; +} + +static void cadence_sdhci_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + CadenceSDHCIState *s = opaque; + uint32_t val32 = (uint32_t)val; + + switch (addr) { + case CADENCE_SDHCI_HRS00: + /* + * The only writable bit is SWR (software reset) and it automatically + * clears to zero, so essentially this register remains unchanged. + */ + if (val32 & CADENCE_SDHCI_HRS00_SWR) { + cadence_sdhci_reset(DEVICE(s)); + } + + break; + case CADENCE_SDHCI_HRS04: + /* + * Only emulate the ACK bit behavior when read or write transaction + * are requested. + */ + if (val32 & (CADENCE_SDHCI_HRS04_WR | CADENCE_SDHCI_HRS04_RD)) { + val32 |= CADENCE_SDHCI_HRS04_ACK; + } else { + val32 &= ~CADENCE_SDHCI_HRS04_ACK; + } + + s->regs[TO_REG(addr)] = val32; + break; + case CADENCE_SDHCI_HRS06: + if (val32 & CADENCE_SDHCI_HRS06_TUNE_UP) { + val32 &= ~CADENCE_SDHCI_HRS06_TUNE_UP; + } + + s->regs[TO_REG(addr)] = val32; + break; + default: + s->regs[TO_REG(addr)] = val32; + break; + } +} + +static const MemoryRegionOps cadence_sdhci_ops = { + .read = cadence_sdhci_read, + .write = cadence_sdhci_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + } +}; + +static void cadence_sdhci_realize(DeviceState *dev, Error **errp) +{ + CadenceSDHCIState *s = CADENCE_SDHCI(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + SysBusDevice *sbd_sdhci = SYS_BUS_DEVICE(&s->sdhci); + + memory_region_init(&s->container, OBJECT(s), + "cadence.sdhci-container", 0x1000); + sysbus_init_mmio(sbd, &s->container); + + memory_region_init_io(&s->iomem, OBJECT(s), &cadence_sdhci_ops, + s, TYPE_CADENCE_SDHCI, CADENCE_SDHCI_REG_SIZE); + memory_region_add_subregion(&s->container, 0, &s->iomem); + + sysbus_realize(sbd_sdhci, errp); + memory_region_add_subregion(&s->container, CADENCE_SDHCI_SRS_BASE, + sysbus_mmio_get_region(sbd_sdhci, 0)); + + /* propagate irq and "sd-bus" from generic-sdhci */ + sysbus_pass_irq(sbd, sbd_sdhci); + s->bus = qdev_get_child_bus(DEVICE(sbd_sdhci), "sd-bus"); +} + +static const VMStateDescription vmstate_cadence_sdhci = { + .name = TYPE_CADENCE_SDHCI, + .version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, CadenceSDHCIState, CADENCE_SDHCI_NUM_REGS), + VMSTATE_END_OF_LIST(), + }, +}; + +static void cadence_sdhci_class_init(ObjectClass *classp, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(classp); + + dc->desc = "Cadence SD/SDIO/eMMC Host Controller (SD4HC)"; + dc->realize = cadence_sdhci_realize; + dc->reset = cadence_sdhci_reset; + dc->vmsd = &vmstate_cadence_sdhci; +} + +static TypeInfo cadence_sdhci_info = { + .name = TYPE_CADENCE_SDHCI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(CadenceSDHCIState), + .instance_init = cadence_sdhci_instance_init, + .class_init = cadence_sdhci_class_init, +}; + +static void cadence_sdhci_register_types(void) +{ + type_register_static(&cadence_sdhci_info); +} + +type_init(cadence_sdhci_register_types) diff --git a/hw/sd/core.c b/hw/sd/core.c new file mode 100644 index 000000000..30ee62c51 --- /dev/null +++ b/hw/sd/core.c @@ -0,0 +1,274 @@ +/* + * SD card bus interface code. + * + * Copyright (c) 2015 Linaro Limited + * + * Author: + * Peter Maydell <peter.maydell@linaro.org> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2 or later, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "hw/qdev-core.h" +#include "hw/sd/sd.h" +#include "qemu/module.h" +#include "qapi/error.h" +#include "trace.h" + +static inline const char *sdbus_name(SDBus *sdbus) +{ + return sdbus->qbus.name; +} + +static SDState *get_card(SDBus *sdbus) +{ + /* We only ever have one child on the bus so just return it */ + BusChild *kid = QTAILQ_FIRST(&sdbus->qbus.children); + + if (!kid) { + return NULL; + } + return SD_CARD(kid->child); +} + +uint8_t sdbus_get_dat_lines(SDBus *sdbus) +{ + SDState *slave = get_card(sdbus); + uint8_t dat_lines = 0b1111; /* 4 bit bus width */ + + if (slave) { + SDCardClass *sc = SD_CARD_GET_CLASS(slave); + + if (sc->get_dat_lines) { + dat_lines = sc->get_dat_lines(slave); + } + } + trace_sdbus_get_dat_lines(sdbus_name(sdbus), dat_lines); + + return dat_lines; +} + +bool sdbus_get_cmd_line(SDBus *sdbus) +{ + SDState *slave = get_card(sdbus); + bool cmd_line = true; + + if (slave) { + SDCardClass *sc = SD_CARD_GET_CLASS(slave); + + if (sc->get_cmd_line) { + cmd_line = sc->get_cmd_line(slave); + } + } + trace_sdbus_get_cmd_line(sdbus_name(sdbus), cmd_line); + + return cmd_line; +} + +void sdbus_set_voltage(SDBus *sdbus, uint16_t millivolts) +{ + SDState *card = get_card(sdbus); + + trace_sdbus_set_voltage(sdbus_name(sdbus), millivolts); + if (card) { + SDCardClass *sc = SD_CARD_GET_CLASS(card); + + assert(sc->set_voltage); + sc->set_voltage(card, millivolts); + } +} + +int sdbus_do_command(SDBus *sdbus, SDRequest *req, uint8_t *response) +{ + SDState *card = get_card(sdbus); + + trace_sdbus_command(sdbus_name(sdbus), req->cmd, req->arg); + if (card) { + SDCardClass *sc = SD_CARD_GET_CLASS(card); + + return sc->do_command(card, req, response); + } + + return 0; +} + +void sdbus_write_byte(SDBus *sdbus, uint8_t value) +{ + SDState *card = get_card(sdbus); + + trace_sdbus_write(sdbus_name(sdbus), value); + if (card) { + SDCardClass *sc = SD_CARD_GET_CLASS(card); + + sc->write_byte(card, value); + } +} + +void sdbus_write_data(SDBus *sdbus, const void *buf, size_t length) +{ + SDState *card = get_card(sdbus); + const uint8_t *data = buf; + + if (card) { + SDCardClass *sc = SD_CARD_GET_CLASS(card); + + for (size_t i = 0; i < length; i++) { + trace_sdbus_write(sdbus_name(sdbus), data[i]); + sc->write_byte(card, data[i]); + } + } +} + +uint8_t sdbus_read_byte(SDBus *sdbus) +{ + SDState *card = get_card(sdbus); + uint8_t value = 0; + + if (card) { + SDCardClass *sc = SD_CARD_GET_CLASS(card); + + value = sc->read_byte(card); + } + trace_sdbus_read(sdbus_name(sdbus), value); + + return value; +} + +void sdbus_read_data(SDBus *sdbus, void *buf, size_t length) +{ + SDState *card = get_card(sdbus); + uint8_t *data = buf; + + if (card) { + SDCardClass *sc = SD_CARD_GET_CLASS(card); + + for (size_t i = 0; i < length; i++) { + data[i] = sc->read_byte(card); + trace_sdbus_read(sdbus_name(sdbus), data[i]); + } + } +} + +bool sdbus_receive_ready(SDBus *sdbus) +{ + SDState *card = get_card(sdbus); + + if (card) { + SDCardClass *sc = SD_CARD_GET_CLASS(card); + + return sc->receive_ready(card); + } + + return false; +} + +bool sdbus_data_ready(SDBus *sdbus) +{ + SDState *card = get_card(sdbus); + + if (card) { + SDCardClass *sc = SD_CARD_GET_CLASS(card); + + return sc->data_ready(card); + } + + return false; +} + +bool sdbus_get_inserted(SDBus *sdbus) +{ + SDState *card = get_card(sdbus); + + if (card) { + SDCardClass *sc = SD_CARD_GET_CLASS(card); + + return sc->get_inserted(card); + } + + return false; +} + +bool sdbus_get_readonly(SDBus *sdbus) +{ + SDState *card = get_card(sdbus); + + if (card) { + SDCardClass *sc = SD_CARD_GET_CLASS(card); + + return sc->get_readonly(card); + } + + return false; +} + +void sdbus_set_inserted(SDBus *sdbus, bool inserted) +{ + SDBusClass *sbc = SD_BUS_GET_CLASS(sdbus); + BusState *qbus = BUS(sdbus); + + if (sbc->set_inserted) { + sbc->set_inserted(qbus->parent, inserted); + } +} + +void sdbus_set_readonly(SDBus *sdbus, bool readonly) +{ + SDBusClass *sbc = SD_BUS_GET_CLASS(sdbus); + BusState *qbus = BUS(sdbus); + + if (sbc->set_readonly) { + sbc->set_readonly(qbus->parent, readonly); + } +} + +void sdbus_reparent_card(SDBus *from, SDBus *to) +{ + SDState *card = get_card(from); + SDCardClass *sc; + bool readonly; + + /* We directly reparent the card object rather than implementing this + * as a hotpluggable connection because we don't want to expose SD cards + * to users as being hotpluggable, and we can get away with it in this + * limited use case. This could perhaps be implemented more cleanly in + * future by adding support to the hotplug infrastructure for "device + * can be hotplugged only via code, not by user". + */ + + if (!card) { + return; + } + + sc = SD_CARD_GET_CLASS(card); + readonly = sc->get_readonly(card); + + sdbus_set_inserted(from, false); + qdev_set_parent_bus(DEVICE(card), &to->qbus, &error_abort); + sdbus_set_inserted(to, true); + sdbus_set_readonly(to, readonly); +} + +static const TypeInfo sd_bus_info = { + .name = TYPE_SD_BUS, + .parent = TYPE_BUS, + .instance_size = sizeof(SDBus), + .class_size = sizeof(SDBusClass), +}; + +static void sd_bus_register_types(void) +{ + type_register_static(&sd_bus_info); +} + +type_init(sd_bus_register_types) diff --git a/hw/sd/meson.build b/hw/sd/meson.build new file mode 100644 index 000000000..807ca07b7 --- /dev/null +++ b/hw/sd/meson.build @@ -0,0 +1,13 @@ +softmmu_ss.add(when: 'CONFIG_PL181', if_true: files('pl181.c')) +softmmu_ss.add(when: 'CONFIG_SD', if_true: files('sd.c', 'core.c', 'sdmmc-internal.c')) +softmmu_ss.add(when: 'CONFIG_SDHCI', if_true: files('sdhci.c')) +softmmu_ss.add(when: 'CONFIG_SDHCI_PCI', if_true: files('sdhci-pci.c')) +softmmu_ss.add(when: 'CONFIG_SSI_SD', if_true: files('ssi-sd.c')) + +softmmu_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_mmc.c')) +softmmu_ss.add(when: 'CONFIG_PXA2XX', if_true: files('pxa2xx_mmci.c')) +softmmu_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_sdhost.c')) +softmmu_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_sdhci.c')) +softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-sdhost.c')) +softmmu_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_sdhci.c')) +softmmu_ss.add(when: 'CONFIG_CADENCE_SDHCI', if_true: files('cadence_sdhci.c')) diff --git a/hw/sd/npcm7xx_sdhci.c b/hw/sd/npcm7xx_sdhci.c new file mode 100644 index 000000000..ef503365d --- /dev/null +++ b/hw/sd/npcm7xx_sdhci.c @@ -0,0 +1,182 @@ +/* + * NPCM7xx SD-3.0 / eMMC-4.51 Host Controller + * + * Copyright (c) 2021 Google LLC + * + * 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. + */ + +#include "qemu/osdep.h" + +#include "hw/sd/npcm7xx_sdhci.h" +#include "migration/vmstate.h" +#include "sdhci-internal.h" +#include "qemu/log.h" + +static uint64_t npcm7xx_sdhci_read(void *opaque, hwaddr addr, unsigned int size) +{ + NPCM7xxSDHCIState *s = opaque; + uint64_t val = 0; + + switch (addr) { + case NPCM7XX_PRSTVALS_0: + case NPCM7XX_PRSTVALS_1: + case NPCM7XX_PRSTVALS_2: + case NPCM7XX_PRSTVALS_3: + case NPCM7XX_PRSTVALS_4: + case NPCM7XX_PRSTVALS_5: + val = s->regs.prstvals[(addr - NPCM7XX_PRSTVALS_0) / 2]; + break; + case NPCM7XX_BOOTTOCTRL: + val = s->regs.boottoctrl; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "SDHCI read of nonexistent reg: 0x%02" + HWADDR_PRIx, addr); + break; + } + + return val; +} + +static void npcm7xx_sdhci_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + NPCM7xxSDHCIState *s = opaque; + + switch (addr) { + case NPCM7XX_BOOTTOCTRL: + s->regs.boottoctrl = val; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "SDHCI write of nonexistent reg: 0x%02" + HWADDR_PRIx, addr); + break; + } +} + +static bool npcm7xx_sdhci_check_mem_op(void *opaque, hwaddr addr, + unsigned size, bool is_write, + MemTxAttrs attrs) +{ + switch (addr) { + case NPCM7XX_PRSTVALS_0: + case NPCM7XX_PRSTVALS_1: + case NPCM7XX_PRSTVALS_2: + case NPCM7XX_PRSTVALS_3: + case NPCM7XX_PRSTVALS_4: + case NPCM7XX_PRSTVALS_5: + /* RO Word */ + return !is_write && size == 2; + case NPCM7XX_BOOTTOCTRL: + /* R/W Dword */ + return size == 4; + default: + return false; + } +} + +static const MemoryRegionOps npcm7xx_sdhci_ops = { + .read = npcm7xx_sdhci_read, + .write = npcm7xx_sdhci_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = false, + .accepts = npcm7xx_sdhci_check_mem_op, + }, +}; + +static void npcm7xx_sdhci_realize(DeviceState *dev, Error **errp) +{ + NPCM7xxSDHCIState *s = NPCM7XX_SDHCI(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + SysBusDevice *sbd_sdhci = SYS_BUS_DEVICE(&s->sdhci); + + memory_region_init(&s->container, OBJECT(s), + "npcm7xx.sdhci-container", 0x1000); + sysbus_init_mmio(sbd, &s->container); + + memory_region_init_io(&s->iomem, OBJECT(s), &npcm7xx_sdhci_ops, s, + TYPE_NPCM7XX_SDHCI, NPCM7XX_SDHCI_REGSIZE); + memory_region_add_subregion_overlap(&s->container, NPCM7XX_PRSTVALS, + &s->iomem, 1); + + sysbus_realize(sbd_sdhci, errp); + memory_region_add_subregion(&s->container, 0, + sysbus_mmio_get_region(sbd_sdhci, 0)); + + /* propagate irq and "sd-bus" from generic-sdhci */ + sysbus_pass_irq(sbd, sbd_sdhci); + s->bus = qdev_get_child_bus(DEVICE(sbd_sdhci), "sd-bus"); + + /* Set the read only preset values. */ + memset(s->regs.prstvals, 0, sizeof(s->regs.prstvals)); + s->regs.prstvals[0] = NPCM7XX_PRSTVALS_0_RESET; + s->regs.prstvals[1] = NPCM7XX_PRSTVALS_1_RESET; + s->regs.prstvals[3] = NPCM7XX_PRSTVALS_3_RESET; +} + +static void npcm7xx_sdhci_reset(DeviceState *dev) +{ + NPCM7xxSDHCIState *s = NPCM7XX_SDHCI(dev); + device_cold_reset(DEVICE(&s->sdhci)); + s->regs.boottoctrl = 0; + + s->sdhci.prnsts = NPCM7XX_PRSNTS_RESET; + s->sdhci.blkgap = NPCM7XX_BLKGAP_RESET; + s->sdhci.capareg = NPCM7XX_CAPAB_RESET; + s->sdhci.maxcurr = NPCM7XX_MAXCURR_RESET; + s->sdhci.version = NPCM7XX_HCVER_RESET; +} + +static const VMStateDescription vmstate_npcm7xx_sdhci = { + .name = TYPE_NPCM7XX_SDHCI, + .version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT32(regs.boottoctrl, NPCM7xxSDHCIState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void npcm7xx_sdhci_class_init(ObjectClass *classp, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(classp); + + dc->desc = "NPCM7xx SD/eMMC Host Controller"; + dc->realize = npcm7xx_sdhci_realize; + dc->reset = npcm7xx_sdhci_reset; + dc->vmsd = &vmstate_npcm7xx_sdhci; +} + +static void npcm7xx_sdhci_instance_init(Object *obj) +{ + NPCM7xxSDHCIState *s = NPCM7XX_SDHCI(obj); + + object_initialize_child(OBJECT(s), "generic-sdhci", &s->sdhci, + TYPE_SYSBUS_SDHCI); +} + +static TypeInfo npcm7xx_sdhci_info = { + .name = TYPE_NPCM7XX_SDHCI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(NPCM7xxSDHCIState), + .instance_init = npcm7xx_sdhci_instance_init, + .class_init = npcm7xx_sdhci_class_init, +}; + +static void npcm7xx_sdhci_register_types(void) +{ + type_register_static(&npcm7xx_sdhci_info); +} + +type_init(npcm7xx_sdhci_register_types) diff --git a/hw/sd/omap_mmc.c b/hw/sd/omap_mmc.c new file mode 100644 index 000000000..b67def638 --- /dev/null +++ b/hw/sd/omap_mmc.c @@ -0,0 +1,665 @@ +/* + * OMAP on-chip MMC/SD host emulation. + * + * Datasheet: TI Multimedia Card (MMC/SD/SDIO) Interface (SPRU765A) + * + * Copyright (C) 2006-2007 Andrzej Zaborowski <balrog@zabor.org> + * + * 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 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/irq.h" +#include "hw/arm/omap.h" +#include "hw/sd/sdcard_legacy.h" + +struct omap_mmc_s { + qemu_irq irq; + qemu_irq *dma; + qemu_irq coverswitch; + MemoryRegion iomem; + omap_clk clk; + SDState *card; + uint16_t last_cmd; + uint16_t sdio; + uint16_t rsp[8]; + uint32_t arg; + int lines; + int dw; + int mode; + int enable; + int be; + int rev; + uint16_t status; + uint16_t mask; + uint8_t cto; + uint16_t dto; + int clkdiv; + uint16_t fifo[32]; + int fifo_start; + int fifo_len; + uint16_t blen; + uint16_t blen_counter; + uint16_t nblk; + uint16_t nblk_counter; + int tx_dma; + int rx_dma; + int af_level; + int ae_level; + + int ddir; + int transfer; + + int cdet_wakeup; + int cdet_enable; + int cdet_state; + qemu_irq cdet; +}; + +static void omap_mmc_interrupts_update(struct omap_mmc_s *s) +{ + qemu_set_irq(s->irq, !!(s->status & s->mask)); +} + +static void omap_mmc_fifolevel_update(struct omap_mmc_s *host) +{ + if (!host->transfer && !host->fifo_len) { + host->status &= 0xf3ff; + return; + } + + if (host->fifo_len > host->af_level && host->ddir) { + if (host->rx_dma) { + host->status &= 0xfbff; + qemu_irq_raise(host->dma[1]); + } else + host->status |= 0x0400; + } else { + host->status &= 0xfbff; + qemu_irq_lower(host->dma[1]); + } + + if (host->fifo_len < host->ae_level && !host->ddir) { + if (host->tx_dma) { + host->status &= 0xf7ff; + qemu_irq_raise(host->dma[0]); + } else + host->status |= 0x0800; + } else { + qemu_irq_lower(host->dma[0]); + host->status &= 0xf7ff; + } +} + +typedef enum { + sd_nore = 0, /* no response */ + sd_r1, /* normal response command */ + sd_r2, /* CID, CSD registers */ + sd_r3, /* OCR register */ + sd_r6 = 6, /* Published RCA response */ + sd_r1b = -1, +} sd_rsp_type_t; + +static void omap_mmc_command(struct omap_mmc_s *host, int cmd, int dir, + sd_cmd_type_t type, int busy, sd_rsp_type_t resptype, int init) +{ + uint32_t rspstatus, mask; + int rsplen, timeout; + SDRequest request; + uint8_t response[16]; + + if (init && cmd == 0) { + host->status |= 0x0001; + return; + } + + if (resptype == sd_r1 && busy) + resptype = sd_r1b; + + if (type == sd_adtc) { + host->fifo_start = 0; + host->fifo_len = 0; + host->transfer = 1; + host->ddir = dir; + } else + host->transfer = 0; + timeout = 0; + mask = 0; + rspstatus = 0; + + request.cmd = cmd; + request.arg = host->arg; + request.crc = 0; /* FIXME */ + + rsplen = sd_do_command(host->card, &request, response); + + /* TODO: validate CRCs */ + switch (resptype) { + case sd_nore: + rsplen = 0; + break; + + case sd_r1: + case sd_r1b: + if (rsplen < 4) { + timeout = 1; + break; + } + rsplen = 4; + + mask = OUT_OF_RANGE | ADDRESS_ERROR | BLOCK_LEN_ERROR | + ERASE_SEQ_ERROR | ERASE_PARAM | WP_VIOLATION | + LOCK_UNLOCK_FAILED | COM_CRC_ERROR | ILLEGAL_COMMAND | + CARD_ECC_FAILED | CC_ERROR | SD_ERROR | + CID_CSD_OVERWRITE; + if (host->sdio & (1 << 13)) + mask |= AKE_SEQ_ERROR; + rspstatus = ldl_be_p(response); + break; + + case sd_r2: + if (rsplen < 16) { + timeout = 1; + break; + } + rsplen = 16; + break; + + case sd_r3: + if (rsplen < 4) { + timeout = 1; + break; + } + rsplen = 4; + + rspstatus = ldl_be_p(response); + if (rspstatus & 0x80000000) + host->status &= 0xe000; + else + host->status |= 0x1000; + break; + + case sd_r6: + if (rsplen < 4) { + timeout = 1; + break; + } + rsplen = 4; + + mask = 0xe000 | AKE_SEQ_ERROR; + rspstatus = (response[2] << 8) | (response[3] << 0); + } + + if (rspstatus & mask) + host->status |= 0x4000; + else + host->status &= 0xb000; + + if (rsplen) + for (rsplen = 0; rsplen < 8; rsplen ++) + host->rsp[~rsplen & 7] = response[(rsplen << 1) | 1] | + (response[(rsplen << 1) | 0] << 8); + + if (timeout) + host->status |= 0x0080; + else if (cmd == 12) + host->status |= 0x0005; /* Makes it more real */ + else + host->status |= 0x0001; +} + +static void omap_mmc_transfer(struct omap_mmc_s *host) +{ + uint8_t value; + + if (!host->transfer) + return; + + while (1) { + if (host->ddir) { + if (host->fifo_len > host->af_level) + break; + + value = sd_read_byte(host->card); + host->fifo[(host->fifo_start + host->fifo_len) & 31] = value; + if (-- host->blen_counter) { + value = sd_read_byte(host->card); + host->fifo[(host->fifo_start + host->fifo_len) & 31] |= + value << 8; + host->blen_counter --; + } + + host->fifo_len ++; + } else { + if (!host->fifo_len) + break; + + value = host->fifo[host->fifo_start] & 0xff; + sd_write_byte(host->card, value); + if (-- host->blen_counter) { + value = host->fifo[host->fifo_start] >> 8; + sd_write_byte(host->card, value); + host->blen_counter --; + } + + host->fifo_start ++; + host->fifo_len --; + host->fifo_start &= 31; + } + + if (host->blen_counter == 0) { + host->nblk_counter --; + host->blen_counter = host->blen; + + if (host->nblk_counter == 0) { + host->nblk_counter = host->nblk; + host->transfer = 0; + host->status |= 0x0008; + break; + } + } + } +} + +static void omap_mmc_update(void *opaque) +{ + struct omap_mmc_s *s = opaque; + omap_mmc_transfer(s); + omap_mmc_fifolevel_update(s); + omap_mmc_interrupts_update(s); +} + +static void omap_mmc_pseudo_reset(struct omap_mmc_s *host) +{ + host->status = 0; + host->fifo_len = 0; +} + +void omap_mmc_reset(struct omap_mmc_s *host) +{ + host->last_cmd = 0; + memset(host->rsp, 0, sizeof(host->rsp)); + host->arg = 0; + host->dw = 0; + host->mode = 0; + host->enable = 0; + host->mask = 0; + host->cto = 0; + host->dto = 0; + host->blen = 0; + host->blen_counter = 0; + host->nblk = 0; + host->nblk_counter = 0; + host->tx_dma = 0; + host->rx_dma = 0; + host->ae_level = 0x00; + host->af_level = 0x1f; + host->transfer = 0; + host->cdet_wakeup = 0; + host->cdet_enable = 0; + qemu_set_irq(host->coverswitch, host->cdet_state); + host->clkdiv = 0; + + omap_mmc_pseudo_reset(host); + + /* Since we're still using the legacy SD API the card is not plugged + * into any bus, and we must reset it manually. When omap_mmc is + * QOMified this must move into the QOM reset function. + */ + device_cold_reset(DEVICE(host->card)); +} + +static uint64_t omap_mmc_read(void *opaque, hwaddr offset, + unsigned size) +{ + uint16_t i; + struct omap_mmc_s *s = (struct omap_mmc_s *) opaque; + + if (size != 2) { + return omap_badwidth_read16(opaque, offset); + } + + switch (offset) { + case 0x00: /* MMC_CMD */ + return s->last_cmd; + + case 0x04: /* MMC_ARGL */ + return s->arg & 0x0000ffff; + + case 0x08: /* MMC_ARGH */ + return s->arg >> 16; + + case 0x0c: /* MMC_CON */ + return (s->dw << 15) | (s->mode << 12) | (s->enable << 11) | + (s->be << 10) | s->clkdiv; + + case 0x10: /* MMC_STAT */ + return s->status; + + case 0x14: /* MMC_IE */ + return s->mask; + + case 0x18: /* MMC_CTO */ + return s->cto; + + case 0x1c: /* MMC_DTO */ + return s->dto; + + case 0x20: /* MMC_DATA */ + /* TODO: support 8-bit access */ + i = s->fifo[s->fifo_start]; + if (s->fifo_len == 0) { + printf("MMC: FIFO underrun\n"); + return i; + } + s->fifo_start ++; + s->fifo_len --; + s->fifo_start &= 31; + omap_mmc_transfer(s); + omap_mmc_fifolevel_update(s); + omap_mmc_interrupts_update(s); + return i; + + case 0x24: /* MMC_BLEN */ + return s->blen_counter; + + case 0x28: /* MMC_NBLK */ + return s->nblk_counter; + + case 0x2c: /* MMC_BUF */ + return (s->rx_dma << 15) | (s->af_level << 8) | + (s->tx_dma << 7) | s->ae_level; + + case 0x30: /* MMC_SPI */ + return 0x0000; + case 0x34: /* MMC_SDIO */ + return (s->cdet_wakeup << 2) | (s->cdet_enable) | s->sdio; + case 0x38: /* MMC_SYST */ + return 0x0000; + + case 0x3c: /* MMC_REV */ + return s->rev; + + case 0x40: /* MMC_RSP0 */ + case 0x44: /* MMC_RSP1 */ + case 0x48: /* MMC_RSP2 */ + case 0x4c: /* MMC_RSP3 */ + case 0x50: /* MMC_RSP4 */ + case 0x54: /* MMC_RSP5 */ + case 0x58: /* MMC_RSP6 */ + case 0x5c: /* MMC_RSP7 */ + return s->rsp[(offset - 0x40) >> 2]; + + /* OMAP2-specific */ + case 0x60: /* MMC_IOSR */ + case 0x64: /* MMC_SYSC */ + return 0; + case 0x68: /* MMC_SYSS */ + return 1; /* RSTD */ + } + + OMAP_BAD_REG(offset); + return 0; +} + +static void omap_mmc_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + int i; + struct omap_mmc_s *s = (struct omap_mmc_s *) opaque; + + if (size != 2) { + omap_badwidth_write16(opaque, offset, value); + return; + } + + switch (offset) { + case 0x00: /* MMC_CMD */ + if (!s->enable) + break; + + s->last_cmd = value; + for (i = 0; i < 8; i ++) + s->rsp[i] = 0x0000; + omap_mmc_command(s, value & 63, (value >> 15) & 1, + (sd_cmd_type_t) ((value >> 12) & 3), + (value >> 11) & 1, + (sd_rsp_type_t) ((value >> 8) & 7), + (value >> 7) & 1); + omap_mmc_update(s); + break; + + case 0x04: /* MMC_ARGL */ + s->arg &= 0xffff0000; + s->arg |= 0x0000ffff & value; + break; + + case 0x08: /* MMC_ARGH */ + s->arg &= 0x0000ffff; + s->arg |= value << 16; + break; + + case 0x0c: /* MMC_CON */ + s->dw = (value >> 15) & 1; + s->mode = (value >> 12) & 3; + s->enable = (value >> 11) & 1; + s->be = (value >> 10) & 1; + s->clkdiv = (value >> 0) & (s->rev >= 2 ? 0x3ff : 0xff); + if (s->mode != 0) { + qemu_log_mask(LOG_UNIMP, + "omap_mmc_wr: mode #%i unimplemented\n", s->mode); + } + if (s->be != 0) { + qemu_log_mask(LOG_UNIMP, + "omap_mmc_wr: Big Endian not implemented\n"); + } + if (s->dw != 0 && s->lines < 4) + printf("4-bit SD bus enabled\n"); + if (!s->enable) + omap_mmc_pseudo_reset(s); + break; + + case 0x10: /* MMC_STAT */ + s->status &= ~value; + omap_mmc_interrupts_update(s); + break; + + case 0x14: /* MMC_IE */ + s->mask = value & 0x7fff; + omap_mmc_interrupts_update(s); + break; + + case 0x18: /* MMC_CTO */ + s->cto = value & 0xff; + if (s->cto > 0xfd && s->rev <= 1) + printf("MMC: CTO of 0xff and 0xfe cannot be used!\n"); + break; + + case 0x1c: /* MMC_DTO */ + s->dto = value & 0xffff; + break; + + case 0x20: /* MMC_DATA */ + /* TODO: support 8-bit access */ + if (s->fifo_len == 32) + break; + s->fifo[(s->fifo_start + s->fifo_len) & 31] = value; + s->fifo_len ++; + omap_mmc_transfer(s); + omap_mmc_fifolevel_update(s); + omap_mmc_interrupts_update(s); + break; + + case 0x24: /* MMC_BLEN */ + s->blen = (value & 0x07ff) + 1; + s->blen_counter = s->blen; + break; + + case 0x28: /* MMC_NBLK */ + s->nblk = (value & 0x07ff) + 1; + s->nblk_counter = s->nblk; + s->blen_counter = s->blen; + break; + + case 0x2c: /* MMC_BUF */ + s->rx_dma = (value >> 15) & 1; + s->af_level = (value >> 8) & 0x1f; + s->tx_dma = (value >> 7) & 1; + s->ae_level = value & 0x1f; + + if (s->rx_dma) + s->status &= 0xfbff; + if (s->tx_dma) + s->status &= 0xf7ff; + omap_mmc_fifolevel_update(s); + omap_mmc_interrupts_update(s); + break; + + /* SPI, SDIO and TEST modes unimplemented */ + case 0x30: /* MMC_SPI (OMAP1 only) */ + break; + case 0x34: /* MMC_SDIO */ + s->sdio = value & (s->rev >= 2 ? 0xfbf3 : 0x2020); + s->cdet_wakeup = (value >> 9) & 1; + s->cdet_enable = (value >> 2) & 1; + break; + case 0x38: /* MMC_SYST */ + break; + + case 0x3c: /* MMC_REV */ + case 0x40: /* MMC_RSP0 */ + case 0x44: /* MMC_RSP1 */ + case 0x48: /* MMC_RSP2 */ + case 0x4c: /* MMC_RSP3 */ + case 0x50: /* MMC_RSP4 */ + case 0x54: /* MMC_RSP5 */ + case 0x58: /* MMC_RSP6 */ + case 0x5c: /* MMC_RSP7 */ + OMAP_RO_REG(offset); + break; + + /* OMAP2-specific */ + case 0x60: /* MMC_IOSR */ + if (value & 0xf) + printf("MMC: SDIO bits used!\n"); + break; + case 0x64: /* MMC_SYSC */ + if (value & (1 << 2)) /* SRTS */ + omap_mmc_reset(s); + break; + case 0x68: /* MMC_SYSS */ + OMAP_RO_REG(offset); + break; + + default: + OMAP_BAD_REG(offset); + } +} + +static const MemoryRegionOps omap_mmc_ops = { + .read = omap_mmc_read, + .write = omap_mmc_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap_mmc_cover_cb(void *opaque, int line, int level) +{ + struct omap_mmc_s *host = (struct omap_mmc_s *) opaque; + + if (!host->cdet_state && level) { + host->status |= 0x0002; + omap_mmc_interrupts_update(host); + if (host->cdet_wakeup) { + /* TODO: Assert wake-up */ + } + } + + if (host->cdet_state != level) { + qemu_set_irq(host->coverswitch, level); + host->cdet_state = level; + } +} + +struct omap_mmc_s *omap_mmc_init(hwaddr base, + MemoryRegion *sysmem, + BlockBackend *blk, + qemu_irq irq, qemu_irq dma[], omap_clk clk) +{ + struct omap_mmc_s *s = g_new0(struct omap_mmc_s, 1); + + s->irq = irq; + s->dma = dma; + s->clk = clk; + s->lines = 1; /* TODO: needs to be settable per-board */ + s->rev = 1; + + memory_region_init_io(&s->iomem, NULL, &omap_mmc_ops, s, "omap.mmc", 0x800); + memory_region_add_subregion(sysmem, base, &s->iomem); + + /* Instantiate the storage */ + s->card = sd_init(blk, false); + if (s->card == NULL) { + exit(1); + } + + omap_mmc_reset(s); + + return s; +} + +struct omap_mmc_s *omap2_mmc_init(struct omap_target_agent_s *ta, + BlockBackend *blk, qemu_irq irq, qemu_irq dma[], + omap_clk fclk, omap_clk iclk) +{ + struct omap_mmc_s *s = g_new0(struct omap_mmc_s, 1); + + s->irq = irq; + s->dma = dma; + s->clk = fclk; + s->lines = 4; + s->rev = 2; + + memory_region_init_io(&s->iomem, NULL, &omap_mmc_ops, s, "omap.mmc", + omap_l4_region_size(ta, 0)); + omap_l4_attach(ta, 0, &s->iomem); + + /* Instantiate the storage */ + s->card = sd_init(blk, false); + if (s->card == NULL) { + exit(1); + } + + s->cdet = qemu_allocate_irq(omap_mmc_cover_cb, s, 0); + sd_set_cb(s->card, NULL, s->cdet); + + omap_mmc_reset(s); + + return s; +} + +void omap_mmc_handlers(struct omap_mmc_s *s, qemu_irq ro, qemu_irq cover) +{ + if (s->cdet) { + sd_set_cb(s->card, ro, s->cdet); + s->coverswitch = cover; + qemu_set_irq(cover, s->cdet_state); + } else + sd_set_cb(s->card, ro, cover); +} + +void omap_mmc_enable(struct omap_mmc_s *s, int enable) +{ + sd_enable(s->card, enable); +} diff --git a/hw/sd/pl181.c b/hw/sd/pl181.c new file mode 100644 index 000000000..5e554bd46 --- /dev/null +++ b/hw/sd/pl181.c @@ -0,0 +1,551 @@ +/* + * Arm PrimeCell PL181 MultiMedia Card Interface + * + * Copyright (c) 2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the GPL. + */ + +#include "qemu/osdep.h" +#include "sysemu/blockdev.h" +#include "hw/sysbus.h" +#include "migration/vmstate.h" +#include "hw/irq.h" +#include "hw/sd/sd.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/error-report.h" +#include "qapi/error.h" +#include "trace.h" +#include "qom/object.h" + +#define PL181_FIFO_LEN 16 + +#define TYPE_PL181 "pl181" +OBJECT_DECLARE_SIMPLE_TYPE(PL181State, PL181) + +#define TYPE_PL181_BUS "pl181-bus" + +struct PL181State { + SysBusDevice parent_obj; + + MemoryRegion iomem; + SDBus sdbus; + uint32_t clock; + uint32_t power; + uint32_t cmdarg; + uint32_t cmd; + uint32_t datatimer; + uint32_t datalength; + uint32_t respcmd; + uint32_t response[4]; + uint32_t datactrl; + uint32_t datacnt; + uint32_t status; + uint32_t mask[2]; + int32_t fifo_pos; + int32_t fifo_len; + /* The linux 2.6.21 driver is buggy, and misbehaves if new data arrives + while it is reading the FIFO. We hack around this by deferring + subsequent transfers until after the driver polls the status word. + http://www.arm.linux.org.uk/developer/patches/viewpatch.php?id=4446/1 + */ + int32_t linux_hack; + uint32_t fifo[PL181_FIFO_LEN]; /* TODO use Fifo32 */ + qemu_irq irq[2]; + /* GPIO outputs for 'card is readonly' and 'card inserted' */ + qemu_irq card_readonly; + qemu_irq card_inserted; +}; + +static const VMStateDescription vmstate_pl181 = { + .name = "pl181", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(clock, PL181State), + VMSTATE_UINT32(power, PL181State), + VMSTATE_UINT32(cmdarg, PL181State), + VMSTATE_UINT32(cmd, PL181State), + VMSTATE_UINT32(datatimer, PL181State), + VMSTATE_UINT32(datalength, PL181State), + VMSTATE_UINT32(respcmd, PL181State), + VMSTATE_UINT32_ARRAY(response, PL181State, 4), + VMSTATE_UINT32(datactrl, PL181State), + VMSTATE_UINT32(datacnt, PL181State), + VMSTATE_UINT32(status, PL181State), + VMSTATE_UINT32_ARRAY(mask, PL181State, 2), + VMSTATE_INT32(fifo_pos, PL181State), + VMSTATE_INT32(fifo_len, PL181State), + VMSTATE_INT32(linux_hack, PL181State), + VMSTATE_UINT32_ARRAY(fifo, PL181State, PL181_FIFO_LEN), + VMSTATE_END_OF_LIST() + } +}; + +#define PL181_CMD_INDEX 0x3f +#define PL181_CMD_RESPONSE (1 << 6) +#define PL181_CMD_LONGRESP (1 << 7) +#define PL181_CMD_INTERRUPT (1 << 8) +#define PL181_CMD_PENDING (1 << 9) +#define PL181_CMD_ENABLE (1 << 10) + +#define PL181_DATA_ENABLE (1 << 0) +#define PL181_DATA_DIRECTION (1 << 1) +#define PL181_DATA_MODE (1 << 2) +#define PL181_DATA_DMAENABLE (1 << 3) + +#define PL181_STATUS_CMDCRCFAIL (1 << 0) +#define PL181_STATUS_DATACRCFAIL (1 << 1) +#define PL181_STATUS_CMDTIMEOUT (1 << 2) +#define PL181_STATUS_DATATIMEOUT (1 << 3) +#define PL181_STATUS_TXUNDERRUN (1 << 4) +#define PL181_STATUS_RXOVERRUN (1 << 5) +#define PL181_STATUS_CMDRESPEND (1 << 6) +#define PL181_STATUS_CMDSENT (1 << 7) +#define PL181_STATUS_DATAEND (1 << 8) +#define PL181_STATUS_DATABLOCKEND (1 << 10) +#define PL181_STATUS_CMDACTIVE (1 << 11) +#define PL181_STATUS_TXACTIVE (1 << 12) +#define PL181_STATUS_RXACTIVE (1 << 13) +#define PL181_STATUS_TXFIFOHALFEMPTY (1 << 14) +#define PL181_STATUS_RXFIFOHALFFULL (1 << 15) +#define PL181_STATUS_TXFIFOFULL (1 << 16) +#define PL181_STATUS_RXFIFOFULL (1 << 17) +#define PL181_STATUS_TXFIFOEMPTY (1 << 18) +#define PL181_STATUS_RXFIFOEMPTY (1 << 19) +#define PL181_STATUS_TXDATAAVLBL (1 << 20) +#define PL181_STATUS_RXDATAAVLBL (1 << 21) + +#define PL181_STATUS_TX_FIFO (PL181_STATUS_TXACTIVE \ + |PL181_STATUS_TXFIFOHALFEMPTY \ + |PL181_STATUS_TXFIFOFULL \ + |PL181_STATUS_TXFIFOEMPTY \ + |PL181_STATUS_TXDATAAVLBL) +#define PL181_STATUS_RX_FIFO (PL181_STATUS_RXACTIVE \ + |PL181_STATUS_RXFIFOHALFFULL \ + |PL181_STATUS_RXFIFOFULL \ + |PL181_STATUS_RXFIFOEMPTY \ + |PL181_STATUS_RXDATAAVLBL) + +static const unsigned char pl181_id[] = +{ 0x81, 0x11, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; + +static void pl181_update(PL181State *s) +{ + int i; + for (i = 0; i < 2; i++) { + qemu_set_irq(s->irq[i], (s->status & s->mask[i]) != 0); + } +} + +static void pl181_fifo_push(PL181State *s, uint32_t value) +{ + int n; + + if (s->fifo_len == PL181_FIFO_LEN) { + error_report("%s: FIFO overflow", __func__); + return; + } + n = (s->fifo_pos + s->fifo_len) & (PL181_FIFO_LEN - 1); + s->fifo_len++; + s->fifo[n] = value; + trace_pl181_fifo_push(value); +} + +static uint32_t pl181_fifo_pop(PL181State *s) +{ + uint32_t value; + + if (s->fifo_len == 0) { + error_report("%s: FIFO underflow", __func__); + return 0; + } + value = s->fifo[s->fifo_pos]; + s->fifo_len--; + s->fifo_pos = (s->fifo_pos + 1) & (PL181_FIFO_LEN - 1); + trace_pl181_fifo_pop(value); + return value; +} + +static void pl181_do_command(PL181State *s) +{ + SDRequest request; + uint8_t response[16]; + int rlen; + + request.cmd = s->cmd & PL181_CMD_INDEX; + request.arg = s->cmdarg; + trace_pl181_command_send(request.cmd, request.arg); + rlen = sdbus_do_command(&s->sdbus, &request, response); + if (rlen < 0) + goto error; + if (s->cmd & PL181_CMD_RESPONSE) { + if (rlen == 0 || (rlen == 4 && (s->cmd & PL181_CMD_LONGRESP))) + goto error; + if (rlen != 4 && rlen != 16) + goto error; + s->response[0] = ldl_be_p(&response[0]); + if (rlen == 4) { + s->response[1] = s->response[2] = s->response[3] = 0; + } else { + s->response[1] = ldl_be_p(&response[4]); + s->response[2] = ldl_be_p(&response[8]); + s->response[3] = ldl_be_p(&response[12]) & ~1; + } + trace_pl181_command_response_pending(); + s->status |= PL181_STATUS_CMDRESPEND; + } else { + trace_pl181_command_sent(); + s->status |= PL181_STATUS_CMDSENT; + } + return; + +error: + trace_pl181_command_timeout(); + s->status |= PL181_STATUS_CMDTIMEOUT; +} + +/* Transfer data between the card and the FIFO. This is complicated by + the FIFO holding 32-bit words and the card taking data in single byte + chunks. FIFO bytes are transferred in little-endian order. */ + +static void pl181_fifo_run(PL181State *s) +{ + uint32_t bits; + uint32_t value = 0; + int n; + int is_read; + + is_read = (s->datactrl & PL181_DATA_DIRECTION) != 0; + if (s->datacnt != 0 && (!is_read || sdbus_data_ready(&s->sdbus)) + && !s->linux_hack) { + if (is_read) { + n = 0; + while (s->datacnt && s->fifo_len < PL181_FIFO_LEN) { + value |= (uint32_t)sdbus_read_byte(&s->sdbus) << (n * 8); + s->datacnt--; + n++; + if (n == 4) { + pl181_fifo_push(s, value); + n = 0; + value = 0; + } + } + if (n != 0) { + pl181_fifo_push(s, value); + } + } else { /* write */ + n = 0; + while (s->datacnt > 0 && (s->fifo_len > 0 || n > 0)) { + if (n == 0) { + value = pl181_fifo_pop(s); + n = 4; + } + n--; + s->datacnt--; + sdbus_write_byte(&s->sdbus, value & 0xff); + value >>= 8; + } + } + } + s->status &= ~(PL181_STATUS_RX_FIFO | PL181_STATUS_TX_FIFO); + if (s->datacnt == 0) { + s->status |= PL181_STATUS_DATAEND; + /* HACK: */ + s->status |= PL181_STATUS_DATABLOCKEND; + trace_pl181_fifo_transfer_complete(); + } + if (s->datacnt == 0 && s->fifo_len == 0) { + s->datactrl &= ~PL181_DATA_ENABLE; + trace_pl181_data_engine_idle(); + } else { + /* Update FIFO bits. */ + bits = PL181_STATUS_TXACTIVE | PL181_STATUS_RXACTIVE; + if (s->fifo_len == 0) { + bits |= PL181_STATUS_TXFIFOEMPTY; + bits |= PL181_STATUS_RXFIFOEMPTY; + } else { + bits |= PL181_STATUS_TXDATAAVLBL; + bits |= PL181_STATUS_RXDATAAVLBL; + } + if (s->fifo_len == 16) { + bits |= PL181_STATUS_TXFIFOFULL; + bits |= PL181_STATUS_RXFIFOFULL; + } + if (s->fifo_len <= 8) { + bits |= PL181_STATUS_TXFIFOHALFEMPTY; + } + if (s->fifo_len >= 8) { + bits |= PL181_STATUS_RXFIFOHALFFULL; + } + if (s->datactrl & PL181_DATA_DIRECTION) { + bits &= PL181_STATUS_RX_FIFO; + } else { + bits &= PL181_STATUS_TX_FIFO; + } + s->status |= bits; + } +} + +static uint64_t pl181_read(void *opaque, hwaddr offset, + unsigned size) +{ + PL181State *s = (PL181State *)opaque; + uint32_t tmp; + + if (offset >= 0xfe0 && offset < 0x1000) { + return pl181_id[(offset - 0xfe0) >> 2]; + } + switch (offset) { + case 0x00: /* Power */ + return s->power; + case 0x04: /* Clock */ + return s->clock; + case 0x08: /* Argument */ + return s->cmdarg; + case 0x0c: /* Command */ + return s->cmd; + case 0x10: /* RespCmd */ + return s->respcmd; + case 0x14: /* Response0 */ + return s->response[0]; + case 0x18: /* Response1 */ + return s->response[1]; + case 0x1c: /* Response2 */ + return s->response[2]; + case 0x20: /* Response3 */ + return s->response[3]; + case 0x24: /* DataTimer */ + return s->datatimer; + case 0x28: /* DataLength */ + return s->datalength; + case 0x2c: /* DataCtrl */ + return s->datactrl; + case 0x30: /* DataCnt */ + return s->datacnt; + case 0x34: /* Status */ + tmp = s->status; + if (s->linux_hack) { + s->linux_hack = 0; + pl181_fifo_run(s); + pl181_update(s); + } + return tmp; + case 0x3c: /* Mask0 */ + return s->mask[0]; + case 0x40: /* Mask1 */ + return s->mask[1]; + case 0x48: /* FifoCnt */ + /* The documentation is somewhat vague about exactly what FifoCnt + does. On real hardware it appears to be when decrememnted + when a word is transferred between the FIFO and the serial + data engine. DataCnt is decremented after each byte is + transferred between the serial engine and the card. + We don't emulate this level of detail, so both can be the same. */ + tmp = (s->datacnt + 3) >> 2; + if (s->linux_hack) { + s->linux_hack = 0; + pl181_fifo_run(s); + pl181_update(s); + } + return tmp; + case 0x80: case 0x84: case 0x88: case 0x8c: /* FifoData */ + case 0x90: case 0x94: case 0x98: case 0x9c: + case 0xa0: case 0xa4: case 0xa8: case 0xac: + case 0xb0: case 0xb4: case 0xb8: case 0xbc: + if (s->fifo_len == 0) { + qemu_log_mask(LOG_GUEST_ERROR, "pl181: Unexpected FIFO read\n"); + return 0; + } else { + uint32_t value; + value = pl181_fifo_pop(s); + s->linux_hack = 1; + pl181_fifo_run(s); + pl181_update(s); + return value; + } + default: + qemu_log_mask(LOG_GUEST_ERROR, + "pl181_read: Bad offset %x\n", (int)offset); + return 0; + } +} + +static void pl181_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + PL181State *s = (PL181State *)opaque; + + switch (offset) { + case 0x00: /* Power */ + s->power = value & 0xff; + break; + case 0x04: /* Clock */ + s->clock = value & 0xff; + break; + case 0x08: /* Argument */ + s->cmdarg = value; + break; + case 0x0c: /* Command */ + s->cmd = value; + if (s->cmd & PL181_CMD_ENABLE) { + if (s->cmd & PL181_CMD_INTERRUPT) { + qemu_log_mask(LOG_UNIMP, + "pl181: Interrupt mode not implemented\n"); + } if (s->cmd & PL181_CMD_PENDING) { + qemu_log_mask(LOG_UNIMP, + "pl181: Pending commands not implemented\n"); + } else { + pl181_do_command(s); + pl181_fifo_run(s); + } + /* The command has completed one way or the other. */ + s->cmd &= ~PL181_CMD_ENABLE; + } + break; + case 0x24: /* DataTimer */ + s->datatimer = value; + break; + case 0x28: /* DataLength */ + s->datalength = value & 0xffff; + break; + case 0x2c: /* DataCtrl */ + s->datactrl = value & 0xff; + if (value & PL181_DATA_ENABLE) { + s->datacnt = s->datalength; + pl181_fifo_run(s); + } + break; + case 0x38: /* Clear */ + s->status &= ~(value & 0x7ff); + break; + case 0x3c: /* Mask0 */ + s->mask[0] = value; + break; + case 0x40: /* Mask1 */ + s->mask[1] = value; + break; + case 0x80: case 0x84: case 0x88: case 0x8c: /* FifoData */ + case 0x90: case 0x94: case 0x98: case 0x9c: + case 0xa0: case 0xa4: case 0xa8: case 0xac: + case 0xb0: case 0xb4: case 0xb8: case 0xbc: + if (s->datacnt == 0) { + qemu_log_mask(LOG_GUEST_ERROR, "pl181: Unexpected FIFO write\n"); + } else { + pl181_fifo_push(s, value); + pl181_fifo_run(s); + } + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "pl181_write: Bad offset %x\n", (int)offset); + } + pl181_update(s); +} + +static const MemoryRegionOps pl181_ops = { + .read = pl181_read, + .write = pl181_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void pl181_set_readonly(DeviceState *dev, bool level) +{ + PL181State *s = (PL181State *)dev; + + qemu_set_irq(s->card_readonly, level); +} + +static void pl181_set_inserted(DeviceState *dev, bool level) +{ + PL181State *s = (PL181State *)dev; + + qemu_set_irq(s->card_inserted, level); +} + +static void pl181_reset(DeviceState *d) +{ + PL181State *s = PL181(d); + + s->power = 0; + s->cmdarg = 0; + s->cmd = 0; + s->datatimer = 0; + s->datalength = 0; + s->respcmd = 0; + s->response[0] = 0; + s->response[1] = 0; + s->response[2] = 0; + s->response[3] = 0; + s->datatimer = 0; + s->datalength = 0; + s->datactrl = 0; + s->datacnt = 0; + s->status = 0; + s->linux_hack = 0; + s->mask[0] = 0; + s->mask[1] = 0; + + /* Reset other state based on current card insertion/readonly status */ + pl181_set_inserted(DEVICE(s), sdbus_get_inserted(&s->sdbus)); + pl181_set_readonly(DEVICE(s), sdbus_get_readonly(&s->sdbus)); +} + +static void pl181_init(Object *obj) +{ + DeviceState *dev = DEVICE(obj); + PL181State *s = PL181(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + memory_region_init_io(&s->iomem, obj, &pl181_ops, s, "pl181", 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq[0]); + sysbus_init_irq(sbd, &s->irq[1]); + qdev_init_gpio_out_named(dev, &s->card_readonly, "card-read-only", 1); + qdev_init_gpio_out_named(dev, &s->card_inserted, "card-inserted", 1); + + qbus_init(&s->sdbus, sizeof(s->sdbus), TYPE_PL181_BUS, dev, "sd-bus"); +} + +static void pl181_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *k = DEVICE_CLASS(klass); + + k->vmsd = &vmstate_pl181; + k->reset = pl181_reset; + /* Reason: output IRQs should be wired up */ + k->user_creatable = false; +} + +static const TypeInfo pl181_info = { + .name = TYPE_PL181, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(PL181State), + .instance_init = pl181_init, + .class_init = pl181_class_init, +}; + +static void pl181_bus_class_init(ObjectClass *klass, void *data) +{ + SDBusClass *sbc = SD_BUS_CLASS(klass); + + sbc->set_inserted = pl181_set_inserted; + sbc->set_readonly = pl181_set_readonly; +} + +static const TypeInfo pl181_bus_info = { + .name = TYPE_PL181_BUS, + .parent = TYPE_SD_BUS, + .instance_size = sizeof(SDBus), + .class_init = pl181_bus_class_init, +}; + +static void pl181_register_types(void) +{ + type_register_static(&pl181_info); + type_register_static(&pl181_bus_info); +} + +type_init(pl181_register_types) diff --git a/hw/sd/pxa2xx_mmci.c b/hw/sd/pxa2xx_mmci.c new file mode 100644 index 000000000..124fbf8bb --- /dev/null +++ b/hw/sd/pxa2xx_mmci.c @@ -0,0 +1,604 @@ +/* + * Intel XScale PXA255/270 MultiMediaCard/SD/SDIO Controller emulation. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski <balrog@zabor.org> + * + * This code is licensed under the GPLv2. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "migration/vmstate.h" +#include "hw/arm/pxa.h" +#include "hw/sd/sd.h" +#include "hw/qdev-properties.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "trace.h" +#include "qom/object.h" + +#define TYPE_PXA2XX_MMCI_BUS "pxa2xx-mmci-bus" +/* This is reusing the SDBus typedef from SD_BUS */ +DECLARE_INSTANCE_CHECKER(SDBus, PXA2XX_MMCI_BUS, + TYPE_PXA2XX_MMCI_BUS) + +struct PXA2xxMMCIState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + qemu_irq irq; + qemu_irq rx_dma; + qemu_irq tx_dma; + qemu_irq inserted; + qemu_irq readonly; + + BlockBackend *blk; + SDBus sdbus; + + uint32_t status; + uint32_t clkrt; + uint32_t spi; + uint32_t cmdat; + uint32_t resp_tout; + uint32_t read_tout; + int32_t blklen; + int32_t numblk; + uint32_t intmask; + uint32_t intreq; + int32_t cmd; + uint32_t arg; + + int32_t active; + int32_t bytesleft; + uint8_t tx_fifo[64]; + uint32_t tx_start; + uint32_t tx_len; + uint8_t rx_fifo[32]; + uint32_t rx_start; + uint32_t rx_len; + uint16_t resp_fifo[9]; + uint32_t resp_len; + + int32_t cmdreq; +}; + +static bool pxa2xx_mmci_vmstate_validate(void *opaque, int version_id) +{ + PXA2xxMMCIState *s = opaque; + + return s->tx_start < ARRAY_SIZE(s->tx_fifo) + && s->rx_start < ARRAY_SIZE(s->rx_fifo) + && s->tx_len <= ARRAY_SIZE(s->tx_fifo) + && s->rx_len <= ARRAY_SIZE(s->rx_fifo) + && s->resp_len <= ARRAY_SIZE(s->resp_fifo); +} + + +static const VMStateDescription vmstate_pxa2xx_mmci = { + .name = "pxa2xx-mmci", + .version_id = 2, + .minimum_version_id = 2, + .fields = (VMStateField[]) { + VMSTATE_UINT32(status, PXA2xxMMCIState), + VMSTATE_UINT32(clkrt, PXA2xxMMCIState), + VMSTATE_UINT32(spi, PXA2xxMMCIState), + VMSTATE_UINT32(cmdat, PXA2xxMMCIState), + VMSTATE_UINT32(resp_tout, PXA2xxMMCIState), + VMSTATE_UINT32(read_tout, PXA2xxMMCIState), + VMSTATE_INT32(blklen, PXA2xxMMCIState), + VMSTATE_INT32(numblk, PXA2xxMMCIState), + VMSTATE_UINT32(intmask, PXA2xxMMCIState), + VMSTATE_UINT32(intreq, PXA2xxMMCIState), + VMSTATE_INT32(cmd, PXA2xxMMCIState), + VMSTATE_UINT32(arg, PXA2xxMMCIState), + VMSTATE_INT32(cmdreq, PXA2xxMMCIState), + VMSTATE_INT32(active, PXA2xxMMCIState), + VMSTATE_INT32(bytesleft, PXA2xxMMCIState), + VMSTATE_UINT32(tx_start, PXA2xxMMCIState), + VMSTATE_UINT32(tx_len, PXA2xxMMCIState), + VMSTATE_UINT32(rx_start, PXA2xxMMCIState), + VMSTATE_UINT32(rx_len, PXA2xxMMCIState), + VMSTATE_UINT32(resp_len, PXA2xxMMCIState), + VMSTATE_VALIDATE("fifo size incorrect", pxa2xx_mmci_vmstate_validate), + VMSTATE_UINT8_ARRAY(tx_fifo, PXA2xxMMCIState, 64), + VMSTATE_UINT8_ARRAY(rx_fifo, PXA2xxMMCIState, 32), + VMSTATE_UINT16_ARRAY(resp_fifo, PXA2xxMMCIState, 9), + VMSTATE_END_OF_LIST() + } +}; + +#define MMC_STRPCL 0x00 /* MMC Clock Start/Stop register */ +#define MMC_STAT 0x04 /* MMC Status register */ +#define MMC_CLKRT 0x08 /* MMC Clock Rate register */ +#define MMC_SPI 0x0c /* MMC SPI Mode register */ +#define MMC_CMDAT 0x10 /* MMC Command/Data register */ +#define MMC_RESTO 0x14 /* MMC Response Time-Out register */ +#define MMC_RDTO 0x18 /* MMC Read Time-Out register */ +#define MMC_BLKLEN 0x1c /* MMC Block Length register */ +#define MMC_NUMBLK 0x20 /* MMC Number of Blocks register */ +#define MMC_PRTBUF 0x24 /* MMC Buffer Partly Full register */ +#define MMC_I_MASK 0x28 /* MMC Interrupt Mask register */ +#define MMC_I_REG 0x2c /* MMC Interrupt Request register */ +#define MMC_CMD 0x30 /* MMC Command register */ +#define MMC_ARGH 0x34 /* MMC Argument High register */ +#define MMC_ARGL 0x38 /* MMC Argument Low register */ +#define MMC_RES 0x3c /* MMC Response FIFO */ +#define MMC_RXFIFO 0x40 /* MMC Receive FIFO */ +#define MMC_TXFIFO 0x44 /* MMC Transmit FIFO */ +#define MMC_RDWAIT 0x48 /* MMC RD_WAIT register */ +#define MMC_BLKS_REM 0x4c /* MMC Blocks Remaining register */ + +/* Bitfield masks */ +#define STRPCL_STOP_CLK (1 << 0) +#define STRPCL_STRT_CLK (1 << 1) +#define STAT_TOUT_RES (1 << 1) +#define STAT_CLK_EN (1 << 8) +#define STAT_DATA_DONE (1 << 11) +#define STAT_PRG_DONE (1 << 12) +#define STAT_END_CMDRES (1 << 13) +#define SPI_SPI_MODE (1 << 0) +#define CMDAT_RES_TYPE (3 << 0) +#define CMDAT_DATA_EN (1 << 2) +#define CMDAT_WR_RD (1 << 3) +#define CMDAT_DMA_EN (1 << 7) +#define CMDAT_STOP_TRAN (1 << 10) +#define INT_DATA_DONE (1 << 0) +#define INT_PRG_DONE (1 << 1) +#define INT_END_CMD (1 << 2) +#define INT_STOP_CMD (1 << 3) +#define INT_CLK_OFF (1 << 4) +#define INT_RXFIFO_REQ (1 << 5) +#define INT_TXFIFO_REQ (1 << 6) +#define INT_TINT (1 << 7) +#define INT_DAT_ERR (1 << 8) +#define INT_RES_ERR (1 << 9) +#define INT_RD_STALLED (1 << 10) +#define INT_SDIO_INT (1 << 11) +#define INT_SDIO_SACK (1 << 12) +#define PRTBUF_PRT_BUF (1 << 0) + +/* Route internal interrupt lines to the global IC and DMA */ +static void pxa2xx_mmci_int_update(PXA2xxMMCIState *s) +{ + uint32_t mask = s->intmask; + if (s->cmdat & CMDAT_DMA_EN) { + mask |= INT_RXFIFO_REQ | INT_TXFIFO_REQ; + + qemu_set_irq(s->rx_dma, !!(s->intreq & INT_RXFIFO_REQ)); + qemu_set_irq(s->tx_dma, !!(s->intreq & INT_TXFIFO_REQ)); + } + + qemu_set_irq(s->irq, !!(s->intreq & ~mask)); +} + +static void pxa2xx_mmci_fifo_update(PXA2xxMMCIState *s) +{ + if (!s->active) + return; + + if (s->cmdat & CMDAT_WR_RD) { + while (s->bytesleft && s->tx_len) { + sdbus_write_byte(&s->sdbus, s->tx_fifo[s->tx_start++]); + s->tx_start &= 0x1f; + s->tx_len --; + s->bytesleft --; + } + if (s->bytesleft) + s->intreq |= INT_TXFIFO_REQ; + } else + while (s->bytesleft && s->rx_len < 32) { + s->rx_fifo[(s->rx_start + (s->rx_len ++)) & 0x1f] = + sdbus_read_byte(&s->sdbus); + s->bytesleft --; + s->intreq |= INT_RXFIFO_REQ; + } + + if (!s->bytesleft) { + s->active = 0; + s->intreq |= INT_DATA_DONE; + s->status |= STAT_DATA_DONE; + + if (s->cmdat & CMDAT_WR_RD) { + s->intreq |= INT_PRG_DONE; + s->status |= STAT_PRG_DONE; + } + } + + pxa2xx_mmci_int_update(s); +} + +static void pxa2xx_mmci_wakequeues(PXA2xxMMCIState *s) +{ + int rsplen, i; + SDRequest request; + uint8_t response[16]; + + s->active = 1; + s->rx_len = 0; + s->tx_len = 0; + s->cmdreq = 0; + + request.cmd = s->cmd; + request.arg = s->arg; + request.crc = 0; /* FIXME */ + + rsplen = sdbus_do_command(&s->sdbus, &request, response); + s->intreq |= INT_END_CMD; + + memset(s->resp_fifo, 0, sizeof(s->resp_fifo)); + switch (s->cmdat & CMDAT_RES_TYPE) { +#define PXAMMCI_RESP(wd, value0, value1) \ + s->resp_fifo[(wd) + 0] |= (value0); \ + s->resp_fifo[(wd) + 1] |= (value1) << 8; + case 0: /* No response */ + goto complete; + + case 1: /* R1, R4, R5 or R6 */ + if (rsplen < 4) + goto timeout; + goto complete; + + case 2: /* R2 */ + if (rsplen < 16) + goto timeout; + goto complete; + + case 3: /* R3 */ + if (rsplen < 4) + goto timeout; + goto complete; + + complete: + for (i = 0; rsplen > 0; i ++, rsplen -= 2) { + PXAMMCI_RESP(i, response[i * 2], response[i * 2 + 1]); + } + s->status |= STAT_END_CMDRES; + + if (!(s->cmdat & CMDAT_DATA_EN)) + s->active = 0; + else + s->bytesleft = s->numblk * s->blklen; + + s->resp_len = 0; + break; + + timeout: + s->active = 0; + s->status |= STAT_TOUT_RES; + break; + } + + pxa2xx_mmci_fifo_update(s); +} + +static uint64_t pxa2xx_mmci_read(void *opaque, hwaddr offset, unsigned size) +{ + PXA2xxMMCIState *s = (PXA2xxMMCIState *) opaque; + uint32_t ret = 0; + + switch (offset) { + case MMC_STRPCL: + break; + case MMC_STAT: + ret = s->status; + break; + case MMC_CLKRT: + ret = s->clkrt; + break; + case MMC_SPI: + ret = s->spi; + break; + case MMC_CMDAT: + ret = s->cmdat; + break; + case MMC_RESTO: + ret = s->resp_tout; + break; + case MMC_RDTO: + ret = s->read_tout; + break; + case MMC_BLKLEN: + ret = s->blklen; + break; + case MMC_NUMBLK: + ret = s->numblk; + break; + case MMC_PRTBUF: + break; + case MMC_I_MASK: + ret = s->intmask; + break; + case MMC_I_REG: + ret = s->intreq; + break; + case MMC_CMD: + ret = s->cmd | 0x40; + break; + case MMC_ARGH: + ret = s->arg >> 16; + break; + case MMC_ARGL: + ret = s->arg & 0xffff; + break; + case MMC_RES: + ret = (s->resp_len < 9) ? s->resp_fifo[s->resp_len++] : 0; + break; + case MMC_RXFIFO: + while (size-- && s->rx_len) { + ret |= s->rx_fifo[s->rx_start++] << (size << 3); + s->rx_start &= 0x1f; + s->rx_len --; + } + s->intreq &= ~INT_RXFIFO_REQ; + pxa2xx_mmci_fifo_update(s); + break; + case MMC_RDWAIT: + break; + case MMC_BLKS_REM: + ret = s->numblk; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: incorrect register 0x%02" HWADDR_PRIx "\n", + __func__, offset); + } + trace_pxa2xx_mmci_read(size, offset, ret); + + return ret; +} + +static void pxa2xx_mmci_write(void *opaque, + hwaddr offset, uint64_t value, unsigned size) +{ + PXA2xxMMCIState *s = (PXA2xxMMCIState *) opaque; + + trace_pxa2xx_mmci_write(size, offset, value); + switch (offset) { + case MMC_STRPCL: + if (value & STRPCL_STRT_CLK) { + s->status |= STAT_CLK_EN; + s->intreq &= ~INT_CLK_OFF; + + if (s->cmdreq && !(s->cmdat & CMDAT_STOP_TRAN)) { + s->status &= STAT_CLK_EN; + pxa2xx_mmci_wakequeues(s); + } + } + + if (value & STRPCL_STOP_CLK) { + s->status &= ~STAT_CLK_EN; + s->intreq |= INT_CLK_OFF; + s->active = 0; + } + + pxa2xx_mmci_int_update(s); + break; + + case MMC_CLKRT: + s->clkrt = value & 7; + break; + + case MMC_SPI: + s->spi = value & 0xf; + if (value & SPI_SPI_MODE) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: attempted to use card in SPI mode\n", __func__); + } + break; + + case MMC_CMDAT: + s->cmdat = value & 0x3dff; + s->active = 0; + s->cmdreq = 1; + if (!(value & CMDAT_STOP_TRAN)) { + s->status &= STAT_CLK_EN; + + if (s->status & STAT_CLK_EN) + pxa2xx_mmci_wakequeues(s); + } + + pxa2xx_mmci_int_update(s); + break; + + case MMC_RESTO: + s->resp_tout = value & 0x7f; + break; + + case MMC_RDTO: + s->read_tout = value & 0xffff; + break; + + case MMC_BLKLEN: + s->blklen = value & 0xfff; + break; + + case MMC_NUMBLK: + s->numblk = value & 0xffff; + break; + + case MMC_PRTBUF: + if (value & PRTBUF_PRT_BUF) { + s->tx_start ^= 32; + s->tx_len = 0; + } + pxa2xx_mmci_fifo_update(s); + break; + + case MMC_I_MASK: + s->intmask = value & 0x1fff; + pxa2xx_mmci_int_update(s); + break; + + case MMC_CMD: + s->cmd = value & 0x3f; + break; + + case MMC_ARGH: + s->arg &= 0x0000ffff; + s->arg |= value << 16; + break; + + case MMC_ARGL: + s->arg &= 0xffff0000; + s->arg |= value & 0x0000ffff; + break; + + case MMC_TXFIFO: + while (size-- && s->tx_len < 0x20) + s->tx_fifo[(s->tx_start + (s->tx_len ++)) & 0x1f] = + (value >> (size << 3)) & 0xff; + s->intreq &= ~INT_TXFIFO_REQ; + pxa2xx_mmci_fifo_update(s); + break; + + case MMC_RDWAIT: + case MMC_BLKS_REM: + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: incorrect reg 0x%02" HWADDR_PRIx " " + "(value 0x%08" PRIx64 ")\n", __func__, offset, value); + } +} + +static const MemoryRegionOps pxa2xx_mmci_ops = { + .read = pxa2xx_mmci_read, + .write = pxa2xx_mmci_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +PXA2xxMMCIState *pxa2xx_mmci_init(MemoryRegion *sysmem, + hwaddr base, + qemu_irq irq, qemu_irq rx_dma, qemu_irq tx_dma) +{ + DeviceState *dev; + SysBusDevice *sbd; + + dev = qdev_new(TYPE_PXA2XX_MMCI); + sbd = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(sbd, 0, base); + sysbus_connect_irq(sbd, 0, irq); + qdev_connect_gpio_out_named(dev, "rx-dma", 0, rx_dma); + qdev_connect_gpio_out_named(dev, "tx-dma", 0, tx_dma); + sysbus_realize_and_unref(sbd, &error_fatal); + + return PXA2XX_MMCI(dev); +} + +static void pxa2xx_mmci_set_inserted(DeviceState *dev, bool inserted) +{ + PXA2xxMMCIState *s = PXA2XX_MMCI(dev); + + qemu_set_irq(s->inserted, inserted); +} + +static void pxa2xx_mmci_set_readonly(DeviceState *dev, bool readonly) +{ + PXA2xxMMCIState *s = PXA2XX_MMCI(dev); + + qemu_set_irq(s->readonly, readonly); +} + +void pxa2xx_mmci_handlers(PXA2xxMMCIState *s, qemu_irq readonly, + qemu_irq coverswitch) +{ + DeviceState *dev = DEVICE(s); + + s->readonly = readonly; + s->inserted = coverswitch; + + pxa2xx_mmci_set_inserted(dev, sdbus_get_inserted(&s->sdbus)); + pxa2xx_mmci_set_readonly(dev, sdbus_get_readonly(&s->sdbus)); +} + +static void pxa2xx_mmci_reset(DeviceState *d) +{ + PXA2xxMMCIState *s = PXA2XX_MMCI(d); + + s->status = 0; + s->clkrt = 0; + s->spi = 0; + s->cmdat = 0; + s->resp_tout = 0; + s->read_tout = 0; + s->blklen = 0; + s->numblk = 0; + s->intmask = 0; + s->intreq = 0; + s->cmd = 0; + s->arg = 0; + s->active = 0; + s->bytesleft = 0; + s->tx_start = 0; + s->tx_len = 0; + s->rx_start = 0; + s->rx_len = 0; + s->resp_len = 0; + s->cmdreq = 0; + memset(s->tx_fifo, 0, sizeof(s->tx_fifo)); + memset(s->rx_fifo, 0, sizeof(s->rx_fifo)); + memset(s->resp_fifo, 0, sizeof(s->resp_fifo)); +} + +static void pxa2xx_mmci_instance_init(Object *obj) +{ + PXA2xxMMCIState *s = PXA2XX_MMCI(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + DeviceState *dev = DEVICE(obj); + + memory_region_init_io(&s->iomem, obj, &pxa2xx_mmci_ops, s, + "pxa2xx-mmci", 0x00100000); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq); + qdev_init_gpio_out_named(dev, &s->rx_dma, "rx-dma", 1); + qdev_init_gpio_out_named(dev, &s->tx_dma, "tx-dma", 1); + + qbus_init(&s->sdbus, sizeof(s->sdbus), + TYPE_PXA2XX_MMCI_BUS, DEVICE(obj), "sd-bus"); +} + +static void pxa2xx_mmci_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->vmsd = &vmstate_pxa2xx_mmci; + dc->reset = pxa2xx_mmci_reset; +} + +static void pxa2xx_mmci_bus_class_init(ObjectClass *klass, void *data) +{ + SDBusClass *sbc = SD_BUS_CLASS(klass); + + sbc->set_inserted = pxa2xx_mmci_set_inserted; + sbc->set_readonly = pxa2xx_mmci_set_readonly; +} + +static const TypeInfo pxa2xx_mmci_info = { + .name = TYPE_PXA2XX_MMCI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(PXA2xxMMCIState), + .instance_init = pxa2xx_mmci_instance_init, + .class_init = pxa2xx_mmci_class_init, +}; + +static const TypeInfo pxa2xx_mmci_bus_info = { + .name = TYPE_PXA2XX_MMCI_BUS, + .parent = TYPE_SD_BUS, + .instance_size = sizeof(SDBus), + .class_init = pxa2xx_mmci_bus_class_init, +}; + +static void pxa2xx_mmci_register_types(void) +{ + type_register_static(&pxa2xx_mmci_info); + type_register_static(&pxa2xx_mmci_bus_info); +} + +type_init(pxa2xx_mmci_register_types) diff --git a/hw/sd/sd.c b/hw/sd/sd.c new file mode 100644 index 000000000..bb5dbff68 --- /dev/null +++ b/hw/sd/sd.c @@ -0,0 +1,2227 @@ +/* + * SD Memory Card emulation as defined in the "SD Memory Card Physical + * layer specification, Version 2.00." + * + * Copyright (c) 2006 Andrzej Zaborowski <balrog@zabor.org> + * Copyright (c) 2007 CodeSourcery + * Copyright (c) 2018 Philippe Mathieu-Daudé <f4bug@amsat.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "qemu/cutils.h" +#include "hw/irq.h" +#include "hw/registerfields.h" +#include "sysemu/block-backend.h" +#include "hw/sd/sd.h" +#include "hw/sd/sdcard_legacy.h" +#include "migration/vmstate.h" +#include "qapi/error.h" +#include "qemu/bitmap.h" +#include "hw/qdev-properties.h" +#include "hw/qdev-properties-system.h" +#include "qemu/error-report.h" +#include "qemu/timer.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu-common.h" +#include "sdmmc-internal.h" +#include "trace.h" + +//#define DEBUG_SD 1 + +#define SDSC_MAX_CAPACITY (2 * GiB) + +#define INVALID_ADDRESS UINT32_MAX + +typedef enum { + sd_r0 = 0, /* no response */ + sd_r1, /* normal response command */ + sd_r2_i, /* CID register */ + sd_r2_s, /* CSD register */ + sd_r3, /* OCR register */ + sd_r6 = 6, /* Published RCA response */ + sd_r7, /* Operating voltage */ + sd_r1b = -1, + sd_illegal = -2, +} sd_rsp_type_t; + +enum SDCardModes { + sd_inactive, + sd_card_identification_mode, + sd_data_transfer_mode, +}; + +enum SDCardStates { + sd_inactive_state = -1, + sd_idle_state = 0, + sd_ready_state, + sd_identification_state, + sd_standby_state, + sd_transfer_state, + sd_sendingdata_state, + sd_receivingdata_state, + sd_programming_state, + sd_disconnect_state, +}; + +struct SDState { + DeviceState parent_obj; + + /* If true, created by sd_init() for a non-qdevified caller */ + /* TODO purge them with fire */ + bool me_no_qdev_me_kill_mammoth_with_rocks; + + /* SD Memory Card Registers */ + uint32_t ocr; + uint8_t scr[8]; + uint8_t cid[16]; + uint8_t csd[16]; + uint16_t rca; + uint32_t card_status; + uint8_t sd_status[64]; + + /* Static properties */ + + uint8_t spec_version; + BlockBackend *blk; + bool spi; + + /* Runtime changeables */ + + uint32_t mode; /* current card mode, one of SDCardModes */ + int32_t state; /* current card state, one of SDCardStates */ + uint32_t vhs; + bool wp_switch; + unsigned long *wp_groups; + int32_t wpgrps_size; + uint64_t size; + uint32_t blk_len; + uint32_t multi_blk_cnt; + uint32_t erase_start; + uint32_t erase_end; + uint8_t pwd[16]; + uint32_t pwd_len; + uint8_t function_group[6]; + uint8_t current_cmd; + /* True if we will handle the next command as an ACMD. Note that this does + * *not* track the APP_CMD status bit! + */ + bool expecting_acmd; + uint32_t blk_written; + uint64_t data_start; + uint32_t data_offset; + uint8_t data[512]; + qemu_irq readonly_cb; + qemu_irq inserted_cb; + QEMUTimer *ocr_power_timer; + const char *proto_name; + bool enable; + uint8_t dat_lines; + bool cmd_line; +}; + +static void sd_realize(DeviceState *dev, Error **errp); + +static const char *sd_state_name(enum SDCardStates state) +{ + static const char *state_name[] = { + [sd_idle_state] = "idle", + [sd_ready_state] = "ready", + [sd_identification_state] = "identification", + [sd_standby_state] = "standby", + [sd_transfer_state] = "transfer", + [sd_sendingdata_state] = "sendingdata", + [sd_receivingdata_state] = "receivingdata", + [sd_programming_state] = "programming", + [sd_disconnect_state] = "disconnect", + }; + if (state == sd_inactive_state) { + return "inactive"; + } + assert(state < ARRAY_SIZE(state_name)); + return state_name[state]; +} + +static const char *sd_response_name(sd_rsp_type_t rsp) +{ + static const char *response_name[] = { + [sd_r0] = "RESP#0 (no response)", + [sd_r1] = "RESP#1 (normal cmd)", + [sd_r2_i] = "RESP#2 (CID reg)", + [sd_r2_s] = "RESP#2 (CSD reg)", + [sd_r3] = "RESP#3 (OCR reg)", + [sd_r6] = "RESP#6 (RCA)", + [sd_r7] = "RESP#7 (operating voltage)", + }; + if (rsp == sd_illegal) { + return "ILLEGAL RESP"; + } + if (rsp == sd_r1b) { + rsp = sd_r1; + } + assert(rsp < ARRAY_SIZE(response_name)); + return response_name[rsp]; +} + +static uint8_t sd_get_dat_lines(SDState *sd) +{ + return sd->enable ? sd->dat_lines : 0; +} + +static bool sd_get_cmd_line(SDState *sd) +{ + return sd->enable ? sd->cmd_line : false; +} + +static void sd_set_voltage(SDState *sd, uint16_t millivolts) +{ + trace_sdcard_set_voltage(millivolts); + + switch (millivolts) { + case 3001 ... 3600: /* SD_VOLTAGE_3_3V */ + case 2001 ... 3000: /* SD_VOLTAGE_3_0V */ + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "SD card voltage not supported: %.3fV", + millivolts / 1000.f); + } +} + +static void sd_set_mode(SDState *sd) +{ + switch (sd->state) { + case sd_inactive_state: + sd->mode = sd_inactive; + break; + + case sd_idle_state: + case sd_ready_state: + case sd_identification_state: + sd->mode = sd_card_identification_mode; + break; + + case sd_standby_state: + case sd_transfer_state: + case sd_sendingdata_state: + case sd_receivingdata_state: + case sd_programming_state: + case sd_disconnect_state: + sd->mode = sd_data_transfer_mode; + break; + } +} + +static const sd_cmd_type_t sd_cmd_type[SDMMC_CMD_MAX] = { + sd_bc, sd_none, sd_bcr, sd_bcr, sd_none, sd_none, sd_none, sd_ac, + sd_bcr, sd_ac, sd_ac, sd_adtc, sd_ac, sd_ac, sd_none, sd_ac, + /* 16 */ + sd_ac, sd_adtc, sd_adtc, sd_none, sd_none, sd_none, sd_none, sd_none, + sd_adtc, sd_adtc, sd_adtc, sd_adtc, sd_ac, sd_ac, sd_adtc, sd_none, + /* 32 */ + sd_ac, sd_ac, sd_none, sd_none, sd_none, sd_none, sd_ac, sd_none, + sd_none, sd_none, sd_bc, sd_none, sd_none, sd_none, sd_none, sd_none, + /* 48 */ + sd_none, sd_none, sd_none, sd_none, sd_none, sd_none, sd_none, sd_ac, + sd_adtc, sd_none, sd_none, sd_none, sd_none, sd_none, sd_none, sd_none, +}; + +static const int sd_cmd_class[SDMMC_CMD_MAX] = { + 0, 0, 0, 0, 0, 9, 10, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 6, 6, 6, 6, + 5, 5, 10, 10, 10, 10, 5, 9, 9, 9, 7, 7, 7, 7, 7, 7, + 7, 7, 10, 7, 9, 9, 9, 8, 8, 10, 8, 8, 8, 8, 8, 8, +}; + +static uint8_t sd_crc7(const void *message, size_t width) +{ + int i, bit; + uint8_t shift_reg = 0x00; + const uint8_t *msg = (const uint8_t *)message; + + for (i = 0; i < width; i ++, msg ++) + for (bit = 7; bit >= 0; bit --) { + shift_reg <<= 1; + if ((shift_reg >> 7) ^ ((*msg >> bit) & 1)) + shift_reg ^= 0x89; + } + + return shift_reg; +} + +#define OCR_POWER_DELAY_NS 500000 /* 0.5ms */ + +FIELD(OCR, VDD_VOLTAGE_WINDOW, 0, 24) +FIELD(OCR, VDD_VOLTAGE_WIN_LO, 0, 8) +FIELD(OCR, DUAL_VOLTAGE_CARD, 7, 1) +FIELD(OCR, VDD_VOLTAGE_WIN_HI, 8, 16) +FIELD(OCR, ACCEPT_SWITCH_1V8, 24, 1) /* Only UHS-I */ +FIELD(OCR, UHS_II_CARD, 29, 1) /* Only UHS-II */ +FIELD(OCR, CARD_CAPACITY, 30, 1) /* 0:SDSC, 1:SDHC/SDXC */ +FIELD(OCR, CARD_POWER_UP, 31, 1) + +#define ACMD41_ENQUIRY_MASK 0x00ffffff +#define ACMD41_R3_MASK (R_OCR_VDD_VOLTAGE_WIN_HI_MASK \ + | R_OCR_ACCEPT_SWITCH_1V8_MASK \ + | R_OCR_UHS_II_CARD_MASK \ + | R_OCR_CARD_CAPACITY_MASK \ + | R_OCR_CARD_POWER_UP_MASK) + +static void sd_set_ocr(SDState *sd) +{ + /* All voltages OK */ + sd->ocr = R_OCR_VDD_VOLTAGE_WIN_HI_MASK; +} + +static void sd_ocr_powerup(void *opaque) +{ + SDState *sd = opaque; + + trace_sdcard_powerup(); + assert(!FIELD_EX32(sd->ocr, OCR, CARD_POWER_UP)); + + /* card power-up OK */ + sd->ocr = FIELD_DP32(sd->ocr, OCR, CARD_POWER_UP, 1); + + if (sd->size > SDSC_MAX_CAPACITY) { + sd->ocr = FIELD_DP32(sd->ocr, OCR, CARD_CAPACITY, 1); + } +} + +static void sd_set_scr(SDState *sd) +{ + sd->scr[0] = 0 << 4; /* SCR structure version 1.0 */ + if (sd->spec_version == SD_PHY_SPECv1_10_VERS) { + sd->scr[0] |= 1; /* Spec Version 1.10 */ + } else { + sd->scr[0] |= 2; /* Spec Version 2.00 or Version 3.0X */ + } + sd->scr[1] = (2 << 4) /* SDSC Card (Security Version 1.01) */ + | 0b0101; /* 1-bit or 4-bit width bus modes */ + sd->scr[2] = 0x00; /* Extended Security is not supported. */ + if (sd->spec_version >= SD_PHY_SPECv3_01_VERS) { + sd->scr[2] |= 1 << 7; /* Spec Version 3.0X */ + } + sd->scr[3] = 0x00; + /* reserved for manufacturer usage */ + sd->scr[4] = 0x00; + sd->scr[5] = 0x00; + sd->scr[6] = 0x00; + sd->scr[7] = 0x00; +} + +#define MID 0xaa +#define OID "XY" +#define PNM "QEMU!" +#define PRV 0x01 +#define MDT_YR 2006 +#define MDT_MON 2 + +static void sd_set_cid(SDState *sd) +{ + sd->cid[0] = MID; /* Fake card manufacturer ID (MID) */ + sd->cid[1] = OID[0]; /* OEM/Application ID (OID) */ + sd->cid[2] = OID[1]; + sd->cid[3] = PNM[0]; /* Fake product name (PNM) */ + sd->cid[4] = PNM[1]; + sd->cid[5] = PNM[2]; + sd->cid[6] = PNM[3]; + sd->cid[7] = PNM[4]; + sd->cid[8] = PRV; /* Fake product revision (PRV) */ + sd->cid[9] = 0xde; /* Fake serial number (PSN) */ + sd->cid[10] = 0xad; + sd->cid[11] = 0xbe; + sd->cid[12] = 0xef; + sd->cid[13] = 0x00 | /* Manufacture date (MDT) */ + ((MDT_YR - 2000) / 10); + sd->cid[14] = ((MDT_YR % 10) << 4) | MDT_MON; + sd->cid[15] = (sd_crc7(sd->cid, 15) << 1) | 1; +} + +#define HWBLOCK_SHIFT 9 /* 512 bytes */ +#define SECTOR_SHIFT 5 /* 16 kilobytes */ +#define WPGROUP_SHIFT 7 /* 2 megs */ +#define CMULT_SHIFT 9 /* 512 times HWBLOCK_SIZE */ +#define WPGROUP_SIZE (1 << (HWBLOCK_SHIFT + SECTOR_SHIFT + WPGROUP_SHIFT)) + +static const uint8_t sd_csd_rw_mask[16] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfe, +}; + +static void sd_set_csd(SDState *sd, uint64_t size) +{ + int hwblock_shift = HWBLOCK_SHIFT; + uint32_t csize; + uint32_t sectsize = (1 << (SECTOR_SHIFT + 1)) - 1; + uint32_t wpsize = (1 << (WPGROUP_SHIFT + 1)) - 1; + + /* To indicate 2 GiB card, BLOCK_LEN shall be 1024 bytes */ + if (size == SDSC_MAX_CAPACITY) { + hwblock_shift += 1; + } + csize = (size >> (CMULT_SHIFT + hwblock_shift)) - 1; + + if (size <= SDSC_MAX_CAPACITY) { /* 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 */ + sd->csd[3] = 0x32; /* Max. data transfer rate: 25 MHz */ + sd->csd[4] = 0x5f; /* Card Command Classes */ + sd->csd[5] = 0x50 | /* Max. read data block length */ + hwblock_shift; + sd->csd[6] = 0xe0 | /* Partial block for read allowed */ + ((csize >> 10) & 0x03); + sd->csd[7] = 0x00 | /* Device size */ + ((csize >> 2) & 0xff); + sd->csd[8] = 0x3f | /* Max. read current */ + ((csize << 6) & 0xc0); + sd->csd[9] = 0xfc | /* Max. write current */ + ((CMULT_SHIFT - 2) >> 1); + sd->csd[10] = 0x40 | /* Erase sector size */ + (((CMULT_SHIFT - 2) << 7) & 0x80) | (sectsize >> 1); + sd->csd[11] = 0x00 | /* Write protect group size */ + ((sectsize << 7) & 0x80) | wpsize; + sd->csd[12] = 0x90 | /* Write speed factor */ + (hwblock_shift >> 2); + sd->csd[13] = 0x20 | /* Max. write data block length */ + ((hwblock_shift << 6) & 0xc0); + sd->csd[14] = 0x00; /* File format group */ + } else { /* SDHC */ + size /= 512 * KiB; + size -= 1; + sd->csd[0] = 0x40; + sd->csd[1] = 0x0e; + sd->csd[2] = 0x00; + sd->csd[3] = 0x32; + sd->csd[4] = 0x5b; + sd->csd[5] = 0x59; + sd->csd[6] = 0x00; + sd->csd[7] = (size >> 16) & 0xff; + sd->csd[8] = (size >> 8) & 0xff; + sd->csd[9] = (size & 0xff); + sd->csd[10] = 0x7f; + sd->csd[11] = 0x80; + sd->csd[12] = 0x0a; + sd->csd[13] = 0x40; + sd->csd[14] = 0x00; + } + sd->csd[15] = (sd_crc7(sd->csd, 15) << 1) | 1; +} + +static void sd_set_rca(SDState *sd) +{ + sd->rca += 0x4567; +} + +FIELD(CSR, AKE_SEQ_ERROR, 3, 1) +FIELD(CSR, APP_CMD, 5, 1) +FIELD(CSR, FX_EVENT, 6, 1) +FIELD(CSR, READY_FOR_DATA, 8, 1) +FIELD(CSR, CURRENT_STATE, 9, 4) +FIELD(CSR, ERASE_RESET, 13, 1) +FIELD(CSR, CARD_ECC_DISABLED, 14, 1) +FIELD(CSR, WP_ERASE_SKIP, 15, 1) +FIELD(CSR, CSD_OVERWRITE, 16, 1) +FIELD(CSR, DEFERRED_RESPONSE, 17, 1) +FIELD(CSR, ERROR, 19, 1) +FIELD(CSR, CC_ERROR, 20, 1) +FIELD(CSR, CARD_ECC_FAILED, 21, 1) +FIELD(CSR, ILLEGAL_COMMAND, 22, 1) +FIELD(CSR, COM_CRC_ERROR, 23, 1) +FIELD(CSR, LOCK_UNLOCK_FAILED, 24, 1) +FIELD(CSR, CARD_IS_LOCKED, 25, 1) +FIELD(CSR, WP_VIOLATION, 26, 1) +FIELD(CSR, ERASE_PARAM, 27, 1) +FIELD(CSR, ERASE_SEQ_ERROR, 28, 1) +FIELD(CSR, BLOCK_LEN_ERROR, 29, 1) +FIELD(CSR, ADDRESS_ERROR, 30, 1) +FIELD(CSR, OUT_OF_RANGE, 31, 1) + +/* Card status bits, split by clear condition: + * A : According to the card current state + * B : Always related to the previous command + * C : Cleared by read + */ +#define CARD_STATUS_A (R_CSR_READY_FOR_DATA_MASK \ + | R_CSR_CARD_ECC_DISABLED_MASK \ + | R_CSR_CARD_IS_LOCKED_MASK) +#define CARD_STATUS_B (R_CSR_CURRENT_STATE_MASK \ + | R_CSR_ILLEGAL_COMMAND_MASK \ + | R_CSR_COM_CRC_ERROR_MASK) +#define CARD_STATUS_C (R_CSR_AKE_SEQ_ERROR_MASK \ + | R_CSR_APP_CMD_MASK \ + | R_CSR_ERASE_RESET_MASK \ + | R_CSR_WP_ERASE_SKIP_MASK \ + | R_CSR_CSD_OVERWRITE_MASK \ + | R_CSR_ERROR_MASK \ + | R_CSR_CC_ERROR_MASK \ + | R_CSR_CARD_ECC_FAILED_MASK \ + | R_CSR_LOCK_UNLOCK_FAILED_MASK \ + | R_CSR_WP_VIOLATION_MASK \ + | R_CSR_ERASE_PARAM_MASK \ + | R_CSR_ERASE_SEQ_ERROR_MASK \ + | R_CSR_BLOCK_LEN_ERROR_MASK \ + | R_CSR_ADDRESS_ERROR_MASK \ + | R_CSR_OUT_OF_RANGE_MASK) + +static void sd_set_cardstatus(SDState *sd) +{ + sd->card_status = 0x00000100; +} + +static void sd_set_sdstatus(SDState *sd) +{ + memset(sd->sd_status, 0, 64); +} + +static int sd_req_crc_validate(SDRequest *req) +{ + uint8_t buffer[5]; + buffer[0] = 0x40 | req->cmd; + stl_be_p(&buffer[1], req->arg); + return 0; + return sd_crc7(buffer, 5) != req->crc; /* TODO */ +} + +static void sd_response_r1_make(SDState *sd, uint8_t *response) +{ + stl_be_p(response, sd->card_status); + + /* Clear the "clear on read" status bits */ + sd->card_status &= ~CARD_STATUS_C; +} + +static void sd_response_r3_make(SDState *sd, uint8_t *response) +{ + stl_be_p(response, sd->ocr & ACMD41_R3_MASK); +} + +static void sd_response_r6_make(SDState *sd, uint8_t *response) +{ + uint16_t status; + + status = ((sd->card_status >> 8) & 0xc000) | + ((sd->card_status >> 6) & 0x2000) | + (sd->card_status & 0x1fff); + sd->card_status &= ~(CARD_STATUS_C & 0xc81fff); + stw_be_p(response + 0, sd->rca); + stw_be_p(response + 2, status); +} + +static void sd_response_r7_make(SDState *sd, uint8_t *response) +{ + stl_be_p(response, sd->vhs); +} + +static inline uint64_t sd_addr_to_wpnum(uint64_t addr) +{ + return addr >> (HWBLOCK_SHIFT + SECTOR_SHIFT + WPGROUP_SHIFT); +} + +static void sd_reset(DeviceState *dev) +{ + SDState *sd = SD_CARD(dev); + uint64_t size; + uint64_t sect; + + trace_sdcard_reset(); + if (sd->blk) { + blk_get_geometry(sd->blk, §); + } else { + sect = 0; + } + size = sect << 9; + + sect = sd_addr_to_wpnum(size) + 1; + + sd->state = sd_idle_state; + sd->rca = 0x0000; + sd_set_ocr(sd); + sd_set_scr(sd); + sd_set_cid(sd); + sd_set_csd(sd, size); + sd_set_cardstatus(sd); + sd_set_sdstatus(sd); + + g_free(sd->wp_groups); + sd->wp_switch = sd->blk ? !blk_is_writable(sd->blk) : false; + sd->wpgrps_size = sect; + sd->wp_groups = bitmap_new(sd->wpgrps_size); + memset(sd->function_group, 0, sizeof(sd->function_group)); + sd->erase_start = INVALID_ADDRESS; + sd->erase_end = INVALID_ADDRESS; + sd->size = size; + sd->blk_len = 0x200; + sd->pwd_len = 0; + sd->expecting_acmd = false; + sd->dat_lines = 0xf; + sd->cmd_line = true; + sd->multi_blk_cnt = 0; +} + +static bool sd_get_inserted(SDState *sd) +{ + return sd->blk && blk_is_inserted(sd->blk); +} + +static bool sd_get_readonly(SDState *sd) +{ + return sd->wp_switch; +} + +static void sd_cardchange(void *opaque, bool load, Error **errp) +{ + SDState *sd = opaque; + DeviceState *dev = DEVICE(sd); + SDBus *sdbus; + bool inserted = sd_get_inserted(sd); + bool readonly = sd_get_readonly(sd); + + if (inserted) { + trace_sdcard_inserted(readonly); + sd_reset(dev); + } else { + trace_sdcard_ejected(); + } + + if (sd->me_no_qdev_me_kill_mammoth_with_rocks) { + qemu_set_irq(sd->inserted_cb, inserted); + if (inserted) { + qemu_set_irq(sd->readonly_cb, readonly); + } + } else { + sdbus = SD_BUS(qdev_get_parent_bus(dev)); + sdbus_set_inserted(sdbus, inserted); + if (inserted) { + sdbus_set_readonly(sdbus, readonly); + } + } +} + +static const BlockDevOps sd_block_ops = { + .change_media_cb = sd_cardchange, +}; + +static bool sd_ocr_vmstate_needed(void *opaque) +{ + SDState *sd = opaque; + + /* Include the OCR state (and timer) if it is not yet powered up */ + return !FIELD_EX32(sd->ocr, OCR, CARD_POWER_UP); +} + +static const VMStateDescription sd_ocr_vmstate = { + .name = "sd-card/ocr-state", + .version_id = 1, + .minimum_version_id = 1, + .needed = sd_ocr_vmstate_needed, + .fields = (VMStateField[]) { + VMSTATE_UINT32(ocr, SDState), + VMSTATE_TIMER_PTR(ocr_power_timer, SDState), + VMSTATE_END_OF_LIST() + }, +}; + +static int sd_vmstate_pre_load(void *opaque) +{ + SDState *sd = opaque; + + /* If the OCR state is not included (prior versions, or not + * needed), then the OCR must be set as powered up. If the OCR state + * is included, this will be replaced by the state restore. + */ + sd_ocr_powerup(sd); + + return 0; +} + +static const VMStateDescription sd_vmstate = { + .name = "sd-card", + .version_id = 2, + .minimum_version_id = 2, + .pre_load = sd_vmstate_pre_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32(mode, SDState), + VMSTATE_INT32(state, SDState), + VMSTATE_UINT8_ARRAY(cid, SDState, 16), + VMSTATE_UINT8_ARRAY(csd, SDState, 16), + VMSTATE_UINT16(rca, SDState), + VMSTATE_UINT32(card_status, SDState), + VMSTATE_PARTIAL_BUFFER(sd_status, SDState, 1), + VMSTATE_UINT32(vhs, SDState), + VMSTATE_BITMAP(wp_groups, SDState, 0, wpgrps_size), + VMSTATE_UINT32(blk_len, SDState), + VMSTATE_UINT32(multi_blk_cnt, SDState), + VMSTATE_UINT32(erase_start, SDState), + VMSTATE_UINT32(erase_end, SDState), + VMSTATE_UINT8_ARRAY(pwd, SDState, 16), + VMSTATE_UINT32(pwd_len, SDState), + VMSTATE_UINT8_ARRAY(function_group, SDState, 6), + VMSTATE_UINT8(current_cmd, SDState), + VMSTATE_BOOL(expecting_acmd, SDState), + VMSTATE_UINT32(blk_written, SDState), + VMSTATE_UINT64(data_start, SDState), + VMSTATE_UINT32(data_offset, SDState), + VMSTATE_UINT8_ARRAY(data, SDState, 512), + VMSTATE_UNUSED_V(1, 512), + VMSTATE_BOOL(enable, SDState), + VMSTATE_END_OF_LIST() + }, + .subsections = (const VMStateDescription*[]) { + &sd_ocr_vmstate, + NULL + }, +}; + +/* Legacy initialization function for use by non-qdevified callers */ +SDState *sd_init(BlockBackend *blk, bool is_spi) +{ + Object *obj; + DeviceState *dev; + SDState *sd; + Error *err = NULL; + + obj = object_new(TYPE_SD_CARD); + dev = DEVICE(obj); + if (!qdev_prop_set_drive_err(dev, "drive", blk, &err)) { + error_reportf_err(err, "sd_init failed: "); + return NULL; + } + qdev_prop_set_bit(dev, "spi", is_spi); + + /* + * Realizing the device properly would put it into the QOM + * composition tree even though it is not plugged into an + * appropriate bus. That's a no-no. Hide the device from + * QOM/qdev, and call its qdev realize callback directly. + */ + object_ref(obj); + object_unparent(obj); + sd_realize(dev, &err); + if (err) { + error_reportf_err(err, "sd_init failed: "); + return NULL; + } + + sd = SD_CARD(dev); + sd->me_no_qdev_me_kill_mammoth_with_rocks = true; + return sd; +} + +void sd_set_cb(SDState *sd, qemu_irq readonly, qemu_irq insert) +{ + sd->readonly_cb = readonly; + sd->inserted_cb = insert; + qemu_set_irq(readonly, sd->blk ? !blk_is_writable(sd->blk) : 0); + qemu_set_irq(insert, sd->blk ? blk_is_inserted(sd->blk) : 0); +} + +static void sd_blk_read(SDState *sd, uint64_t addr, uint32_t len) +{ + trace_sdcard_read_block(addr, len); + if (!sd->blk || blk_pread(sd->blk, addr, sd->data, len) < 0) { + fprintf(stderr, "sd_blk_read: read error on host side\n"); + } +} + +static void sd_blk_write(SDState *sd, uint64_t addr, uint32_t len) +{ + trace_sdcard_write_block(addr, len); + if (!sd->blk || blk_pwrite(sd->blk, addr, sd->data, len, 0) < 0) { + fprintf(stderr, "sd_blk_write: write error on host side\n"); + } +} + +#define BLK_READ_BLOCK(a, len) sd_blk_read(sd, a, len) +#define BLK_WRITE_BLOCK(a, len) sd_blk_write(sd, a, len) +#define APP_READ_BLOCK(a, len) memset(sd->data, 0xec, len) +#define APP_WRITE_BLOCK(a, len) + +static void sd_erase(SDState *sd) +{ + uint64_t erase_start = sd->erase_start; + uint64_t erase_end = sd->erase_end; + bool sdsc = true; + uint64_t wpnum; + uint64_t erase_addr; + int erase_len = 1 << HWBLOCK_SHIFT; + + trace_sdcard_erase(sd->erase_start, sd->erase_end); + if (sd->erase_start == INVALID_ADDRESS + || sd->erase_end == INVALID_ADDRESS) { + sd->card_status |= ERASE_SEQ_ERROR; + sd->erase_start = INVALID_ADDRESS; + sd->erase_end = INVALID_ADDRESS; + return; + } + + if (FIELD_EX32(sd->ocr, OCR, CARD_CAPACITY)) { + /* High capacity memory card: erase units are 512 byte blocks */ + erase_start *= 512; + erase_end *= 512; + sdsc = false; + } + + if (erase_start > sd->size || erase_end > sd->size) { + sd->card_status |= OUT_OF_RANGE; + sd->erase_start = INVALID_ADDRESS; + sd->erase_end = INVALID_ADDRESS; + return; + } + + sd->erase_start = INVALID_ADDRESS; + sd->erase_end = INVALID_ADDRESS; + sd->csd[14] |= 0x40; + + memset(sd->data, 0xff, erase_len); + for (erase_addr = erase_start; erase_addr <= erase_end; + erase_addr += erase_len) { + if (sdsc) { + /* Only SDSC cards support write protect groups */ + wpnum = sd_addr_to_wpnum(erase_addr); + assert(wpnum < sd->wpgrps_size); + if (test_bit(wpnum, sd->wp_groups)) { + sd->card_status |= WP_ERASE_SKIP; + continue; + } + } + BLK_WRITE_BLOCK(erase_addr, erase_len); + } +} + +static uint32_t sd_wpbits(SDState *sd, uint64_t addr) +{ + uint32_t i, wpnum; + uint32_t ret = 0; + + wpnum = sd_addr_to_wpnum(addr); + + for (i = 0; i < 32; i++, wpnum++, addr += WPGROUP_SIZE) { + if (addr >= sd->size) { + /* + * If the addresses of the last groups are outside the valid range, + * then the corresponding write protection bits shall be set to 0. + */ + continue; + } + assert(wpnum < sd->wpgrps_size); + if (test_bit(wpnum, sd->wp_groups)) { + ret |= (1 << i); + } + } + + return ret; +} + +static void sd_function_switch(SDState *sd, uint32_t arg) +{ + int i, mode, new_func; + mode = !!(arg & 0x80000000); + + sd->data[0] = 0x00; /* Maximum current consumption */ + sd->data[1] = 0x01; + sd->data[2] = 0x80; /* Supported group 6 functions */ + sd->data[3] = 0x01; + sd->data[4] = 0x80; /* Supported group 5 functions */ + sd->data[5] = 0x01; + sd->data[6] = 0x80; /* Supported group 4 functions */ + sd->data[7] = 0x01; + sd->data[8] = 0x80; /* Supported group 3 functions */ + sd->data[9] = 0x01; + sd->data[10] = 0x80; /* Supported group 2 functions */ + sd->data[11] = 0x43; + sd->data[12] = 0x80; /* Supported group 1 functions */ + sd->data[13] = 0x03; + + memset(&sd->data[14], 0, 3); + for (i = 0; i < 6; i ++) { + new_func = (arg >> (i * 4)) & 0x0f; + if (mode && new_func != 0x0f) + sd->function_group[i] = new_func; + sd->data[16 - (i >> 1)] |= new_func << ((i % 2) * 4); + } + memset(&sd->data[17], 0, 47); +} + +static inline bool sd_wp_addr(SDState *sd, uint64_t addr) +{ + return test_bit(sd_addr_to_wpnum(addr), sd->wp_groups); +} + +static void sd_lock_command(SDState *sd) +{ + int erase, lock, clr_pwd, set_pwd, pwd_len; + erase = !!(sd->data[0] & 0x08); + lock = sd->data[0] & 0x04; + clr_pwd = sd->data[0] & 0x02; + set_pwd = sd->data[0] & 0x01; + + if (sd->blk_len > 1) + pwd_len = sd->data[1]; + else + pwd_len = 0; + + if (lock) { + trace_sdcard_lock(); + } else { + trace_sdcard_unlock(); + } + if (erase) { + if (!(sd->card_status & CARD_IS_LOCKED) || sd->blk_len > 1 || + set_pwd || clr_pwd || lock || sd->wp_switch || + (sd->csd[14] & 0x20)) { + sd->card_status |= LOCK_UNLOCK_FAILED; + return; + } + bitmap_zero(sd->wp_groups, sd->wpgrps_size); + sd->csd[14] &= ~0x10; + sd->card_status &= ~CARD_IS_LOCKED; + sd->pwd_len = 0; + /* Erasing the entire card here! */ + fprintf(stderr, "SD: Card force-erased by CMD42\n"); + return; + } + + if (sd->blk_len < 2 + pwd_len || + pwd_len <= sd->pwd_len || + pwd_len > sd->pwd_len + 16) { + sd->card_status |= LOCK_UNLOCK_FAILED; + return; + } + + if (sd->pwd_len && memcmp(sd->pwd, sd->data + 2, sd->pwd_len)) { + sd->card_status |= LOCK_UNLOCK_FAILED; + return; + } + + pwd_len -= sd->pwd_len; + if ((pwd_len && !set_pwd) || + (clr_pwd && (set_pwd || lock)) || + (lock && !sd->pwd_len && !set_pwd) || + (!set_pwd && !clr_pwd && + (((sd->card_status & CARD_IS_LOCKED) && lock) || + (!(sd->card_status & CARD_IS_LOCKED) && !lock)))) { + sd->card_status |= LOCK_UNLOCK_FAILED; + return; + } + + if (set_pwd) { + memcpy(sd->pwd, sd->data + 2 + sd->pwd_len, pwd_len); + sd->pwd_len = pwd_len; + } + + if (clr_pwd) { + sd->pwd_len = 0; + } + + if (lock) + sd->card_status |= CARD_IS_LOCKED; + else + sd->card_status &= ~CARD_IS_LOCKED; +} + +static bool address_in_range(SDState *sd, const char *desc, + uint64_t addr, uint32_t length) +{ + if (addr + length > sd->size) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s offset %"PRIu64" > card %"PRIu64" [%%%u]\n", + desc, addr, sd->size, length); + sd->card_status |= ADDRESS_ERROR; + return false; + } + return true; +} + +static sd_rsp_type_t sd_normal_command(SDState *sd, SDRequest req) +{ + uint32_t rca = 0x0000; + uint64_t addr = (sd->ocr & (1 << 30)) ? (uint64_t) req.arg << 9 : req.arg; + + /* CMD55 precedes an ACMD, so we are not interested in tracing it. + * However there is no ACMD55, so we want to trace this particular case. + */ + if (req.cmd != 55 || sd->expecting_acmd) { + trace_sdcard_normal_command(sd->proto_name, + sd_cmd_name(req.cmd), req.cmd, + req.arg, sd_state_name(sd->state)); + } + + /* Not interpreting this as an app command */ + sd->card_status &= ~APP_CMD; + + if (sd_cmd_type[req.cmd] == sd_ac + || sd_cmd_type[req.cmd] == sd_adtc) { + rca = req.arg >> 16; + } + + /* CMD23 (set block count) must be immediately followed by CMD18 or CMD25 + * if not, its effects are cancelled */ + if (sd->multi_blk_cnt != 0 && !(req.cmd == 18 || req.cmd == 25)) { + sd->multi_blk_cnt = 0; + } + + if (sd_cmd_class[req.cmd] == 6 && FIELD_EX32(sd->ocr, OCR, CARD_CAPACITY)) { + /* Only Standard Capacity cards support class 6 commands */ + return sd_illegal; + } + + switch (req.cmd) { + /* Basic commands (Class 0 and Class 1) */ + case 0: /* CMD0: GO_IDLE_STATE */ + switch (sd->state) { + case sd_inactive_state: + return sd->spi ? sd_r1 : sd_r0; + + default: + sd->state = sd_idle_state; + sd_reset(DEVICE(sd)); + return sd->spi ? sd_r1 : sd_r0; + } + break; + + case 1: /* CMD1: SEND_OP_CMD */ + if (!sd->spi) + goto bad_cmd; + + sd->state = sd_transfer_state; + return sd_r1; + + case 2: /* CMD2: ALL_SEND_CID */ + if (sd->spi) + goto bad_cmd; + switch (sd->state) { + case sd_ready_state: + sd->state = sd_identification_state; + return sd_r2_i; + + default: + break; + } + break; + + case 3: /* CMD3: SEND_RELATIVE_ADDR */ + if (sd->spi) + goto bad_cmd; + switch (sd->state) { + case sd_identification_state: + case sd_standby_state: + sd->state = sd_standby_state; + sd_set_rca(sd); + return sd_r6; + + default: + break; + } + break; + + case 4: /* CMD4: SEND_DSR */ + if (sd->spi) + goto bad_cmd; + switch (sd->state) { + case sd_standby_state: + break; + + default: + break; + } + break; + + case 5: /* CMD5: reserved for SDIO cards */ + return sd_illegal; + + case 6: /* CMD6: SWITCH_FUNCTION */ + switch (sd->mode) { + case sd_data_transfer_mode: + sd_function_switch(sd, req.arg); + sd->state = sd_sendingdata_state; + sd->data_start = 0; + sd->data_offset = 0; + return sd_r1; + + default: + break; + } + break; + + case 7: /* CMD7: SELECT/DESELECT_CARD */ + if (sd->spi) + goto bad_cmd; + switch (sd->state) { + case sd_standby_state: + if (sd->rca != rca) + return sd_r0; + + sd->state = sd_transfer_state; + return sd_r1b; + + case sd_transfer_state: + case sd_sendingdata_state: + if (sd->rca == rca) + break; + + sd->state = sd_standby_state; + return sd_r1b; + + case sd_disconnect_state: + if (sd->rca != rca) + return sd_r0; + + sd->state = sd_programming_state; + return sd_r1b; + + case sd_programming_state: + if (sd->rca == rca) + break; + + sd->state = sd_disconnect_state; + return sd_r1b; + + default: + break; + } + break; + + case 8: /* CMD8: SEND_IF_COND */ + if (sd->spec_version < SD_PHY_SPECv2_00_VERS) { + break; + } + if (sd->state != sd_idle_state) { + break; + } + sd->vhs = 0; + + /* No response if not exactly one VHS bit is set. */ + if (!(req.arg >> 8) || (req.arg >> (ctz32(req.arg & ~0xff) + 1))) { + return sd->spi ? sd_r7 : sd_r0; + } + + /* Accept. */ + sd->vhs = req.arg; + return sd_r7; + + case 9: /* CMD9: SEND_CSD */ + switch (sd->state) { + case sd_standby_state: + if (sd->rca != rca) + return sd_r0; + + return sd_r2_s; + + case sd_transfer_state: + if (!sd->spi) + break; + sd->state = sd_sendingdata_state; + memcpy(sd->data, sd->csd, 16); + sd->data_start = addr; + sd->data_offset = 0; + return sd_r1; + + default: + break; + } + break; + + case 10: /* CMD10: SEND_CID */ + switch (sd->state) { + case sd_standby_state: + if (sd->rca != rca) + return sd_r0; + + return sd_r2_i; + + case sd_transfer_state: + if (!sd->spi) + break; + sd->state = sd_sendingdata_state; + memcpy(sd->data, sd->cid, 16); + sd->data_start = addr; + sd->data_offset = 0; + return sd_r1; + + default: + break; + } + break; + + case 12: /* CMD12: STOP_TRANSMISSION */ + switch (sd->state) { + case sd_sendingdata_state: + sd->state = sd_transfer_state; + return sd_r1b; + + case sd_receivingdata_state: + sd->state = sd_programming_state; + /* Bzzzzzzztt .... Operation complete. */ + sd->state = sd_transfer_state; + return sd_r1b; + + default: + break; + } + break; + + case 13: /* CMD13: SEND_STATUS */ + switch (sd->mode) { + case sd_data_transfer_mode: + if (!sd->spi && sd->rca != rca) { + return sd_r0; + } + + return sd_r1; + + default: + break; + } + break; + + case 15: /* CMD15: GO_INACTIVE_STATE */ + if (sd->spi) + goto bad_cmd; + switch (sd->mode) { + case sd_data_transfer_mode: + if (sd->rca != rca) + return sd_r0; + + sd->state = sd_inactive_state; + return sd_r0; + + default: + break; + } + break; + + /* Block read commands (Classs 2) */ + case 16: /* CMD16: SET_BLOCKLEN */ + switch (sd->state) { + case sd_transfer_state: + if (req.arg > (1 << HWBLOCK_SHIFT)) { + sd->card_status |= BLOCK_LEN_ERROR; + } else { + trace_sdcard_set_blocklen(req.arg); + sd->blk_len = req.arg; + } + + return sd_r1; + + default: + break; + } + break; + + case 17: /* CMD17: READ_SINGLE_BLOCK */ + case 18: /* CMD18: READ_MULTIPLE_BLOCK */ + switch (sd->state) { + case sd_transfer_state: + + if (!address_in_range(sd, "READ_BLOCK", addr, sd->blk_len)) { + return sd_r1; + } + + sd->state = sd_sendingdata_state; + sd->data_start = addr; + sd->data_offset = 0; + return sd_r1; + + default: + break; + } + break; + + case 19: /* CMD19: SEND_TUNING_BLOCK (SD) */ + if (sd->spec_version < SD_PHY_SPECv3_01_VERS) { + break; + } + if (sd->state == sd_transfer_state) { + sd->state = sd_sendingdata_state; + sd->data_offset = 0; + return sd_r1; + } + break; + + case 23: /* CMD23: SET_BLOCK_COUNT */ + if (sd->spec_version < SD_PHY_SPECv3_01_VERS) { + break; + } + switch (sd->state) { + case sd_transfer_state: + sd->multi_blk_cnt = req.arg; + return sd_r1; + + default: + break; + } + break; + + /* Block write commands (Class 4) */ + case 24: /* CMD24: WRITE_SINGLE_BLOCK */ + case 25: /* CMD25: WRITE_MULTIPLE_BLOCK */ + switch (sd->state) { + case sd_transfer_state: + + if (!address_in_range(sd, "WRITE_BLOCK", addr, sd->blk_len)) { + return sd_r1; + } + + sd->state = sd_receivingdata_state; + sd->data_start = addr; + sd->data_offset = 0; + sd->blk_written = 0; + + if (sd->size <= SDSC_MAX_CAPACITY) { + if (sd_wp_addr(sd, sd->data_start)) { + sd->card_status |= WP_VIOLATION; + } + } + if (sd->csd[14] & 0x30) { + sd->card_status |= WP_VIOLATION; + } + return sd_r1; + + default: + break; + } + break; + + case 26: /* CMD26: PROGRAM_CID */ + if (sd->spi) + goto bad_cmd; + switch (sd->state) { + case sd_transfer_state: + sd->state = sd_receivingdata_state; + sd->data_start = 0; + sd->data_offset = 0; + return sd_r1; + + default: + break; + } + break; + + case 27: /* CMD27: PROGRAM_CSD */ + switch (sd->state) { + case sd_transfer_state: + sd->state = sd_receivingdata_state; + sd->data_start = 0; + sd->data_offset = 0; + return sd_r1; + + default: + break; + } + break; + + /* Write protection (Class 6) */ + case 28: /* CMD28: SET_WRITE_PROT */ + if (sd->size > SDSC_MAX_CAPACITY) { + return sd_illegal; + } + + switch (sd->state) { + case sd_transfer_state: + if (!address_in_range(sd, "SET_WRITE_PROT", addr, 1)) { + return sd_r1b; + } + + sd->state = sd_programming_state; + set_bit(sd_addr_to_wpnum(addr), sd->wp_groups); + /* Bzzzzzzztt .... Operation complete. */ + sd->state = sd_transfer_state; + return sd_r1b; + + default: + break; + } + break; + + case 29: /* CMD29: CLR_WRITE_PROT */ + if (sd->size > SDSC_MAX_CAPACITY) { + return sd_illegal; + } + + switch (sd->state) { + case sd_transfer_state: + if (!address_in_range(sd, "CLR_WRITE_PROT", addr, 1)) { + return sd_r1b; + } + + sd->state = sd_programming_state; + clear_bit(sd_addr_to_wpnum(addr), sd->wp_groups); + /* Bzzzzzzztt .... Operation complete. */ + sd->state = sd_transfer_state; + return sd_r1b; + + default: + break; + } + break; + + case 30: /* CMD30: SEND_WRITE_PROT */ + if (sd->size > SDSC_MAX_CAPACITY) { + return sd_illegal; + } + + switch (sd->state) { + case sd_transfer_state: + if (!address_in_range(sd, "SEND_WRITE_PROT", + req.arg, sd->blk_len)) { + return sd_r1; + } + + sd->state = sd_sendingdata_state; + *(uint32_t *) sd->data = sd_wpbits(sd, req.arg); + sd->data_start = addr; + sd->data_offset = 0; + return sd_r1; + + default: + break; + } + break; + + /* Erase commands (Class 5) */ + case 32: /* CMD32: ERASE_WR_BLK_START */ + switch (sd->state) { + case sd_transfer_state: + sd->erase_start = req.arg; + return sd_r1; + + default: + break; + } + break; + + case 33: /* CMD33: ERASE_WR_BLK_END */ + switch (sd->state) { + case sd_transfer_state: + sd->erase_end = req.arg; + return sd_r1; + + default: + break; + } + break; + + case 38: /* CMD38: ERASE */ + switch (sd->state) { + case sd_transfer_state: + if (sd->csd[14] & 0x30) { + sd->card_status |= WP_VIOLATION; + return sd_r1b; + } + + sd->state = sd_programming_state; + sd_erase(sd); + /* Bzzzzzzztt .... Operation complete. */ + sd->state = sd_transfer_state; + return sd_r1b; + + default: + break; + } + break; + + /* Lock card commands (Class 7) */ + case 42: /* CMD42: LOCK_UNLOCK */ + switch (sd->state) { + case sd_transfer_state: + sd->state = sd_receivingdata_state; + sd->data_start = 0; + sd->data_offset = 0; + return sd_r1; + + default: + break; + } + break; + + case 52 ... 54: + /* CMD52, CMD53, CMD54: reserved for SDIO cards + * (see the SDIO Simplified Specification V2.0) + * Handle as illegal command but do not complain + * on stderr, as some OSes may use these in their + * probing for presence of an SDIO card. + */ + return sd_illegal; + + /* Application specific commands (Class 8) */ + case 55: /* CMD55: APP_CMD */ + switch (sd->state) { + case sd_ready_state: + case sd_identification_state: + case sd_inactive_state: + return sd_illegal; + case sd_idle_state: + if (rca) { + qemu_log_mask(LOG_GUEST_ERROR, + "SD: illegal RCA 0x%04x for APP_CMD\n", req.cmd); + } + default: + break; + } + if (!sd->spi) { + if (sd->rca != rca) { + return sd_r0; + } + } + sd->expecting_acmd = true; + sd->card_status |= APP_CMD; + return sd_r1; + + case 56: /* CMD56: GEN_CMD */ + switch (sd->state) { + case sd_transfer_state: + sd->data_offset = 0; + if (req.arg & 1) + sd->state = sd_sendingdata_state; + else + sd->state = sd_receivingdata_state; + return sd_r1; + + default: + break; + } + break; + + case 58: /* CMD58: READ_OCR (SPI) */ + if (!sd->spi) { + goto bad_cmd; + } + return sd_r3; + + case 59: /* CMD59: CRC_ON_OFF (SPI) */ + if (!sd->spi) { + goto bad_cmd; + } + return sd_r1; + + default: + bad_cmd: + qemu_log_mask(LOG_GUEST_ERROR, "SD: Unknown CMD%i\n", req.cmd); + return sd_illegal; + } + + qemu_log_mask(LOG_GUEST_ERROR, "SD: CMD%i in a wrong state: %s\n", + req.cmd, sd_state_name(sd->state)); + return sd_illegal; +} + +static sd_rsp_type_t sd_app_command(SDState *sd, + SDRequest req) +{ + trace_sdcard_app_command(sd->proto_name, sd_acmd_name(req.cmd), + req.cmd, req.arg, sd_state_name(sd->state)); + sd->card_status |= APP_CMD; + switch (req.cmd) { + case 6: /* ACMD6: SET_BUS_WIDTH */ + if (sd->spi) { + goto unimplemented_spi_cmd; + } + switch (sd->state) { + case sd_transfer_state: + sd->sd_status[0] &= 0x3f; + sd->sd_status[0] |= (req.arg & 0x03) << 6; + return sd_r1; + + default: + break; + } + break; + + case 13: /* ACMD13: SD_STATUS */ + switch (sd->state) { + case sd_transfer_state: + sd->state = sd_sendingdata_state; + sd->data_start = 0; + sd->data_offset = 0; + return sd_r1; + + default: + break; + } + break; + + case 22: /* ACMD22: SEND_NUM_WR_BLOCKS */ + switch (sd->state) { + case sd_transfer_state: + *(uint32_t *) sd->data = sd->blk_written; + + sd->state = sd_sendingdata_state; + sd->data_start = 0; + sd->data_offset = 0; + return sd_r1; + + default: + break; + } + break; + + case 23: /* ACMD23: SET_WR_BLK_ERASE_COUNT */ + switch (sd->state) { + case sd_transfer_state: + return sd_r1; + + default: + break; + } + break; + + case 41: /* ACMD41: SD_APP_OP_COND */ + if (sd->spi) { + /* SEND_OP_CMD */ + sd->state = sd_transfer_state; + return sd_r1; + } + if (sd->state != sd_idle_state) { + break; + } + /* If it's the first ACMD41 since reset, we need to decide + * whether to power up. If this is not an enquiry ACMD41, + * we immediately report power on and proceed below to the + * ready state, but if it is, we set a timer to model a + * delay for power up. This works around a bug in EDK2 + * UEFI, which sends an initial enquiry ACMD41, but + * assumes that the card is in ready state as soon as it + * sees the power up bit set. */ + if (!FIELD_EX32(sd->ocr, OCR, CARD_POWER_UP)) { + if ((req.arg & ACMD41_ENQUIRY_MASK) != 0) { + timer_del(sd->ocr_power_timer); + sd_ocr_powerup(sd); + } else { + trace_sdcard_inquiry_cmd41(); + if (!timer_pending(sd->ocr_power_timer)) { + timer_mod_ns(sd->ocr_power_timer, + (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + OCR_POWER_DELAY_NS)); + } + } + } + + if (FIELD_EX32(sd->ocr & req.arg, OCR, VDD_VOLTAGE_WINDOW)) { + /* We accept any voltage. 10000 V is nothing. + * + * Once we're powered up, we advance straight to ready state + * unless it's an enquiry ACMD41 (bits 23:0 == 0). + */ + sd->state = sd_ready_state; + } + + return sd_r3; + + case 42: /* ACMD42: SET_CLR_CARD_DETECT */ + switch (sd->state) { + case sd_transfer_state: + /* Bringing in the 50KOhm pull-up resistor... Done. */ + return sd_r1; + + default: + break; + } + break; + + case 51: /* ACMD51: SEND_SCR */ + switch (sd->state) { + case sd_transfer_state: + sd->state = sd_sendingdata_state; + sd->data_start = 0; + sd->data_offset = 0; + return sd_r1; + + default: + break; + } + break; + + case 18: /* Reserved for SD security applications */ + case 25: + case 26: + case 38: + case 43 ... 49: + /* Refer to the "SD Specifications Part3 Security Specification" for + * information about the SD Security Features. + */ + qemu_log_mask(LOG_UNIMP, "SD: CMD%i Security not implemented\n", + req.cmd); + return sd_illegal; + + default: + /* Fall back to standard commands. */ + return sd_normal_command(sd, req); + + unimplemented_spi_cmd: + /* Commands that are recognised but not yet implemented in SPI mode. */ + qemu_log_mask(LOG_UNIMP, "SD: CMD%i not implemented in SPI mode\n", + req.cmd); + return sd_illegal; + } + + qemu_log_mask(LOG_GUEST_ERROR, "SD: ACMD%i in a wrong state\n", req.cmd); + return sd_illegal; +} + +static int cmd_valid_while_locked(SDState *sd, const uint8_t cmd) +{ + /* Valid commands in locked state: + * basic class (0) + * lock card class (7) + * CMD16 + * implicitly, the ACMD prefix CMD55 + * ACMD41 and ACMD42 + * Anything else provokes an "illegal command" response. + */ + if (sd->expecting_acmd) { + return cmd == 41 || cmd == 42; + } + if (cmd == 16 || cmd == 55) { + return 1; + } + return sd_cmd_class[cmd] == 0 || sd_cmd_class[cmd] == 7; +} + +int sd_do_command(SDState *sd, SDRequest *req, + uint8_t *response) { + int last_state; + sd_rsp_type_t rtype; + int rsplen; + + if (!sd->blk || !blk_is_inserted(sd->blk) || !sd->enable) { + return 0; + } + + if (sd_req_crc_validate(req)) { + sd->card_status |= COM_CRC_ERROR; + rtype = sd_illegal; + goto send_response; + } + + if (req->cmd >= SDMMC_CMD_MAX) { + qemu_log_mask(LOG_GUEST_ERROR, "SD: incorrect command 0x%02x\n", + req->cmd); + req->cmd &= 0x3f; + } + + if (sd->card_status & CARD_IS_LOCKED) { + if (!cmd_valid_while_locked(sd, req->cmd)) { + sd->card_status |= ILLEGAL_COMMAND; + sd->expecting_acmd = false; + qemu_log_mask(LOG_GUEST_ERROR, "SD: Card is locked\n"); + rtype = sd_illegal; + goto send_response; + } + } + + last_state = sd->state; + sd_set_mode(sd); + + if (sd->expecting_acmd) { + sd->expecting_acmd = false; + rtype = sd_app_command(sd, *req); + } else { + rtype = sd_normal_command(sd, *req); + } + + if (rtype == sd_illegal) { + sd->card_status |= ILLEGAL_COMMAND; + } else { + /* Valid command, we can update the 'state before command' bits. + * (Do this now so they appear in r1 responses.) + */ + sd->current_cmd = req->cmd; + sd->card_status &= ~CURRENT_STATE; + sd->card_status |= (last_state << 9); + } + +send_response: + switch (rtype) { + case sd_r1: + case sd_r1b: + sd_response_r1_make(sd, response); + rsplen = 4; + break; + + case sd_r2_i: + memcpy(response, sd->cid, sizeof(sd->cid)); + rsplen = 16; + break; + + case sd_r2_s: + memcpy(response, sd->csd, sizeof(sd->csd)); + rsplen = 16; + break; + + case sd_r3: + sd_response_r3_make(sd, response); + rsplen = 4; + break; + + case sd_r6: + sd_response_r6_make(sd, response); + rsplen = 4; + break; + + case sd_r7: + sd_response_r7_make(sd, response); + rsplen = 4; + break; + + case sd_r0: + case sd_illegal: + rsplen = 0; + break; + default: + g_assert_not_reached(); + } + trace_sdcard_response(sd_response_name(rtype), rsplen); + + if (rtype != sd_illegal) { + /* Clear the "clear on valid command" status bits now we've + * sent any response + */ + sd->card_status &= ~CARD_STATUS_B; + } + +#ifdef DEBUG_SD + qemu_hexdump(stderr, "Response", response, rsplen); +#endif + + return rsplen; +} + +void sd_write_byte(SDState *sd, uint8_t value) +{ + int i; + + if (!sd->blk || !blk_is_inserted(sd->blk) || !sd->enable) + return; + + if (sd->state != sd_receivingdata_state) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: not in Receiving-Data state\n", __func__); + return; + } + + if (sd->card_status & (ADDRESS_ERROR | WP_VIOLATION)) + return; + + trace_sdcard_write_data(sd->proto_name, + sd_acmd_name(sd->current_cmd), + sd->current_cmd, value); + switch (sd->current_cmd) { + case 24: /* CMD24: WRITE_SINGLE_BLOCK */ + sd->data[sd->data_offset ++] = value; + if (sd->data_offset >= sd->blk_len) { + /* TODO: Check CRC before committing */ + sd->state = sd_programming_state; + BLK_WRITE_BLOCK(sd->data_start, sd->data_offset); + sd->blk_written ++; + sd->csd[14] |= 0x40; + /* Bzzzzzzztt .... Operation complete. */ + sd->state = sd_transfer_state; + } + break; + + case 25: /* CMD25: WRITE_MULTIPLE_BLOCK */ + if (sd->data_offset == 0) { + /* Start of the block - let's check the address is valid */ + if (!address_in_range(sd, "WRITE_MULTIPLE_BLOCK", + sd->data_start, sd->blk_len)) { + break; + } + if (sd->size <= SDSC_MAX_CAPACITY) { + if (sd_wp_addr(sd, sd->data_start)) { + sd->card_status |= WP_VIOLATION; + break; + } + } + } + sd->data[sd->data_offset++] = value; + if (sd->data_offset >= sd->blk_len) { + /* TODO: Check CRC before committing */ + sd->state = sd_programming_state; + BLK_WRITE_BLOCK(sd->data_start, sd->data_offset); + sd->blk_written++; + sd->data_start += sd->blk_len; + sd->data_offset = 0; + sd->csd[14] |= 0x40; + + /* Bzzzzzzztt .... Operation complete. */ + if (sd->multi_blk_cnt != 0) { + if (--sd->multi_blk_cnt == 0) { + /* Stop! */ + sd->state = sd_transfer_state; + break; + } + } + + sd->state = sd_receivingdata_state; + } + break; + + case 26: /* CMD26: PROGRAM_CID */ + sd->data[sd->data_offset ++] = value; + if (sd->data_offset >= sizeof(sd->cid)) { + /* TODO: Check CRC before committing */ + sd->state = sd_programming_state; + for (i = 0; i < sizeof(sd->cid); i ++) + if ((sd->cid[i] | 0x00) != sd->data[i]) + sd->card_status |= CID_CSD_OVERWRITE; + + if (!(sd->card_status & CID_CSD_OVERWRITE)) + for (i = 0; i < sizeof(sd->cid); i ++) { + sd->cid[i] |= 0x00; + sd->cid[i] &= sd->data[i]; + } + /* Bzzzzzzztt .... Operation complete. */ + sd->state = sd_transfer_state; + } + break; + + case 27: /* CMD27: PROGRAM_CSD */ + sd->data[sd->data_offset ++] = value; + if (sd->data_offset >= sizeof(sd->csd)) { + /* TODO: Check CRC before committing */ + sd->state = sd_programming_state; + for (i = 0; i < sizeof(sd->csd); i ++) + if ((sd->csd[i] | sd_csd_rw_mask[i]) != + (sd->data[i] | sd_csd_rw_mask[i])) + sd->card_status |= CID_CSD_OVERWRITE; + + /* Copy flag (OTP) & Permanent write protect */ + if (sd->csd[14] & ~sd->data[14] & 0x60) + sd->card_status |= CID_CSD_OVERWRITE; + + if (!(sd->card_status & CID_CSD_OVERWRITE)) + for (i = 0; i < sizeof(sd->csd); i ++) { + sd->csd[i] |= sd_csd_rw_mask[i]; + sd->csd[i] &= sd->data[i]; + } + /* Bzzzzzzztt .... Operation complete. */ + sd->state = sd_transfer_state; + } + break; + + case 42: /* CMD42: LOCK_UNLOCK */ + sd->data[sd->data_offset ++] = value; + if (sd->data_offset >= sd->blk_len) { + /* TODO: Check CRC before committing */ + sd->state = sd_programming_state; + sd_lock_command(sd); + /* Bzzzzzzztt .... Operation complete. */ + sd->state = sd_transfer_state; + } + break; + + case 56: /* CMD56: GEN_CMD */ + sd->data[sd->data_offset ++] = value; + if (sd->data_offset >= sd->blk_len) { + APP_WRITE_BLOCK(sd->data_start, sd->data_offset); + sd->state = sd_transfer_state; + } + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: unknown command\n", __func__); + break; + } +} + +#define SD_TUNING_BLOCK_SIZE 64 + +static const uint8_t sd_tuning_block_pattern[SD_TUNING_BLOCK_SIZE] = { + /* See: Physical Layer Simplified Specification Version 3.01, Table 4-2 */ + 0xff, 0x0f, 0xff, 0x00, 0x0f, 0xfc, 0xc3, 0xcc, + 0xc3, 0x3c, 0xcc, 0xff, 0xfe, 0xff, 0xfe, 0xef, + 0xff, 0xdf, 0xff, 0xdd, 0xff, 0xfb, 0xff, 0xfb, + 0xbf, 0xff, 0x7f, 0xff, 0x77, 0xf7, 0xbd, 0xef, + 0xff, 0xf0, 0xff, 0xf0, 0x0f, 0xfc, 0xcc, 0x3c, + 0xcc, 0x33, 0xcc, 0xcf, 0xff, 0xef, 0xff, 0xee, + 0xff, 0xfd, 0xff, 0xfd, 0xdf, 0xff, 0xbf, 0xff, + 0xbb, 0xff, 0xf7, 0xff, 0xf7, 0x7f, 0x7b, 0xde, +}; + +uint8_t sd_read_byte(SDState *sd) +{ + /* TODO: Append CRCs */ + uint8_t ret; + uint32_t io_len; + + if (!sd->blk || !blk_is_inserted(sd->blk) || !sd->enable) + return 0x00; + + if (sd->state != sd_sendingdata_state) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: not in Sending-Data state\n", __func__); + return 0x00; + } + + if (sd->card_status & (ADDRESS_ERROR | WP_VIOLATION)) + return 0x00; + + io_len = (sd->ocr & (1 << 30)) ? 512 : sd->blk_len; + + trace_sdcard_read_data(sd->proto_name, + sd_acmd_name(sd->current_cmd), + sd->current_cmd, io_len); + switch (sd->current_cmd) { + case 6: /* CMD6: SWITCH_FUNCTION */ + ret = sd->data[sd->data_offset ++]; + + if (sd->data_offset >= 64) + sd->state = sd_transfer_state; + break; + + case 9: /* CMD9: SEND_CSD */ + case 10: /* CMD10: SEND_CID */ + ret = sd->data[sd->data_offset ++]; + + if (sd->data_offset >= 16) + sd->state = sd_transfer_state; + break; + + case 13: /* ACMD13: SD_STATUS */ + ret = sd->sd_status[sd->data_offset ++]; + + if (sd->data_offset >= sizeof(sd->sd_status)) + sd->state = sd_transfer_state; + break; + + case 17: /* CMD17: READ_SINGLE_BLOCK */ + if (sd->data_offset == 0) + BLK_READ_BLOCK(sd->data_start, io_len); + ret = sd->data[sd->data_offset ++]; + + if (sd->data_offset >= io_len) + sd->state = sd_transfer_state; + break; + + case 18: /* CMD18: READ_MULTIPLE_BLOCK */ + if (sd->data_offset == 0) { + if (!address_in_range(sd, "READ_MULTIPLE_BLOCK", + sd->data_start, io_len)) { + return 0x00; + } + BLK_READ_BLOCK(sd->data_start, io_len); + } + ret = sd->data[sd->data_offset ++]; + + if (sd->data_offset >= io_len) { + sd->data_start += io_len; + sd->data_offset = 0; + + if (sd->multi_blk_cnt != 0) { + if (--sd->multi_blk_cnt == 0) { + /* Stop! */ + sd->state = sd_transfer_state; + break; + } + } + } + break; + + case 19: /* CMD19: SEND_TUNING_BLOCK (SD) */ + if (sd->data_offset >= SD_TUNING_BLOCK_SIZE - 1) { + sd->state = sd_transfer_state; + } + ret = sd_tuning_block_pattern[sd->data_offset++]; + break; + + case 22: /* ACMD22: SEND_NUM_WR_BLOCKS */ + ret = sd->data[sd->data_offset ++]; + + if (sd->data_offset >= 4) + sd->state = sd_transfer_state; + break; + + case 30: /* CMD30: SEND_WRITE_PROT */ + ret = sd->data[sd->data_offset ++]; + + if (sd->data_offset >= 4) + sd->state = sd_transfer_state; + break; + + case 51: /* ACMD51: SEND_SCR */ + ret = sd->scr[sd->data_offset ++]; + + if (sd->data_offset >= sizeof(sd->scr)) + sd->state = sd_transfer_state; + break; + + case 56: /* CMD56: GEN_CMD */ + if (sd->data_offset == 0) + APP_READ_BLOCK(sd->data_start, sd->blk_len); + ret = sd->data[sd->data_offset ++]; + + if (sd->data_offset >= sd->blk_len) + sd->state = sd_transfer_state; + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: unknown command\n", __func__); + return 0x00; + } + + return ret; +} + +static bool sd_receive_ready(SDState *sd) +{ + return sd->state == sd_receivingdata_state; +} + +static bool sd_data_ready(SDState *sd) +{ + return sd->state == sd_sendingdata_state; +} + +void sd_enable(SDState *sd, bool enable) +{ + sd->enable = enable; +} + +static void sd_instance_init(Object *obj) +{ + SDState *sd = SD_CARD(obj); + + sd->enable = true; + sd->ocr_power_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, sd_ocr_powerup, sd); +} + +static void sd_instance_finalize(Object *obj) +{ + SDState *sd = SD_CARD(obj); + + timer_free(sd->ocr_power_timer); +} + +static void sd_realize(DeviceState *dev, Error **errp) +{ + SDState *sd = SD_CARD(dev); + int ret; + + sd->proto_name = sd->spi ? "SPI" : "SD"; + + switch (sd->spec_version) { + case SD_PHY_SPECv1_10_VERS + ... SD_PHY_SPECv3_01_VERS: + break; + default: + error_setg(errp, "Invalid SD card Spec version: %u", sd->spec_version); + return; + } + + if (sd->blk) { + int64_t blk_size; + + if (!blk_supports_write_perm(sd->blk)) { + error_setg(errp, "Cannot use read-only drive as SD card"); + return; + } + + blk_size = blk_getlength(sd->blk); + if (blk_size > 0 && !is_power_of_2(blk_size)) { + int64_t blk_size_aligned = pow2ceil(blk_size); + char *blk_size_str; + + blk_size_str = size_to_str(blk_size); + error_setg(errp, "Invalid SD card size: %s", blk_size_str); + g_free(blk_size_str); + + blk_size_str = size_to_str(blk_size_aligned); + error_append_hint(errp, + "SD card size has to be a power of 2, e.g. %s.\n" + "You can resize disk images with" + " 'qemu-img resize <imagefile> <new-size>'\n" + "(note that this will lose data if you make the" + " image smaller than it currently is).\n", + blk_size_str); + g_free(blk_size_str); + + return; + } + + ret = blk_set_perm(sd->blk, BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE, + BLK_PERM_ALL, errp); + if (ret < 0) { + return; + } + blk_set_dev_ops(sd->blk, &sd_block_ops, sd); + } +} + +static Property sd_properties[] = { + DEFINE_PROP_UINT8("spec_version", SDState, + spec_version, SD_PHY_SPECv2_00_VERS), + DEFINE_PROP_DRIVE("drive", SDState, blk), + /* We do not model the chip select pin, so allow the board to select + * whether card should be in SSI or MMC/SD mode. It is also up to the + * board to ensure that ssi transfers only occur when the chip select + * is asserted. */ + DEFINE_PROP_BOOL("spi", SDState, spi, false), + DEFINE_PROP_END_OF_LIST() +}; + +static void sd_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SDCardClass *sc = SD_CARD_CLASS(klass); + + dc->realize = sd_realize; + device_class_set_props(dc, sd_properties); + dc->vmsd = &sd_vmstate; + dc->reset = sd_reset; + dc->bus_type = TYPE_SD_BUS; + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); + + sc->set_voltage = sd_set_voltage; + sc->get_dat_lines = sd_get_dat_lines; + sc->get_cmd_line = sd_get_cmd_line; + sc->do_command = sd_do_command; + sc->write_byte = sd_write_byte; + sc->read_byte = sd_read_byte; + sc->receive_ready = sd_receive_ready; + sc->data_ready = sd_data_ready; + sc->enable = sd_enable; + sc->get_inserted = sd_get_inserted; + sc->get_readonly = sd_get_readonly; +} + +static const TypeInfo sd_info = { + .name = TYPE_SD_CARD, + .parent = TYPE_DEVICE, + .instance_size = sizeof(SDState), + .class_size = sizeof(SDCardClass), + .class_init = sd_class_init, + .instance_init = sd_instance_init, + .instance_finalize = sd_instance_finalize, +}; + +static void sd_register_types(void) +{ + type_register_static(&sd_info); +} + +type_init(sd_register_types) diff --git a/hw/sd/sdhci-internal.h b/hw/sd/sdhci-internal.h new file mode 100644 index 000000000..e8c753d6d --- /dev/null +++ b/hw/sd/sdhci-internal.h @@ -0,0 +1,346 @@ +/* + * SD Association Host Standard Specification v2.0 controller emulation + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * Mitsyanko Igor <i.mitsyanko@samsung.com> + * Peter A.G. Crosthwaite <peter.crosthwaite@petalogix.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, see <http://www.gnu.org/licenses/>. + */ +#ifndef SDHCI_INTERNAL_H +#define SDHCI_INTERNAL_H + +#include "hw/registerfields.h" + +/* 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_ACMD23 0x0008 /* since v3 */ +#define SDHC_TRNS_READ 0x0010 +#define SDHC_TRNS_MULTI 0x0020 +#define SDHC_TRNMOD_MASK 0x0037 + +/* 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 (1 << 6) +#define SDHC_CMD_RESUME (1 << 7) +#define SDHC_CMD_ABORT ((1 << 6)|(1 << 7)) +#define SDHC_CMD_TYPE_MASK ((1 << 6)|(1 << 7)) +#define SDHC_COMMAND_TYPE(x) ((x) & SDHC_CMD_TYPE_MASK) + +/* ROC Response Register 0 0x0 */ +#define SDHC_RSPREG0 0x10 +/* ROC Response Register 1 0x0 */ +#define SDHC_RSPREG1 0x14 +/* ROC Response Register 2 0x0 */ +#define SDHC_RSPREG2 0x18 +/* ROC Response Register 3 0x0 */ +#define SDHC_RSPREG3 0x1C + +/* 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_IMX_CLOCK_GATE_OFF 0x00000080 +#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_CARD_DETECT 0x00040000 +#define SDHC_WRITE_PROTECT 0x00080000 +FIELD(SDHC_PRNSTS, DAT_LVL, 20, 4); +FIELD(SDHC_PRNSTS, CMD_LVL, 24, 1); +#define TRANSFERRING_DATA(x) \ + ((x) & (SDHC_DOING_READ | SDHC_DOING_WRITE)) + +/* R/W Host control Register 0x0 */ +#define SDHC_HOSTCTL 0x28 +#define SDHC_CTRL_LED 0x01 +#define SDHC_CTRL_DATATRANSFERWIDTH 0x02 /* SD mode only */ +#define SDHC_CTRL_HIGH_SPEED 0x04 +#define SDHC_CTRL_DMA_CHECK_MASK 0x18 +#define SDHC_CTRL_SDMA 0x00 +#define SDHC_CTRL_ADMA1_32 0x08 /* NOT ALLOWED since v2 */ +#define SDHC_CTRL_ADMA2_32 0x10 +#define SDHC_CTRL_ADMA2_64 0x18 +#define SDHC_DMA_TYPE(x) ((x) & SDHC_CTRL_DMA_CHECK_MASK) +#define SDHC_CTRL_4BITBUS 0x02 +#define SDHC_CTRL_8BITBUS 0x20 +#define SDHC_CTRL_CDTEST_INS 0x40 +#define SDHC_CTRL_CDTEST_EN 0x80 + +/* R/W Power Control Register 0x0 */ +#define SDHC_PWRCON 0x29 +#define SDHC_POWER_ON (1 << 0) +FIELD(SDHC_PWRCON, BUS_VOLTAGE, 1, 3); + +/* R/W Block Gap Control Register 0x0 */ +#define SDHC_BLKGAP 0x2A +#define SDHC_STOP_AT_GAP_REQ 0x01 +#define SDHC_CONTINUE_REQ 0x02 + +/* R/W WakeUp Control Register 0x0 */ +#define SDHC_WAKCON 0x2B +#define SDHC_WKUP_ON_INS (1 << 1) +#define SDHC_WKUP_ON_RMV (1 << 2) + +/* CLKCON */ +#define SDHC_CLKCON 0x2C +#define SDHC_CLOCK_INT_STABLE 0x0002 +#define SDHC_CLOCK_INT_EN 0x0001 +#define SDHC_CLOCK_SDCLK_EN (1 << 2) +#define SDHC_CLOCK_CHK_MASK 0x0007 +#define SDHC_CLOCK_IS_ON(x) \ + (((x) & SDHC_CLOCK_CHK_MASK) == SDHC_CLOCK_CHK_MASK) + +/* R/W Timeout Control Register 0x0 */ +#define SDHC_TIMEOUTCON 0x2E +FIELD(SDHC_TIMEOUTCON, COUNTER, 0, 4); + +/* 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_CMDIDX 0x0008 +#define SDHC_EIS_CMD12ERR 0x0100 +#define SDHC_EIS_ADMAERR 0x0200 + +/* 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_CMDIDX 0x0008 +#define SDHC_EISEN_ADMAERR 0x0200 + +/* R/W Normal Interrupt Signal Enable Register 0x0 */ +#define SDHC_NORINTSIGEN 0x38 +#define SDHC_NORINTSIG_INSERT (1 << 6) +#define SDHC_NORINTSIG_REMOVE (1 << 7) + +/* R/W Error Interrupt Signal Enable Register 0x0 */ +#define SDHC_ERRINTSIGEN 0x3A + +/* ROC Auto CMD12 error status register 0x0 */ +#define SDHC_ACMD12ERRSTS 0x3C +FIELD(SDHC_ACMD12ERRSTS, TIMEOUT_ERR, 1, 1); +FIELD(SDHC_ACMD12ERRSTS, CRC_ERR, 2, 1); +FIELD(SDHC_ACMD12ERRSTS, INDEX_ERR, 4, 1); + +/* Host Control Register 2 (since v3) */ +#define SDHC_HOSTCTL2 0x3E +FIELD(SDHC_HOSTCTL2, UHS_MODE_SEL, 0, 3); +FIELD(SDHC_HOSTCTL2, V18_ENA, 3, 1); /* UHS-I only */ +FIELD(SDHC_HOSTCTL2, DRIVER_STRENGTH, 4, 2); /* UHS-I only */ +FIELD(SDHC_HOSTCTL2, EXECUTE_TUNING, 6, 1); /* UHS-I only */ +FIELD(SDHC_HOSTCTL2, SAMPLING_CLKSEL, 7, 1); /* UHS-I only */ +FIELD(SDHC_HOSTCTL2, UHS_II_ENA, 8, 1); /* since v4 */ +FIELD(SDHC_HOSTCTL2, ADMA2_LENGTH, 10, 1); /* since v4 */ +FIELD(SDHC_HOSTCTL2, CMD23_ENA, 11, 1); /* since v4 */ +FIELD(SDHC_HOSTCTL2, VERSION4, 12, 1); /* since v4 */ +FIELD(SDHC_HOSTCTL2, ASYNC_INT, 14, 1); +FIELD(SDHC_HOSTCTL2, PRESET_ENA, 15, 1); + +/* HWInit Capabilities Register 0x05E80080 */ +#define SDHC_CAPAB 0x40 +FIELD(SDHC_CAPAB, TOCLKFREQ, 0, 6); +FIELD(SDHC_CAPAB, TOUNIT, 7, 1); +FIELD(SDHC_CAPAB, BASECLKFREQ, 8, 8); +FIELD(SDHC_CAPAB, MAXBLOCKLENGTH, 16, 2); +FIELD(SDHC_CAPAB, EMBEDDED_8BIT, 18, 1); /* since v3 */ +FIELD(SDHC_CAPAB, ADMA2, 19, 1); /* since v2 */ +FIELD(SDHC_CAPAB, ADMA1, 20, 1); /* v1 only? */ +FIELD(SDHC_CAPAB, HIGHSPEED, 21, 1); +FIELD(SDHC_CAPAB, SDMA, 22, 1); +FIELD(SDHC_CAPAB, SUSPRESUME, 23, 1); +FIELD(SDHC_CAPAB, V33, 24, 1); +FIELD(SDHC_CAPAB, V30, 25, 1); +FIELD(SDHC_CAPAB, V18, 26, 1); +FIELD(SDHC_CAPAB, BUS64BIT_V4, 27, 1); /* since v4.10 */ +FIELD(SDHC_CAPAB, BUS64BIT, 28, 1); /* since v2 */ +FIELD(SDHC_CAPAB, ASYNC_INT, 29, 1); /* since v3 */ +FIELD(SDHC_CAPAB, SLOT_TYPE, 30, 2); /* since v3 */ +FIELD(SDHC_CAPAB, BUS_SPEED, 32, 3); /* since v3 */ +FIELD(SDHC_CAPAB, UHS_II, 35, 8); /* since v4.20 */ +FIELD(SDHC_CAPAB, DRIVER_STRENGTH, 36, 3); /* since v3 */ +FIELD(SDHC_CAPAB, DRIVER_TYPE_A, 36, 1); /* since v3 */ +FIELD(SDHC_CAPAB, DRIVER_TYPE_C, 37, 1); /* since v3 */ +FIELD(SDHC_CAPAB, DRIVER_TYPE_D, 38, 1); /* since v3 */ +FIELD(SDHC_CAPAB, TIMER_RETUNING, 40, 4); /* since v3 */ +FIELD(SDHC_CAPAB, SDR50_TUNING, 45, 1); /* since v3 */ +FIELD(SDHC_CAPAB, RETUNING_MODE, 46, 2); /* since v3 */ +FIELD(SDHC_CAPAB, CLOCK_MULT, 48, 8); /* since v3 */ +FIELD(SDHC_CAPAB, ADMA3, 59, 1); /* since v4.20 */ +FIELD(SDHC_CAPAB, V18_VDD2, 60, 1); /* since v4.20 */ + +/* HWInit Maximum Current Capabilities Register 0x0 */ +#define SDHC_MAXCURR 0x48 +FIELD(SDHC_MAXCURR, V33_VDD1, 0, 8); +FIELD(SDHC_MAXCURR, V30_VDD1, 8, 8); +FIELD(SDHC_MAXCURR, V18_VDD1, 16, 8); +FIELD(SDHC_MAXCURR, V18_VDD2, 32, 8); /* since v4.20 */ + +/* 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_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_SET_LEN (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)) + +/* Slot interrupt status */ +#define SDHC_SLOT_INT_STATUS 0xFC + +/* HWInit Host Controller Version Register */ +#define SDHC_HCVER 0xFE +#define SDHC_HCVER_VENDOR 0x24 + +#define SDHC_REGISTERS_MAP_SIZE 0x100 +#define SDHC_INSERTION_DELAY (NANOSECONDS_PER_SECOND) +#define SDHC_TRANSFER_DELAY 100 +#define SDHC_ADMA_DESCS_PER_DELAY 5 +#define SDHC_CMD_RESPONSE (3 << 0) + +enum { + sdhc_not_stopped = 0, /* normal SDHC state */ + sdhc_gap_read = 1, /* SDHC stopped at block gap during read operation */ + sdhc_gap_write = 2 /* SDHC stopped at block gap during write operation */ +}; + +extern const VMStateDescription sdhci_vmstate; + + +#define ESDHC_MIX_CTRL 0x48 + +#define ESDHC_VENDOR_SPEC 0xc0 +#define ESDHC_IMX_FRC_SDCLK_ON (1 << 8) + +#define ESDHC_DLL_CTRL 0x60 + +#define ESDHC_TUNING_CTRL 0xcc +#define ESDHC_TUNE_CTRL_STATUS 0x68 +#define ESDHC_WTMK_LVL 0x44 + +/* Undocumented register used by guests working around erratum ERR004536 */ +#define ESDHC_UNDOCUMENTED_REG27 0x6c + +#define ESDHC_CTRL_4BITBUS (0x1 << 1) +#define ESDHC_CTRL_8BITBUS (0x2 << 1) + +#define ESDHC_PRNSTS_SDSTB (1 << 3) + +/* + * Default SD/MMC host controller features information, which will be + * presented in CAPABILITIES register of generic SD host controller at reset. + * + * support: + * - 3.3v and 1.8v voltages + * - SDMA/ADMA1/ADMA2 + * - high-speed + * max host controller R/W buffers size: 512B + * max clock frequency for SDclock: 52 MHz + * timeout clock frequency: 52 MHz + * + * does not support: + * - 3.0v voltage + * - 64-bit system bus + * - suspend/resume + */ +#define SDHC_CAPAB_REG_DEFAULT 0x057834b4 + +#define DEFINE_SDHCI_COMMON_PROPERTIES(_state) \ + DEFINE_PROP_UINT8("sd-spec-version", _state, sd_spec_version, 2), \ + DEFINE_PROP_UINT8("uhs", _state, uhs_mode, UHS_NOT_SUPPORTED), \ + DEFINE_PROP_UINT8("vendor", _state, vendor, SDHCI_VENDOR_NONE), \ + \ + /* Capabilities registers provide information on supported + * features of this specific host controller implementation */ \ + DEFINE_PROP_UINT64("capareg", _state, capareg, SDHC_CAPAB_REG_DEFAULT), \ + DEFINE_PROP_UINT64("maxcurr", _state, maxcurr, 0) + +void sdhci_initfn(SDHCIState *s); +void sdhci_uninitfn(SDHCIState *s); +void sdhci_common_realize(SDHCIState *s, Error **errp); +void sdhci_common_unrealize(SDHCIState *s); +void sdhci_common_class_init(ObjectClass *klass, void *data); + +#endif diff --git a/hw/sd/sdhci-pci.c b/hw/sd/sdhci-pci.c new file mode 100644 index 000000000..c737c8b93 --- /dev/null +++ b/hw/sd/sdhci-pci.c @@ -0,0 +1,87 @@ +/* + * SDHCI device on PCI + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/module.h" +#include "hw/qdev-properties.h" +#include "hw/sd/sdhci.h" +#include "sdhci-internal.h" + +static Property sdhci_pci_properties[] = { + DEFINE_SDHCI_COMMON_PROPERTIES(SDHCIState), + DEFINE_PROP_END_OF_LIST(), +}; + +static void sdhci_pci_realize(PCIDevice *dev, Error **errp) +{ + ERRP_GUARD(); + SDHCIState *s = PCI_SDHCI(dev); + + sdhci_initfn(s); + sdhci_common_realize(s, errp); + if (*errp) { + return; + } + + dev->config[PCI_CLASS_PROG] = 0x01; /* Standard Host supported DMA */ + dev->config[PCI_INTERRUPT_PIN] = 0x01; /* interrupt pin A */ + s->irq = pci_allocate_irq(dev); + s->dma_as = pci_get_address_space(dev); + pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->iomem); +} + +static void sdhci_pci_exit(PCIDevice *dev) +{ + SDHCIState *s = PCI_SDHCI(dev); + + sdhci_common_unrealize(s); + sdhci_uninitfn(s); +} + +static void sdhci_pci_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->realize = sdhci_pci_realize; + k->exit = sdhci_pci_exit; + k->vendor_id = PCI_VENDOR_ID_REDHAT; + k->device_id = PCI_DEVICE_ID_REDHAT_SDHCI; + k->class_id = PCI_CLASS_SYSTEM_SDHCI; + device_class_set_props(dc, sdhci_pci_properties); + + sdhci_common_class_init(klass, data); +} + +static const TypeInfo sdhci_pci_info = { + .name = TYPE_PCI_SDHCI, + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(SDHCIState), + .class_init = sdhci_pci_class_init, + .interfaces = (InterfaceInfo[]) { + { INTERFACE_CONVENTIONAL_PCI_DEVICE }, + { }, + }, +}; + +static void sdhci_pci_register_type(void) +{ + type_register_static(&sdhci_pci_info); +} + +type_init(sdhci_pci_register_type) diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c new file mode 100644 index 000000000..c9dc065cc --- /dev/null +++ b/hw/sd/sdhci.c @@ -0,0 +1,1869 @@ +/* + * 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> + * + * Based on MMC controller for Samsung S5PC1xx-based board emulation + * by Alexey Merkulov and Vladimir Monakhov. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "qemu/error-report.h" +#include "qapi/error.h" +#include "hw/irq.h" +#include "hw/qdev-properties.h" +#include "sysemu/dma.h" +#include "qemu/timer.h" +#include "qemu/bitops.h" +#include "hw/sd/sdhci.h" +#include "migration/vmstate.h" +#include "sdhci-internal.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "trace.h" +#include "qom/object.h" + +#define TYPE_SDHCI_BUS "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)) + +static inline unsigned int sdhci_get_fifolen(SDHCIState *s) +{ + return 1 << (9 + FIELD_EX32(s->capareg, SDHC_CAPAB, MAXBLOCKLENGTH)); +} + +/* return true on error */ +static bool sdhci_check_capab_freq_range(SDHCIState *s, const char *desc, + uint8_t freq, Error **errp) +{ + if (s->sd_spec_version >= 3) { + return false; + } + switch (freq) { + case 0: + case 10 ... 63: + break; + default: + error_setg(errp, "SD %s clock frequency can have value" + "in range 0-63 only", desc); + return true; + } + return false; +} + +static void sdhci_check_capareg(SDHCIState *s, Error **errp) +{ + uint64_t msk = s->capareg; + uint32_t val; + bool y; + + switch (s->sd_spec_version) { + case 4: + val = FIELD_EX64(s->capareg, SDHC_CAPAB, BUS64BIT_V4); + trace_sdhci_capareg("64-bit system bus (v4)", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, BUS64BIT_V4, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, UHS_II); + trace_sdhci_capareg("UHS-II", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, UHS_II, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, ADMA3); + trace_sdhci_capareg("ADMA3", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, ADMA3, 0); + + /* fallthrough */ + case 3: + val = FIELD_EX64(s->capareg, SDHC_CAPAB, ASYNC_INT); + trace_sdhci_capareg("async interrupt", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, ASYNC_INT, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, SLOT_TYPE); + if (val) { + error_setg(errp, "slot-type not supported"); + return; + } + trace_sdhci_capareg("slot type", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, SLOT_TYPE, 0); + + if (val != 2) { + val = FIELD_EX64(s->capareg, SDHC_CAPAB, EMBEDDED_8BIT); + trace_sdhci_capareg("8-bit bus", val); + } + msk = FIELD_DP64(msk, SDHC_CAPAB, EMBEDDED_8BIT, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, BUS_SPEED); + trace_sdhci_capareg("bus speed mask", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, BUS_SPEED, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, DRIVER_STRENGTH); + trace_sdhci_capareg("driver strength mask", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, DRIVER_STRENGTH, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, TIMER_RETUNING); + trace_sdhci_capareg("timer re-tuning", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, TIMER_RETUNING, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, SDR50_TUNING); + trace_sdhci_capareg("use SDR50 tuning", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, SDR50_TUNING, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, RETUNING_MODE); + trace_sdhci_capareg("re-tuning mode", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, RETUNING_MODE, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, CLOCK_MULT); + trace_sdhci_capareg("clock multiplier", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, CLOCK_MULT, 0); + + /* fallthrough */ + case 2: /* default version */ + val = FIELD_EX64(s->capareg, SDHC_CAPAB, ADMA2); + trace_sdhci_capareg("ADMA2", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, ADMA2, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, ADMA1); + trace_sdhci_capareg("ADMA1", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, ADMA1, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, BUS64BIT); + trace_sdhci_capareg("64-bit system bus (v3)", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, BUS64BIT, 0); + + /* fallthrough */ + case 1: + y = FIELD_EX64(s->capareg, SDHC_CAPAB, TOUNIT); + msk = FIELD_DP64(msk, SDHC_CAPAB, TOUNIT, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, TOCLKFREQ); + trace_sdhci_capareg(y ? "timeout (MHz)" : "Timeout (KHz)", val); + if (sdhci_check_capab_freq_range(s, "timeout", val, errp)) { + return; + } + msk = FIELD_DP64(msk, SDHC_CAPAB, TOCLKFREQ, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, BASECLKFREQ); + trace_sdhci_capareg(y ? "base (MHz)" : "Base (KHz)", val); + if (sdhci_check_capab_freq_range(s, "base", val, errp)) { + return; + } + msk = FIELD_DP64(msk, SDHC_CAPAB, BASECLKFREQ, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, MAXBLOCKLENGTH); + if (val >= 3) { + error_setg(errp, "block size can be 512, 1024 or 2048 only"); + return; + } + trace_sdhci_capareg("max block length", sdhci_get_fifolen(s)); + msk = FIELD_DP64(msk, SDHC_CAPAB, MAXBLOCKLENGTH, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, HIGHSPEED); + trace_sdhci_capareg("high speed", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, HIGHSPEED, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, SDMA); + trace_sdhci_capareg("SDMA", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, SDMA, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, SUSPRESUME); + trace_sdhci_capareg("suspend/resume", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, SUSPRESUME, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, V33); + trace_sdhci_capareg("3.3v", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, V33, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, V30); + trace_sdhci_capareg("3.0v", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, V30, 0); + + val = FIELD_EX64(s->capareg, SDHC_CAPAB, V18); + trace_sdhci_capareg("1.8v", val); + msk = FIELD_DP64(msk, SDHC_CAPAB, V18, 0); + break; + + default: + error_setg(errp, "Unsupported spec version: %u", s->sd_spec_version); + } + if (msk) { + qemu_log_mask(LOG_UNIMP, + "SDHCI: unknown CAPAB mask: 0x%016" PRIx64 "\n", msk); + } +} + +static uint8_t sdhci_slotint(SDHCIState *s) +{ + return (s->norintsts & s->norintsigen) || (s->errintsts & s->errintsigen) || + ((s->norintsts & SDHC_NIS_INSERT) && (s->wakcon & SDHC_WKUP_ON_INS)) || + ((s->norintsts & SDHC_NIS_REMOVE) && (s->wakcon & SDHC_WKUP_ON_RMV)); +} + +/* Return true if IRQ was pending and delivered */ +static bool sdhci_update_irq(SDHCIState *s) +{ + bool pending = sdhci_slotint(s); + + qemu_set_irq(s->irq, pending); + + return pending; +} + +static void sdhci_raise_insertion_irq(void *opaque) +{ + SDHCIState *s = (SDHCIState *)opaque; + + if (s->norintsts & SDHC_NIS_REMOVE) { + timer_mod(s->insert_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + SDHC_INSERTION_DELAY); + } else { + s->prnsts = 0x1ff0000; + if (s->norintstsen & SDHC_NISEN_INSERT) { + s->norintsts |= SDHC_NIS_INSERT; + } + sdhci_update_irq(s); + } +} + +static void sdhci_set_inserted(DeviceState *dev, bool level) +{ + SDHCIState *s = (SDHCIState *)dev; + + trace_sdhci_set_inserted(level ? "insert" : "eject"); + if ((s->norintsts & SDHC_NIS_REMOVE) && level) { + /* Give target some time to notice card ejection */ + timer_mod(s->insert_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + SDHC_INSERTION_DELAY); + } else { + if (level) { + s->prnsts = 0x1ff0000; + if (s->norintstsen & SDHC_NISEN_INSERT) { + s->norintsts |= SDHC_NIS_INSERT; + } + } else { + s->prnsts = 0x1fa0000; + s->pwrcon &= ~SDHC_POWER_ON; + s->clkcon &= ~SDHC_CLOCK_SDCLK_EN; + if (s->norintstsen & SDHC_NISEN_REMOVE) { + s->norintsts |= SDHC_NIS_REMOVE; + } + } + sdhci_update_irq(s); + } +} + +static void sdhci_set_readonly(DeviceState *dev, bool level) +{ + SDHCIState *s = (SDHCIState *)dev; + + if (level) { + s->prnsts &= ~SDHC_WRITE_PROTECT; + } else { + /* Write enabled */ + s->prnsts |= SDHC_WRITE_PROTECT; + } +} + +static void sdhci_reset(SDHCIState *s) +{ + DeviceState *dev = DEVICE(s); + + timer_del(s->insert_timer); + timer_del(s->transfer_timer); + + /* Set all registers to 0. Capabilities/Version registers are not cleared + * and assumed to always preserve their value, given to them during + * initialization */ + memset(&s->sdmasysad, 0, (uintptr_t)&s->capareg - (uintptr_t)&s->sdmasysad); + + /* Reset other state based on current card insertion/readonly status */ + sdhci_set_inserted(dev, sdbus_get_inserted(&s->sdbus)); + sdhci_set_readonly(dev, sdbus_get_readonly(&s->sdbus)); + + s->data_count = 0; + s->stopped_state = sdhc_not_stopped; + s->pending_insert_state = false; +} + +static void sdhci_poweron_reset(DeviceState *dev) +{ + /* QOM (ie power-on) reset. This is identical to reset + * commanded via device register apart from handling of the + * 'pending insert on powerup' quirk. + */ + SDHCIState *s = (SDHCIState *)dev; + + sdhci_reset(s); + + if (s->pending_insert_quirk) { + s->pending_insert_state = true; + } +} + +static void sdhci_data_transfer(void *opaque); + +static void sdhci_send_command(SDHCIState *s) +{ + SDRequest request; + uint8_t response[16]; + int rlen; + bool timeout = false; + + s->errintsts = 0; + s->acmd12errsts = 0; + request.cmd = s->cmdreg >> 8; + request.arg = s->argument; + + trace_sdhci_send_command(request.cmd, request.arg); + rlen = sdbus_do_command(&s->sdbus, &request, response); + + if (s->cmdreg & SDHC_CMD_RESPONSE) { + if (rlen == 4) { + s->rspreg[0] = ldl_be_p(response); + s->rspreg[1] = s->rspreg[2] = s->rspreg[3] = 0; + trace_sdhci_response4(s->rspreg[0]); + } else if (rlen == 16) { + s->rspreg[0] = ldl_be_p(&response[11]); + s->rspreg[1] = ldl_be_p(&response[7]); + s->rspreg[2] = ldl_be_p(&response[3]); + s->rspreg[3] = (response[0] << 16) | (response[1] << 8) | + response[2]; + 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; + s->norintsts |= SDHC_NIS_ERR; + } + } + + if (!(s->quirks & SDHCI_QUIRK_NO_BUSY_IRQ) && + (s->norintstsen & SDHC_NISEN_TRSCMP) && + (s->cmdreg & SDHC_CMD_RESPONSE) == SDHC_CMD_RSP_WITH_BUSY) { + s->norintsts |= SDHC_NIS_TRSCMP; + } + } + + if (s->norintstsen & SDHC_NISEN_CMDCMP) { + s->norintsts |= SDHC_NIS_CMDCMP; + } + + sdhci_update_irq(s); + + if (!timeout && s->blksize && (s->cmdreg & SDHC_CMD_DATA_PRESENT)) { + s->data_count = 0; + sdhci_data_transfer(s); + } +} + +static void sdhci_end_transfer(SDHCIState *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; + trace_sdhci_end_transfer(request.cmd, request.arg); + sdbus_do_command(&s->sdbus, &request, response); + /* Auto CMD12 response goes to the upper Response register */ + s->rspreg[3] = ldl_be_p(response); + } + + 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; + } + + sdhci_update_irq(s); +} + +/* + * Programmed i/o data transfer + */ +#define BLOCK_SIZE_MASK (4 * KiB - 1) + +/* Fill host controller's read buffer with BLKSIZE bytes of data from card */ +static void sdhci_read_block_from_card(SDHCIState *s) +{ + const uint16_t blk_size = s->blksize & BLOCK_SIZE_MASK; + + if ((s->trnmod & SDHC_TRNS_MULTI) && + (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0)) { + return; + } + + 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)) { + /* Device is in tuning */ + s->hostctl2 &= ~R_SDHC_HOSTCTL2_EXECUTE_TUNING_MASK; + s->hostctl2 |= R_SDHC_HOSTCTL2_SAMPLING_CLKSEL_MASK; + s->prnsts &= ~(SDHC_DAT_LINE_ACTIVE | SDHC_DOING_READ | + SDHC_DATA_INHIBIT); + goto read_done; + } + + /* New data now available for READ through Buffer Port Register */ + s->prnsts |= SDHC_DATA_AVAILABLE; + if (s->norintstsen & SDHC_NISEN_RBUFRDY) { + s->norintsts |= SDHC_NIS_RBUFRDY; + } + + /* Clear DAT line active status if that was the last block */ + if ((s->trnmod & SDHC_TRNS_MULTI) == 0 || + ((s->trnmod & SDHC_TRNS_MULTI) && s->blkcnt == 1)) { + s->prnsts &= ~SDHC_DAT_LINE_ACTIVE; + } + + /* If stop at block gap request was set and it's not the last block of + * data - generate Block Event interrupt */ + if (s->stopped_state == sdhc_gap_read && (s->trnmod & SDHC_TRNS_MULTI) && + s->blkcnt != 1) { + s->prnsts &= ~SDHC_DAT_LINE_ACTIVE; + if (s->norintstsen & SDHC_EISEN_BLKGAP) { + s->norintsts |= SDHC_EIS_BLKGAP; + } + } + +read_done: + sdhci_update_irq(s); +} + +/* Read @size byte of data from host controller @s BUFFER DATA PORT register */ +static uint32_t sdhci_read_dataport(SDHCIState *s, unsigned size) +{ + uint32_t value = 0; + int i; + + /* first check that a valid data exists in host controller input buffer */ + if ((s->prnsts & SDHC_DATA_AVAILABLE) == 0) { + trace_sdhci_error("read from empty buffer"); + return 0; + } + + for (i = 0; i < size; i++) { + value |= s->fifo_buffer[s->data_count] << i * 8; + s->data_count++; + /* check if we've read all valid data (blksize bytes) from buffer */ + if ((s->data_count) >= (s->blksize & BLOCK_SIZE_MASK)) { + trace_sdhci_read_dataport(s->data_count); + s->prnsts &= ~SDHC_DATA_AVAILABLE; /* no more data in a buffer */ + s->data_count = 0; /* next buff read must start at position [0] */ + + if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) { + s->blkcnt--; + } + + /* if that was the last block of data */ + if ((s->trnmod & SDHC_TRNS_MULTI) == 0 || + ((s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0)) || + /* stop at gap request */ + (s->stopped_state == sdhc_gap_read && + !(s->prnsts & SDHC_DAT_LINE_ACTIVE))) { + sdhci_end_transfer(s); + } else { /* if there are more data, read next block from card */ + sdhci_read_block_from_card(s); + } + break; + } + } + + return value; +} + +/* Write data from host controller FIFO to card */ +static void sdhci_write_block_to_card(SDHCIState *s) +{ + if (s->prnsts & SDHC_SPACE_AVAILABLE) { + if (s->norintstsen & SDHC_NISEN_WBUFRDY) { + s->norintsts |= SDHC_NIS_WBUFRDY; + } + sdhci_update_irq(s); + return; + } + + if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) { + if (s->blkcnt == 0) { + return; + } else { + s->blkcnt--; + } + } + + 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; + + /* 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))) { + sdhci_end_transfer(s); + } else if (s->norintstsen & SDHC_NISEN_WBUFRDY) { + s->norintsts |= SDHC_NIS_WBUFRDY; + } + + /* Generate Block Gap Event if requested and if not the last block */ + if (s->stopped_state == sdhc_gap_write && (s->trnmod & SDHC_TRNS_MULTI) && + s->blkcnt > 0) { + s->prnsts &= ~SDHC_DOING_WRITE; + if (s->norintstsen & SDHC_EISEN_BLKGAP) { + s->norintsts |= SDHC_EIS_BLKGAP; + } + sdhci_end_transfer(s); + } + + sdhci_update_irq(s); +} + +/* Write @size bytes of @value data to host controller @s Buffer Data Port + * register */ +static void sdhci_write_dataport(SDHCIState *s, uint32_t value, unsigned size) +{ + unsigned i; + + /* Check that there is free space left in a buffer */ + if (!(s->prnsts & SDHC_SPACE_AVAILABLE)) { + trace_sdhci_error("Can't write to data buffer: buffer full"); + return; + } + + for (i = 0; i < size; i++) { + s->fifo_buffer[s->data_count] = value & 0xFF; + s->data_count++; + value >>= 8; + if (s->data_count >= (s->blksize & BLOCK_SIZE_MASK)) { + trace_sdhci_write_dataport(s->data_count); + s->data_count = 0; + s->prnsts &= ~SDHC_SPACE_AVAILABLE; + if (s->prnsts & SDHC_DOING_WRITE) { + sdhci_write_block_to_card(s); + } + } + } +} + +/* + * Single DMA data transfer + */ + +/* Multi block SDMA transfer */ +static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) +{ + bool page_aligned = false; + 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); + + if (!(s->trnmod & SDHC_TRNS_BLK_CNT_EN) || !s->blkcnt) { + qemu_log_mask(LOG_UNIMP, "infinite transfer is not supported\n"); + return; + } + + /* XXX: Some sd/mmc drivers (for example, u-boot-slp) do not account for + * possible stop at page boundary if initial address is not page aligned, + * allow them to work properly */ + if ((s->sdmasysad % boundary_chk) == 0) { + page_aligned = true; + } + + s->prnsts |= SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE; + if (s->trnmod & SDHC_TRNS_READ) { + s->prnsts |= SDHC_DOING_READ; + while (s->blkcnt) { + if (s->data_count == 0) { + sdbus_read_data(&s->sdbus, s->fifo_buffer, block_size); + } + begin = s->data_count; + if (((boundary_count + begin) < block_size) && page_aligned) { + s->data_count = boundary_count + begin; + boundary_count = 0; + } else { + s->data_count = block_size; + boundary_count -= block_size - begin; + if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) { + s->blkcnt--; + } + } + dma_memory_write(s->dma_as, s->sdmasysad, + &s->fifo_buffer[begin], s->data_count - begin); + s->sdmasysad += s->data_count - begin; + if (s->data_count == block_size) { + s->data_count = 0; + } + if (page_aligned && boundary_count == 0) { + break; + } + } + } else { + s->prnsts |= SDHC_DOING_WRITE; + while (s->blkcnt) { + begin = s->data_count; + if (((boundary_count + begin) < block_size) && page_aligned) { + s->data_count = boundary_count + begin; + boundary_count = 0; + } else { + s->data_count = block_size; + boundary_count -= block_size - begin; + } + dma_memory_read(s->dma_as, s->sdmasysad, + &s->fifo_buffer[begin], s->data_count - begin); + s->sdmasysad += s->data_count - begin; + if (s->data_count == block_size) { + sdbus_write_data(&s->sdbus, s->fifo_buffer, block_size); + s->data_count = 0; + if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) { + s->blkcnt--; + } + } + if (page_aligned && boundary_count == 0) { + break; + } + } + } + + if (s->blkcnt == 0) { + sdhci_end_transfer(s); + } else { + if (s->norintstsen & SDHC_NISEN_DMA) { + s->norintsts |= SDHC_NIS_DMA; + } + sdhci_update_irq(s); + } +} + +/* single block SDMA transfer */ +static void sdhci_sdma_transfer_single_block(SDHCIState *s) +{ + uint32_t datacnt = s->blksize & BLOCK_SIZE_MASK; + + if (s->trnmod & SDHC_TRNS_READ) { + sdbus_read_data(&s->sdbus, s->fifo_buffer, datacnt); + dma_memory_write(s->dma_as, s->sdmasysad, s->fifo_buffer, datacnt); + } else { + dma_memory_read(s->dma_as, s->sdmasysad, s->fifo_buffer, datacnt); + sdbus_write_data(&s->sdbus, s->fifo_buffer, datacnt); + } + s->blkcnt--; + + sdhci_end_transfer(s); +} + +typedef struct ADMADescr { + hwaddr addr; + uint16_t length; + uint8_t attr; + uint8_t incr; +} ADMADescr; + +static void get_adma_description(SDHCIState *s, ADMADescr *dscr) +{ + uint32_t adma1 = 0; + uint64_t adma2 = 0; + 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, &adma2, sizeof(adma2)); + adma2 = le64_to_cpu(adma2); + /* The spec does not specify endianness of descriptor table. + * We currently assume that it is LE. + */ + dscr->addr = (hwaddr)extract64(adma2, 32, 32) & ~0x3ull; + dscr->length = (uint16_t)extract64(adma2, 16, 16); + dscr->attr = (uint8_t)extract64(adma2, 0, 7); + dscr->incr = 8; + break; + case SDHC_CTRL_ADMA1_32: + dma_memory_read(s->dma_as, entry_addr, &adma1, sizeof(adma1)); + adma1 = le32_to_cpu(adma1); + dscr->addr = (hwaddr)(adma1 & 0xFFFFF000); + dscr->attr = (uint8_t)extract32(adma1, 0, 7); + dscr->incr = 4; + if ((dscr->attr & SDHC_ADMA_ATTR_ACT_MASK) == SDHC_ADMA_ATTR_SET_LEN) { + dscr->length = (uint16_t)extract32(adma1, 12, 16); + } else { + dscr->length = 4 * KiB; + } + break; + case SDHC_CTRL_ADMA2_64: + dma_memory_read(s->dma_as, entry_addr, &dscr->attr, 1); + dma_memory_read(s->dma_as, entry_addr + 2, &dscr->length, 2); + dscr->length = le16_to_cpu(dscr->length); + dma_memory_read(s->dma_as, entry_addr + 4, &dscr->addr, 8); + dscr->addr = le64_to_cpu(dscr->addr); + dscr->attr &= (uint8_t) ~0xC0; + dscr->incr = 12; + break; + } +} + +/* Advanced DMA data transfer */ + +static void sdhci_do_adma(SDHCIState *s) +{ + unsigned int begin, length; + const uint16_t block_size = s->blksize & BLOCK_SIZE_MASK; + ADMADescr dscr = {}; + 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; + + get_adma_description(s, &dscr); + trace_sdhci_adma_loop(dscr.addr, dscr.length, dscr.attr); + + if ((dscr.attr & SDHC_ADMA_ATTR_VALID) == 0) { + /* Indicate that error occurred in ST_FDS state */ + s->admaerr &= ~SDHC_ADMAERR_STATE_MASK; + s->admaerr |= SDHC_ADMAERR_STATE_ST_FDS; + + /* Generate ADMA error interrupt */ + if (s->errintstsen & SDHC_EISEN_ADMAERR) { + s->errintsts |= SDHC_EIS_ADMAERR; + s->norintsts |= SDHC_NIS_ERR; + } + + sdhci_update_irq(s); + return; + } + + length = dscr.length ? dscr.length : 64 * KiB; + + 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) { + sdbus_read_data(&s->sdbus, s->fifo_buffer, block_size); + } + begin = s->data_count; + if ((length + begin) < block_size) { + s->data_count = length + begin; + length = 0; + } else { + s->data_count = block_size; + length -= block_size - begin; + } + dma_memory_write(s->dma_as, dscr.addr, + &s->fifo_buffer[begin], + s->data_count - begin); + dscr.addr += s->data_count - begin; + if (s->data_count == block_size) { + s->data_count = 0; + if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) { + s->blkcnt--; + if (s->blkcnt == 0) { + break; + } + } + } + } + } else { + s->prnsts |= SDHC_DOING_WRITE; + while (length) { + begin = s->data_count; + if ((length + begin) < block_size) { + s->data_count = length + begin; + length = 0; + } else { + s->data_count = block_size; + length -= block_size - begin; + } + dma_memory_read(s->dma_as, dscr.addr, + &s->fifo_buffer[begin], + s->data_count - begin); + dscr.addr += s->data_count - begin; + if (s->data_count == block_size) { + sdbus_write_data(&s->sdbus, s->fifo_buffer, block_size); + s->data_count = 0; + if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) { + s->blkcnt--; + if (s->blkcnt == 0) { + break; + } + } + } + } + } + s->admasysaddr += dscr.incr; + break; + case SDHC_ADMA_ATTR_ACT_LINK: /* link to next descriptor table */ + s->admasysaddr = dscr.addr; + trace_sdhci_adma("link", s->admasysaddr); + break; + default: + s->admasysaddr += dscr.incr; + break; + } + + if (dscr.attr & SDHC_ADMA_ATTR_INT) { + trace_sdhci_adma("interrupt", s->admasysaddr); + if (s->norintstsen & SDHC_NISEN_DMA) { + s->norintsts |= SDHC_NIS_DMA; + } + + 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 */ + if (((s->trnmod & SDHC_TRNS_BLK_CNT_EN) && + (s->blkcnt == 0)) || (dscr.attr & SDHC_ADMA_ATTR_END)) { + trace_sdhci_adma_transfer_completed(); + if (length || ((dscr.attr & SDHC_ADMA_ATTR_END) && + (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && + s->blkcnt != 0)) { + trace_sdhci_error("SD/MMC host ADMA length mismatch"); + s->admaerr |= SDHC_ADMAERR_LENGTH_MISMATCH | + SDHC_ADMAERR_STATE_ST_TFR; + 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); + } + sdhci_end_transfer(s); + return; + } + + } + + /* we have unfinished business - reschedule to continue ADMA */ + timer_mod(s->transfer_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + SDHC_TRANSFER_DELAY); +} + +/* Perform data transfer according to controller configuration */ + +static void sdhci_data_transfer(void *opaque) +{ + SDHCIState *s = (SDHCIState *)opaque; + + if (s->trnmod & SDHC_TRNS_DMA) { + switch (SDHC_DMA_TYPE(s->hostctl1)) { + case SDHC_CTRL_SDMA: + if ((s->blkcnt == 1) || !(s->trnmod & SDHC_TRNS_MULTI)) { + sdhci_sdma_transfer_single_block(s); + } else { + sdhci_sdma_transfer_multi_blocks(s); + } + + break; + case SDHC_CTRL_ADMA1_32: + if (!(s->capareg & R_SDHC_CAPAB_ADMA1_MASK)) { + trace_sdhci_error("ADMA1 not supported"); + break; + } + + sdhci_do_adma(s); + break; + case SDHC_CTRL_ADMA2_32: + if (!(s->capareg & R_SDHC_CAPAB_ADMA2_MASK)) { + trace_sdhci_error("ADMA2 not supported"); + break; + } + + sdhci_do_adma(s); + break; + case SDHC_CTRL_ADMA2_64: + if (!(s->capareg & R_SDHC_CAPAB_ADMA2_MASK) || + !(s->capareg & R_SDHC_CAPAB_BUS64BIT_MASK)) { + trace_sdhci_error("64 bit ADMA not supported"); + break; + } + + sdhci_do_adma(s); + break; + default: + trace_sdhci_error("Unsupported DMA type"); + break; + } + } else { + if ((s->trnmod & SDHC_TRNS_READ) && sdbus_data_ready(&s->sdbus)) { + s->prnsts |= SDHC_DOING_READ | SDHC_DATA_INHIBIT | + SDHC_DAT_LINE_ACTIVE; + sdhci_read_block_from_card(s); + } else { + s->prnsts |= SDHC_DOING_WRITE | SDHC_DAT_LINE_ACTIVE | + SDHC_SPACE_AVAILABLE | SDHC_DATA_INHIBIT; + sdhci_write_block_to_card(s); + } + } +} + +static bool sdhci_can_issue_command(SDHCIState *s) +{ + if (!SDHC_CLOCK_IS_ON(s->clkcon) || + (((s->prnsts & SDHC_DATA_INHIBIT) || s->stopped_state) && + ((s->cmdreg & SDHC_CMD_DATA_PRESENT) || + ((s->cmdreg & SDHC_CMD_RESPONSE) == SDHC_CMD_RSP_WITH_BUSY && + !(SDHC_COMMAND_TYPE(s->cmdreg) == SDHC_CMD_ABORT))))) { + return false; + } + + return true; +} + +/* The Buffer Data Port register must be accessed in sequential and + * continuous manner */ +static inline bool +sdhci_buff_access_is_sequential(SDHCIState *s, unsigned byte_num) +{ + if ((s->data_count & 0x3) != byte_num) { + trace_sdhci_error("Non-sequential access to Buffer Data Port register" + "is prohibited\n"); + return false; + } + 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; + break; + case SDHC_BLKSIZE: + ret = s->blksize | (s->blkcnt << 16); + break; + case SDHC_ARGUMENT: + ret = s->argument; + break; + case SDHC_TRNMOD: + ret = s->trnmod | (s->cmdreg << 16); + break; + case SDHC_RSPREG0 ... SDHC_RSPREG3: + ret = s->rspreg[((offset & ~0x3) - SDHC_RSPREG0) >> 2]; + break; + case SDHC_BDATA: + if (sdhci_buff_access_is_sequential(s, offset - SDHC_BDATA)) { + ret = sdhci_read_dataport(s, size); + trace_sdhci_access("rd", size << 3, offset, "->", ret, ret); + return ret; + } + break; + case SDHC_PRNSTS: + ret = s->prnsts; + ret = FIELD_DP32(ret, SDHC_PRNSTS, DAT_LVL, + sdbus_get_dat_lines(&s->sdbus)); + ret = FIELD_DP32(ret, SDHC_PRNSTS, CMD_LVL, + sdbus_get_cmd_line(&s->sdbus)); + break; + case SDHC_HOSTCTL: + ret = s->hostctl1 | (s->pwrcon << 8) | (s->blkgap << 16) | + (s->wakcon << 24); + break; + case SDHC_CLKCON: + ret = s->clkcon | (s->timeoutcon << 16); + break; + case SDHC_NORINTSTS: + ret = s->norintsts | (s->errintsts << 16); + break; + case SDHC_NORINTSTSEN: + ret = s->norintstsen | (s->errintstsen << 16); + break; + case SDHC_NORINTSIGEN: + ret = s->norintsigen | (s->errintsigen << 16); + break; + case SDHC_ACMD12ERRSTS: + ret = s->acmd12errsts | (s->hostctl2 << 16); + break; + case SDHC_CAPAB: + ret = (uint32_t)s->capareg; + break; + case SDHC_CAPAB + 4: + ret = (uint32_t)(s->capareg >> 32); + break; + case SDHC_MAXCURR: + ret = (uint32_t)s->maxcurr; + break; + case SDHC_MAXCURR + 4: + ret = (uint32_t)(s->maxcurr >> 32); + break; + case SDHC_ADMAERR: + ret = s->admaerr; + break; + case SDHC_ADMASYSADDR: + ret = (uint32_t)s->admasysaddr; + break; + case SDHC_ADMASYSADDR + 4: + ret = (uint32_t)(s->admasysaddr >> 32); + break; + case SDHC_SLOT_INT_STATUS: + ret = (s->version << 16) | sdhci_slotint(s); + break; + default: + qemu_log_mask(LOG_UNIMP, "SDHC rd_%ub @0x%02" HWADDR_PRIx " " + "not implemented\n", size, offset); + break; + } + + ret >>= (offset & 0x3) * 8; + ret &= (1ULL << (size * 8)) - 1; + trace_sdhci_access("rd", size << 3, offset, "->", ret, ret); + return ret; +} + +static inline void sdhci_blkgap_write(SDHCIState *s, uint8_t value) +{ + if ((value & SDHC_STOP_AT_GAP_REQ) && (s->blkgap & SDHC_STOP_AT_GAP_REQ)) { + return; + } + s->blkgap = value & SDHC_STOP_AT_GAP_REQ; + + if ((value & SDHC_CONTINUE_REQ) && s->stopped_state && + (s->blkgap & SDHC_STOP_AT_GAP_REQ) == 0) { + if (s->stopped_state == sdhc_gap_read) { + s->prnsts |= SDHC_DAT_LINE_ACTIVE | SDHC_DOING_READ; + sdhci_read_block_from_card(s); + } else { + s->prnsts |= SDHC_DAT_LINE_ACTIVE | SDHC_DOING_WRITE; + sdhci_write_block_to_card(s); + } + s->stopped_state = sdhc_not_stopped; + } else if (!s->stopped_state && (value & SDHC_STOP_AT_GAP_REQ)) { + if (s->prnsts & SDHC_DOING_READ) { + s->stopped_state = sdhc_gap_read; + } else if (s->prnsts & SDHC_DOING_WRITE) { + s->stopped_state = sdhc_gap_write; + } + } +} + +static inline void sdhci_reset_write(SDHCIState *s, uint8_t value) +{ + switch (value) { + case SDHC_RESET_ALL: + sdhci_reset(s); + break; + case SDHC_RESET_CMD: + s->prnsts &= ~SDHC_CMD_INHIBIT; + s->norintsts &= ~SDHC_NIS_CMDCMP; + break; + case SDHC_RESET_DATA: + s->data_count = 0; + 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->stopped_state = sdhc_not_stopped; + s->norintsts &= ~(SDHC_NIS_WBUFRDY | SDHC_NIS_RBUFRDY | + SDHC_NIS_DMA | SDHC_NIS_TRSCMP | SDHC_NIS_BLKGAP); + break; + } +} + +static void +sdhci_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) +{ + SDHCIState *s = (SDHCIState *)opaque; + unsigned shift = 8 * (offset & 0x3); + uint32_t mask = ~(((1ULL << (size * 8)) - 1) << shift); + uint32_t value = val; + value <<= shift; + + if (timer_pending(s->transfer_timer)) { + sdhci_resume_pending_transfer(s); + } + + switch (offset & ~0x3) { + case SDHC_SYSAD: + 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)) { + 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\n", __func__, s->blksize, + 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; + case SDHC_ARGUMENT: + MASKED_WRITE(s->argument, mask, value); + break; + case SDHC_TRNMOD: + /* DMA can be enabled only if it is supported as indicated by + * capabilities register */ + if (!(s->capareg & R_SDHC_CAPAB_SDMA_MASK)) { + value &= ~SDHC_TRNS_DMA; + } + MASKED_WRITE(s->trnmod, mask, value & SDHC_TRNMOD_MASK); + MASKED_WRITE(s->cmdreg, mask >> 16, value >> 16); + + /* Writing to the upper byte of CMDREG triggers SD command generation */ + if ((mask & 0xFF000000) || !sdhci_can_issue_command(s)) { + break; + } + + sdhci_send_command(s); + break; + case SDHC_BDATA: + if (sdhci_buff_access_is_sequential(s, offset - SDHC_BDATA)) { + sdhci_write_dataport(s, value >> shift, size); + } + break; + case SDHC_HOSTCTL: + if (!(mask & 0xFF0000)) { + sdhci_blkgap_write(s, value >> 16); + } + MASKED_WRITE(s->hostctl1, mask, value); + MASKED_WRITE(s->pwrcon, mask >> 8, value >> 8); + MASKED_WRITE(s->wakcon, mask >> 24, value >> 24); + if (!(s->prnsts & SDHC_CARD_PRESENT) || ((s->pwrcon >> 1) & 0x7) < 5 || + !(s->capareg & (1 << (31 - ((s->pwrcon >> 1) & 0x7))))) { + s->pwrcon &= ~SDHC_POWER_ON; + } + break; + case SDHC_CLKCON: + if (!(mask & 0xFF000000)) { + sdhci_reset_write(s, value >> 24); + } + MASKED_WRITE(s->clkcon, mask, value); + MASKED_WRITE(s->timeoutcon, mask >> 16, value >> 16); + if (s->clkcon & SDHC_CLOCK_INT_EN) { + s->clkcon |= SDHC_CLOCK_INT_STABLE; + } else { + s->clkcon &= ~SDHC_CLOCK_INT_STABLE; + } + break; + case SDHC_NORINTSTS: + if (s->norintstsen & SDHC_NISEN_CARDINT) { + value &= ~SDHC_NIS_CARDINT; + } + s->norintsts &= mask | ~value; + s->errintsts &= (mask >> 16) | ~(value >> 16); + if (s->errintsts) { + s->norintsts |= SDHC_NIS_ERR; + } else { + s->norintsts &= ~SDHC_NIS_ERR; + } + sdhci_update_irq(s); + break; + case SDHC_NORINTSTSEN: + MASKED_WRITE(s->norintstsen, mask, value); + MASKED_WRITE(s->errintstsen, mask >> 16, value >> 16); + s->norintsts &= s->norintstsen; + s->errintsts &= s->errintstsen; + if (s->errintsts) { + s->norintsts |= SDHC_NIS_ERR; + } else { + s->norintsts &= ~SDHC_NIS_ERR; + } + /* Quirk for Raspberry Pi: pending card insert interrupt + * appears when first enabled after power on */ + if ((s->norintstsen & SDHC_NISEN_INSERT) && s->pending_insert_state) { + assert(s->pending_insert_quirk); + s->norintsts |= SDHC_NIS_INSERT; + s->pending_insert_state = false; + } + sdhci_update_irq(s); + break; + case SDHC_NORINTSIGEN: + MASKED_WRITE(s->norintsigen, mask, value); + MASKED_WRITE(s->errintsigen, mask >> 16, value >> 16); + sdhci_update_irq(s); + break; + case SDHC_ADMAERR: + MASKED_WRITE(s->admaerr, mask, value); + break; + case SDHC_ADMASYSADDR: + s->admasysaddr = (s->admasysaddr & (0xFFFFFFFF00000000ULL | + (uint64_t)mask)) | (uint64_t)value; + break; + case SDHC_ADMASYSADDR + 4: + s->admasysaddr = (s->admasysaddr & (0x00000000FFFFFFFFULL | + ((uint64_t)mask << 32))) | ((uint64_t)value << 32); + break; + case SDHC_FEAER: + s->acmd12errsts |= value; + s->errintsts |= (value >> 16) & s->errintstsen; + if (s->acmd12errsts) { + s->errintsts |= SDHC_EIS_CMD12ERR; + } + if (s->errintsts) { + s->norintsts |= SDHC_NIS_ERR; + } + sdhci_update_irq(s); + break; + case SDHC_ACMD12ERRSTS: + MASKED_WRITE(s->acmd12errsts, mask, value & UINT16_MAX); + if (s->uhs_mode >= UHS_I) { + MASKED_WRITE(s->hostctl2, mask >> 16, value >> 16); + + if (FIELD_EX32(s->hostctl2, SDHC_HOSTCTL2, V18_ENA)) { + sdbus_set_voltage(&s->sdbus, SD_VOLTAGE_1_8V); + } else { + sdbus_set_voltage(&s->sdbus, SD_VOLTAGE_3_3V); + } + } + break; + + case SDHC_CAPAB: + case SDHC_CAPAB + 4: + case SDHC_MAXCURR: + case SDHC_MAXCURR + 4: + qemu_log_mask(LOG_GUEST_ERROR, "SDHC wr_%ub @0x%02" HWADDR_PRIx + " <- 0x%08x read-only\n", size, offset, value >> shift); + break; + + default: + qemu_log_mask(LOG_UNIMP, "SDHC wr_%ub @0x%02" HWADDR_PRIx " <- 0x%08x " + "not implemented\n", size, offset, value >> shift); + break; + } + trace_sdhci_access("wr", size << 3, offset, "<-", + value >> shift, value >> shift); +} + +static const MemoryRegionOps sdhci_mmio_ops = { + .read = sdhci_read, + .write = sdhci_write, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = false + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static void sdhci_init_readonly_registers(SDHCIState *s, Error **errp) +{ + ERRP_GUARD(); + + switch (s->sd_spec_version) { + case 2 ... 3: + break; + default: + error_setg(errp, "Only Spec v2/v3 are supported"); + return; + } + s->version = (SDHC_HCVER_VENDOR << 8) | (s->sd_spec_version - 1); + + sdhci_check_capareg(s, errp); + if (*errp) { + return; + } +} + +/* --- qdev common --- */ + +void sdhci_initfn(SDHCIState *s) +{ + 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); + + s->io_ops = &sdhci_mmio_ops; +} + +void sdhci_uninitfn(SDHCIState *s) +{ + timer_free(s->insert_timer); + timer_free(s->transfer_timer); + + g_free(s->fifo_buffer); + s->fifo_buffer = NULL; +} + +void sdhci_common_realize(SDHCIState *s, Error **errp) +{ + ERRP_GUARD(); + + sdhci_init_readonly_registers(s, errp); + if (*errp) { + return; + } + s->buf_maxsz = sdhci_get_fifolen(s); + s->fifo_buffer = g_malloc0(s->buf_maxsz); + + memory_region_init_io(&s->iomem, OBJECT(s), s->io_ops, s, "sdhci", + SDHC_REGISTERS_MAP_SIZE); +} + +void sdhci_common_unrealize(SDHCIState *s) +{ + /* This function is expected to be called only once for each class: + * - SysBus: via DeviceClass->unrealize(), + * - PCI: via PCIDeviceClass->exit(). + * However to avoid double-free and/or use-after-free we still nullify + * this variable (better safe than sorry!). */ + g_free(s->fifo_buffer); + s->fifo_buffer = NULL; +} + +static bool sdhci_pending_insert_vmstate_needed(void *opaque) +{ + SDHCIState *s = opaque; + + return s->pending_insert_state; +} + +static const VMStateDescription sdhci_pending_insert_vmstate = { + .name = "sdhci/pending-insert", + .version_id = 1, + .minimum_version_id = 1, + .needed = sdhci_pending_insert_vmstate_needed, + .fields = (VMStateField[]) { + VMSTATE_BOOL(pending_insert_state, SDHCIState), + VMSTATE_END_OF_LIST() + }, +}; + +const VMStateDescription sdhci_vmstate = { + .name = "sdhci", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(sdmasysad, SDHCIState), + VMSTATE_UINT16(blksize, SDHCIState), + VMSTATE_UINT16(blkcnt, SDHCIState), + VMSTATE_UINT32(argument, SDHCIState), + VMSTATE_UINT16(trnmod, SDHCIState), + VMSTATE_UINT16(cmdreg, SDHCIState), + VMSTATE_UINT32_ARRAY(rspreg, SDHCIState, 4), + VMSTATE_UINT32(prnsts, SDHCIState), + VMSTATE_UINT8(hostctl1, SDHCIState), + VMSTATE_UINT8(pwrcon, SDHCIState), + VMSTATE_UINT8(blkgap, SDHCIState), + VMSTATE_UINT8(wakcon, SDHCIState), + VMSTATE_UINT16(clkcon, SDHCIState), + VMSTATE_UINT8(timeoutcon, SDHCIState), + VMSTATE_UINT8(admaerr, SDHCIState), + VMSTATE_UINT16(norintsts, SDHCIState), + VMSTATE_UINT16(errintsts, SDHCIState), + VMSTATE_UINT16(norintstsen, SDHCIState), + VMSTATE_UINT16(errintstsen, SDHCIState), + VMSTATE_UINT16(norintsigen, SDHCIState), + VMSTATE_UINT16(errintsigen, SDHCIState), + VMSTATE_UINT16(acmd12errsts, SDHCIState), + VMSTATE_UINT16(data_count, SDHCIState), + VMSTATE_UINT64(admasysaddr, SDHCIState), + VMSTATE_UINT8(stopped_state, SDHCIState), + VMSTATE_VBUFFER_UINT32(fifo_buffer, SDHCIState, 1, NULL, buf_maxsz), + VMSTATE_TIMER_PTR(insert_timer, SDHCIState), + VMSTATE_TIMER_PTR(transfer_timer, SDHCIState), + VMSTATE_END_OF_LIST() + }, + .subsections = (const VMStateDescription*[]) { + &sdhci_pending_insert_vmstate, + NULL + }, +}; + +void sdhci_common_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); + dc->vmsd = &sdhci_vmstate; + dc->reset = sdhci_poweron_reset; +} + +/* --- qdev SysBus --- */ + +static Property sdhci_sysbus_properties[] = { + DEFINE_SDHCI_COMMON_PROPERTIES(SDHCIState), + DEFINE_PROP_BOOL("pending-insert-quirk", SDHCIState, pending_insert_quirk, + false), + DEFINE_PROP_LINK("dma", SDHCIState, + dma_mr, TYPE_MEMORY_REGION, MemoryRegion *), + DEFINE_PROP_END_OF_LIST(), +}; + +static void sdhci_sysbus_init(Object *obj) +{ + SDHCIState *s = SYSBUS_SDHCI(obj); + + sdhci_initfn(s); +} + +static void sdhci_sysbus_finalize(Object *obj) +{ + SDHCIState *s = SYSBUS_SDHCI(obj); + + if (s->dma_mr) { + object_unparent(OBJECT(s->dma_mr)); + } + + sdhci_uninitfn(s); +} + +static void sdhci_sysbus_realize(DeviceState *dev, Error **errp) +{ + ERRP_GUARD(); + SDHCIState *s = SYSBUS_SDHCI(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + sdhci_common_realize(s, errp); + if (*errp) { + return; + } + + if (s->dma_mr) { + s->dma_as = &s->sysbus_dma_as; + address_space_init(s->dma_as, s->dma_mr, "sdhci-dma"); + } else { + /* use system_memory() if property "dma" not set */ + s->dma_as = &address_space_memory; + } + + sysbus_init_irq(sbd, &s->irq); + + sysbus_init_mmio(sbd, &s->iomem); +} + +static void sdhci_sysbus_unrealize(DeviceState *dev) +{ + SDHCIState *s = SYSBUS_SDHCI(dev); + + sdhci_common_unrealize(s); + + if (s->dma_mr) { + address_space_destroy(s->dma_as); + } +} + +static void sdhci_sysbus_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_props(dc, sdhci_sysbus_properties); + dc->realize = sdhci_sysbus_realize; + dc->unrealize = sdhci_sysbus_unrealize; + + sdhci_common_class_init(klass, data); +} + +static const TypeInfo sdhci_sysbus_info = { + .name = TYPE_SYSBUS_SDHCI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SDHCIState), + .instance_init = sdhci_sysbus_init, + .instance_finalize = sdhci_sysbus_finalize, + .class_init = sdhci_sysbus_class_init, +}; + +/* --- qdev bus master --- */ + +static void sdhci_bus_class_init(ObjectClass *klass, void *data) +{ + SDBusClass *sbc = SD_BUS_CLASS(klass); + + sbc->set_inserted = sdhci_set_inserted; + sbc->set_readonly = sdhci_set_readonly; +} + +static const TypeInfo sdhci_bus_info = { + .name = TYPE_SDHCI_BUS, + .parent = TYPE_SD_BUS, + .instance_size = sizeof(SDBus), + .class_init = sdhci_bus_class_init, +}; + +/* --- qdev i.MX eSDHC --- */ + +static uint64_t usdhc_read(void *opaque, hwaddr offset, unsigned size) +{ + SDHCIState *s = SYSBUS_SDHCI(opaque); + uint32_t ret; + uint16_t hostctl1; + + switch (offset) { + default: + return sdhci_read(opaque, offset, size); + + case SDHC_HOSTCTL: + /* + * For a detailed explanation on the following bit + * manipulation code see comments in a similar part of + * usdhc_write() + */ + hostctl1 = SDHC_DMA_TYPE(s->hostctl1) << (8 - 3); + + if (s->hostctl1 & SDHC_CTRL_8BITBUS) { + hostctl1 |= ESDHC_CTRL_8BITBUS; + } + + if (s->hostctl1 & SDHC_CTRL_4BITBUS) { + hostctl1 |= ESDHC_CTRL_4BITBUS; + } + + ret = hostctl1; + ret |= (uint32_t)s->blkgap << 16; + ret |= (uint32_t)s->wakcon << 24; + + break; + + case SDHC_PRNSTS: + /* Add SDSTB (SD Clock Stable) bit to PRNSTS */ + ret = sdhci_read(opaque, offset, size) & ~ESDHC_PRNSTS_SDSTB; + if (s->clkcon & SDHC_CLOCK_INT_STABLE) { + ret |= ESDHC_PRNSTS_SDSTB; + } + 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_MIX_CTRL: + case ESDHC_WTMK_LVL: + ret = 0; + break; + } + + return ret; +} + +static void +usdhc_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) +{ + SDHCIState *s = SYSBUS_SDHCI(opaque); + uint8_t hostctl1; + uint32_t value = (uint32_t)val; + + switch (offset) { + case ESDHC_DLL_CTRL: + case ESDHC_TUNE_CTRL_STATUS: + 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: + /* + * Here's What ESDHCI has at offset 0x28 (SDHC_HOSTCTL) + * + * 7 6 5 4 3 2 1 0 + * |-----------+--------+--------+-----------+----------+---------| + * | Card | Card | Endian | DATA3 | Data | Led | + * | Detect | Detect | Mode | as Card | Transfer | Control | + * | Signal | Test | | Detection | Width | | + * | Selection | Level | | Pin | | | + * |-----------+--------+--------+-----------+----------+---------| + * + * and 0x29 + * + * 15 10 9 8 + * |----------+------| + * | Reserved | DMA | + * | | Sel. | + * | | | + * |----------+------| + * + * and here's what SDCHI spec expects those offsets to be: + * + * 0x28 (Host Control Register) + * + * 7 6 5 4 3 2 1 0 + * |--------+--------+----------+------+--------+----------+---------| + * | Card | Card | Extended | DMA | High | Data | LED | + * | Detect | Detect | Data | Sel. | Speed | Transfer | Control | + * | Signal | Test | Transfer | | Enable | Width | | + * | Sel. | Level | Width | | | | | + * |--------+--------+----------+------+--------+----------+---------| + * + * and 0x29 (Power Control Register) + * + * |----------------------------------| + * | Power Control Register | + * | | + * | Description omitted, | + * | since it has no analog in ESDHCI | + * | | + * |----------------------------------| + * + * Since offsets 0x2A and 0x2B should be compatible between + * both IP specs we only need to reconcile least 16-bit of the + * word we've been given. + */ + + /* + * First, save bits 7 6 and 0 since they are identical + */ + hostctl1 = value & (SDHC_CTRL_LED | + SDHC_CTRL_CDTEST_INS | + SDHC_CTRL_CDTEST_EN); + /* + * Second, split "Data Transfer Width" from bits 2 and 1 in to + * bits 5 and 1 + */ + if (value & ESDHC_CTRL_8BITBUS) { + hostctl1 |= SDHC_CTRL_8BITBUS; + } + + if (value & ESDHC_CTRL_4BITBUS) { + hostctl1 |= ESDHC_CTRL_4BITBUS; + } + + /* + * Third, move DMA select from bits 9 and 8 to bits 4 and 3 + */ + hostctl1 |= SDHC_DMA_TYPE(value >> (8 - 3)); + + /* + * Now place the corrected value into low 16-bit of the value + * we are going to give standard SDHCI write function + * + * NOTE: This transformation should be the inverse of what can + * be found in drivers/mmc/host/sdhci-esdhc-imx.c in Linux + * kernel + */ + value &= ~UINT16_MAX; + value |= hostctl1; + value |= (uint16_t)s->pwrcon << 8; + + sdhci_write(opaque, offset, value, size); + break; + + case ESDHC_MIX_CTRL: + /* + * So, when SD/MMC stack in Linux tries to write to "Transfer + * Mode Register", ESDHC i.MX quirk code will translate it + * into a write to ESDHC_MIX_CTRL, so we do the opposite in + * order to get where we started + * + * Note that Auto CMD23 Enable bit is located in a wrong place + * on i.MX, but since it is not used by QEMU we do not care. + * + * We don't want to call sdhci_write(.., SDHC_TRNMOD, ...) + * here becuase it will result in a call to + * sdhci_send_command(s) which we don't want. + * + */ + s->trnmod = value & UINT16_MAX; + break; + case SDHC_TRNMOD: + /* + * Similar to above, but this time a write to "Command + * Register" will be translated into a 4-byte write to + * "Transfer Mode register" where lower 16-bit of value would + * be set to zero. So what we do is fill those bits with + * cached value from s->trnmod and let the SDHCI + * infrastructure handle the rest + */ + sdhci_write(opaque, offset, val | s->trnmod, size); + break; + case SDHC_BLKSIZE: + /* + * ESDHCI does not implement "Host SDMA Buffer Boundary", and + * Linux driver will try to zero this field out which will + * break the rest of SDHCI emulation. + * + * Linux defaults to maximum possible setting (512K boundary) + * and it seems to be the only option that i.MX IP implements, + * so we artificially set it to that value. + */ + val |= 0x7 << 12; + /* FALLTHROUGH */ + default: + sdhci_write(opaque, offset, val, size); + break; + } +} + +static const MemoryRegionOps usdhc_mmio_ops = { + .read = usdhc_read, + .write = usdhc_write, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = false + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static void imx_usdhc_init(Object *obj) +{ + SDHCIState *s = SYSBUS_SDHCI(obj); + + s->io_ops = &usdhc_mmio_ops; + s->quirks = SDHCI_QUIRK_NO_BUSY_IRQ; +} + +static const TypeInfo imx_usdhc_info = { + .name = TYPE_IMX_USDHC, + .parent = TYPE_SYSBUS_SDHCI, + .instance_init = imx_usdhc_init, +}; + +/* --- qdev Samsung s3c --- */ + +#define S3C_SDHCI_CONTROL2 0x80 +#define S3C_SDHCI_CONTROL3 0x84 +#define S3C_SDHCI_CONTROL4 0x8c + +static uint64_t sdhci_s3c_read(void *opaque, hwaddr offset, unsigned size) +{ + uint64_t ret; + + switch (offset) { + case S3C_SDHCI_CONTROL2: + case S3C_SDHCI_CONTROL3: + case S3C_SDHCI_CONTROL4: + /* ignore */ + ret = 0; + break; + default: + ret = sdhci_read(opaque, offset, size); + break; + } + + return ret; +} + +static void sdhci_s3c_write(void *opaque, hwaddr offset, uint64_t val, + unsigned size) +{ + switch (offset) { + case S3C_SDHCI_CONTROL2: + case S3C_SDHCI_CONTROL3: + case S3C_SDHCI_CONTROL4: + /* ignore */ + break; + default: + sdhci_write(opaque, offset, val, size); + break; + } +} + +static const MemoryRegionOps sdhci_s3c_mmio_ops = { + .read = sdhci_s3c_read, + .write = sdhci_s3c_write, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = false + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static void sdhci_s3c_init(Object *obj) +{ + SDHCIState *s = SYSBUS_SDHCI(obj); + + s->io_ops = &sdhci_s3c_mmio_ops; +} + +static const TypeInfo sdhci_s3c_info = { + .name = TYPE_S3C_SDHCI , + .parent = TYPE_SYSBUS_SDHCI, + .instance_init = sdhci_s3c_init, +}; + +static void sdhci_register_types(void) +{ + type_register_static(&sdhci_sysbus_info); + type_register_static(&sdhci_bus_info); + type_register_static(&imx_usdhc_info); + type_register_static(&sdhci_s3c_info); +} + +type_init(sdhci_register_types) diff --git a/hw/sd/sdmmc-internal.c b/hw/sd/sdmmc-internal.c new file mode 100644 index 000000000..2053def3f --- /dev/null +++ b/hw/sd/sdmmc-internal.c @@ -0,0 +1,72 @@ +/* + * SD/MMC cards common helpers + * + * Copyright (c) 2018 Philippe Mathieu-Daudé <f4bug@amsat.org> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "sdmmc-internal.h" + +const char *sd_cmd_name(uint8_t cmd) +{ + static const char *cmd_abbrev[SDMMC_CMD_MAX] = { + [0] = "GO_IDLE_STATE", + [2] = "ALL_SEND_CID", [3] = "SEND_RELATIVE_ADDR", + [4] = "SET_DSR", [5] = "IO_SEND_OP_COND", + [6] = "SWITCH_FUNC", [7] = "SELECT/DESELECT_CARD", + [8] = "SEND_IF_COND", [9] = "SEND_CSD", + [10] = "SEND_CID", [11] = "VOLTAGE_SWITCH", + [12] = "STOP_TRANSMISSION", [13] = "SEND_STATUS", + [15] = "GO_INACTIVE_STATE", + [16] = "SET_BLOCKLEN", [17] = "READ_SINGLE_BLOCK", + [18] = "READ_MULTIPLE_BLOCK", [19] = "SEND_TUNING_BLOCK", + [20] = "SPEED_CLASS_CONTROL", [21] = "DPS_spec", + [23] = "SET_BLOCK_COUNT", + [24] = "WRITE_BLOCK", [25] = "WRITE_MULTIPLE_BLOCK", + [26] = "MANUF_RSVD", [27] = "PROGRAM_CSD", + [28] = "SET_WRITE_PROT", [29] = "CLR_WRITE_PROT", + [30] = "SEND_WRITE_PROT", + [32] = "ERASE_WR_BLK_START", [33] = "ERASE_WR_BLK_END", + [34] = "SW_FUNC_RSVD", [35] = "SW_FUNC_RSVD", + [36] = "SW_FUNC_RSVD", [37] = "SW_FUNC_RSVD", + [38] = "ERASE", + [40] = "DPS_spec", + [42] = "LOCK_UNLOCK", [43] = "Q_MANAGEMENT", + [44] = "Q_TASK_INFO_A", [45] = "Q_TASK_INFO_B", + [46] = "Q_RD_TASK", [47] = "Q_WR_TASK", + [48] = "READ_EXTR_SINGLE", [49] = "WRITE_EXTR_SINGLE", + [50] = "SW_FUNC_RSVD", + [52] = "IO_RW_DIRECT", [53] = "IO_RW_EXTENDED", + [54] = "SDIO_RSVD", [55] = "APP_CMD", + [56] = "GEN_CMD", [57] = "SW_FUNC_RSVD", + [58] = "READ_EXTR_MULTI", [59] = "WRITE_EXTR_MULTI", + [60] = "MANUF_RSVD", [61] = "MANUF_RSVD", + [62] = "MANUF_RSVD", [63] = "MANUF_RSVD", + }; + return cmd_abbrev[cmd] ? cmd_abbrev[cmd] : "UNKNOWN_CMD"; +} + +const char *sd_acmd_name(uint8_t cmd) +{ + static const char *acmd_abbrev[SDMMC_CMD_MAX] = { + [6] = "SET_BUS_WIDTH", + [13] = "SD_STATUS", + [14] = "DPS_spec", [15] = "DPS_spec", + [16] = "DPS_spec", + [18] = "SECU_spec", + [22] = "SEND_NUM_WR_BLOCKS", [23] = "SET_WR_BLK_ERASE_COUNT", + [41] = "SD_SEND_OP_COND", + [42] = "SET_CLR_CARD_DETECT", + [51] = "SEND_SCR", + [52] = "SECU_spec", [53] = "SECU_spec", + [54] = "SECU_spec", + [56] = "SECU_spec", [57] = "SECU_spec", + [58] = "SECU_spec", [59] = "SECU_spec", + }; + + return acmd_abbrev[cmd] ? acmd_abbrev[cmd] : "UNKNOWN_ACMD"; +} diff --git a/hw/sd/sdmmc-internal.h b/hw/sd/sdmmc-internal.h new file mode 100644 index 000000000..d8bf17d20 --- /dev/null +++ b/hw/sd/sdmmc-internal.h @@ -0,0 +1,40 @@ +/* + * SD/MMC cards common + * + * Copyright (c) 2018 Philippe Mathieu-Daudé <f4bug@amsat.org> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef SDMMC_INTERNAL_H +#define SDMMC_INTERNAL_H + +#define SDMMC_CMD_MAX 64 + +/** + * sd_cmd_name: + * @cmd: A SD "normal" command, up to SDMMC_CMD_MAX. + * + * Returns a human-readable name describing the command. + * The return value is always a static string which does not need + * to be freed after use. + * + * Returns: The command name of @cmd or "UNKNOWN_CMD". + */ +const char *sd_cmd_name(uint8_t cmd); + +/** + * sd_acmd_name: + * @cmd: A SD "Application-Specific" command, up to SDMMC_CMD_MAX. + * + * Returns a human-readable name describing the application command. + * The return value is always a static string which does not need + * to be freed after use. + * + * Returns: The application command name of @cmd or "UNKNOWN_ACMD". + */ +const char *sd_acmd_name(uint8_t cmd); + +#endif diff --git a/hw/sd/ssi-sd.c b/hw/sd/ssi-sd.c new file mode 100644 index 000000000..e60854eee --- /dev/null +++ b/hw/sd/ssi-sd.c @@ -0,0 +1,445 @@ +/* + * SSI to SD card adapter. + * + * Copyright (c) 2007-2009 CodeSourcery. + * Written by Paul Brook + * + * Copyright (c) 2021 Wind River Systems, Inc. + * Improved by Bin Meng <bin.meng@windriver.com> + * + * Validated with U-Boot v2021.01 and Linux v5.10 mmc_spi driver + * + * This code is licensed under the GNU GPL v2. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ + +#include "qemu/osdep.h" +#include "sysemu/blockdev.h" +#include "hw/ssi/ssi.h" +#include "migration/vmstate.h" +#include "hw/qdev-properties.h" +#include "hw/sd/sd.h" +#include "qapi/error.h" +#include "qemu/crc-ccitt.h" +#include "qemu/module.h" +#include "qom/object.h" + +//#define DEBUG_SSI_SD 1 + +#ifdef DEBUG_SSI_SD +#define DPRINTF(fmt, ...) \ +do { printf("ssi_sd: " fmt , ## __VA_ARGS__); } while (0) +#define BADF(fmt, ...) \ +do { fprintf(stderr, "ssi_sd: error: " fmt , ## __VA_ARGS__); exit(1);} while (0) +#else +#define DPRINTF(fmt, ...) do {} while(0) +#define BADF(fmt, ...) \ +do { fprintf(stderr, "ssi_sd: error: " fmt , ## __VA_ARGS__);} while (0) +#endif + +typedef enum { + SSI_SD_CMD = 0, + SSI_SD_CMDARG, + SSI_SD_PREP_RESP, + SSI_SD_RESPONSE, + SSI_SD_PREP_DATA, + SSI_SD_DATA_START, + SSI_SD_DATA_READ, + SSI_SD_DATA_CRC16, + SSI_SD_DATA_WRITE, + SSI_SD_SKIP_CRC16, +} ssi_sd_mode; + +struct ssi_sd_state { + SSIPeripheral ssidev; + uint32_t mode; + int cmd; + uint8_t cmdarg[4]; + uint8_t response[5]; + uint16_t crc16; + int32_t read_bytes; + int32_t write_bytes; + int32_t arglen; + int32_t response_pos; + int32_t stopping; + SDBus sdbus; +}; + +#define TYPE_SSI_SD "ssi-sd" +OBJECT_DECLARE_SIMPLE_TYPE(ssi_sd_state, SSI_SD) + +/* State word bits. */ +#define SSI_SDR_LOCKED 0x0001 +#define SSI_SDR_WP_ERASE 0x0002 +#define SSI_SDR_ERROR 0x0004 +#define SSI_SDR_CC_ERROR 0x0008 +#define SSI_SDR_ECC_FAILED 0x0010 +#define SSI_SDR_WP_VIOLATION 0x0020 +#define SSI_SDR_ERASE_PARAM 0x0040 +#define SSI_SDR_OUT_OF_RANGE 0x0080 +#define SSI_SDR_IDLE 0x0100 +#define SSI_SDR_ERASE_RESET 0x0200 +#define SSI_SDR_ILLEGAL_COMMAND 0x0400 +#define SSI_SDR_COM_CRC_ERROR 0x0800 +#define SSI_SDR_ERASE_SEQ_ERROR 0x1000 +#define SSI_SDR_ADDRESS_ERROR 0x2000 +#define SSI_SDR_PARAMETER_ERROR 0x4000 + +/* multiple block write */ +#define SSI_TOKEN_MULTI_WRITE 0xfc +/* terminate multiple block write */ +#define SSI_TOKEN_STOP_TRAN 0xfd +/* single block read/write, multiple block read */ +#define SSI_TOKEN_SINGLE 0xfe + +/* dummy value - don't care */ +#define SSI_DUMMY 0xff + +/* data accepted */ +#define DATA_RESPONSE_ACCEPTED 0x05 + +static uint32_t ssi_sd_transfer(SSIPeripheral *dev, uint32_t val) +{ + ssi_sd_state *s = SSI_SD(dev); + SDRequest request; + uint8_t longresp[16]; + + /* + * Special case: allow CMD12 (STOP TRANSMISSION) while reading data. + * + * See "Physical Layer Specification Version 8.00" chapter 7.5.2.2, + * to avoid conflict between CMD12 response and next data block, + * timing of CMD12 should be controlled as follows: + * + * - CMD12 issued at the timing that end bit of CMD12 and end bit of + * data block is overlapped + * - CMD12 issued after one clock cycle after host receives a token + * (either Start Block token or Data Error token) + * + * We need to catch CMD12 in all of the data read states. + */ + if (s->mode >= SSI_SD_PREP_DATA && s->mode <= SSI_SD_DATA_CRC16) { + if (val == 0x4c) { + s->mode = SSI_SD_CMD; + /* There must be at least one byte delay before the card responds */ + s->stopping = 1; + } + } + + switch (s->mode) { + case SSI_SD_CMD: + switch (val) { + case SSI_DUMMY: + DPRINTF("NULL command\n"); + return SSI_DUMMY; + break; + case SSI_TOKEN_SINGLE: + case SSI_TOKEN_MULTI_WRITE: + DPRINTF("Start write block\n"); + s->mode = SSI_SD_DATA_WRITE; + return SSI_DUMMY; + case SSI_TOKEN_STOP_TRAN: + DPRINTF("Stop multiple write\n"); + + /* manually issue cmd12 to stop the transfer */ + request.cmd = 12; + request.arg = 0; + s->arglen = sdbus_do_command(&s->sdbus, &request, longresp); + if (s->arglen <= 0) { + s->arglen = 1; + /* a zero value indicates the card is busy */ + s->response[0] = 0; + DPRINTF("SD card busy\n"); + } else { + s->arglen = 1; + /* a non-zero value indicates the card is ready */ + s->response[0] = SSI_DUMMY; + } + + return SSI_DUMMY; + } + + s->cmd = val & 0x3f; + s->mode = SSI_SD_CMDARG; + s->arglen = 0; + return SSI_DUMMY; + case SSI_SD_CMDARG: + if (s->arglen == 4) { + /* FIXME: Check CRC. */ + request.cmd = s->cmd; + request.arg = ldl_be_p(s->cmdarg); + DPRINTF("CMD%d arg 0x%08x\n", s->cmd, request.arg); + s->arglen = sdbus_do_command(&s->sdbus, &request, longresp); + if (s->arglen <= 0) { + s->arglen = 1; + s->response[0] = 4; + DPRINTF("SD command failed\n"); + } else if (s->cmd == 8 || s->cmd == 58) { + /* CMD8/CMD58 returns R3/R7 response */ + DPRINTF("Returned R3/R7\n"); + s->arglen = 5; + s->response[0] = 1; + memcpy(&s->response[1], longresp, 4); + } else if (s->arglen != 4) { + BADF("Unexpected response to cmd %d\n", s->cmd); + /* Illegal command is about as near as we can get. */ + s->arglen = 1; + s->response[0] = 4; + } else { + /* All other commands return status. */ + uint32_t cardstatus; + uint16_t status; + /* CMD13 returns a 2-byte statuse work. Other commands + only return the first byte. */ + s->arglen = (s->cmd == 13) ? 2 : 1; + + /* handle R1b */ + if (s->cmd == 28 || s->cmd == 29 || s->cmd == 38) { + s->stopping = 1; + } + + cardstatus = ldl_be_p(longresp); + status = 0; + if (((cardstatus >> 9) & 0xf) < 4) + status |= SSI_SDR_IDLE; + if (cardstatus & ERASE_RESET) + status |= SSI_SDR_ERASE_RESET; + if (cardstatus & ILLEGAL_COMMAND) + status |= SSI_SDR_ILLEGAL_COMMAND; + if (cardstatus & COM_CRC_ERROR) + status |= SSI_SDR_COM_CRC_ERROR; + if (cardstatus & ERASE_SEQ_ERROR) + status |= SSI_SDR_ERASE_SEQ_ERROR; + if (cardstatus & ADDRESS_ERROR) + status |= SSI_SDR_ADDRESS_ERROR; + if (cardstatus & CARD_IS_LOCKED) + status |= SSI_SDR_LOCKED; + if (cardstatus & (LOCK_UNLOCK_FAILED | WP_ERASE_SKIP)) + status |= SSI_SDR_WP_ERASE; + if (cardstatus & SD_ERROR) + status |= SSI_SDR_ERROR; + if (cardstatus & CC_ERROR) + status |= SSI_SDR_CC_ERROR; + if (cardstatus & CARD_ECC_FAILED) + status |= SSI_SDR_ECC_FAILED; + if (cardstatus & WP_VIOLATION) + status |= SSI_SDR_WP_VIOLATION; + if (cardstatus & ERASE_PARAM) + status |= SSI_SDR_ERASE_PARAM; + if (cardstatus & (OUT_OF_RANGE | CID_CSD_OVERWRITE)) + status |= SSI_SDR_OUT_OF_RANGE; + /* ??? Don't know what Parameter Error really means, so + assume it's set if the second byte is nonzero. */ + if (status & 0xff) + status |= SSI_SDR_PARAMETER_ERROR; + s->response[0] = status >> 8; + s->response[1] = status; + DPRINTF("Card status 0x%02x\n", status); + } + s->mode = SSI_SD_PREP_RESP; + s->response_pos = 0; + } else { + s->cmdarg[s->arglen++] = val; + } + return SSI_DUMMY; + case SSI_SD_PREP_RESP: + DPRINTF("Prepare card response (Ncr)\n"); + s->mode = SSI_SD_RESPONSE; + return SSI_DUMMY; + case SSI_SD_RESPONSE: + if (s->response_pos < s->arglen) { + DPRINTF("Response 0x%02x\n", s->response[s->response_pos]); + return s->response[s->response_pos++]; + } + if (s->stopping) { + s->stopping = 0; + s->mode = SSI_SD_CMD; + return SSI_DUMMY; + } + if (sdbus_data_ready(&s->sdbus)) { + DPRINTF("Data read\n"); + s->mode = SSI_SD_DATA_START; + } else { + DPRINTF("End of command\n"); + s->mode = SSI_SD_CMD; + } + return SSI_DUMMY; + case SSI_SD_PREP_DATA: + DPRINTF("Prepare data block (Nac)\n"); + s->mode = SSI_SD_DATA_START; + return SSI_DUMMY; + case SSI_SD_DATA_START: + DPRINTF("Start read block\n"); + s->mode = SSI_SD_DATA_READ; + s->response_pos = 0; + return SSI_TOKEN_SINGLE; + case SSI_SD_DATA_READ: + val = sdbus_read_byte(&s->sdbus); + s->read_bytes++; + s->crc16 = crc_ccitt_false(s->crc16, (uint8_t *)&val, 1); + if (!sdbus_data_ready(&s->sdbus) || s->read_bytes == 512) { + DPRINTF("Data read end\n"); + s->mode = SSI_SD_DATA_CRC16; + } + return val; + case SSI_SD_DATA_CRC16: + val = (s->crc16 & 0xff00) >> 8; + s->crc16 <<= 8; + s->response_pos++; + if (s->response_pos == 2) { + DPRINTF("CRC16 read end\n"); + if (s->read_bytes == 512 && s->cmd != 17) { + s->mode = SSI_SD_PREP_DATA; + } else { + s->mode = SSI_SD_CMD; + } + s->read_bytes = 0; + s->response_pos = 0; + } + return val; + case SSI_SD_DATA_WRITE: + sdbus_write_byte(&s->sdbus, val); + s->write_bytes++; + if (!sdbus_receive_ready(&s->sdbus) || s->write_bytes == 512) { + DPRINTF("Data write end\n"); + s->mode = SSI_SD_SKIP_CRC16; + s->response_pos = 0; + } + return val; + case SSI_SD_SKIP_CRC16: + /* we don't verify the crc16 */ + s->response_pos++; + if (s->response_pos == 2) { + DPRINTF("CRC16 receive end\n"); + s->mode = SSI_SD_RESPONSE; + s->write_bytes = 0; + s->arglen = 1; + s->response[0] = DATA_RESPONSE_ACCEPTED; + s->response_pos = 0; + } + return SSI_DUMMY; + } + /* Should never happen. */ + return SSI_DUMMY; +} + +static int ssi_sd_post_load(void *opaque, int version_id) +{ + ssi_sd_state *s = (ssi_sd_state *)opaque; + + if (s->mode > SSI_SD_SKIP_CRC16) { + return -EINVAL; + } + if (s->mode == SSI_SD_CMDARG && + (s->arglen < 0 || s->arglen >= ARRAY_SIZE(s->cmdarg))) { + return -EINVAL; + } + if (s->mode == SSI_SD_RESPONSE && + (s->response_pos < 0 || s->response_pos >= ARRAY_SIZE(s->response) || + (!s->stopping && s->arglen > ARRAY_SIZE(s->response)))) { + return -EINVAL; + } + + return 0; +} + +static const VMStateDescription vmstate_ssi_sd = { + .name = "ssi_sd", + .version_id = 7, + .minimum_version_id = 7, + .post_load = ssi_sd_post_load, + .fields = (VMStateField []) { + VMSTATE_UINT32(mode, ssi_sd_state), + VMSTATE_INT32(cmd, ssi_sd_state), + VMSTATE_UINT8_ARRAY(cmdarg, ssi_sd_state, 4), + VMSTATE_UINT8_ARRAY(response, ssi_sd_state, 5), + VMSTATE_UINT16(crc16, ssi_sd_state), + VMSTATE_INT32(read_bytes, ssi_sd_state), + VMSTATE_INT32(write_bytes, ssi_sd_state), + VMSTATE_INT32(arglen, ssi_sd_state), + VMSTATE_INT32(response_pos, ssi_sd_state), + VMSTATE_INT32(stopping, ssi_sd_state), + VMSTATE_SSI_PERIPHERAL(ssidev, ssi_sd_state), + VMSTATE_END_OF_LIST() + } +}; + +static void ssi_sd_realize(SSIPeripheral *d, Error **errp) +{ + ERRP_GUARD(); + ssi_sd_state *s = SSI_SD(d); + DeviceState *carddev; + DriveInfo *dinfo; + + qbus_init(&s->sdbus, sizeof(s->sdbus), TYPE_SD_BUS, DEVICE(d), "sd-bus"); + + /* Create and plug in the sd card */ + /* FIXME use a qdev drive property instead of drive_get_next() */ + dinfo = drive_get_next(IF_SD); + carddev = qdev_new(TYPE_SD_CARD); + if (dinfo) { + if (!qdev_prop_set_drive_err(carddev, "drive", + blk_by_legacy_dinfo(dinfo), errp)) { + goto fail; + } + } + + if (!object_property_set_bool(OBJECT(carddev), "spi", true, errp)) { + goto fail; + } + + if (!qdev_realize_and_unref(carddev, BUS(&s->sdbus), errp)) { + goto fail; + } + + return; + +fail: + error_prepend(errp, "failed to init SD card: "); +} + +static void ssi_sd_reset(DeviceState *dev) +{ + ssi_sd_state *s = SSI_SD(dev); + + s->mode = SSI_SD_CMD; + s->cmd = 0; + memset(s->cmdarg, 0, sizeof(s->cmdarg)); + memset(s->response, 0, sizeof(s->response)); + s->crc16 = 0; + s->read_bytes = 0; + s->write_bytes = 0; + s->arglen = 0; + s->response_pos = 0; + s->stopping = 0; +} + +static void ssi_sd_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SSIPeripheralClass *k = SSI_PERIPHERAL_CLASS(klass); + + k->realize = ssi_sd_realize; + k->transfer = ssi_sd_transfer; + k->cs_polarity = SSI_CS_LOW; + dc->vmsd = &vmstate_ssi_sd; + dc->reset = ssi_sd_reset; + /* Reason: init() method uses drive_get_next() */ + dc->user_creatable = false; +} + +static const TypeInfo ssi_sd_info = { + .name = TYPE_SSI_SD, + .parent = TYPE_SSI_PERIPHERAL, + .instance_size = sizeof(ssi_sd_state), + .class_init = ssi_sd_class_init, +}; + +static void ssi_sd_register_types(void) +{ + type_register_static(&ssi_sd_info); +} + +type_init(ssi_sd_register_types) diff --git a/hw/sd/trace-events b/hw/sd/trace-events new file mode 100644 index 000000000..94a00557b --- /dev/null +++ b/hw/sd/trace-events @@ -0,0 +1,74 @@ +# See docs/devel/tracing.rst for syntax documentation. + +# allwinner-sdhost.c +allwinner_sdhost_set_inserted(bool inserted) "inserted %u" +allwinner_sdhost_process_desc(uint64_t desc_addr, uint32_t desc_size, bool is_write, uint32_t max_bytes) "desc_addr 0x%" PRIx64 " desc_size %" PRIu32 " is_write %u max_bytes %" PRIu32 +allwinner_sdhost_read(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32 +allwinner_sdhost_write(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32 +allwinner_sdhost_update_irq(uint32_t irq) "IRQ bits 0x%" PRIx32 + +# bcm2835_sdhost.c +bcm2835_sdhost_read(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +bcm2835_sdhost_write(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +bcm2835_sdhost_edm_change(const char *why, uint32_t edm) "(%s) EDM now 0x%x" +bcm2835_sdhost_update_irq(uint32_t irq) "IRQ bits 0x%x" + +# core.c +sdbus_command(const char *bus_name, uint8_t cmd, uint32_t arg) "@%s CMD%02d arg 0x%08x" +sdbus_read(const char *bus_name, uint8_t value) "@%s value 0x%02x" +sdbus_write(const char *bus_name, uint8_t value) "@%s value 0x%02x" +sdbus_set_voltage(const char *bus_name, uint16_t millivolts) "@%s %u (mV)" +sdbus_get_dat_lines(const char *bus_name, uint8_t dat_lines) "@%s dat_lines: %u" +sdbus_get_cmd_line(const char *bus_name, bool cmd_line) "@%s cmd_line: %u" + +# sdhci.c +sdhci_set_inserted(const char *level) "card state changed: %s" +sdhci_send_command(uint8_t cmd, uint32_t arg) "CMD%02u ARG[0x%08x]" +sdhci_error(const char *msg) "%s" +sdhci_response4(uint32_t r0) "RSPREG[31..0]=0x%08x" +sdhci_response16(uint32_t r3, uint32_t r2, uint32_t r1, uint32_t r0) "RSPREG[127..96]=0x%08x, RSPREG[95..64]=0x%08x, RSPREG[63..32]=0x%08x, RSPREG[31..0]=0x%08x" +sdhci_end_transfer(uint8_t cmd, uint32_t arg) "Automatically issue CMD%02u 0x%08x" +sdhci_adma(const char *desc, uint32_t sysad) "%s: admasysaddr=0x%" PRIx32 +sdhci_adma_loop(uint64_t addr, uint16_t length, uint8_t attr) "addr=0x%08" PRIx64 ", len=%d, attr=0x%x" +sdhci_adma_transfer_completed(void) "" +sdhci_access(const char *access, unsigned int size, uint64_t offset, const char *dir, uint64_t val, uint64_t val2) "%s%u: addr[0x%04" PRIx64 "] %s 0x%08" PRIx64 " (%" PRIu64 ")" +sdhci_read_dataport(uint16_t data_count) "all %u bytes of data have been read from input buffer" +sdhci_write_dataport(uint16_t data_count) "write buffer filled with %u bytes of data" +sdhci_capareg(const char *desc, uint16_t val) "%s: %u" + +# sd.c +sdcard_normal_command(const char *proto, const char *cmd_desc, uint8_t cmd, uint32_t arg, const char *state) "%s %20s/ CMD%02d arg 0x%08x (state %s)" +sdcard_app_command(const char *proto, const char *acmd_desc, uint8_t acmd, uint32_t arg, const char *state) "%s %23s/ACMD%02d arg 0x%08x (state %s)" +sdcard_response(const char *rspdesc, int rsplen) "%s (sz:%d)" +sdcard_powerup(void) "" +sdcard_inquiry_cmd41(void) "" +sdcard_reset(void) "" +sdcard_set_blocklen(uint16_t length) "0x%03x" +sdcard_inserted(bool readonly) "read_only: %u" +sdcard_ejected(void) "" +sdcard_erase(uint32_t first, uint32_t last) "addr first 0x%" PRIx32" last 0x%" PRIx32 +sdcard_lock(void) "" +sdcard_unlock(void) "" +sdcard_read_block(uint64_t addr, uint32_t len) "addr 0x%" PRIx64 " size 0x%x" +sdcard_write_block(uint64_t addr, uint32_t len) "addr 0x%" PRIx64 " size 0x%x" +sdcard_write_data(const char *proto, const char *cmd_desc, uint8_t cmd, uint8_t value) "%s %20s/ CMD%02d value 0x%02x" +sdcard_read_data(const char *proto, const char *cmd_desc, uint8_t cmd, uint32_t length) "%s %20s/ CMD%02d len %" PRIu32 +sdcard_set_voltage(uint16_t millivolts) "%u mV" + +# pxa2xx_mmci.c +pxa2xx_mmci_read(uint8_t size, uint32_t addr, uint32_t value) "size %d addr 0x%02x value 0x%08x" +pxa2xx_mmci_write(uint8_t size, uint32_t addr, uint32_t value) "size %d addr 0x%02x value 0x%08x" + +# pl181.c +pl181_command_send(uint8_t cmd, uint32_t arg) "sending CMD%02d arg 0x%08" PRIx32 +pl181_command_sent(void) "command sent" +pl181_command_response_pending(void) "response received" +pl181_command_timeout(void) "command timeouted" +pl181_fifo_push(uint32_t data) "FIFO push 0x%08" PRIx32 +pl181_fifo_pop(uint32_t data) "FIFO pop 0x%08" PRIx32 +pl181_fifo_transfer_complete(void) "FIFO transfer complete" +pl181_data_engine_idle(void) "data engine idle" + +# aspeed_sdhci.c +aspeed_sdhci_read(uint64_t addr, uint32_t size, uint64_t data) "@0x%" PRIx64 " size %u: 0x%" PRIx64 +aspeed_sdhci_write(uint64_t addr, uint32_t size, uint64_t data) "@0x%" PRIx64 " size %u: 0x%" PRIx64 diff --git a/hw/sd/trace.h b/hw/sd/trace.h new file mode 100644 index 000000000..f3d0c5856 --- /dev/null +++ b/hw/sd/trace.h @@ -0,0 +1 @@ +#include "trace/trace-hw_sd.h" |