[PATCH 4/5] hw/sd: add K230 SD/eMMC (DesignWare dw_mmc).
Leo Cheng <[email protected]>
| Newsgroups | gmane.comp.emulators.qemu |
|---|---|
| Message-ID | <[email protected]> |
Model the register interface of the DesignWare dw_mmc host used by the K230: self-clearing CTRL reset bits, a self-clearing CMD.START that raises command completion, W1C RINTSTS and masked-interrupt delivery. The FIFO/DMA data path is not modelled; this is enough for a driver to probe. Register map from the DesignWare dw_mmc driver. Covered by tests/qtest/k230-dwmmc-test. Signed-off-by: Leo Cheng <[email protected]> --- hw/sd/Kconfig | 3 + hw/sd/k230_dwmmc.c | 139 ++++++++++++++++++++++++++++++++++ hw/sd/meson.build | 1 + hw/sd/trace-events | 4 + include/hw/sd/k230_dwmmc.h | 61 +++++++++++++++ tests/qtest/k230-dwmmc-test.c | 79 +++++++++++++++++++ 6 files changed, 287 insertions(+) create mode 100644 hw/sd/k230_dwmmc.c create mode 100644 include/hw/sd/k230_dwmmc.h create mode 100644 tests/qtest/k230-dwmmc-test.c diff --git a/hw/sd/Kconfig b/hw/sd/Kconfig index 633b9af..15f1fd9 100644 --- a/hw/sd/Kconfig +++ b/hw/sd/Kconfig @@ -23,3 +23,6 @@ config SDHCI_PCI config CADENCE_SDHCI bool select SDHCI + +config K230_DWMMC + bool diff --git a/hw/sd/k230_dwmmc.c b/hw/sd/k230_dwmmc.c new file mode 100644 index 0000000..47a68a0 --- /dev/null +++ b/hw/sd/k230_dwmmc.c @@ -0,0 +1,139 @@ +/* + * K230 SD/eMMC host (Synopsys DesignWare dw_mmc) + * + * Copyright (c) 2026 Leo Cheng <[email protected]> + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Register map from the DesignWare dw_mmc driver (Canaan k230_sdk). Models the + * register interface and command completion; the FIFO/DMA data path is not + * modelled. + */ +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "hw/core/sysbus.h" +#include "hw/core/irq.h" +#include "migration/vmstate.h" +#include "hw/sd/k230_dwmmc.h" +#include "trace.h" + +static void k230_dwmmc_update_irq(K230DwmmcState *s) +{ + uint32_t masked = s->regs[SDMMC_RINTSTS / 4] & s->regs[SDMMC_INTMASK / 4]; + + s->regs[SDMMC_MINTSTS / 4] = masked; + qemu_set_irq(s->irq, masked != 0); +} + +static uint64_t k230_dwmmc_read(void *opaque, hwaddr addr, unsigned int size) +{ + K230DwmmcState *s = K230_DWMMC(opaque); + uint32_t value = addr / 4 < K230_DWMMC_NREGS ? s->regs[addr / 4] : 0; + + trace_k230_dwmmc_read(addr, value); + return value; +} + +static void k230_dwmmc_write(void *opaque, hwaddr addr, + uint64_t value, unsigned int size) +{ + K230DwmmcState *s = K230_DWMMC(opaque); + + if (addr / 4 >= K230_DWMMC_NREGS) { + return; + } + trace_k230_dwmmc_write(addr, value); + + switch (addr) { + case SDMMC_CTRL: + /* the three reset bits are self-clearing */ + s->regs[SDMMC_CTRL / 4] = value & ~SDMMC_CTRL_RESET_ALL; + break; + case SDMMC_CMD: + /* START self-clears; completing a command raises command-done */ + s->regs[SDMMC_CMD / 4] = value & ~SDMMC_CMD_START; + if (value & SDMMC_CMD_START) { + s->regs[SDMMC_RINTSTS / 4] |= SDMMC_INT_CMD_DONE; + k230_dwmmc_update_irq(s); + } + break; + case SDMMC_RINTSTS: + s->regs[SDMMC_RINTSTS / 4] &= ~(uint32_t)value; /* W1C */ + k230_dwmmc_update_irq(s); + break; + case SDMMC_INTMASK: + s->regs[SDMMC_INTMASK / 4] = value; + k230_dwmmc_update_irq(s); + break; + case SDMMC_STATUS: + case SDMMC_MINTSTS: + break; /* read-only */ + default: + s->regs[addr / 4] = value; + break; + } +} + +static const MemoryRegionOps k230_dwmmc_ops = { + .read = k230_dwmmc_read, + .write = k230_dwmmc_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void k230_dwmmc_reset(DeviceState *dev) +{ + K230DwmmcState *s = K230_DWMMC(dev); + + memset(s->regs, 0, sizeof(s->regs)); + s->regs[SDMMC_STATUS / 4] = SDMMC_STATUS_FIFO_EMPTY; + qemu_set_irq(s->irq, 0); +} + +static const VMStateDescription vmstate_k230_dwmmc = { + .name = "k230.dwmmc", + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, K230DwmmcState, K230_DWMMC_NREGS), + VMSTATE_END_OF_LIST() + } +}; + +static void k230_dwmmc_realize(DeviceState *dev, Error **errp) +{ + K230DwmmcState *s = K230_DWMMC(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_dwmmc_ops, s, + TYPE_K230_DWMMC, K230_DWMMC_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->mmio); + sysbus_init_irq(sbd, &s->irq); + s->regs[SDMMC_STATUS / 4] = SDMMC_STATUS_FIFO_EMPTY; +} + +static void k230_dwmmc_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = k230_dwmmc_realize; + device_class_set_legacy_reset(dc, k230_dwmmc_reset); + dc->vmsd = &vmstate_k230_dwmmc; + dc->desc = "K230 SD/eMMC (dw_mmc)"; +} + +static const TypeInfo k230_dwmmc_info = { + .name = TYPE_K230_DWMMC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230DwmmcState), + .class_init = k230_dwmmc_class_init, +}; + +static void k230_dwmmc_register_type(void) +{ + type_register_static(&k230_dwmmc_info); +} + +type_init(k230_dwmmc_register_type) diff --git a/hw/sd/meson.build b/hw/sd/meson.build index b43d45b..d642de3 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_K230_DWMMC', if_true: files('k230_dwmmc.c')) diff --git a/hw/sd/trace-events b/hw/sd/trace-events index c6811f3..8537b25 100644 --- a/hw/sd/trace-events +++ b/hw/sd/trace-events @@ -75,3 +75,7 @@ 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 + +# k230_dwmmc.c +k230_dwmmc_read(uint64_t addr, uint32_t val) "addr 0x%" PRIx64 " val 0x%08x" +k230_dwmmc_write(uint64_t addr, uint64_t val) "addr 0x%" PRIx64 " val 0x%" PRIx64 diff --git a/include/hw/sd/k230_dwmmc.h b/include/hw/sd/k230_dwmmc.h new file mode 100644 index 0000000..6c5c255 --- /dev/null +++ b/include/hw/sd/k230_dwmmc.h @@ -0,0 +1,61 @@ +/* + * K230 SD/eMMC host (Synopsys DesignWare dw_mmc) + * + * Copyright (c) 2026 Leo Cheng <[email protected]> + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Register map from the DesignWare dw_mmc driver (Canaan k230_sdk + * drivers/mmc/host/dw_mmc.h). This models the register interface and command + * completion enough for a driver to probe; the FIFO/DMA data path is not + * modelled (documented limitation). + */ +#ifndef HW_SD_K230_DWMMC_H +#define HW_SD_K230_DWMMC_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_K230_DWMMC "riscv.k230.dwmmc" +OBJECT_DECLARE_SIMPLE_TYPE(K230DwmmcState, K230_DWMMC) + +#define K230_DWMMC_MMIO_SIZE 0x1000 +#define K230_DWMMC_NREGS (0x100 / 4) + +#define SDMMC_CTRL 0x000 +#define SDMMC_PWREN 0x004 +#define SDMMC_CLKDIV 0x008 +#define SDMMC_CLKENA 0x010 +#define SDMMC_TMOUT 0x014 +#define SDMMC_CTYPE 0x018 +#define SDMMC_BLKSIZ 0x01c +#define SDMMC_BYTCNT 0x020 +#define SDMMC_INTMASK 0x024 +#define SDMMC_CMDARG 0x028 +#define SDMMC_CMD 0x02c +#define SDMMC_RESP0 0x030 +#define SDMMC_RESP3 0x03c +#define SDMMC_MINTSTS 0x040 +#define SDMMC_RINTSTS 0x044 +#define SDMMC_STATUS 0x048 + +#define SDMMC_CTRL_RESET (1u << 0) +#define SDMMC_CTRL_FIFO_RESET (1u << 1) +#define SDMMC_CTRL_DMA_RESET (1u << 2) +#define SDMMC_CTRL_RESET_ALL (SDMMC_CTRL_RESET | SDMMC_CTRL_FIFO_RESET | \ + SDMMC_CTRL_DMA_RESET) +#define SDMMC_CMD_START (1u << 31) +#define SDMMC_INT_CMD_DONE (1u << 2) + +/* STATUS: FIFO empty so a polling driver does not stall */ +#define SDMMC_STATUS_FIFO_EMPTY (1u << 2) + +struct K230DwmmcState { + SysBusDevice parent_obj; + + MemoryRegion mmio; + qemu_irq irq; + uint32_t regs[K230_DWMMC_NREGS]; +}; + +#endif diff --git a/tests/qtest/k230-dwmmc-test.c b/tests/qtest/k230-dwmmc-test.c new file mode 100644 index 0000000..1adeef0 --- /dev/null +++ b/tests/qtest/k230-dwmmc-test.c @@ -0,0 +1,79 @@ +/* + * QTest testcase for K230 SD/eMMC (dw_mmc) + * + * Copyright (c) 2026 Leo Cheng <[email protected]> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "qemu/osdep.h" +#include "libqtest.h" +#include "hw/sd/k230_dwmmc.h" + +#define SD0_BASE 0x91580000 +#define PLIC_BASE 0xF00000000ULL +#define PLIC_PENDING (PLIC_BASE + 0x1000) +#define SD0_IRQ 142 + +static bool plic_pending(QTestState *qts, int irq) +{ + uint32_t word = qtest_readl(qts, PLIC_PENDING + (irq / 32) * 4); + return (word >> (irq % 32)) & 1; +} + +static void test_reset_values(void) +{ + QTestState *qts = qtest_init("-machine k230"); + + g_assert_cmphex(qtest_readl(qts, SD0_BASE + SDMMC_CTRL), ==, 0); + g_assert_cmphex(qtest_readl(qts, SD0_BASE + SDMMC_STATUS), ==, + SDMMC_STATUS_FIFO_EMPTY); + + qtest_quit(qts); +} + +/* The three CTRL reset bits are self-clearing */ +static void test_ctrl_reset_selfclear(void) +{ + QTestState *qts = qtest_init("-machine k230"); + + qtest_writel(qts, SD0_BASE + SDMMC_CTRL, SDMMC_CTRL_RESET_ALL | 0x10); + g_assert_cmphex(qtest_readl(qts, SD0_BASE + SDMMC_CTRL) & + SDMMC_CTRL_RESET_ALL, ==, 0); + g_assert_cmphex(qtest_readl(qts, SD0_BASE + SDMMC_CTRL) & 0x10, ==, 0x10); + + qtest_quit(qts); +} + +/* Starting a command auto-clears START, raises command-done, W1C clears it */ +static void test_cmd_done(void) +{ + QTestState *qts = qtest_init("-machine k230"); + + qtest_writel(qts, SD0_BASE + SDMMC_INTMASK, SDMMC_INT_CMD_DONE); + qtest_writel(qts, SD0_BASE + SDMMC_CMD, SDMMC_CMD_START | 0x1); + + g_assert_cmphex(qtest_readl(qts, SD0_BASE + SDMMC_CMD) & SDMMC_CMD_START, + ==, 0); + g_assert_cmphex(qtest_readl(qts, SD0_BASE + SDMMC_RINTSTS) & + SDMMC_INT_CMD_DONE, ==, SDMMC_INT_CMD_DONE); + g_assert_true(plic_pending(qts, SD0_IRQ)); + + /* RINTSTS is write-1-to-clear */ + qtest_writel(qts, SD0_BASE + SDMMC_RINTSTS, SDMMC_INT_CMD_DONE); + g_assert_cmphex(qtest_readl(qts, SD0_BASE + SDMMC_RINTSTS) & + SDMMC_INT_CMD_DONE, ==, 0); + + qtest_quit(qts); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/k230-dwmmc/reset_values", test_reset_values); + qtest_add_func("/k230-dwmmc/ctrl_reset_selfclear", + test_ctrl_reset_selfclear); + qtest_add_func("/k230-dwmmc/cmd_done", test_cmd_done); + + return g_test_run(); +} -- 2.43.0