diff --git a/hw/sd/Kconfig b/hw/sd/Kconfig index 633b9afec9..684e6bd6e7 100644 --- a/hw/sd/Kconfig +++ b/hw/sd/Kconfig @@ -23,3 +23,7 @@ config SDHCI_PCI config CADENCE_SDHCI bool select SDHCI + +config DWC_SDMMC + bool + select SD diff --git a/hw/sd/dwc_sdmmc.c b/hw/sd/dwc_sdmmc.c new file mode 100644 index 0000000000..8fcc65237a --- /dev/null +++ b/hw/sd/dwc_sdmmc.c @@ -0,0 +1,592 @@ +/* + * Designware SD/MMC controller emulation + * + * Copyright (c) 2021 Espressif Systems (Shanghai) Co. Ltd. + * + * Based on Allwinner SD Host controller emulation (allwinner-sdhost.c), + * Copyright (C) 2019 Niek Linnenbank + * + * 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 . + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/error-report.h" +#include "hw/sd/dwc_sdmmc.h" +#include "qapi/error.h" +#include "hw/irq.h" +#include "hw/qdev-properties.h" +#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 "migration/vmstate.h" +#include "trace.h" +#include "qom/object.h" +#include "hw/registerfields.h" + +/* This implementation is simplified: + * - Only DMA mode is implemented, with 32-bit DMA descriptor pointers. + * - FIFO is not implemented. + * - No error interrupts or conditions are generated. + * - Boot mode is not implemented. + * - Clock settings are ignored. + * + * The hardware seems to work in a manner very similar to Allwinner SD Host controller (allwinner-sdhost.c), + * notable exceptions being: + * - slightly different IDMAC descriptor structure + * - slightly different interrupt handling logic + * It is possible that Allwinner SD Host is based on an earlier version of Designware SD/MMC IP, + * or is heavily inspired by it. + */ + +#ifdef DWC_SDMMC_DEBUG +#define DEBUG(...) qemu_log(__VA_ARGS__) +#else +#define DEBUG(...) +#endif + +typedef struct { + uint32_t reserved1: 1; + uint32_t disable_int_on_completion: 1; + uint32_t last_descriptor: 1; + uint32_t first_descriptor: 1; + uint32_t second_address_chained: 1; + uint32_t end_of_ring: 1; + uint32_t reserved2: 24; + uint32_t card_error_summary: 1; + uint32_t owned_by_idmac: 1; + uint32_t buffer1_size: 13; + uint32_t buffer2_size: 13; + uint32_t reserved3: 6; + uint32_t buffer1_ptr; + union { + uint32_t buffer2_ptr; + uint32_t next_desc_ptr; + }; +} sdmmc_desc_t; + +typedef struct { + uint32_t cmd_index: 6; ///< Command index + uint32_t response_expect: 1; ///< set if response is expected + uint32_t response_long: 1; ///< 0: short response expected, 1: long response expected + uint32_t check_response_crc: 1; ///< set if controller should check response CRC + uint32_t data_expected: 1; ///< 0: no data expected, 1: data expected + uint32_t rw: 1; ///< 0: read from card, 1: write to card (don't care if no data expected) + uint32_t stream_mode: 1; ///< 0: block transfer, 1: stream transfer (don't care if no data expected) + uint32_t send_auto_stop: 1; ///< set to send stop at the end of the transfer + uint32_t wait_complete: 1; ///< 0: send command at once, 1: wait for previous command to complete + uint32_t stop_abort_cmd: 1; ///< set if this is a stop or abort command intended to stop current transfer + uint32_t send_init: 1; ///< set to send init sequence (80 clocks of 1) + uint32_t card_num: 5; ///< card number + uint32_t update_clk_reg: 1; ///< 0: normal command, 1: don't send command, just update clock registers + uint32_t read_ceata: 1; ///< set if performing read from CE-ATA device + uint32_t ccs_expected: 1; ///< set if CCS is expected from CE-ATA device + uint32_t enable_boot: 1; ///< set for mandatory boot mode + uint32_t expect_boot_ack: 1; ///< when set along with enable_boot, controller expects boot ack pattern + uint32_t disable_boot: 1; ///< set to terminate boot operation (don't set along with enable_boot) + uint32_t boot_mode: 1; ///< 0: mandatory boot operation, 1: alternate boot operation + uint32_t volt_switch: 1; ///< set to enable voltage switching (for CMD11 only) + uint32_t use_hold_reg: 1; ///< clear to bypass HOLD register + uint32_t reserved: 1; + uint32_t start_command: 1; ///< Start command; once command is sent to the card, bit is cleared. +} sdmmc_hw_cmd_t; ///< command format used in cmd register; this structure is defined to make it easier to build command values + +_Static_assert(sizeof(sdmmc_hw_cmd_t) == 4, "invalid size of sdmmc_cmd_t structure"); + + +REG32(SDMMC_CTRL, 0x00) + FIELD(SDMMC_CTRL, RST, 0, 1); + FIELD(SDMMC_CTRL, FIFO_RST, 1, 1); + FIELD(SDMMC_CTRL, DMA_RST, 2, 1); + FIELD(SDMMC_CTRL, INTEN, 4, 1); + FIELD(SDMMC_CTRL, DMAEN, 5, 1); + +REG32(SDMMC_PWREN, 0x04) +REG32(SDMMC_CLKDIV, 0x08) +REG32(SDMMC_CLKSRC, 0x0c) +REG32(SDMMC_CLKENA, 0x10) +REG32(SDMMC_TMOUT, 0x14) +REG32(SDMMC_CTYPE, 0x18) +REG32(SDMMC_BLKSIZ, 0x1c) +REG32(SDMMC_BYTCNT, 0x20) +REG32(SDMMC_INTMASK, 0x24) +REG32(SDMMC_CMDARG, 0x28) +REG32(SDMMC_CMD, 0x2c) + FIELD(SDMMC_CMD, START, 31, 1); + +REG32(SDMMC_RESP0, 0x30) +REG32(SDMMC_RESP1, 0x34) +REG32(SDMMC_RESP2, 0x38) +REG32(SDMMC_RESP3, 0x3c) + +REG32(SDMMC_MINTSTS, 0x40) +REG32(SDMMC_RINTSTS, 0x44) +REG32(SDMMC_STATUS, 0x48) +REG32(SDMMC_FIFOTH, 0x4c) +REG32(SDMMC_CDETECT, 0x50) +REG32(SDMMC_WRTPRT, 0x54) +REG32(SDMMC_GPIO, 0x58) +REG32(SDMMC_TCBCNT, 0x5c) +REG32(SDMMC_TBBCNT, 0x60) +REG32(SDMMC_DEBNCE, 0x64) +REG32(SDMMC_USRID, 0x68) +REG32(SDMMC_VERID, 0x6c) +REG32(SDMMC_HCON, 0x70) +REG32(SDMMC_UHS_REG, 0x74) +REG32(SDMMC_RST_N, 0x78) +REG32(SDMMC_BMOD, 0x80) +REG32(SDMMC_PLDMND, 0x84) +REG32(SDMMC_DBADDR, 0x88) +REG32(SDMMC_DBADDRU, 0x8c) +REG32(SDMMC_IDSTS, 0x8c) +REG32(SDMMC_IDINTEN, 0x90) +REG32(SDMMC_DSCADDR, 0x94) +REG32(SDMMC_DSCADDRL, 0x98) +REG32(SDMMC_DSCADDRU, 0x9c) +REG32(SDMMC_BUFADDRL, 0xa0) +REG32(SDMMC_BUFADDRU, 0xa4) +REG32(SDMMC_CARDTHRCTL, 0x100) +REG32(SDMMC_BACK_END_POWER, 0x104) +REG32(SDMMC_UHS_REG_EXT, 0x108) +REG32(SDMMC_EMMC_DDR_REG, 0x10c) +REG32(SDMMC_ENABLE_SHIFT, 0x110) + +#define SDMMC_INTMASK_IO_SLOT1 BIT(17) +#define SDMMC_INTMASK_IO_SLOT0 BIT(16) +#define SDMMC_INTMASK_EBE BIT(15) +#define SDMMC_INTMASK_ACD BIT(14) +#define SDMMC_INTMASK_SBE BIT(13) +#define SDMMC_INTMASK_HLE BIT(12) +#define SDMMC_INTMASK_FRUN BIT(11) +#define SDMMC_INTMASK_HTO BIT(10) +#define SDMMC_INTMASK_DTO BIT(9) +#define SDMMC_INTMASK_RTO BIT(8) +#define SDMMC_INTMASK_DCRC BIT(7) +#define SDMMC_INTMASK_RCRC BIT(6) +#define SDMMC_INTMASK_RXDR BIT(5) +#define SDMMC_INTMASK_TXDR BIT(4) +#define SDMMC_INTMASK_DATA_OVER BIT(3) +#define SDMMC_INTMASK_CMD_DONE BIT(2) +#define SDMMC_INTMASK_RESP_ERR BIT(1) +#define SDMMC_INTMASK_CD BIT(0) + +#define SDMMC_IDMAC_INTMASK_AI BIT(9) +#define SDMMC_IDMAC_INTMASK_NI BIT(8) +#define SDMMC_IDMAC_INTMASK_CES BIT(5) +#define SDMMC_IDMAC_INTMASK_DU BIT(4) +#define SDMMC_IDMAC_INTMASK_FBE BIT(2) +#define SDMMC_IDMAC_INTMASK_RI BIT(1) +#define SDMMC_IDMAC_INTMASK_TI BIT(0) + + +#define SDMMC_DMA_MAX_BUF_LEN 4096 + +static bool dwc_sdmmc_ctrl_interrupts_enabled(uint32_t ctrl_reg) +{ + return FIELD_EX32(ctrl_reg, SDMMC_CTRL, INTEN) == 1; +} + +static sdmmc_hw_cmd_t dwc_sdmmc_get_hw_cmd(DWCSDMMCState *s) +{ + sdmmc_hw_cmd_t hw_cmd; + memcpy(&hw_cmd, &s->cmd, sizeof(hw_cmd)); + return hw_cmd; +} + +static void dwc_sdmmc_handle_reset(DWCSDMMCState *s) +{ + const uint32_t reset_mask = R_SDMMC_CTRL_FIFO_RST_MASK | R_SDMMC_CTRL_RST_MASK | R_SDMMC_CTRL_DMA_RST_MASK; + if (s->ctrl & reset_mask) { + /* just clear it */ + s->ctrl &= ~reset_mask; + } +} + +static void dwc_sdmmc_update_irq(DWCSDMMCState *s) +{ + uint32_t irq = 0; + if (dwc_sdmmc_ctrl_interrupts_enabled(s->ctrl)) { + irq = s->rintsts & s->intmask; + irq |= (s->idsts & s->idinten) << 16; + } + DEBUG("%s: mask=0x%04x, sts=0x%04x irq=%d\n", __func__, s->intmask, s->rintsts, irq); + qemu_set_irq(s->irq, irq); +} + +static void dwc_sdmmc_handle_cmd(DWCSDMMCState *s) +{ + SDRequest request; + uint8_t resp[16]; + int rlen; + DEBUG("%s: hw_cmd=0x%08x\n", __func__, s->cmd); + sdmmc_hw_cmd_t hw_cmd = dwc_sdmmc_get_hw_cmd(s); + if (hw_cmd.update_clk_reg) { + goto done; + } + + request.cmd = hw_cmd.cmd_index; + request.arg = s->cmdarg; + + rlen = sdbus_do_command(&s->sdbus, &request, resp); + s->rintsts |= SDMMC_INTMASK_CMD_DONE; + if (rlen < 0) { + DEBUG("%s: error: rlen=%d\n", __func__, rlen); + } else { + if (hw_cmd.response_expect) { + if (rlen == 4 && !hw_cmd.response_long) { + s->resp[0] = ldl_be_p(&resp[0]); + s->resp[1] = s->resp[2] = s->resp[3] = 0; + + } else if (rlen == 16 && hw_cmd.response_long) { + s->resp[0] = ldl_be_p(&resp[12]); + s->resp[1] = ldl_be_p(&resp[8]); + s->resp[2] = ldl_be_p(&resp[4]); + s->resp[3] = ldl_be_p(&resp[0]); + } else { + s->rintsts |= SDMMC_INTMASK_RTO; + goto done; + } + } + } + +done: + DEBUG("%s: ending with rinsts=0x%04x\n", __func__, s->rintsts); + s->cmd = FIELD_DP32(s->cmd, SDMMC_CMD, START, 0); + dwc_sdmmc_update_irq(s); +} + +static void dwc_sdmmc_update_bytes_left(DWCSDMMCState *s, size_t bytes_done) +{ + if (s->bytes_left > bytes_done) { + s->bytes_left -= bytes_done; + } else { + s->bytes_left = 0; + } + DEBUG("%s: bytes_left = %d\n", __func__, (unsigned) s->bytes_left); + if (s->bytes_left == 0) { + DEBUG("%s: bytes_left = 0, setting SDMMC_INTMASK_DATA_OVER\n", __func__); + s->rintsts |= SDMMC_INTMASK_DATA_OVER; + } +} + +static size_t dwc_sdmmc_handle_one_desc(DWCSDMMCState *s, hwaddr desc_addr, sdmmc_desc_t *desc, bool is_write, size_t max_bytes) +{ + uint32_t num_done = 0; + uint32_t num_bytes = max_bytes; + uint8_t buf[4096]; + + /* Read descriptor */ + dma_memory_read(&address_space_memory, desc_addr, desc, sizeof(*desc), MEMTXATTRS_UNSPECIFIED); + if (desc->buffer1_size < num_bytes) { + num_bytes = desc->buffer1_size; + } + + if (desc->owned_by_idmac == 0) { + /* ran into a descriptor owned by software */ + return 0; + } + + 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); + } + DEBUG("%s: %sing %d bytes from descr 0x%08x\n", __func__, is_write?"write":"read", buf_bytes, (uint32_t) desc_addr); + /* Write to SD bus */ + if (is_write) { + dma_memory_read(&address_space_memory, + desc->buffer1_ptr + num_done, + buf, buf_bytes, MEMTXATTRS_UNSPECIFIED); + sdbus_write_data(&s->sdbus, buf, buf_bytes); + + /* Read from SD bus */ + } else { + sdbus_read_data(&s->sdbus, buf, buf_bytes); + dma_memory_write(&address_space_memory, + desc->buffer1_ptr + num_done, + buf, buf_bytes, MEMTXATTRS_UNSPECIFIED); + } + num_done += buf_bytes; + } + + /* Clear hold flag and flush descriptor */ + sdmmc_desc_t new_desc = *desc; + new_desc.owned_by_idmac = 0; + dma_memory_write(&address_space_memory, desc_addr, &new_desc, sizeof(new_desc), MEMTXATTRS_UNSPECIFIED); + + /* Update DMAC bits */ + s->idsts |= is_write ? SDMMC_IDMAC_INTMASK_TI : SDMMC_IDMAC_INTMASK_RI; + + return num_done; + +} + +static void dwc_sdmmc_handle_dma(DWCSDMMCState *s) +{ + sdmmc_desc_t desc; + hwaddr desc_addr = s->dscaddr; + sdmmc_hw_cmd_t hw_cmd = dwc_sdmmc_get_hw_cmd(s); + bool is_write = hw_cmd.rw == 1; + uint32_t bytes_done = 0; + + if (s->bytcnt == 0 || s->blksiz == 0 || + !FIELD_EX32(s->ctrl, SDMMC_CTRL, DMAEN)) { + DEBUG("%s: nothing to do (bytcnt=%d, blksiz=%d)\n", __func__, s->bytcnt, s->blksiz); + return; + } + + if (!is_write && !sdbus_data_ready(&s->sdbus)) { + return; + } + + while (s->bytcnt > 0) { + DEBUG("%s: handling descriptor @0x%0x, bytcnt=%d\n", __func__, (uint32_t) desc_addr, s->bytcnt); + bytes_done = dwc_sdmmc_handle_one_desc(s, desc_addr, &desc, is_write, s->bytcnt); + dwc_sdmmc_update_bytes_left(s, bytes_done); + + if (bytes_done <= s->bytcnt) { + s->bytcnt -= bytes_done; + } else { + s->bytcnt = 0; + } + + if (desc.last_descriptor || desc.owned_by_idmac == 0) { + break; + } else { + desc_addr = desc.next_desc_ptr; + s->dscaddr = desc_addr; + } + } + DEBUG("%s: finished with bytcnt=%d at dscaddr=0x%08x\n", __func__, s->bytcnt, s->dscaddr); +} + +static void dwc_sdmmc_maybe_autostop(DWCSDMMCState *s) +{ + sdmmc_hw_cmd_t hw_cmd = dwc_sdmmc_get_hw_cmd(s); + if (hw_cmd.send_auto_stop && s->bytes_left == 0) { + /* First save current command registers */ + uint32_t saved_cmd = s->cmd; + uint32_t saved_arg = s->cmdarg; + + /* Prepare stop command (CMD12) */ + sdmmc_hw_cmd_t auto_stop_cmd = hw_cmd; + auto_stop_cmd.cmd_index = 12; + memcpy(&s->cmd, &auto_stop_cmd, sizeof(s->cmd)); + s->cmdarg = 0; + + /* Put the command on SD bus */ + dwc_sdmmc_handle_cmd(s); + + /* Restore command values */ + s->cmd = saved_cmd; + s->cmdarg = saved_arg; + + /* Set IRQ status bit for automatic stop done */ + s->rintsts |= SDMMC_INTMASK_ACD; + } +} + + +static void dwc_sdmmc_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + DWCSDMMCState *s = DWC_SDMMC(opaque); + + switch (offset) { + case A_SDMMC_CTRL: + s->ctrl = value; + dwc_sdmmc_handle_reset(s); + dwc_sdmmc_update_irq(s); + break; + + case A_SDMMC_CLKSRC: + s->clksrc = value; + break; + + case A_SDMMC_CMD: { + s->cmd = value; + if (FIELD_EX32(value, SDMMC_CMD, START)) { + dwc_sdmmc_handle_cmd(s); + dwc_sdmmc_handle_dma(s); + dwc_sdmmc_maybe_autostop(s); + dwc_sdmmc_update_irq(s); + } + break; + } + case A_SDMMC_PLDMND: + dwc_sdmmc_handle_dma(s); + dwc_sdmmc_maybe_autostop(s); + dwc_sdmmc_update_irq(s); + break; + + case A_SDMMC_CMDARG: + s->cmdarg = value; + break; + + case A_SDMMC_INTMASK: + s->intmask = value; + dwc_sdmmc_update_irq(s); + break; + + case A_SDMMC_RINTSTS: + s->rintsts &= ~value; + dwc_sdmmc_update_irq(s); + break; + + case A_SDMMC_IDSTS: + s->idsts &= ~value; + dwc_sdmmc_update_irq(s); + break; + + case A_SDMMC_IDINTEN: + s->idinten = value; + dwc_sdmmc_update_irq(s); + break; + + case A_SDMMC_DBADDR: + s->dbaddr = value; + s->dscaddr = value; + break; + + case A_SDMMC_BYTCNT: + s->bytcnt = value; + s->bytes_left = value; + break; + + case A_SDMMC_BLKSIZ: + s->blksiz = value; + break; + + default: + qemu_log_mask(LOG_UNIMP, "%s: write@0x%02x value=0x%08x\n", __func__, (uint32_t) offset, (uint32_t) value); + } + +} + +static uint64_t dwc_sdmmc_read(void *opaque, hwaddr offset, + unsigned size) +{ + DWCSDMMCState *s = DWC_SDMMC(opaque); + switch (offset) { + case A_SDMMC_CTRL: + return s->ctrl; + case A_SDMMC_CLKSRC: + return s->clksrc; + case A_SDMMC_INTMASK: + return s->intmask; + case A_SDMMC_CMD: + return s->cmd; + case A_SDMMC_CMDARG: + return s->cmdarg; + case A_SDMMC_RESP0 ... A_SDMMC_RESP3: + return s->resp[(offset - A_SDMMC_RESP0) / 4]; + case A_SDMMC_RINTSTS: + return s->rintsts; + case A_SDMMC_MINTSTS: + return s->intmask & s->rintsts; + case A_SDMMC_IDSTS: + return s->idsts; + case A_SDMMC_IDINTEN: + return s->idinten; + case A_SDMMC_BLKSIZ: + return s->blksiz; + case A_SDMMC_BYTCNT: + return s->bytcnt; + case A_SDMMC_DBADDR: + return s->dbaddr; + case A_SDMMC_DSCADDR: + return s->dscaddr; + default: + qemu_log_mask(LOG_UNIMP, "%s: read@0x%02x\n", __func__, (uint32_t) offset); + } + return 0; +} + + +static const MemoryRegionOps dwc_sdmmc_ops = { + .read = dwc_sdmmc_read, + .write = dwc_sdmmc_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + } +}; + + + +static Property dwc_sdmmc_properties[] = { + DEFINE_PROP_END_OF_LIST(), +}; + +static void dwc_sdmmc_init(Object *obj) +{ + DWCSDMMCState *s = DWC_SDMMC(obj); + + qbus_init(&s->sdbus, sizeof(s->sdbus), + TYPE_SD_BUS, DEVICE(s), "sd-bus"); + + memory_region_init_io(&s->iomem, obj, &dwc_sdmmc_ops, s, + TYPE_DWC_SDMMC, 4 * KiB); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); + sysbus_init_irq(SYS_BUS_DEVICE(s), &s->irq); +} + +static void dwc_sdmmc_reset(DeviceState *dev) +{ + DWCSDMMCState *s = DWC_SDMMC(dev); + s->cmd = 0; + s->cmdarg = 0; + s->intmask = 0; + s->rintsts = 0; + s->ctrl = 0; + s->bytes_left = 0; + s->idinten = 0; + s->idsts = 0; + s->blksiz = 512; + s->bytcnt = 0; + s->dbaddr = 0; + s->pldmnd = 0; + memset(s->resp, 0, sizeof(s->resp)); +} + +static void dwc_sdmmc_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->legacy_reset = dwc_sdmmc_reset; + device_class_set_props(dc, dwc_sdmmc_properties); +} + +static TypeInfo dwc_sdmmc_info = { + .name = TYPE_DWC_SDMMC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_init = dwc_sdmmc_init, + .instance_size = sizeof(DWCSDMMCState), + .class_init = dwc_sdmmc_class_init, +}; + +static void dwc_sdmmc_register_types(void) +{ + type_register_static(&dwc_sdmmc_info); +} + +type_init(dwc_sdmmc_register_types) diff --git a/hw/sd/meson.build b/hw/sd/meson.build index b43d45bc56..1c0d124db7 100644 --- a/hw/sd/meson.build +++ b/hw/sd/meson.build @@ -10,3 +10,4 @@ system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_sdhci.c')) system_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-sdhost.c')) system_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_sdhci.c')) system_ss.add(when: 'CONFIG_CADENCE_SDHCI', if_true: files('cadence_sdhci.c')) +system_ss.add(when: 'CONFIG_DWC_SDMMC', if_true: files('dwc_sdmmc.c')) diff --git a/include/hw/sd/dwc_sdmmc.h b/include/hw/sd/dwc_sdmmc.h new file mode 100644 index 0000000000..4a762e1dba --- /dev/null +++ b/include/hw/sd/dwc_sdmmc.h @@ -0,0 +1,61 @@ +/* + * Designware SD/MMC controller emulation + * + * Copyright (c) 2021 Espressif Systems (Shanghai) Co. Ltd. + * + * 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 . + */ + +#ifndef DWC_SDMMC_H +#define DWC_SDMMC_H + +#include "qom/object.h" +#include "hw/sysbus.h" +#include "hw/sd/sd.h" + + +typedef struct DWCSDMMCState { + /*< private >*/ + SysBusDevice busdev; + + /*< public >*/ + SDBus sdbus; + MemoryRegion iomem; + qemu_irq irq; + + uint32_t ctrl; + uint32_t clksrc; + uint32_t cmd; + uint32_t cmdarg; + uint32_t resp[4]; + uint32_t intmask; + uint32_t rintsts; + uint32_t dbaddr; + uint32_t dscaddr; + uint32_t blksiz; + uint32_t bytcnt; + uint32_t pldmnd; + uint32_t idinten; + uint32_t idsts; + + size_t bytes_left; + + +} DWCSDMMCState; + +#define TYPE_DWC_SDMMC "dwc_sdmmc" +#define DWC_SDMMC(obj) OBJECT_CHECK(DWCSDMMCState, (obj), \ + TYPE_DWC_SDMMC) + +#endif /* DWC_SDMMC_H */