diff options
author | Niek Linnenbank <nieklinnenbank@gmail.com> | 2020-03-11 23:18:44 +0100 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2020-03-12 16:27:33 +0000 |
commit | 82e4838249b23c3fe20cee295f9c1b3e6abd68d1 (patch) | |
tree | 74024e6c97b7e6d90ccc64b3cf5ac8c79849526e /hw/sd/allwinner-sdhost.c | |
parent | 6556617ce1a3c4a2ad91e5c5d1c936ee9134ed04 (diff) | |
download | qemu-82e4838249b23c3fe20cee295f9c1b3e6abd68d1.zip qemu-82e4838249b23c3fe20cee295f9c1b3e6abd68d1.tar.gz qemu-82e4838249b23c3fe20cee295f9c1b3e6abd68d1.tar.bz2 |
hw/arm/allwinner: add SD/MMC host controller
The Allwinner System on Chip families sun4i and above contain
an integrated storage controller for Secure Digital (SD) and
Multi Media Card (MMC) interfaces. This commit adds support
for the Allwinner SD/MMC storage controller with the following
emulated features:
* DMA transfers
* Direct FIFO I/O
* Short/Long format command responses
* Auto-Stop command (CMD12)
* Insert & remove card detection
The following boards are extended with the SD host controller:
* Cubieboard (hw/arm/cubieboard.c)
* Orange Pi PC (hw/arm/orangepi.c)
Signed-off-by: Niek Linnenbank <nieklinnenbank@gmail.com>
Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Message-id: 20200311221854.30370-9-nieklinnenbank@gmail.com
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'hw/sd/allwinner-sdhost.c')
-rw-r--r-- | hw/sd/allwinner-sdhost.c | 854 |
1 files changed, 854 insertions, 0 deletions
diff --git a/hw/sd/allwinner-sdhost.c b/hw/sd/allwinner-sdhost.c new file mode 100644 index 0000000..f404e1f --- /dev/null +++ b/hw/sd/allwinner-sdhost.c @@ -0,0 +1,854 @@ +/* + * 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 "sysemu/blockdev.h" +#include "hw/irq.h" +#include "hw/sd/allwinner-sdhost.h" +#include "migration/vmstate.h" +#include "trace.h" + +#define TYPE_AW_SDHOST_BUS "allwinner-sdhost-bus" +#define AW_SDHOST_BUS(obj) \ + OBJECT_CHECK(SDBus, (obj), 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 */ + cpu_physical_memory_read(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) { + cpu_physical_memory_read((desc->addr & DESC_SIZE_MASK) + num_done, + buf, buf_bytes); + + for (uint32_t i = 0; i < buf_bytes; i++) { + sdbus_write_data(&s->sdbus, buf[i]); + } + + /* Read from SD bus */ + } else { + for (uint32_t i = 0; i < buf_bytes; i++) { + buf[i] = sdbus_read_data(&s->sdbus); + } + cpu_physical_memory_write((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; + cpu_physical_memory_write(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)) { + res = sdbus_read_data(&s->sdbus); + res |= sdbus_read_data(&s->sdbus) << 8; + res |= sdbus_read_data(&s->sdbus) << 16; + res |= sdbus_read_data(&s->sdbus) << 24; + 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); + + 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 */ + sdbus_write_data(&s->sdbus, value & 0xff); + sdbus_write_data(&s->sdbus, (value >> 8) & 0xff); + sdbus_write_data(&s->sdbus, (value >> 16) & 0xff); + sdbus_write_data(&s->sdbus, (value >> 24) & 0xff); + allwinner_sdhost_update_transfer_cnt(s, sizeof(uint32_t)); + 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 void allwinner_sdhost_init(Object *obj) +{ + AwSdHostState *s = AW_SDHOST(obj); + + qbus_create_inplace(&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_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; +} + +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) |