The Synopsys DesignWare Mobile Storage Host predates SDHCI and uses a distinct MCI programming interface. It is integrated by Samsung Exynos, Rockchip, HiSilicon, and Altera/Intel SoCFPGA platforms, among others.
Add a reusable controller model based on the DesignWare Mobile Storage Host 2.40a databook. Cover command and response handling, PIO FIFO aliases, reset and interrupt semantics, card state, and the standard 32-bit internal descriptor DMA engine. Also support the later 64-bit descriptor layout used by vendor variants. Expose class hooks and immutable configuration fields so vendor extensions can reuse the common implementation. Signed-off-by: Bin Meng <[email protected]> --- MAINTAINERS | 2 + hw/sd/Kconfig | 5 + hw/sd/dw_mci.c | 1382 ++++++++++++++++++++++++++++++++++++++++ hw/sd/meson.build | 1 + include/hw/sd/dw_mci.h | 74 +++ 5 files changed, 1464 insertions(+) create mode 100644 hw/sd/dw_mci.c create mode 100644 include/hw/sd/dw_mci.h diff --git a/MAINTAINERS b/MAINTAINERS index a1830cdad1..1ea3af260f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2411,8 +2411,10 @@ M: Philippe Mathieu-Daudé <[email protected]> M: Bin Meng <[email protected]> L: [email protected] S: Odd Fixes +F: include/hw/sd/dw_mci.h F: include/hw/sd/sd* F: hw/sd/core.c +F: hw/sd/dw_mci.c F: hw/sd/sd* F: hw/sd/ssi-sd.c F: tests/qtest/fuzz-sdcard-test.c diff --git a/hw/sd/Kconfig b/hw/sd/Kconfig index c69bf24f8d..dc3ebefa52 100644 --- a/hw/sd/Kconfig +++ b/hw/sd/Kconfig @@ -10,6 +10,11 @@ config SSI_SD config SD bool +config DW_MCI + bool + select REGISTER + select SD + config SDHCI bool select SD diff --git a/hw/sd/dw_mci.c b/hw/sd/dw_mci.c new file mode 100644 index 0000000000..245435f43e --- /dev/null +++ b/hw/sd/dw_mci.c @@ -0,0 +1,1382 @@ +/* + * Synopsys DesignWare Multimedia Card Interface + * + * Copyright (c) 2026 Process Mission + * + * Author: + * Bin Meng <[email protected]> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/sd/dw_mci.h" + +#include "hw/core/irq.h" +#include "migration/vmstate.h" +#include "qapi/error.h" +#include "qemu/bswap.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "system/address-spaces.h" +#include "system/dma.h" + +REG32(CTRL, 0x000) + FIELD(CTRL, RESET, 0, 1) + FIELD(CTRL, FIFO_RESET, 1, 1) + FIELD(CTRL, DMA_RESET, 2, 1) + FIELD(CTRL, INT_ENABLE, 4, 1) + FIELD(CTRL, DMA_ENABLE, 5, 1) + FIELD(CTRL, USE_IDMAC, 25, 1) +REG32(PWREN, 0x004) +REG32(CLKDIV, 0x008) +REG32(CLKSRC, 0x00c) +REG32(CLKENA, 0x010) +REG32(TMOUT, 0x014) +REG32(CTYPE, 0x018) +REG32(BLKSIZ, 0x01c) +REG32(BYTCNT, 0x020) +REG32(INTMASK, 0x024) +REG32(CMDARG, 0x028) +REG32(CMD, 0x02c) + FIELD(CMD, INDEX, 0, 6) + FIELD(CMD, RESP_EXP, 6, 1) + FIELD(CMD, RESP_LONG, 7, 1) + FIELD(CMD, RESP_CRC, 8, 1) + FIELD(CMD, DAT_EXP, 9, 1) + FIELD(CMD, DAT_WR, 10, 1) + FIELD(CMD, SEND_STOP, 12, 1) + FIELD(CMD, STOP, 14, 1) + FIELD(CMD, UPD_CLK, 21, 1) + FIELD(CMD, START, 31, 1) +REG32(RESP0, 0x030) +REG32(RESP1, 0x034) +REG32(RESP2, 0x038) +REG32(RESP3, 0x03c) +REG32(MINTSTS, 0x040) +REG32(RINTSTS, 0x044) +REG32(STATUS, 0x048) + FIELD(STATUS, FIFO_RX_WMARK, 0, 1) + FIELD(STATUS, FIFO_TX_WMARK, 1, 1) + FIELD(STATUS, FIFO_EMPTY, 2, 1) + FIELD(STATUS, FIFO_FULL, 3, 1) + FIELD(STATUS, BUSY, 9, 1) + FIELD(STATUS, RESPONSE_INDEX, 11, 6) + FIELD(STATUS, FIFO_COUNT, 17, 13) +REG32(FIFOTH, 0x04c) + FIELD(FIFOTH, TX_WMARK, 0, 12) + FIELD(FIFOTH, RX_WMARK, 16, 12) + FIELD(FIFOTH, MSIZE, 28, 3) +REG32(CDETECT, 0x050) +REG32(WRTPRT, 0x054) +REG32(GPIO, 0x058) +REG32(TCBCNT, 0x05c) +REG32(TBBCNT, 0x060) +REG32(DEBNCE, 0x064) +REG32(USRID, 0x068) +REG32(VERID, 0x06c) +REG32(HCON, 0x070) + FIELD(HCON, CARD_TYPE, 0, 1) + FIELD(HCON, NUM_CARDS, 1, 5) + FIELD(HCON, HBUS_TYPE, 6, 1) + FIELD(HCON, HDATA_WIDTH, 7, 3) + FIELD(HCON, HADDR_WIDTH, 10, 6) + FIELD(HCON, TRANS_MODE, 16, 2) + FIELD(HCON, FIFO_RAM_INSIDE, 21, 1) + FIELD(HCON, HOLD_REG, 22, 1) + /* Later revisions use bit 27 to advertise 64-bit IDMAC addresses */ + FIELD(HCON, ADDR_CONFIG, 27, 1) +REG32(UHS_REG, 0x074) +REG32(RST_N, 0x078) +REG32(BMOD, 0x080) + FIELD(BMOD, SWRESET, 0, 1) + FIELD(BMOD, FIXED_BURST, 1, 1) + FIELD(BMOD, DSL, 2, 5) + FIELD(BMOD, ENABLE, 7, 1) + FIELD(BMOD, PBL, 8, 3) +REG32(PLDMND, 0x084) +REG32(IDMAC_088, 0x088) +REG32(IDMAC_08C, 0x08c) +REG32(IDMAC_090, 0x090) +REG32(IDMAC_094, 0x094) +REG32(IDMAC_098, 0x098) +REG32(IDMAC_09C, 0x09c) +REG32(IDMAC_0A0, 0x0a0) +REG32(IDMAC_0A4, 0x0a4) +REG32(CDTHRCTL, 0x100) +REG32(BACK_END_POWER, 0x104) +REG32(UHS_REG_EXT, 0x108) +REG32(DDR_REG, 0x10c) +REG32(ENABLE_SHIFT, 0x110) + +#define DW_MCI_INT_CD BIT(0) +#define DW_MCI_INT_RESP_ERR BIT(1) +#define DW_MCI_INT_CMD_DONE BIT(2) +#define DW_MCI_INT_DATA_OVER BIT(3) +#define DW_MCI_INT_TXDR BIT(4) +#define DW_MCI_INT_RXDR BIT(5) +#define DW_MCI_INT_RCRC BIT(6) +#define DW_MCI_INT_DCRC BIT(7) +#define DW_MCI_INT_RTO BIT(8) +#define DW_MCI_INT_DRTO BIT(9) +#define DW_MCI_INT_HTO BIT(10) +#define DW_MCI_INT_FRUN BIT(11) +#define DW_MCI_INT_HLE BIT(12) +#define DW_MCI_INT_SBE BIT(13) +#define DW_MCI_INT_ACD BIT(14) +#define DW_MCI_INT_EBE BIT(15) + +#define DW_MCI_IDSTS_TI BIT(0) +#define DW_MCI_IDSTS_RI BIT(1) +#define DW_MCI_IDSTS_FBE BIT(2) +#define DW_MCI_IDSTS_DU BIT(4) +#define DW_MCI_IDSTS_CES BIT(5) +#define DW_MCI_IDSTS_NI BIT(8) +#define DW_MCI_IDSTS_AI BIT(9) +#define DW_MCI_IDSTS_NORMAL (DW_MCI_IDSTS_TI | DW_MCI_IDSTS_RI) +#define DW_MCI_IDSTS_ABNORMAL (DW_MCI_IDSTS_FBE | DW_MCI_IDSTS_DU | \ + DW_MCI_IDSTS_CES) +#define DW_MCI_IDSTS_MASK (DW_MCI_IDSTS_TI | DW_MCI_IDSTS_RI | \ + DW_MCI_IDSTS_FBE | DW_MCI_IDSTS_DU | \ + DW_MCI_IDSTS_CES | DW_MCI_IDSTS_NI | \ + DW_MCI_IDSTS_AI) + +#define DW_MCI_IDMAC_DIC BIT(1) +#define DW_MCI_IDMAC_LD BIT(2) +#define DW_MCI_IDMAC_FS BIT(3) +#define DW_MCI_IDMAC_CH BIT(4) +#define DW_MCI_IDMAC_ER BIT(5) +#define DW_MCI_IDMAC_CES BIT(30) +#define DW_MCI_IDMAC_OWN BIT(31) +#define DW_MCI_IDMAC_SIZE_MASK 0x1fff +#define DW_MCI_IDMAC_SIZE2_SHIFT 13 +#define DW_MCI_IDMAC_MAX_DESCS 4096 +#define DW_MCI_DMA_CHUNK_SIZE 1024 + +#define DW_MCI_IDMAC_FSM_IDLE 0 +#define DW_MCI_IDMAC_FSM_SUSPEND 1 + +#define TYPE_DW_MCI_BUS "dw-mci-bus" + +typedef struct DwMciIdmac32 { + uint32_t control; + uint32_t sizes; + uint32_t buffer; + uint32_t next; +} DwMciIdmac32; + +typedef struct DwMciIdmac64 { + uint32_t control; + uint32_t reserved0; + uint32_t sizes; + uint32_t reserved1; + uint32_t buffer_lo; + uint32_t buffer_hi; + uint32_t next_lo; + uint32_t next_hi; +} DwMciIdmac64; + +typedef struct DwMciDescriptor { + uint32_t control; + uint32_t size[2]; + uint64_t buffer[2]; + uint64_t next; +} DwMciDescriptor; + +static bool dw_mci_run_idmac(DwMciState *s); + +static bool dw_mci_is_64bit(DwMciState *s) +{ + return s->hcon & R_HCON_ADDR_CONFIG_MASK; +} + +/* + * The 64-bit layout inserts DBADDRU at 0x8c, shifting IDSTS and IDINTEN one + * register higher than their locations in the legacy layout. + */ +static unsigned dw_mci_idsts_reg(DwMciState *s) +{ + return dw_mci_is_64bit(s) ? R_IDMAC_090 : R_IDMAC_08C; +} + +static unsigned dw_mci_idinten_reg(DwMciState *s) +{ + return dw_mci_is_64bit(s) ? R_IDMAC_094 : R_IDMAC_090; +} + +static uint32_t dw_mci_host_bus_bytes(DwMciState *s) +{ + unsigned width = FIELD_EX32(s->hcon, HCON, HDATA_WIDTH); + + return width <= 2 ? 2U << width : sizeof(uint32_t); +} + +static void dw_mci_set_idmac_fsm(DwMciState *s, uint32_t state) +{ + unsigned idsts = dw_mci_idsts_reg(s); + + s->regs[idsts] = deposit32(s->regs[idsts], 13, 4, state); +} + +static uint64_t dw_mci_descriptor_base(DwMciState *s) +{ + uint64_t address = s->regs[R_IDMAC_088]; + + if (dw_mci_is_64bit(s)) { + address |= (uint64_t)s->regs[R_IDMAC_08C] << 32; + } + return address; +} + +static uint32_t dw_mci_fifo_capacity(DwMciState *s) +{ + return MIN(s->fifo_depth, (uint32_t)DW_MCI_FIFO_MAX_WORDS) * 4; +} + +static void dw_mci_fifo_reset(DwMciState *s) +{ + s->fifo_head = 0; + s->fifo_len = 0; + memset(s->fifo, 0, sizeof(s->fifo)); +} + +static void dw_mci_fifo_push(DwMciState *s, const uint8_t *data, + uint32_t length) +{ + uint32_t capacity = dw_mci_fifo_capacity(s); + uint32_t tail = (s->fifo_head + s->fifo_len) % capacity; + + while (length--) { + s->fifo[tail] = *data++; + tail = (tail + 1) % capacity; + s->fifo_len++; + } +} + +static void dw_mci_fifo_pop(DwMciState *s, uint8_t *data, uint32_t length) +{ + uint32_t capacity = dw_mci_fifo_capacity(s); + + while (length--) { + *data++ = s->fifo[s->fifo_head]; + s->fifo_head = (s->fifo_head + 1) % capacity; + s->fifo_len--; + } + if (!s->fifo_len) { + s->fifo_head = 0; + } +} + +static void dw_mci_update_irq(DwMciState *s) +{ + uint32_t core = s->regs[R_RINTSTS] & s->regs[R_INTMASK]; + uint32_t idsts = s->regs[dw_mci_idsts_reg(s)]; + uint32_t idinten = s->regs[dw_mci_idinten_reg(s)]; + bool core_irq = (s->regs[R_CTRL] & R_CTRL_INT_ENABLE_MASK) && core; + bool idmac_irq = ((idsts & DW_MCI_IDSTS_NI) && + (idinten & DW_MCI_IDSTS_NI)) || + ((idsts & DW_MCI_IDSTS_AI) && + (idinten & DW_MCI_IDSTS_AI)); + + /* The top-level interrupt is the logical OR of the BIU and IDMAC lines */ + qemu_set_irq(s->irq, core_irq || idmac_irq); +} + +static void dw_mci_set_idmac_status(DwMciState *s, uint32_t status) +{ + unsigned idsts = dw_mci_idsts_reg(s); + uint32_t idinten = s->regs[dw_mci_idinten_reg(s)]; + + s->regs[idsts] |= status; + + /* + * NIS and AIS are sticky summary events, not aliases of the individual + * status bits. The databook requires both the member interrupt and its + * group interrupt to be enabled before the summary is raised. + */ + if ((status & DW_MCI_IDSTS_NORMAL & idinten) && + (idinten & DW_MCI_IDSTS_NI)) { + s->regs[idsts] |= DW_MCI_IDSTS_NI; + } + if ((status & DW_MCI_IDSTS_ABNORMAL & idinten) && + (idinten & DW_MCI_IDSTS_AI)) { + s->regs[idsts] |= DW_MCI_IDSTS_AI; + } +} + +static void dw_mci_update_fifo_interrupts(DwMciState *s) +{ + uint32_t fifo_words = DIV_ROUND_UP(s->fifo_len, sizeof(uint32_t)); + uint32_t tx_wmark = FIELD_EX32(s->regs[R_FIFOTH], FIFOTH, TX_WMARK); + uint32_t rx_wmark = FIELD_EX32(s->regs[R_FIFOTH], FIFOTH, RX_WMARK); + + if (!s->transfer_active) { + s->regs[R_RINTSTS] &= ~(DW_MCI_INT_RXDR | DW_MCI_INT_TXDR); + } else if (s->transfer_write) { + if (fifo_words <= tx_wmark) { + s->regs[R_RINTSTS] |= DW_MCI_INT_TXDR; + } else { + s->regs[R_RINTSTS] &= ~DW_MCI_INT_TXDR; + } + s->regs[R_RINTSTS] &= ~DW_MCI_INT_RXDR; + } else { + if (fifo_words > rx_wmark) { + s->regs[R_RINTSTS] |= DW_MCI_INT_RXDR; + } else { + s->regs[R_RINTSTS] &= ~DW_MCI_INT_RXDR; + } + s->regs[R_RINTSTS] &= ~DW_MCI_INT_TXDR; + } + dw_mci_update_irq(s); +} + +static void dw_mci_send_auto_stop(DwMciState *s) +{ + SDRequest request = { .cmd = 12, .arg = 0 }; + uint8_t response[16]; + size_t response_len; + + if (!s->transfer_send_stop) { + return; + } + s->transfer_send_stop = false; + response_len = sdbus_do_command(&s->sdbus, &request, response, + sizeof(response)); + if (response_len == sizeof(uint32_t)) { + /* Auto-stop preserves the preceding response in RESP0 */ + s->regs[R_RESP1] = ldl_be_p(response); + s->regs[R_STATUS] = FIELD_DP32(s->regs[R_STATUS], STATUS, + RESPONSE_INDEX, request.cmd); + } + s->regs[R_RINTSTS] |= DW_MCI_INT_ACD; +} + +static void dw_mci_complete_data(DwMciState *s) +{ + dw_mci_send_auto_stop(s); + s->regs[R_RINTSTS] |= DW_MCI_INT_DATA_OVER; + s->transfer_active = false; + s->transfer_remaining = 0; + dw_mci_update_fifo_interrupts(s); +} + +static void dw_mci_set_data_error(DwMciState *s) +{ + s->regs[R_RINTSTS] |= DW_MCI_INT_DRTO; + s->transfer_active = false; + dw_mci_update_fifo_interrupts(s); +} + +static void dw_mci_refill_fifo(DwMciState *s) +{ + uint8_t buffer[DW_MCI_DMA_CHUNK_SIZE]; + uint32_t capacity = dw_mci_fifo_capacity(s); + + while (s->transfer_active && !s->transfer_write && + s->transfer_remaining && s->fifo_len < capacity) { + uint32_t length = MIN((uint32_t)sizeof(buffer), + s->transfer_remaining); + + length = MIN(length, capacity - s->fifo_len); + sdbus_read_data(&s->sdbus, buffer, length); + dw_mci_fifo_push(s, buffer, length); + s->transfer_remaining -= length; + s->regs[R_TCBCNT] += length; + } + if (!s->transfer_remaining) { + /* + * DATA_OVER follows completion at the card side. Keep the transfer + * active until the guest drains prefetched data so FIFO status still + * describes the readable bytes. + */ + s->regs[R_RINTSTS] |= DW_MCI_INT_DATA_OVER; + } + dw_mci_update_fifo_interrupts(s); +} + +static void dw_mci_store_response(DwMciState *s, const uint8_t *response, + size_t length) +{ + s->regs[R_RESP0] = 0; + s->regs[R_RESP1] = 0; + s->regs[R_RESP2] = 0; + s->regs[R_RESP3] = 0; + + if (length == 4) { + s->regs[R_RESP0] = ldl_be_p(response); + } else if (length == 16) { + /* + * SDBus returns a long response most-significant word first, while + * the controller exposes its least-significant word in RESP0. + */ + s->regs[R_RESP0] = ldl_be_p(response + 12); + s->regs[R_RESP1] = ldl_be_p(response + 8); + s->regs[R_RESP2] = ldl_be_p(response + 4); + s->regs[R_RESP3] = ldl_be_p(response); + } +} + +static bool dw_mci_idmac_armed(DwMciState *s) +{ + return (s->regs[R_CTRL] & R_CTRL_USE_IDMAC_MASK) && + (s->regs[R_BMOD] & R_BMOD_ENABLE_MASK) && + FIELD_EX32(s->hcon, HCON, TRANS_MODE) == 0 && + !s->idmac_fatal; +} + +static bool dw_mci_send_command(DwMciState *s, uint32_t command) +{ + SDRequest request = { 0 }; + uint8_t response[16]; + bool response_expected = command & R_CMD_RESP_EXP_MASK; + bool long_response = command & R_CMD_RESP_LONG_MASK; + size_t response_len; + + s->regs[R_CMD] = command & ~R_CMD_START_MASK; + + if (command & R_CMD_UPD_CLK_MASK) { + /* No card command is sent, so the CIU must not raise Command Done */ + return true; + } + + if (!sdbus_get_inserted(&s->sdbus) && response_expected) { + s->regs[R_RINTSTS] |= DW_MCI_INT_CMD_DONE | DW_MCI_INT_RTO; + dw_mci_update_irq(s); + return false; + } + + request.cmd = FIELD_EX32(command, CMD, INDEX); + request.arg = s->regs[R_CMDARG]; + response_len = sdbus_do_command(&s->sdbus, &request, response, + sizeof(response)); + + if (response_expected && + ((!long_response && response_len != 4) || + (long_response && response_len != 16))) { + s->regs[R_RINTSTS] |= DW_MCI_INT_CMD_DONE | DW_MCI_INT_RTO; + dw_mci_update_irq(s); + return false; + } + if (response_expected) { + dw_mci_store_response(s, response, response_len); + s->regs[R_STATUS] = FIELD_DP32(s->regs[R_STATUS], STATUS, + RESPONSE_INDEX, request.cmd); + } + + s->regs[R_RINTSTS] |= DW_MCI_INT_CMD_DONE; + + if (command & R_CMD_STOP_MASK) { + s->transfer_active = false; + s->transfer_remaining = 0; + s->transfer_send_stop = false; + dw_mci_update_fifo_interrupts(s); + return true; + } + + if (command & R_CMD_DAT_EXP_MASK) { + if (!sdbus_get_inserted(&s->sdbus) || !s->regs[R_BYTCNT]) { + dw_mci_set_data_error(s); + return false; + } + dw_mci_fifo_reset(s); + s->regs[R_TCBCNT] = 0; + s->regs[R_TBBCNT] = 0; + s->transfer_remaining = s->regs[R_BYTCNT]; + s->transfer_active = true; + s->transfer_write = command & R_CMD_DAT_WR_MASK; + s->transfer_send_stop = command & R_CMD_SEND_STOP_MASK; + s->idmac_desc_addr = dw_mci_descriptor_base(s); + s->idmac_suspended = false; + dw_mci_set_idmac_fsm(s, DW_MCI_IDMAC_FSM_IDLE); + + if (dw_mci_idmac_armed(s)) { + dw_mci_run_idmac(s); + } else if (s->transfer_write) { + dw_mci_update_fifo_interrupts(s); + } else { + dw_mci_refill_fifo(s); + } + } + + dw_mci_update_irq(s); + return true; +} + +static void dw_mci_set_current_descriptor(DwMciState *s, + uint64_t descriptor, + uint64_t buffer) +{ + if (dw_mci_is_64bit(s)) { + s->regs[R_IDMAC_098] = descriptor; + s->regs[R_IDMAC_09C] = descriptor >> 32; + s->regs[R_IDMAC_0A0] = buffer; + s->regs[R_IDMAC_0A4] = buffer >> 32; + } else { + s->regs[R_IDMAC_094] = descriptor; + s->regs[R_IDMAC_098] = buffer; + } +} + +static bool dw_mci_read_descriptor32(uint64_t address, + DwMciDescriptor *result) +{ + DwMciIdmac32 descriptor; + MemTxResult tx_result; + + tx_result = dma_memory_read(&address_space_memory, address, &descriptor, + sizeof(descriptor), MEMTXATTRS_UNSPECIFIED); + if (tx_result != MEMTX_OK) { + return false; + } + + result->control = le32_to_cpu(descriptor.control); + result->size[0] = le32_to_cpu(descriptor.sizes) & + DW_MCI_IDMAC_SIZE_MASK; + result->size[1] = (le32_to_cpu(descriptor.sizes) >> + DW_MCI_IDMAC_SIZE2_SHIFT) & DW_MCI_IDMAC_SIZE_MASK; + result->buffer[0] = le32_to_cpu(descriptor.buffer); + result->buffer[1] = le32_to_cpu(descriptor.next); + result->next = result->buffer[1]; + return true; +} + +static bool dw_mci_read_descriptor64(uint64_t address, + DwMciDescriptor *result) +{ + DwMciIdmac64 descriptor; + MemTxResult tx_result; + + tx_result = dma_memory_read(&address_space_memory, address, &descriptor, + sizeof(descriptor), MEMTXATTRS_UNSPECIFIED); + if (tx_result != MEMTX_OK) { + return false; + } + + result->control = le32_to_cpu(descriptor.control); + result->size[0] = le32_to_cpu(descriptor.sizes) & + DW_MCI_IDMAC_SIZE_MASK; + result->size[1] = 0; + result->buffer[0] = le32_to_cpu(descriptor.buffer_lo) | + (uint64_t)le32_to_cpu(descriptor.buffer_hi) << 32; + result->buffer[1] = 0; + result->next = le32_to_cpu(descriptor.next_lo) | + (uint64_t)le32_to_cpu(descriptor.next_hi) << 32; + return true; +} + +static bool dw_mci_write_descriptor_control(uint64_t address, + uint32_t control) +{ + uint32_t value = cpu_to_le32(control); + + return dma_memory_write(&address_space_memory, address, &value, + sizeof(value), MEMTXATTRS_UNSPECIFIED) == MEMTX_OK; +} + +static bool dw_mci_transfer_buffer(DwMciState *s, uint64_t address, + uint32_t length) +{ + uint8_t buffer[DW_MCI_DMA_CHUNK_SIZE]; + + while (length) { + uint32_t chunk = MIN(length, (uint32_t)sizeof(buffer)); + MemTxResult result; + + if (s->transfer_write) { + result = dma_memory_read(&address_space_memory, address, buffer, + chunk, MEMTXATTRS_UNSPECIFIED); + if (result != MEMTX_OK) { + return false; + } + sdbus_write_data(&s->sdbus, buffer, chunk); + } else { + sdbus_read_data(&s->sdbus, buffer, chunk); + result = dma_memory_write(&address_space_memory, address, buffer, + chunk, MEMTXATTRS_UNSPECIFIED); + if (result != MEMTX_OK) { + return false; + } + } + + address += chunk; + length -= chunk; + s->transfer_remaining -= chunk; + s->regs[R_TCBCNT] += chunk; + s->regs[R_TBBCNT] += chunk; + } + return true; +} + +static void dw_mci_suspend_idmac(DwMciState *s) +{ + s->idmac_suspended = true; + dw_mci_set_idmac_fsm(s, DW_MCI_IDMAC_FSM_SUSPEND); + dw_mci_set_idmac_status(s, DW_MCI_IDSTS_DU); + dw_mci_update_irq(s); +} + +static void dw_mci_fatal_idmac_error(DwMciState *s) +{ + s->idmac_fatal = true; + s->idmac_suspended = false; + s->transfer_active = false; + dw_mci_set_idmac_fsm(s, DW_MCI_IDMAC_FSM_IDLE); + dw_mci_set_idmac_status(s, DW_MCI_IDSTS_FBE); + dw_mci_update_fifo_interrupts(s); +} + +static uint64_t dw_mci_next_descriptor(DwMciState *s, + const DwMciDescriptor *descriptor, + uint64_t current, + uint32_t descriptor_size) +{ + if (descriptor->control & DW_MCI_IDMAC_CH) { + return descriptor->next & + ~(uint64_t)(dw_mci_host_bus_bytes(s) - 1); + } + if (descriptor->control & DW_MCI_IDMAC_ER) { + return dw_mci_descriptor_base(s); + } + + /* DSL is expressed in host-bus-width units, not bytes */ + return current + descriptor_size + + FIELD_EX32(s->regs[R_BMOD], BMOD, DSL) * + dw_mci_host_bus_bytes(s); +} + +static bool dw_mci_run_idmac(DwMciState *s) +{ + uint32_t descriptor_size = dw_mci_is_64bit(s) ? + sizeof(DwMciIdmac64) : + sizeof(DwMciIdmac32); + unsigned int count = 0; + + if (!s->transfer_active || !dw_mci_idmac_armed(s)) { + return false; + } + if (!s->idmac_desc_addr) { + s->idmac_desc_addr = dw_mci_descriptor_base(s); + } + if (!s->idmac_desc_addr) { + dw_mci_suspend_idmac(s); + return false; + } + + /* + * Bound descriptor traversal so a cyclic guest chain cannot monopolize + * the vCPU. Exhaustion follows the descriptor-unavailable suspend path + * below, allowing the guest to recover through PLDMND. + */ + while (s->transfer_remaining && count++ < DW_MCI_IDMAC_MAX_DESCS) { + DwMciDescriptor descriptor = { 0 }; + uint64_t descriptor_address = s->idmac_desc_addr; + bool read_ok; + unsigned int i; + + dw_mci_set_current_descriptor(s, descriptor_address, 0); + if (dw_mci_is_64bit(s)) { + read_ok = dw_mci_read_descriptor64(descriptor_address, + &descriptor); + } else { + read_ok = dw_mci_read_descriptor32(descriptor_address, + &descriptor); + } + if (!read_ok) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: failed to read IDMAC descriptor at " + "0x%" PRIx64 "\n", + TYPE_DW_MCI, descriptor_address); + dw_mci_fatal_idmac_error(s); + return false; + } + if (!(descriptor.control & DW_MCI_IDMAC_OWN)) { + dw_mci_suspend_idmac(s); + return false; + } + + for (i = 0; i < ARRAY_SIZE(descriptor.buffer) && + s->transfer_remaining; i++) { + uint64_t buffer_address; + uint32_t length = MIN(descriptor.size[i], + s->transfer_remaining); + + /* DES3 is a next-descriptor pointer in chained mode */ + if (i == 1 && (descriptor.control & DW_MCI_IDMAC_CH)) { + break; + } + if (!length) { + continue; + } + buffer_address = descriptor.buffer[i] & + ~(uint64_t)(dw_mci_host_bus_bytes(s) - 1); + dw_mci_set_current_descriptor(s, descriptor_address, + buffer_address); + if (!dw_mci_transfer_buffer(s, buffer_address, length)) { + dw_mci_fatal_idmac_error(s); + return false; + } + } + + descriptor.control &= ~DW_MCI_IDMAC_OWN; + if (!dw_mci_write_descriptor_control(descriptor_address, + descriptor.control)) { + dw_mci_fatal_idmac_error(s); + return false; + } + + if (!(descriptor.control & DW_MCI_IDMAC_DIC)) { + dw_mci_set_idmac_status(s, s->transfer_write ? + DW_MCI_IDSTS_TI : DW_MCI_IDSTS_RI); + } + + if (!s->transfer_remaining) { + break; + } + if (descriptor.control & DW_MCI_IDMAC_LD) { + dw_mci_suspend_idmac(s); + return false; + } + + s->idmac_desc_addr = dw_mci_next_descriptor(s, &descriptor, + descriptor_address, + descriptor_size); + if (!s->idmac_desc_addr) { + dw_mci_suspend_idmac(s); + return false; + } + } + + if (s->transfer_remaining) { + dw_mci_suspend_idmac(s); + return false; + } + + s->idmac_suspended = false; + dw_mci_set_idmac_fsm(s, DW_MCI_IDMAC_FSM_IDLE); + dw_mci_complete_data(s); + dw_mci_update_irq(s); + return true; +} + +static uint64_t dw_mci_read_data(DwMciState *s, unsigned size) +{ + uint8_t buffer[sizeof(uint64_t)] = { 0 }; + uint32_t length; + + if (!s->transfer_active || s->transfer_write) { + return 0; + } + if (!s->fifo_len && s->transfer_remaining) { + dw_mci_refill_fifo(s); + } + + length = MIN((uint32_t)size, s->fifo_len); + if (length) { + dw_mci_fifo_pop(s, buffer, length); + s->regs[R_TBBCNT] += length; + } + if (s->transfer_remaining) { + dw_mci_refill_fifo(s); + } + if (!s->transfer_remaining && !s->fifo_len) { + dw_mci_complete_data(s); + } else { + dw_mci_update_fifo_interrupts(s); + } + return ldn_le_p(buffer, size); +} + +static void dw_mci_write_data(DwMciState *s, uint64_t value, unsigned size) +{ + uint8_t buffer[sizeof(uint64_t)] = { 0 }; + uint32_t length; + + if (!s->transfer_active || !s->transfer_write) { + return; + } + + /* + * The functional model consumes PIO writes synchronously through SDBus. + * Its FIFO therefore remains empty and TXDR continues to advertise room. + */ + length = MIN((uint32_t)size, s->transfer_remaining); + stn_le_p(buffer, size, value); + sdbus_write_data(&s->sdbus, buffer, length); + s->transfer_remaining -= length; + s->regs[R_TCBCNT] += length; + s->regs[R_TBBCNT] += length; + + if (!s->transfer_remaining) { + dw_mci_complete_data(s); + } else { + dw_mci_update_fifo_interrupts(s); + } +} + +static bool dw_mci_idmac_offset(hwaddr offset) +{ + return offset >= A_IDMAC_088 && offset <= A_IDMAC_0A4 && + !(offset & 3); +} + +static uint64_t dw_mci_read_idmac_register(DwMciState *s, hwaddr offset) +{ + unsigned reg = offset / sizeof(uint32_t); + + if (!dw_mci_is_64bit(s) && offset > A_IDMAC_098) { + return 0; + } + return s->regs[reg]; +} + +static void dw_mci_write_idmac_register(DwMciState *s, hwaddr offset, + uint32_t value) +{ + unsigned reg = offset / sizeof(uint32_t); + unsigned idsts = dw_mci_idsts_reg(s); + unsigned idinten = dw_mci_idinten_reg(s); + + if (reg == idsts) { + s->regs[reg] &= ~(value & DW_MCI_IDSTS_MASK); + } else if (reg == idinten) { + s->regs[reg] = value & DW_MCI_IDSTS_MASK; + } else if (offset == A_IDMAC_088) { + s->regs[reg] = value & ~(dw_mci_host_bus_bytes(s) - 1); + } else if (dw_mci_is_64bit(s) && offset == A_IDMAC_08C) { + s->regs[reg] = value; + } + dw_mci_update_irq(s); +} + +static void dw_mci_reset_idmac(DwMciState *s) +{ + s->regs[R_BMOD] = 0; + s->regs[R_IDMAC_088] = 0; + if (dw_mci_is_64bit(s)) { + s->regs[R_IDMAC_08C] = 0; + s->regs[R_IDMAC_090] = 0; + s->regs[R_IDMAC_094] = 0; + s->regs[R_IDMAC_098] = 0; + s->regs[R_IDMAC_09C] = 0; + s->regs[R_IDMAC_0A0] = 0; + s->regs[R_IDMAC_0A4] = 0; + } else { + s->regs[R_IDMAC_08C] = 0; + s->regs[R_IDMAC_090] = 0; + s->regs[R_IDMAC_094] = 0; + s->regs[R_IDMAC_098] = 0; + } + s->idmac_desc_addr = 0; + s->idmac_suspended = false; + s->idmac_fatal = false; +} + +static uint64_t dw_mci_ctrl_pre_write(RegisterInfo *reg, uint64_t value) +{ + DwMciState *s = DW_MCI(reg->opaque); + + if (value & R_CTRL_RESET_MASK) { + /* + * CTRL.controller_reset only resets the BIU/CIU interface and its + * state machines. In particular, it must not clear registers, FIFO + * contents, interrupt state, or the IDMAC CSR block. + */ + s->transfer_active = false; + s->transfer_remaining = 0; + s->transfer_send_stop = false; + value &= ~(BIT(6) | BIT(7) | BIT(8)); + } + if (value & R_CTRL_FIFO_RESET_MASK) { + dw_mci_fifo_reset(s); + } + /* CTRL.dma_reset belongs to the external DMA handshake, not IDMAC */ + return value & ~(R_CTRL_RESET_MASK | R_CTRL_FIFO_RESET_MASK | + R_CTRL_DMA_RESET_MASK); +} + +static void dw_mci_ctrl_post_write(RegisterInfo *reg, uint64_t value) +{ + DwMciState *s = DW_MCI(reg->opaque); + + dw_mci_update_irq(s); +} + +static void dw_mci_pwren_post_write(RegisterInfo *reg, uint64_t value) +{ + DwMciState *s = DW_MCI(reg->opaque); + + if (value & 1) { + sdbus_set_voltage(&s->sdbus, SD_VOLTAGE_3_3V); + } +} + +static uint64_t dw_mci_cmd_pre_write(RegisterInfo *reg, uint64_t value) +{ + DwMciState *s = DW_MCI(reg->opaque); + uint32_t command = value; + + if (command & R_CMD_START_MASK) { + dw_mci_send_command(s, command); + } + return command & ~R_CMD_START_MASK; +} + +static void dw_mci_status_post_write(RegisterInfo *reg, uint64_t value) +{ + DwMciState *s = DW_MCI(reg->opaque); + + dw_mci_update_fifo_interrupts(s); +} + +static uint64_t dw_mci_mintsts_post_read(RegisterInfo *reg, uint64_t value) +{ + DwMciState *s = DW_MCI(reg->opaque); + + return s->regs[R_RINTSTS] & s->regs[R_INTMASK]; +} + +static uint64_t dw_mci_status_post_read(RegisterInfo *reg, uint64_t value) +{ + DwMciState *s = DW_MCI(reg->opaque); + uint32_t capacity = dw_mci_fifo_capacity(s); + uint32_t fifo_words = DIV_ROUND_UP(s->fifo_len, sizeof(uint32_t)); + uint32_t tx_wmark = FIELD_EX32(s->regs[R_FIFOTH], FIFOTH, TX_WMARK); + uint32_t rx_wmark = FIELD_EX32(s->regs[R_FIFOTH], FIFOTH, RX_WMARK); + uint32_t status = s->regs[R_STATUS] & R_STATUS_RESPONSE_INDEX_MASK; + + status = FIELD_DP32(status, STATUS, FIFO_COUNT, fifo_words); + if (!s->fifo_len) { + status |= R_STATUS_FIFO_EMPTY_MASK; + } + if (s->fifo_len >= capacity) { + status |= R_STATUS_FIFO_FULL_MASK; + } + if (fifo_words > rx_wmark) { + status |= R_STATUS_FIFO_RX_WMARK_MASK; + } + if (fifo_words <= tx_wmark) { + status |= R_STATUS_FIFO_TX_WMARK_MASK; + } + if (s->transfer_active) { + status |= R_STATUS_BUSY_MASK; + } + return status; +} + +static uint64_t dw_mci_cdetect_post_read(RegisterInfo *reg, uint64_t value) +{ + DwMciState *s = DW_MCI(reg->opaque); + + return sdbus_get_inserted(&s->sdbus) ? 0 : 1; +} + +static uint64_t dw_mci_wrtprt_post_read(RegisterInfo *reg, uint64_t value) +{ + DwMciState *s = DW_MCI(reg->opaque); + + return sdbus_get_readonly(&s->sdbus) ? 1 : 0; +} + +static uint64_t dw_mci_verid_post_read(RegisterInfo *reg, uint64_t value) +{ + DwMciState *s = DW_MCI(reg->opaque); + + return s->verid; +} + +static uint64_t dw_mci_hcon_post_read(RegisterInfo *reg, uint64_t value) +{ + DwMciState *s = DW_MCI(reg->opaque); + + return s->hcon; +} + +static uint64_t dw_mci_bmod_pre_write(RegisterInfo *reg, uint64_t value) +{ + DwMciState *s = DW_MCI(reg->opaque); + + if (value & R_BMOD_SWRESET_MASK) { + uint32_t pbl = FIELD_EX32(s->regs[R_FIFOTH], FIFOTH, MSIZE); + + dw_mci_reset_idmac(s); + /* The read-only PBL mirror is not reset with the IDMAC state */ + s->regs[R_BMOD] = FIELD_DP32(0, BMOD, PBL, pbl); + return s->regs[R_BMOD]; + } + + /* PBL is a read-only mirror of FIFOTH.MSIZE */ + return FIELD_DP32(value, BMOD, PBL, + FIELD_EX32(s->regs[R_FIFOTH], FIFOTH, MSIZE)); +} + +static void dw_mci_bmod_post_write(RegisterInfo *reg, uint64_t value) +{ + DwMciState *s = DW_MCI(reg->opaque); + + if (s->transfer_active && dw_mci_idmac_armed(s)) { + dw_mci_run_idmac(s); + } + dw_mci_update_irq(s); +} + +static void dw_mci_pldmnd_post_write(RegisterInfo *reg, uint64_t value) +{ + DwMciState *s = DW_MCI(reg->opaque); + + /* Any write, including zero, releases the descriptor-suspend state */ + if (s->idmac_suspended && s->transfer_active && + dw_mci_idmac_armed(s)) { + s->idmac_suspended = false; + dw_mci_set_idmac_fsm(s, DW_MCI_IDMAC_FSM_IDLE); + dw_mci_run_idmac(s); + } +} + +static uint64_t dw_mci_pldmnd_post_read(RegisterInfo *reg, uint64_t value) +{ + return 0; +} + +static void dw_mci_fifoth_post_write(RegisterInfo *reg, uint64_t value) +{ + DwMciState *s = DW_MCI(reg->opaque); + + s->regs[R_BMOD] = FIELD_DP32(s->regs[R_BMOD], BMOD, PBL, + FIELD_EX32(value, FIFOTH, MSIZE)); + dw_mci_update_fifo_interrupts(s); +} + +static void dw_mci_uhs_post_write(RegisterInfo *reg, uint64_t value) +{ + DwMciState *s = DW_MCI(reg->opaque); + + sdbus_set_voltage(&s->sdbus, value & BIT(0) ? + SD_VOLTAGE_1_8V : SD_VOLTAGE_3_3V); +} + +static const RegisterAccessInfo dw_mci_regs_info[] = { + { .name = "CTRL", .addr = A_CTRL, .reset = BIT(24), + .pre_write = dw_mci_ctrl_pre_write, + .post_write = dw_mci_ctrl_post_write }, + { .name = "PWREN", .addr = A_PWREN, .ro = UINT32_MAX ^ BIT(0), + .post_write = dw_mci_pwren_post_write }, + { .name = "CLKDIV", .addr = A_CLKDIV, .ro = 0xffffff00 }, + { .name = "CLKSRC", .addr = A_CLKSRC, .ro = 0xfffffffc }, + { .name = "CLKENA", .addr = A_CLKENA, + .ro = UINT32_MAX ^ (BIT(0) | BIT(16)) }, + { .name = "TMOUT", .addr = A_TMOUT, .reset = 0xffffff40 }, + { .name = "CTYPE", .addr = A_CTYPE, + .ro = UINT32_MAX ^ (BIT(0) | BIT(16)) }, + { .name = "BLKSIZ", .addr = A_BLKSIZ, .reset = 0x200, + .ro = 0xffff0000 }, + { .name = "BYTCNT", .addr = A_BYTCNT, .reset = 0x200 }, + { .name = "INTMASK", .addr = A_INTMASK, + .post_write = dw_mci_ctrl_post_write }, + { .name = "CMDARG", .addr = A_CMDARG }, + { .name = "CMD", .addr = A_CMD, + .pre_write = dw_mci_cmd_pre_write }, + { .name = "RESP0", .addr = A_RESP0, .ro = UINT32_MAX }, + { .name = "RESP1", .addr = A_RESP1, .ro = UINT32_MAX }, + { .name = "RESP2", .addr = A_RESP2, .ro = UINT32_MAX }, + { .name = "RESP3", .addr = A_RESP3, .ro = UINT32_MAX }, + { .name = "MINTSTS", .addr = A_MINTSTS, .ro = UINT32_MAX, + .post_read = dw_mci_mintsts_post_read }, + { .name = "RINTSTS", .addr = A_RINTSTS, .w1c = UINT32_MAX, + .post_write = dw_mci_status_post_write }, + { .name = "STATUS", .addr = A_STATUS, .ro = UINT32_MAX, + .reset = R_STATUS_FIFO_EMPTY_MASK | R_STATUS_FIFO_TX_WMARK_MASK, + .post_read = dw_mci_status_post_read }, + { .name = "FIFOTH", .addr = A_FIFOTH, + .ro = 0x8000f000, + .post_write = dw_mci_fifoth_post_write }, + { .name = "CDETECT", .addr = A_CDETECT, .ro = UINT32_MAX, + .post_read = dw_mci_cdetect_post_read }, + { .name = "WRTPRT", .addr = A_WRTPRT, .ro = UINT32_MAX, + .post_read = dw_mci_wrtprt_post_read }, + { .name = "GPIO", .addr = A_GPIO, + .ro = 0xff0000ff }, + { .name = "TCBCNT", .addr = A_TCBCNT, .ro = UINT32_MAX }, + { .name = "TBBCNT", .addr = A_TBBCNT, .ro = UINT32_MAX }, + { .name = "DEBNCE", .addr = A_DEBNCE, .reset = 0x00ffffff, + .ro = 0xff000000 }, + { .name = "USRID", .addr = A_USRID }, + { .name = "VERID", .addr = A_VERID, .ro = UINT32_MAX, + .post_read = dw_mci_verid_post_read }, + { .name = "HCON", .addr = A_HCON, .ro = UINT32_MAX, + .post_read = dw_mci_hcon_post_read }, + { .name = "UHS_REG", .addr = A_UHS_REG, + .ro = UINT32_MAX ^ (BIT(0) | BIT(16)), + .post_write = dw_mci_uhs_post_write }, + { .name = "RST_N", .addr = A_RST_N, + .ro = UINT32_MAX ^ BIT(0) }, + { .name = "BMOD", .addr = A_BMOD, .ro = 0xffffff00, + .pre_write = dw_mci_bmod_pre_write, + .post_write = dw_mci_bmod_post_write }, + { .name = "PLDMND", .addr = A_PLDMND, + .post_write = dw_mci_pldmnd_post_write, + .post_read = dw_mci_pldmnd_post_read }, + { .name = "CDTHRCTL", .addr = A_CDTHRCTL, + .ro = 0xf000fffe }, + { .name = "BACK_END_POWER", .addr = A_BACK_END_POWER, + .ro = UINT32_MAX ^ BIT(0) }, + { .name = "UHS_REG_EXT", .addr = A_UHS_REG_EXT }, + { .name = "DDR_REG", .addr = A_DDR_REG }, + { .name = "ENABLE_SHIFT", .addr = A_ENABLE_SHIFT }, +}; + +static bool dw_mci_fifo_offset(DwMciState *s, hwaddr offset) +{ + return offset >= s->data_offset && offset < DW_MCI_MMIO_SIZE; +} + +static uint64_t dw_mci_read(void *opaque, hwaddr offset, unsigned size) +{ + RegisterInfoArray *reg_array = opaque; + DwMciState *s = DW_MCI(register_array_get_owner(reg_array)); + DwMciClass *dmc = DW_MCI_GET_CLASS(s); + uint64_t value; + + if (dmc->vendor_read && dmc->vendor_read(s, offset, &value, size)) { + return value; + } + if (dw_mci_fifo_offset(s, offset)) { + if (s->regs[R_CTRL] & R_CTRL_USE_IDMAC_MASK) { + return 0; + } + return dw_mci_read_data(s, size); + } + if (dw_mci_idmac_offset(offset)) { + return size == 4 ? dw_mci_read_idmac_register(s, offset) : 0; + } + if (size != 4) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid %u-byte access at 0x%" HWADDR_PRIx "\n", + TYPE_DW_MCI, size, offset); + return 0; + } + return register_read_memory(opaque, offset, size); +} + +static void dw_mci_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + RegisterInfoArray *reg_array = opaque; + DwMciState *s = DW_MCI(register_array_get_owner(reg_array)); + DwMciClass *dmc = DW_MCI_GET_CLASS(s); + + if (dmc->vendor_write && + dmc->vendor_write(s, offset, value, size)) { + return; + } + if (dw_mci_fifo_offset(s, offset)) { + if (!(s->regs[R_CTRL] & R_CTRL_USE_IDMAC_MASK)) { + dw_mci_write_data(s, value, size); + } + return; + } + if (dw_mci_idmac_offset(offset)) { + if (size == 4) { + dw_mci_write_idmac_register(s, offset, value); + } + return; + } + if (size != 4) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid %u-byte access at 0x%" HWADDR_PRIx "\n", + TYPE_DW_MCI, size, offset); + return; + } + register_write_memory(opaque, offset, value, size); +} + +static const MemoryRegionOps dw_mci_ops = { + .read = dw_mci_read, + .write = dw_mci_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 8, + .unaligned = false, + }, +}; + +static void dw_mci_set_inserted(DeviceState *dev, bool inserted) +{ + DwMciState *s = DW_MCI(dev); + + if (s->card_inserted == inserted) { + return; + } + s->card_inserted = inserted; + s->regs[R_RINTSTS] |= DW_MCI_INT_CD; + dw_mci_update_irq(s); +} + +static void dw_mci_set_readonly(DeviceState *dev, bool readonly) +{ + DwMciState *s = DW_MCI(dev); + + s->card_readonly = readonly; +} + +static void dw_mci_bus_class_init(ObjectClass *klass, const void *data) +{ + SDBusClass *sbc = SD_BUS_CLASS(klass); + + sbc->set_inserted = dw_mci_set_inserted; + sbc->set_readonly = dw_mci_set_readonly; +} + +static void dw_mci_reset(DeviceState *dev) +{ + DwMciState *s = DW_MCI(dev); + DwMciClass *dmc = DW_MCI_GET_CLASS(s); + int i; + + s->transfer_active = false; + s->transfer_write = false; + s->transfer_send_stop = false; + s->transfer_remaining = 0; + s->idmac_desc_addr = 0; + s->idmac_suspended = false; + s->idmac_fatal = false; + dw_mci_fifo_reset(s); + memset(s->regs, 0, sizeof(s->regs)); + + for (i = 0; i < ARRAY_SIZE(s->regs_info); i++) { + register_reset(&s->regs_info[i]); + } + s->regs[R_FIFOTH] = FIELD_DP32(0, FIFOTH, RX_WMARK, + s->fifo_depth - 1); + s->regs[R_RST_N] = 1; + if (dmc->vendor_reset) { + dmc->vendor_reset(s); + } + s->card_inserted = sdbus_get_inserted(&s->sdbus); + s->card_readonly = sdbus_get_readonly(&s->sdbus); + sdbus_set_voltage(&s->sdbus, SD_VOLTAGE_3_3V); + dw_mci_update_irq(s); +} + +static void dw_mci_realize(DeviceState *dev, Error **errp) +{ + DwMciState *s = DW_MCI(dev); + + if (!s->fifo_depth || s->fifo_depth > DW_MCI_FIFO_MAX_WORDS) { + error_setg(errp, "invalid DW MCI FIFO depth %u", s->fifo_depth); + return; + } + if ((s->data_offset != 0x100 && s->data_offset != 0x200) || + s->data_offset >= DW_MCI_MMIO_SIZE) { + error_setg(errp, "invalid DW MCI DATA offset 0x%x", + s->data_offset); + } +} + +static int dw_mci_post_load(void *opaque, int version_id) +{ + DwMciState *s = opaque; + uint32_t capacity = dw_mci_fifo_capacity(s); + + if (s->fifo_head >= capacity || s->fifo_len > capacity) { + return -EINVAL; + } + dw_mci_update_fifo_interrupts(s); + return 0; +} + +static const VMStateDescription dw_mci_vmsd = { + .name = TYPE_DW_MCI, + .version_id = 1, + .minimum_version_id = 1, + .post_load = dw_mci_post_load, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, DwMciState, DW_MCI_REG_COUNT), + VMSTATE_UINT8_ARRAY(fifo, DwMciState, DW_MCI_FIFO_MAX_BYTES), + VMSTATE_UINT32(fifo_head, DwMciState), + VMSTATE_UINT32(fifo_len, DwMciState), + VMSTATE_UINT32(transfer_remaining, DwMciState), + VMSTATE_UINT64(idmac_desc_addr, DwMciState), + VMSTATE_BOOL(transfer_active, DwMciState), + VMSTATE_BOOL(transfer_write, DwMciState), + VMSTATE_BOOL(transfer_send_stop, DwMciState), + VMSTATE_BOOL(idmac_suspended, DwMciState), + VMSTATE_BOOL(idmac_fatal, DwMciState), + VMSTATE_BOOL(card_inserted, DwMciState), + VMSTATE_BOOL(card_readonly, DwMciState), + VMSTATE_END_OF_LIST() + }, +}; + +static void dw_mci_init(Object *obj) +{ + DwMciState *s = DW_MCI(obj); + DwMciClass *dmc = DW_MCI_GET_CLASS(s); + RegisterInfoArray *reg_array; + + s->verid = dmc->verid; + s->hcon = dmc->hcon; + s->data_offset = dmc->data_offset; + s->fifo_depth = dmc->fifo_depth; + + qbus_init(&s->sdbus, sizeof(s->sdbus), TYPE_DW_MCI_BUS, + DEVICE(obj), "sd-bus"); + memory_region_init(&s->iomem, obj, TYPE_DW_MCI, DW_MCI_MMIO_SIZE); + reg_array = register_init_block32(DEVICE(obj), dw_mci_regs_info, + ARRAY_SIZE(dw_mci_regs_info), + s->regs_info, s->regs, + &dw_mci_ops, false, + DW_MCI_MMIO_SIZE); + memory_region_add_subregion(&s->iomem, 0, ®_array->mem); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem); + sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq); +} + +static void dw_mci_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + DwMciClass *dmc = DW_MCI_CLASS(klass); + + dmc->verid = 0x5342240a; + dmc->hcon = FIELD_DP32(0, HCON, CARD_TYPE, 1); + dmc->hcon = FIELD_DP32(dmc->hcon, HCON, HBUS_TYPE, 1); + dmc->hcon = FIELD_DP32(dmc->hcon, HCON, HDATA_WIDTH, 1); + dmc->hcon = FIELD_DP32(dmc->hcon, HCON, HADDR_WIDTH, 9); + dmc->hcon = FIELD_DP32(dmc->hcon, HCON, FIFO_RAM_INSIDE, 1); + dmc->data_offset = 0x200; + dmc->fifo_depth = 256; + + dc->realize = dw_mci_realize; + dc->vmsd = &dw_mci_vmsd; + device_class_set_legacy_reset(dc, dw_mci_reset); +} + +SDBus *dw_mci_get_bus(DwMciState *s) +{ + return &s->sdbus; +} + +static const TypeInfo dw_mci_info = { + .name = TYPE_DW_MCI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(DwMciState), + .instance_init = dw_mci_init, + .class_size = sizeof(DwMciClass), + .class_init = dw_mci_class_init, +}; + +static const TypeInfo dw_mci_bus_info = { + .name = TYPE_DW_MCI_BUS, + .parent = TYPE_SD_BUS, + .instance_size = sizeof(SDBus), + .class_size = sizeof(SDBusClass), + .class_init = dw_mci_bus_class_init, +}; + +static void dw_mci_register_types(void) +{ + type_register_static(&dw_mci_bus_info); + type_register_static(&dw_mci_info); +} + +type_init(dw_mci_register_types) diff --git a/hw/sd/meson.build b/hw/sd/meson.build index ebf09e30a4..d7b97cdca4 100644 --- a/hw/sd/meson.build +++ b/hw/sd/meson.build @@ -1,5 +1,6 @@ system_ss.add(when: 'CONFIG_PL181', if_true: files('pl181.c')) system_ss.add(when: 'CONFIG_SD', if_true: files('sd.c', 'core.c')) +system_ss.add(when: 'CONFIG_DW_MCI', if_true: files('dw_mci.c')) system_ss.add(when: 'CONFIG_SDHCI', if_true: files('sdhci.c')) system_ss.add(when: 'CONFIG_SDHCI_PCI', if_true: files('sdhci-pci.c')) system_ss.add(when: 'CONFIG_SSI_SD', if_true: files('ssi-sd.c')) diff --git a/include/hw/sd/dw_mci.h b/include/hw/sd/dw_mci.h new file mode 100644 index 0000000000..de4175540d --- /dev/null +++ b/include/hw/sd/dw_mci.h @@ -0,0 +1,74 @@ +/* + * Synopsys DesignWare Multimedia Card Interface + * + * Copyright (c) 2026 Process Mission + * + * Author: + * Bin Meng <[email protected]> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_SD_DW_MCI_H +#define HW_SD_DW_MCI_H + +#include "hw/core/register.h" +#include "hw/core/sysbus.h" +#include "hw/sd/sd.h" +#include "qom/object.h" + +#define TYPE_DW_MCI "dw-mci" +OBJECT_DECLARE_TYPE(DwMciState, DwMciClass, DW_MCI) + +#define DW_MCI_MMIO_SIZE 0x1000 +#define DW_MCI_REG_COUNT (DW_MCI_MMIO_SIZE / sizeof(uint32_t)) +#define DW_MCI_FIFO_MAX_WORDS 2048 +#define DW_MCI_FIFO_MAX_BYTES (DW_MCI_FIFO_MAX_WORDS * sizeof(uint32_t)) + +struct DwMciState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + SDBus sdbus; + qemu_irq irq; + + uint32_t regs[DW_MCI_REG_COUNT]; + RegisterInfo regs_info[DW_MCI_REG_COUNT]; + + uint8_t fifo[DW_MCI_FIFO_MAX_BYTES]; + uint32_t fifo_head; + uint32_t fifo_len; + uint32_t transfer_remaining; + uint64_t idmac_desc_addr; + bool transfer_active; + bool transfer_write; + bool transfer_send_stop; + bool idmac_suspended; + bool idmac_fatal; + bool card_inserted; + bool card_readonly; + + uint32_t verid; + uint32_t hcon; + uint32_t data_offset; + uint32_t fifo_depth; +}; + +struct DwMciClass { + SysBusDeviceClass parent_class; + + uint32_t verid; + uint32_t hcon; + uint32_t data_offset; + uint32_t fifo_depth; + + bool (*vendor_read)(DwMciState *s, hwaddr offset, uint64_t *value, + unsigned size); + bool (*vendor_write)(DwMciState *s, hwaddr offset, uint64_t value, + unsigned size); + void (*vendor_reset)(DwMciState *s); +}; + +SDBus *dw_mci_get_bus(DwMciState *s); + +#endif -- 2.53.0
