From 749e9ac6ccd93700fa9c9c1b21bc279af6bebdb0 Mon Sep 17 00:00:00 2001 From: Gabriel Chang Date: Tue, 24 Feb 2026 21:44:19 +0000 Subject: [PATCH 1/3] vibe code SD card support for esp32c3 on SPI2 --- hw/gpio/esp32c3_gpio.c | 145 +++++- hw/riscv/Kconfig | 2 + hw/riscv/esp32c3.c | 107 ++++- hw/sd/sd.c | 30 +- hw/sd/ssi-sd.c | 35 ++ hw/ssi/esp32c3_spi2.c | 780 +++++++++++++++++++++++++++++++++ hw/ssi/meson.build | 1 + include/hw/gpio/esp32c3_gpio.h | 15 +- include/hw/ssi/esp32c3_spi2.h | 136 ++++++ 9 files changed, 1230 insertions(+), 21 deletions(-) create mode 100644 hw/ssi/esp32c3_spi2.c create mode 100644 include/hw/ssi/esp32c3_spi2.h diff --git a/hw/gpio/esp32c3_gpio.c b/hw/gpio/esp32c3_gpio.c index 1e842381f2124..cba1496979760 100644 --- a/hw/gpio/esp32c3_gpio.c +++ b/hw/gpio/esp32c3_gpio.c @@ -19,17 +19,158 @@ #include "hw/qdev-properties.h" #include "hw/gpio/esp32c3_gpio.h" +/* ESP32-C3 GPIO register offsets */ +#define GPIO_OUT_REG 0x04 +#define GPIO_OUT_W1TS_REG 0x08 +#define GPIO_OUT_W1TC_REG 0x0C +#define GPIO_ENABLE_REG 0x20 +#define GPIO_ENABLE_W1TS 0x24 +#define GPIO_ENABLE_W1TC 0x28 +#define GPIO_STRAP_REG 0x38 +#define GPIO_IN_REG 0x3C + +/* Debug output */ +#define GPIO_DEBUG 0 + +static void esp32c3_gpio_update_outputs(ESP32C3GPIOState *s, uint32_t old_out) +{ + uint32_t changed = old_out ^ s->gpio_out; + + for (int i = 0; i < ESP32C3_GPIO_COUNT; i++) { + if (changed & (1 << i)) { + int level = (s->gpio_out >> i) & 1; +#if GPIO_DEBUG + info_report("[GPIO] Pin %d -> %d", i, level); +#endif + qemu_set_irq(s->gpio_out_irq[i], level); + } + } +} + +static uint64_t esp32c3_gpio_read(void *opaque, hwaddr addr, unsigned int size) +{ + ESP32C3GPIOState *s = ESP32C3_GPIO(opaque); + Esp32GpioState *parent = &s->parent; + uint64_t r = 0; + + switch (addr) { + case GPIO_OUT_REG: + r = s->gpio_out; + break; + case GPIO_ENABLE_REG: + r = s->gpio_enable; + break; + case GPIO_STRAP_REG: + r = parent->strap_mode; + break; + case GPIO_IN_REG: + /* Return output values as input (loopback for now) */ + r = s->gpio_out; + break; + default: + /* Many other GPIO registers exist; return 0 for now */ + break; + } + +#if GPIO_DEBUG + if (addr != GPIO_IN_REG) { + info_report("[GPIO] READ 0x%03lx = 0x%08lx", addr, r); + } +#endif + + return r; +} + +static void esp32c3_gpio_write(void *opaque, hwaddr addr, + uint64_t value, unsigned int size) +{ + ESP32C3GPIOState *s = ESP32C3_GPIO(opaque); + uint32_t old_out = s->gpio_out; + +#if GPIO_DEBUG + info_report("[GPIO] WRITE 0x%03lx = 0x%08lx", addr, (unsigned long)value); +#endif + + switch (addr) { + case GPIO_OUT_REG: + s->gpio_out = value & ((1 << ESP32C3_GPIO_COUNT) - 1); + esp32c3_gpio_update_outputs(s, old_out); + break; + case GPIO_OUT_W1TS_REG: + s->gpio_out |= value & ((1 << ESP32C3_GPIO_COUNT) - 1); + esp32c3_gpio_update_outputs(s, old_out); + break; + case GPIO_OUT_W1TC_REG: + s->gpio_out &= ~(value & ((1 << ESP32C3_GPIO_COUNT) - 1)); + esp32c3_gpio_update_outputs(s, old_out); + break; + case GPIO_ENABLE_REG: + s->gpio_enable = value & ((1 << ESP32C3_GPIO_COUNT) - 1); + break; + case GPIO_ENABLE_W1TS: + s->gpio_enable |= value & ((1 << ESP32C3_GPIO_COUNT) - 1); + break; + case GPIO_ENABLE_W1TC: + s->gpio_enable &= ~(value & ((1 << ESP32C3_GPIO_COUNT) - 1)); + break; + default: + /* Many other GPIO registers exist; ignore for now */ + break; + } +} + +static const MemoryRegionOps esp32c3_gpio_ops = { + .read = esp32c3_gpio_read, + .write = esp32c3_gpio_write, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static void esp32c3_gpio_reset_hold(Object *obj, ResetType type) +{ + ESP32C3GPIOState *s = ESP32C3_GPIO(obj); + + s->gpio_out = 0; + s->gpio_enable = 0; + + /* Notify all outputs are now low */ + for (int i = 0; i < ESP32C3_GPIO_COUNT; i++) { + qemu_set_irq(s->gpio_out_irq[i], 0); + } +} + +static void esp32c3_gpio_realize(DeviceState *dev, Error **errp) +{ + /* Parent realize is a no-op */ +} static void esp32c3_gpio_init(Object *obj) { + ESP32C3GPIOState *s = ESP32C3_GPIO(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + /* Set the default value for the property */ object_property_set_int(obj, "strap_mode", ESP32C3_STRAP_MODE_FLASH_BOOT, &error_fatal); + + /* Override parent's iomem with our extended implementation */ + memory_region_init_io(&s->parent.iomem, obj, &esp32c3_gpio_ops, s, + TYPE_ESP32C3_GPIO, 0x1000); + sysbus_init_mmio(sbd, &s->parent.iomem); + + /* Initialize output IRQs for each GPIO pin */ + qdev_init_gpio_out_named(DEVICE(s), s->gpio_out_irq, + ESP32C3_GPIO_OUT_IRQ, ESP32C3_GPIO_COUNT); + + s->gpio_out = 0; + s->gpio_enable = 0; } -/* If we need to override any function from the parent (reset, realize, ...), it shall be done - * in this class_init function */ static void esp32c3_gpio_class_init(ObjectClass *klass, void *data) { + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + rc->phases.hold = esp32c3_gpio_reset_hold; + dc->realize = esp32c3_gpio_realize; } static const TypeInfo esp32c3_gpio_info = { diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index 192ab477b3e13..3724a33fecd11 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -119,4 +119,6 @@ config RISCV_ESP32C3 select OPENCORES_ETH select UNIMP select ESP_RGB + select SSI + select SSI_SD diff --git a/hw/riscv/esp32c3.c b/hw/riscv/esp32c3.c index ed698fbb67802..8442304291f24 100644 --- a/hw/riscv/esp32c3.c +++ b/hw/riscv/esp32c3.c @@ -40,6 +40,8 @@ #include "hw/timer/esp32c3_timg.h" #include "hw/timer/esp32c3_systimer.h" #include "hw/ssi/esp32c3_spi.h" +#include "hw/ssi/esp32c3_spi2.h" +#include "hw/sd/sd.h" #include "hw/misc/esp32c3_rtc_cntl.h" #include "hw/misc/esp32c3_aes.h" #include "hw/misc/esp32c3_rsa.h" @@ -86,10 +88,14 @@ struct Esp32C3MachineState { ESP32C3TimgState timg[2]; ESP32C3SysTimerState systimer; ESP32C3SpiState spi1; + ESP32C3Spi2State spi2; ESP32C3RtcCntlState rtccntl; ESP32C3UsbJtagState jtag; ESPRgbState rgb; Esp32C3TWAIState twai; + + /* SD card ssi-sd device for GPIO CS routing */ + DeviceState *sd_ssi_dev; }; /* Fake register used by ESP-IDF application to determine whether the code is running on real hardware or on QEMU */ @@ -281,27 +287,31 @@ static void esp32c3_load_firmware(MachineState *machine) } if (bios_filename) { - /* Since EspRISCVCPU doens't have a RISCVHartArrayState field, let's bake one on the stack. It will only be - * used to get the type of the RISC-V CPU (32 or 64 bits) in `riscv_load_kernel` */ - RISCVHartArrayState hart = { - .harts = &ms->soc.parent_obj, - .num_harts = 1, - }; - - /* The function `riscv_load_kernel` won't load the ELF file at its entry point, so we have to look - * for the ELF entry point manually here */ uint64_t elf_entry = ESP32C3_RESET_ADDRESS; - /* The entry point address should be populated regardless of the return value */ - load_elf_ram_sym(bios_filename, NULL, NULL, NULL, + /* Try to load as ELF first */ + if (load_elf_ram_sym(bios_filename, NULL, NULL, NULL, &elf_entry, NULL, NULL, NULL, 0, - EM_RISCV, 1, 0, NULL, false, NULL); + EM_RISCV, 1, 0, NULL, true, NULL) > 0) { + qemu_log("Loaded ELF '%s' at entry 0x%08" PRIx64 "\n", bios_filename, elf_entry); + } else { + /* Not an ELF, try as raw binary at reset address */ + int size = load_image_targphys_as(bios_filename, ESP32C3_RESET_ADDRESS, + 0x60000, CPU(&ms->soc)->as); + if (size < 0) { + error_report("Error: could not load firmware '%s'", bios_filename); + exit(1); + } + qemu_log("Loaded raw binary '%s' (%d bytes) at 0x%08x\n", + bios_filename, size, ESP32C3_RESET_ADDRESS); + elf_entry = ESP32C3_RESET_ADDRESS; + } - /* On failure, riscv_load_kernel exits the program */ - qemu_log("Loading kernel at address 0x%08" PRIx64 "\n", elf_entry); - riscv_load_kernel(machine, &hart, elf_entry, false, NULL); if (elf_entry != ESP32C3_RESET_ADDRESS) { + qemu_log("Setting resetvec to 0x%08" PRIx64 "\n", elf_entry); qdev_prop_set_uint64(DEVICE(&ms->soc), "resetvec", elf_entry); + } else { + qemu_log("Not changing resetvec, elf_entry == ESP32C3_RESET_ADDRESS\n"); } } else { /* Open and load the "bios", which is the ROM binary, also named "first stage bootloader" */ @@ -381,6 +391,12 @@ static void esp32c3_machine_init(MachineState *machine) qdev_realize(DEVICE(&ms->soc), NULL, &error_fatal); + /* Initialize stack pointer to top of DRAM (required when loading ELF directly) */ + CPURISCVState *env = &ms->soc.parent_obj.env; + /* DRAM ends at 0x3FCE0000, set SP slightly below that */ + env->gpr[2] = memmap[ESP32C3_MEMREGION_DRAM].base + memmap[ESP32C3_MEMREGION_DRAM].size - 16; + qemu_log("Initialized SP to 0x%08" PRIx64 "\n", (uint64_t)env->gpr[2]); + memory_region_init_io(&ms->iomem, OBJECT(&ms->soc), &esp32c3_io_ops, NULL, "esp32c3.iomem", 0xd1000); memory_region_add_subregion(sys_mem, ESP32C3_IO_START_ADDR, &ms->iomem); @@ -420,6 +436,7 @@ static void esp32c3_machine_init(MachineState *machine) object_initialize_child(OBJECT(machine), "timg1", &ms->timg[1], TYPE_ESP32C3_TIMG); object_initialize_child(OBJECT(machine), "systimer", &ms->systimer, TYPE_ESP32C3_SYSTIMER); object_initialize_child(OBJECT(machine), "spi1", &ms->spi1, TYPE_ESP32C3_SPI); + object_initialize_child(OBJECT(machine), "spi2", &ms->spi2, TYPE_ESP32C3_SPI2); object_initialize_child(OBJECT(machine), "rtccntl", &ms->rtccntl, TYPE_ESP32C3_RTC_CNTL); object_initialize_child(OBJECT(machine), "jtag", &ms->jtag, TYPE_ESP32C3_JTAG); object_initialize_child(OBJECT(machine), "rgb", &ms->rgb, TYPE_ESP_RGB); @@ -476,6 +493,50 @@ static void esp32c3_machine_init(MachineState *machine) } } + /* SPI2 controller (GPSPI2 - General Purpose SPI for SD card) */ + { + /* Link SPI2 to GDMA for DMA-mode transfers */ + ms->spi2.gdma = ESP_GDMA(&ms->gdma); + + sysbus_realize(SYS_BUS_DEVICE(&ms->spi2), &error_fatal); + MemoryRegion *mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&ms->spi2), 0); + memory_region_add_subregion_overlap(sys_mem, DR_REG_SPI2_BASE, mr, 0); + sysbus_connect_irq(SYS_BUS_DEVICE(&ms->spi2), 0, + qdev_get_gpio_in(intmatrix_dev, ETS_SPI2_INTR_SOURCE)); + + /* Attach SD card if if=sd,index=1 drive is specified. + * Use -drive file=xxx.img,if=sd,index=1,format=raw to attach an SD image. + * We use index=1 (unit=1) to avoid conflict with QEMU's default SD drive at unit=0. */ + DriveInfo *sd_dinfo = drive_get(IF_SD, 0, 1); + fprintf(stderr, "Checking for SD card drive: %p\n", sd_dinfo); + if (sd_dinfo) { + DeviceState *card_dev; + BusState *spi_bus = qdev_get_child_bus(DEVICE(&ms->spi2), "spi"); + + qemu_log("Adding SD card device to SPI2\n"); + + /* Create ssi-sd bridge which connects SPI bus to SD card */ + DeviceState *ssi_sd = qdev_new("ssi-sd"); + qdev_realize_and_unref(ssi_sd, spi_bus, &error_fatal); + + /* Create the SD card (SPI variant) and attach to ssi-sd */ + card_dev = qdev_new(TYPE_SD_CARD_SPI); + qdev_prop_set_drive_err(card_dev, "drive", blk_by_legacy_dinfo(sd_dinfo), &error_fatal); + /* Enable relaxed SPI mode for ESP-IDF SDSPI driver compatibility */ + qdev_prop_set_bit(card_dev, "spi-relaxed-mode", true); + qdev_realize_and_unref(card_dev, + qdev_get_child_bus(ssi_sd, "sd-bus"), + &error_fatal); + + /* Connect CS line from SPI2 hardware CS to ssi-sd (fallback) */ + qdev_connect_gpio_out_named(DEVICE(&ms->spi2), SSI_GPIO_CS, 0, + qdev_get_gpio_in_named(ssi_sd, SSI_GPIO_CS, 0)); + + /* Store ssi_sd reference for GPIO CS connection after GPIO is realized */ + ms->sd_ssi_dev = ssi_sd; + } + } + for (int i = 0; i < ESP32C3_UART_COUNT; ++i) { const hwaddr uart_base[] = { DR_REG_UART_BASE, DR_REG_UART1_BASE }; sysbus_realize(SYS_BUS_DEVICE(&ms->uart[i]), &error_fatal); @@ -490,6 +551,15 @@ static void esp32c3_machine_init(MachineState *machine) sysbus_realize(SYS_BUS_DEVICE(&ms->gpio), &error_fatal); MemoryRegion *mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&ms->gpio), 0); memory_region_add_subregion_overlap(sys_mem, DR_REG_GPIO_BASE, mr, 0); + + /* Connect GPIO 10 to SD card CS (ssi-sd device) if SD card is attached. + * The ESP-IDF SDSPI driver controls CS via GPIO, not hardware SPI CS. + * GPIO 10 is the CS pin used by the OROBLANCO_ONE_C3 board in QEMU mode. */ + if (ms->sd_ssi_dev) { + qemu_log("Connecting GPIO 10 to SD card CS\n"); + qdev_connect_gpio_out_named(DEVICE(&ms->gpio), ESP32C3_GPIO_OUT_IRQ, 10, + qdev_get_gpio_in_named(ms->sd_ssi_dev, SSI_GPIO_CS, 0)); + } } /* (Extmem) Cache realization */ @@ -670,6 +740,13 @@ static void esp32c3_machine_class_init(ObjectClass *oc, void *data) mc->default_cpus = 1; // 0x4f600 mc->default_ram_size = 400 * 1024; + /* Disable default devices that don't apply to embedded MCU. + * We allow sdcard via -drive if=sd,index=1, but disable the default + * empty SD card at index=0 to avoid conflicts. */ + mc->no_floppy = true; + mc->no_cdrom = true; + mc->no_parallel = true; + mc->no_sdcard = true; /* Disable default SD card - user must specify via -drive if=sd */ } /* Create a new type of machine ("child class") */ diff --git a/hw/sd/sd.c b/hw/sd/sd.c index f9bd03f3fd9e9..e573dbfb6a773 100644 --- a/hw/sd/sd.c +++ b/hw/sd/sd.c @@ -183,6 +183,9 @@ struct SDState { bool enable; uint8_t dat_lines; bool cmd_line; + + /* SPI mode compatibility flags */ + bool spi_relaxed_mode; /* Enable relaxed SPI mode for ESP-IDF SDSPI driver */ }; static void sd_realize(DeviceState *dev, Error **errp); @@ -1577,7 +1580,10 @@ static sd_rsp_type_t emmc_cmd_SEND_EXT_CSD(SDState *sd, SDRequest req) /* CMD9 */ static sd_rsp_type_t spi_cmd_SEND_CSD(SDState *sd, SDRequest req) { - if (sd->state != sd_standby_state) { + /* In relaxed SPI mode (ESP-IDF), CMD9 works in transfer state too */ + bool valid_state = (sd->state == sd_standby_state) || + (sd->spi_relaxed_mode && sd->state == sd_transfer_state); + if (!valid_state) { return sd_invalid_state_for_cmd(sd, req); } return sd_cmd_to_sendingdata(sd, req, sd_req_get_address(sd, req), @@ -1596,7 +1602,10 @@ static sd_rsp_type_t sd_cmd_SEND_CSD(SDState *sd, SDRequest req) /* CMD10 */ static sd_rsp_type_t spi_cmd_SEND_CID(SDState *sd, SDRequest req) { - if (sd->state != sd_standby_state) { + /* In relaxed SPI mode (ESP-IDF), CMD10 works in transfer state too */ + bool valid_state = (sd->state == sd_standby_state) || + (sd->spi_relaxed_mode && sd->state == sd_transfer_state); + if (!valid_state) { return sd_invalid_state_for_cmd(sd, req); } return sd_cmd_to_sendingdata(sd, req, sd_req_get_address(sd, req), @@ -1649,7 +1658,12 @@ static sd_rsp_type_t sd_cmd_SEND_STATUS(SDState *sd, SDRequest req) } if (sd_is_spi(sd)) { - return sd_r2_s; + /* + * In relaxed SPI mode (ESP-IDF), CMD13 returns R1 response. + * The ssi-sd bridge converts the card_status into SPI R2 format. + * Standard SPI mode returns sd_r2_s (CSD register data). + */ + return sd->spi_relaxed_mode ? sd_r1 : sd_r2_s; } return sd_req_rca_same(sd, req) ? sd_r1 : sd_r0; @@ -2242,6 +2256,15 @@ int sd_do_command(SDState *sd, SDRequest *req, last_state = sd->state; sd_set_mode(sd); + /* + * In relaxed SPI mode (ESP-IDF), clear "error for current command" bits + * at the start of each command. ILLEGAL_COMMAND and COM_CRC_ERROR should + * reflect status of THIS command, not a previous command. + */ + if (sd->spi_relaxed_mode) { + sd->card_status &= ~CARD_STATUS_B; + } + if (sd->expecting_acmd) { sd->expecting_acmd = false; rtype = sd_app_command(sd, *req); @@ -2806,6 +2829,7 @@ static Property sdmmc_common_properties[] = { static Property sd_properties[] = { DEFINE_PROP_UINT8("spec_version", SDState, spec_version, SD_PHY_SPECv3_01_VERS), + DEFINE_PROP_BOOL("spi-relaxed-mode", SDState, spi_relaxed_mode, false), DEFINE_PROP_END_OF_LIST() }; diff --git a/hw/sd/ssi-sd.c b/hw/sd/ssi-sd.c index 15940515ab969..66291dae09914 100644 --- a/hw/sd/ssi-sd.c +++ b/hw/sd/ssi-sd.c @@ -389,6 +389,40 @@ static void ssi_sd_reset(DeviceState *dev) s->stopping = 0; } +/* + * Handle chip select transitions. + * When CS goes HIGH (inactive), normally reset the state machine. + * However, during data read phases, preserve state to allow multi-transaction + * reads (as used by ESP-IDF SDSPI driver which toggles GPIO CS between DMA + * transactions). + */ +static int ssi_sd_set_cs(SSIPeripheral *dev, bool cs) +{ + ssi_sd_state *s = SSI_SD(dev); + + DPRINTF("CS change: %s (mode=%d)\n", cs ? "HIGH (inactive)" : "LOW (active)", s->mode); + + /* + * When CS goes inactive (HIGH), reset state machine to command mode, + * UNLESS we're in a data read phase. Some drivers (like ESP-IDF SDSPI) + * use GPIO-controlled CS and may briefly toggle CS HIGH between DMA + * transactions while reading data. Preserve state during data reads + * to allow the read to continue. + */ + if (cs) { + /* Preserve state during data read phases */ + if (s->mode != SSI_SD_DATA_READ && + s->mode != SSI_SD_DATA_START && + s->mode != SSI_SD_PREP_DATA && + s->mode != SSI_SD_DATA_CRC16) { + s->mode = SSI_SD_CMD; + s->arglen = 0; + s->response_pos = 0; + } + } + return 0; +} + static void ssi_sd_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -396,6 +430,7 @@ static void ssi_sd_class_init(ObjectClass *klass, void *data) k->realize = ssi_sd_realize; k->transfer = ssi_sd_transfer; + k->set_cs = ssi_sd_set_cs; k->cs_polarity = SSI_CS_LOW; dc->vmsd = &vmstate_ssi_sd; device_class_set_legacy_reset(dc, ssi_sd_reset); diff --git a/hw/ssi/esp32c3_spi2.c b/hw/ssi/esp32c3_spi2.c new file mode 100644 index 0000000000000..cb020aef4fcb4 --- /dev/null +++ b/hw/ssi/esp32c3_spi2.c @@ -0,0 +1,780 @@ +/* + * ESP32-C3 GPSPI2 (general-purpose SPI2) controller + * + * Copyright (c) 2026 Oroblanco Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 or + * (at your option) any later version. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "sysemu/sysemu.h" +#include "hw/hw.h" +#include "hw/sysbus.h" +#include "hw/registerfields.h" +#include "hw/irq.h" +#include "hw/qdev-properties.h" +#include "hw/ssi/ssi.h" +#include "hw/ssi/esp32c3_spi2.h" +#include "qemu/error-report.h" + +#define SPI2_DEBUG 0 +#define SPI2_WARNING 1 + + +static void esp32c3_spi2_update_irq(ESP32C3Spi2State *s) +{ + /* + * For polling mode: DMA_INT_ST should reflect DMA_INT_RAW directly + * so that software can poll for TRANS_DONE without enabling interrupts. + * The actual IRQ only fires when the corresponding bit is also enabled + * in DMA_INT_ENA. + */ + s->dma_int_st = s->dma_int_raw; + + /* Only raise IRQ if corresponding bits are enabled */ + uint32_t irq_pending = s->dma_int_raw & s->dma_int_ena; +#if SPI2_DEBUG + static uint32_t last_irq_state = 0; + if (irq_pending != last_irq_state) { + warn_report("[SPI2] IRQ: raw=0x%x ena=0x%x pending=0x%x -> %s", + s->dma_int_raw, s->dma_int_ena, irq_pending, + irq_pending ? "RAISE" : "LOWER"); + last_irq_state = irq_pending; + } +#endif + if (irq_pending) { + qemu_irq_raise(s->irq); + } else { + qemu_irq_lower(s->irq); + } +} + + +static uint64_t esp32c3_spi2_read(void *opaque, hwaddr addr, unsigned int size) +{ + ESP32C3Spi2State *s = ESP32C3_SPI2(opaque); + + uint64_t r = 0; + switch (addr) { + case A_GPSPI2_CMD: + r = s->cmd; + break; + case A_GPSPI2_ADDR: + r = s->addr; + break; + case A_GPSPI2_CTRL: + r = s->ctrl; + break; + case A_GPSPI2_CLOCK: + r = s->clock; + break; + case A_GPSPI2_USER: + r = s->user; + break; + case A_GPSPI2_USER1: + r = s->user1; + break; + case A_GPSPI2_USER2: + r = s->user2; + break; + case A_GPSPI2_MS_DLEN: + r = s->ms_dlen; + break; + case A_GPSPI2_MISC: + r = s->misc; + break; + case A_GPSPI2_DMA_CONF: + r = s->dma_conf; + break; + case A_GPSPI2_DMA_INT_ENA: + r = s->dma_int_ena; + break; + case A_GPSPI2_DMA_INT_CLR: + r = 0; + break; + case A_GPSPI2_DMA_INT_RAW: + r = s->dma_int_raw; + break; + case A_GPSPI2_DMA_INT_ST: + r = s->dma_int_st; + break; + case A_GPSPI2_W0 ... A_GPSPI2_W15: + r = s->data_reg[(addr - A_GPSPI2_W0) / sizeof(uint32_t)]; +#if SPI2_DEBUG + info_report("[SPI2] READ W%lu = 0x%08lx", + (addr - A_GPSPI2_W0) / sizeof(uint32_t), r); +#endif + break; + /* QEMU extension: additional W registers for large CPU-mode transfers */ + case GPSPI2_W_EXT_BASE ... (GPSPI2_W_EXT_END - 1): { + uint32_t idx = 16 + (addr - GPSPI2_W_EXT_BASE) / sizeof(uint32_t); + if (idx < ESP32C3_SPI2_BUF_WORDS) { + r = s->data_reg[idx]; + } + break; + } + case A_GPSPI2_SLAVE: + r = s->slave; + break; + case A_GPSPI2_CLK_GATE: + r = s->clk_gate; + break; + case A_GPSPI2_DATE: + r = s->date; + break; + case 0x24: /* SPI_DIN_MODE_REG */ + case 0x28: /* SPI_DIN_NUM_REG */ + case 0x2C: /* SPI_DOUT_MODE_REG */ + case 0x44: /* Additional registers after DMA_INT_ST */ + case 0x48: + case 0x4C: + case 0x50: + case 0x54: + case 0x58: + case 0x5C: + case 0x60: + case 0x64: + case 0x68: + case 0x6C: + case 0x70: + case 0x74: + case 0x78: + case 0x7C: + case 0x80: + case 0x84: + case 0x88: + case 0x8C: + case 0x90: + case 0x94: + case 0xD8: /* After W15 */ + case 0xDC: + case 0xE4: /* SPI_SLAVE1_REG */ + r = 0; + break; + default: +#if SPI2_DEBUG + warn_report("[SPI2] Unsupported read from 0x%lx", addr); +#endif + break; + } + +#if SPI2_DEBUG + if (addr == A_GPSPI2_CMD || addr == A_GPSPI2_DMA_INT_ST || addr == A_GPSPI2_DMA_INT_RAW) { + info_report("[SPI2] READ 0x%lx = 0x%08lx", addr, r); + } +#endif + + return r; +} + + +/** + * @brief Perform a user-mode SPI transaction on GPSPI2. + * + * This is triggered when bit 24 (SPI_USR) is set in the CMD register. + * The transaction phases (command, address, dummy, MOSI, MISO) are controlled + * by the USER register bits. + */ +static void esp32c3_spi2_begin_transaction(ESP32C3Spi2State *s) +{ + uint8_t *buf = (uint8_t *)s->data_reg; + + /* Determine which CS line to assert. Default to CS0. */ + int cs_line = 0; + + /* Check if this CS line is disabled (software-controlled via GPIO). + * When CSx_DIS is set, the hardware CS is disabled and we shouldn't + * toggle it. The actual CS is controlled via GPIO separately. + * However, for QEMU SD card emulation, we need to keep CS asserted + * during the transaction. */ + uint32_t cs_dis_mask = 1 << cs_line; /* CS0_DIS=bit0, CS1_DIS=bit1, CS2_DIS=bit2 */ + bool cs_disabled = (s->misc & cs_dis_mask) != 0; + + /* Assert CS low - but only if not already low. + * Avoid spurious CS edges which would reset the SSI peripheral state. */ + if (s->cs_state[cs_line] != 0) { +#if SPI2_DEBUG + info_report("[SPI2] CS%d -> LOW (misc=0x%08x, cs_disabled=%d, cs_keep_active=%d)", + cs_line, s->misc, cs_disabled, + !!(s->misc & R_GPSPI2_MISC_CS_KEEP_ACTIVE_MASK)); +#endif + qemu_set_irq(s->cs_gpio[cs_line], 0); + s->cs_state[cs_line] = 0; + } + + /* Command phase */ + if (s->user & R_GPSPI2_USER_USR_COMMAND_MASK) { + uint32_t cmd_bitlen = FIELD_EX32(s->user2, GPSPI2_USER2, USR_COMMAND_BITLEN); + uint32_t cmd_bytes = (cmd_bitlen + 1) / 8; + uint32_t cmd_value = FIELD_EX32(s->user2, GPSPI2_USER2, USR_COMMAND_VALUE); + +#if SPI2_DEBUG + info_report("[SPI2] TX CMD: val=0x%x bytes=%u", cmd_value, cmd_bytes); +#endif + /* Send command bytes, MSB first */ + for (int i = cmd_bytes - 1; i >= 0; i--) { + ssi_transfer(s->spi, (cmd_value >> (i * 8)) & 0xFF); + } + } + + /* Address phase */ + if (s->user & R_GPSPI2_USER_USR_ADDR_MASK) { + uint32_t addr_bitlen = FIELD_EX32(s->user1, GPSPI2_USER1, USR_ADDR_BITLEN); + uint32_t addr_bytes = (addr_bitlen + 1) / 8; + + /* SPI expects address MSB first; byte-swap from little-endian storage */ + uint32_t addr_be = bswap32(s->addr); + + /* Shift so that the relevant bytes are at the beginning */ + if (addr_bytes > 0 && addr_bytes <= 4) { + addr_be = addr_be >> (32 - addr_bytes * 8); + } + +#if SPI2_DEBUG + info_report("[SPI2] TX ADDR: 0x%x bytes=%u", s->addr, addr_bytes); +#endif + uint8_t *addr_ptr = (uint8_t *)&addr_be; + for (uint32_t i = 0; i < addr_bytes; i++) { + ssi_transfer(s->spi, addr_ptr[i]); + } + } + + /* Dummy phase */ + if (s->user & R_GPSPI2_USER_USR_DUMMY_MASK) { + uint32_t dummy_cyclelen = FIELD_EX32(s->user1, GPSPI2_USER1, USR_DUMMY_CYCLELEN); + /* dummy_cyclelen is (cycles - 1), so actual cycles = dummy_cyclelen + 1. + * Each cycle is one bit on the SPI bus, so convert to bytes rounding up. */ + uint32_t dummy_bytes = (dummy_cyclelen + 1 + 7) / 8; +#if SPI2_DEBUG + info_report("[SPI2] DUMMY: %u bytes", dummy_bytes); +#endif + for (uint32_t i = 0; i < dummy_bytes; i++) { + ssi_transfer(s->spi, 0x00); + } + } + + /* Data phase: MS_DLEN contains (bit_count - 1) in bits 17:0 */ + uint32_t data_bitlen = FIELD_EX32(s->ms_dlen, GPSPI2_MS_DLEN, MS_DATA_BITLEN); + uint32_t data_len = (data_bitlen + 1) / 8; + + /* Clamp to the size of our data register buffer. + * For QEMU, buffer is larger than real hardware to support SD card reads */ + if (data_len > ESP32C3_SPI2_BUF_WORDS * 4) { + warn_report("[SPI2] Transaction %u bytes exceeds buffer %u bytes, clamping", + data_len, ESP32C3_SPI2_BUF_WORDS * 4); + data_len = ESP32C3_SPI2_BUF_WORDS * 4; + } + + bool do_mosi = (s->user & R_GPSPI2_USER_USR_MOSI_MASK) != 0; + bool do_miso = (s->user & R_GPSPI2_USER_USR_MISO_MASK) != 0; + +#if SPI2_DEBUG + info_report("[SPI2] DATA: len=%u mosi=%d miso=%d user=0x%08x ms_dlen=0x%x", + data_len, do_mosi, do_miso, s->user, s->ms_dlen); +#endif + + if (do_mosi && do_miso) { + /* Full-duplex: send from data_reg and receive into data_reg simultaneously */ + for (uint32_t i = 0; i < data_len; i++) { + uint8_t tx = buf[i]; + uint32_t res = ssi_transfer(s->spi, tx); + buf[i] = (uint8_t)res; +#if SPI2_DEBUG + if (i < 20) info_report("[SPI2] FD[%u]: tx=0x%02x rx=0x%02x", i, tx, res & 0xff); +#endif + } + } else if (do_mosi) { + /* TX only: send bytes from data_reg */ + for (uint32_t i = 0; i < data_len; i++) { + ssi_transfer(s->spi, buf[i]); + } +#if SPI2_DEBUG + info_report("[SPI2] TX: first bytes: %02x %02x %02x %02x", + data_len > 0 ? buf[0] : 0, data_len > 1 ? buf[1] : 0, + data_len > 2 ? buf[2] : 0, data_len > 3 ? buf[3] : 0); +#endif + } else if (do_miso) { + /* RX only: receive bytes into data_reg (send 0xFF dummy bytes) */ + for (uint32_t i = 0; i < data_len; i++) { + uint32_t res = ssi_transfer(s->spi, 0xFF); + buf[i] = (uint8_t)res; + } +#if SPI2_DEBUG + info_report("[SPI2] RX: first bytes: %02x %02x %02x %02x", + data_len > 0 ? buf[0] : 0, data_len > 1 ? buf[1] : 0, + data_len > 2 ? buf[2] : 0, data_len > 3 ? buf[3] : 0); +#endif + } + + /* Deassert CS high, unless: + * 1. CS_KEEP_ACTIVE is set in MISC register, OR + * 2. CSx_DIS is set (software-controlled CS via GPIO). + * + * When CSx_DIS is set, the actual CS is controlled via GPIO, not the SPI + * peripheral. For QEMU SD card emulation, we keep CS low because the + * ESP-IDF SDSPI driver does multiple single-byte SPI transfers within + * one "CS-asserted" session controlled via GPIO. The ssi-sd state machine + * needs CS to stay low to properly accumulate command bytes across + * multiple SPI transfers. */ + if (!(s->misc & R_GPSPI2_MISC_CS_KEEP_ACTIVE_MASK) && !cs_disabled) { + if (s->cs_state[cs_line] != 1) { +#if SPI2_DEBUG + info_report("[SPI2] CS%d -> HIGH (cs_keep_active=0, cs_disabled=0)", cs_line); +#endif + qemu_set_irq(s->cs_gpio[cs_line], 1); + s->cs_state[cs_line] = 1; + } + } +#if SPI2_DEBUG + else { + /* CS stays low, no need to log every time */ + } +#endif + + /* Clear the USR bit in CMD to indicate transaction complete */ + s->cmd &= ~R_GPSPI2_CMD_USR_MASK; + + /* Set TRANS_DONE in DMA_INT_RAW */ + s->dma_int_raw |= R_GPSPI2_DMA_INT_RAW_TRANS_DONE_MASK; + + /* Update interrupt status and possibly raise IRQ */ + esp32c3_spi2_update_irq(s); +} + + +/** + * @brief Determine if DMA mode should be used for the transaction. + * + * DMA mode is used when: + * 1. GDMA controller is available (linked by machine) + * 2. A GDMA channel is configured for SPI2 (PERI_SEL = GDMA_SPI2) + */ +static bool esp32c3_spi2_use_dma_mode(ESP32C3Spi2State *s) +{ + /* TEMPORARY: Force CPU mode for QEMU SD card testing. + * The GDMA integration needs more work to properly support SPI DMA. + * For now, using CPU mode (W0-W15 data registers) works correctly. */ + (void)s; + return false; + +#if 0 /* Disabled until GDMA integration is complete */ + uint32_t dummy; + + fprintf(stderr, "[SPI2] use_dma_mode: s->gdma=%p\n", (void*)s->gdma); + fflush(stderr); + + /* No GDMA controller linked, must use CPU mode */ + if (s->gdma == NULL) { + fprintf(stderr, "[SPI2] use_dma_mode: GDMA is NULL, using CPU mode\n"); + fflush(stderr); + return false; + } + + /* Check if any GDMA channel is configured for SPI2 */ + bool have_out = esp_gdma_get_channel_periph(s->gdma, GDMA_SPI2, ESP_GDMA_OUT_IDX, &dummy); + bool have_in = esp_gdma_get_channel_periph(s->gdma, GDMA_SPI2, ESP_GDMA_IN_IDX, &dummy); + + /* Use DMA mode if at least one channel is configured */ + return have_out || have_in; +#endif +} + + +/** + * @brief Perform a DMA-mode SPI transaction on GPSPI2. + * + * This reads TX data from GDMA OUT channel, sends via SPI bus, + * receives data, and writes to GDMA IN channel. + */ +static void esp32c3_spi2_dma_transaction(ESP32C3Spi2State *s) +{ + uint32_t gdma_out_idx = 0; + uint32_t gdma_in_idx = 0; + + /* Determine which CS line to assert. Default to CS0. */ + int cs_line = 0; + uint32_t cs_dis_mask = 1 << cs_line; + bool cs_disabled = (s->misc & cs_dis_mask) != 0; + + /* Get GDMA channels configured for SPI2 */ + bool have_out = esp_gdma_get_channel_periph(s->gdma, GDMA_SPI2, ESP_GDMA_OUT_IDX, &gdma_out_idx); + bool have_in = esp_gdma_get_channel_periph(s->gdma, GDMA_SPI2, ESP_GDMA_IN_IDX, &gdma_in_idx); + + /* Get data length from MS_DLEN register */ + uint32_t data_bitlen = FIELD_EX32(s->ms_dlen, GPSPI2_MS_DLEN, MS_DATA_BITLEN); + uint32_t data_len = (data_bitlen + 1) / 8; + +#if SPI2_DEBUG + info_report("[SPI2] DMA TRANSACTION: data_len=%u have_out=%d have_in=%d", + data_len, have_out, have_in); +#endif + + /* Assert CS low */ + if (s->cs_state[cs_line] != 0) { +#if SPI2_DEBUG + info_report("[SPI2] DMA: CS%d -> LOW", cs_line); +#endif + qemu_set_irq(s->cs_gpio[cs_line], 0); + s->cs_state[cs_line] = 0; + } + + /* Command phase */ + if (s->user & R_GPSPI2_USER_USR_COMMAND_MASK) { + uint32_t cmd_bitlen = FIELD_EX32(s->user2, GPSPI2_USER2, USR_COMMAND_BITLEN); + uint32_t cmd_bytes = (cmd_bitlen + 1) / 8; + uint32_t cmd_value = FIELD_EX32(s->user2, GPSPI2_USER2, USR_COMMAND_VALUE); + +#if SPI2_DEBUG + info_report("[SPI2] DMA TX CMD: val=0x%x bytes=%u", cmd_value, cmd_bytes); +#endif + for (int i = cmd_bytes - 1; i >= 0; i--) { + ssi_transfer(s->spi, (cmd_value >> (i * 8)) & 0xFF); + } + } + + /* Address phase */ + if (s->user & R_GPSPI2_USER_USR_ADDR_MASK) { + uint32_t addr_bitlen = FIELD_EX32(s->user1, GPSPI2_USER1, USR_ADDR_BITLEN); + uint32_t addr_bytes = (addr_bitlen + 1) / 8; + uint32_t addr_be = bswap32(s->addr); + + if (addr_bytes > 0 && addr_bytes <= 4) { + addr_be = addr_be >> (32 - addr_bytes * 8); + } + +#if SPI2_DEBUG + info_report("[SPI2] DMA TX ADDR: 0x%x bytes=%u", s->addr, addr_bytes); +#endif + uint8_t *addr_ptr = (uint8_t *)&addr_be; + for (uint32_t i = 0; i < addr_bytes; i++) { + ssi_transfer(s->spi, addr_ptr[i]); + } + } + + /* Dummy phase */ + if (s->user & R_GPSPI2_USER_USR_DUMMY_MASK) { + uint32_t dummy_cyclelen = FIELD_EX32(s->user1, GPSPI2_USER1, USR_DUMMY_CYCLELEN); + uint32_t dummy_bytes = (dummy_cyclelen + 1 + 7) / 8; +#if SPI2_DEBUG + info_report("[SPI2] DMA DUMMY: %u bytes", dummy_bytes); +#endif + for (uint32_t i = 0; i < dummy_bytes; i++) { + ssi_transfer(s->spi, 0x00); + } + } + + /* Data phase via DMA */ + if (data_len > 0) { + bool do_mosi = (s->user & R_GPSPI2_USER_USR_MOSI_MASK) != 0; + bool do_miso = (s->user & R_GPSPI2_USER_USR_MISO_MASK) != 0; + + /* Allocate buffer for transfer */ + uint8_t *buffer = g_malloc(data_len); + if (buffer == NULL) { + error_report("[SPI2] Failed to allocate DMA buffer of %u bytes", data_len); + goto complete; + } + + /* Read TX data from GDMA OUT channel */ + if (do_mosi && have_out) { + if (!esp_gdma_read_channel(s->gdma, gdma_out_idx, buffer, data_len)) { + warn_report("[SPI2] Error reading from GDMA OUT channel"); + memset(buffer, 0xFF, data_len); + } +#if SPI2_DEBUG + info_report("[SPI2] DMA TX: read %u bytes from GDMA, first: %02x %02x %02x %02x", + data_len, + data_len > 0 ? buffer[0] : 0, data_len > 1 ? buffer[1] : 0, + data_len > 2 ? buffer[2] : 0, data_len > 3 ? buffer[3] : 0); +#endif + } else { + /* Fill with 0xFF for RX-only transfers */ + memset(buffer, 0xFF, data_len); + } + + /* Perform SPI transfer */ + for (uint32_t i = 0; i < data_len; i++) { + uint32_t rx_byte = ssi_transfer(s->spi, buffer[i]); + buffer[i] = (uint8_t)rx_byte; + } + + /* Write RX data to GDMA IN channel */ + if (do_miso && have_in) { +#if SPI2_DEBUG + info_report("[SPI2] DMA RX: writing %u bytes to GDMA, first: %02x %02x %02x %02x", + data_len, + data_len > 0 ? buffer[0] : 0, data_len > 1 ? buffer[1] : 0, + data_len > 2 ? buffer[2] : 0, data_len > 3 ? buffer[3] : 0); +#endif + if (!esp_gdma_write_channel(s->gdma, gdma_in_idx, buffer, data_len)) { + warn_report("[SPI2] Error writing to GDMA IN channel"); + } + } + + g_free(buffer); + } + + /* Deassert CS high if appropriate */ + if (!(s->misc & R_GPSPI2_MISC_CS_KEEP_ACTIVE_MASK) && !cs_disabled) { + if (s->cs_state[cs_line] != 1) { +#if SPI2_DEBUG + info_report("[SPI2] DMA: CS%d -> HIGH", cs_line); +#endif + qemu_set_irq(s->cs_gpio[cs_line], 1); + s->cs_state[cs_line] = 1; + } + } + +complete: + /* Clear USR bit and set TRANS_DONE */ + s->cmd &= ~R_GPSPI2_CMD_USR_MASK; + s->dma_int_raw |= R_GPSPI2_DMA_INT_RAW_TRANS_DONE_MASK; + esp32c3_spi2_update_irq(s); +} + + +static void esp32c3_spi2_write(void *opaque, hwaddr addr, + uint64_t value, unsigned int size) +{ + ESP32C3Spi2State *s = ESP32C3_SPI2(opaque); + uint32_t wvalue = (uint32_t)value; + + /* Debug all significant writes */ +#if SPI2_DEBUG + if (addr != A_GPSPI2_DMA_INT_ST) { /* Skip read-only register */ + info_report("[SPI2] WRITE 0x%02lx = 0x%08x", addr, wvalue); + } +#endif + + switch (addr) { + case A_GPSPI2_CMD: + s->cmd = wvalue; + if (wvalue & R_GPSPI2_CMD_UPDATE_MASK) { + /* SPI_UPDATE bit: accept and clear (no-op for emulation; + * real HW uses this to sync APB -> SPI clock domain) */ + s->cmd &= ~R_GPSPI2_CMD_UPDATE_MASK; + } + if (wvalue & R_GPSPI2_CMD_USR_MASK) { +#if SPI2_DEBUG + info_report("[SPI2] TRIGGER: user=0x%08x user1=0x%08x user2=0x%08x " + "ms_dlen=0x%x addr=0x%x dma_conf=0x%x " + "W0=0x%08x W1=0x%08x W2=0x%08x W3=0x%08x", + s->user, s->user1, s->user2, + s->ms_dlen, s->addr, s->dma_conf, + s->data_reg[0], s->data_reg[1], + s->data_reg[2], s->data_reg[3]); +#endif + /* Check if DMA mode should be used */ + if (esp32c3_spi2_use_dma_mode(s)) { + esp32c3_spi2_dma_transaction(s); + } else { + esp32c3_spi2_begin_transaction(s); + } + } + break; + case A_GPSPI2_ADDR: + s->addr = wvalue; + break; + case A_GPSPI2_CTRL: + s->ctrl = wvalue; + break; + case A_GPSPI2_CLOCK: + s->clock = wvalue; + break; + case A_GPSPI2_USER: + s->user = wvalue; + break; + case A_GPSPI2_USER1: + s->user1 = wvalue; + break; + case A_GPSPI2_USER2: + s->user2 = wvalue; + break; + case A_GPSPI2_MS_DLEN: + s->ms_dlen = wvalue; + break; + case A_GPSPI2_MISC: + s->misc = wvalue; + break; + case A_GPSPI2_DMA_CONF: + /* Accept DMA configuration writes; handle AFIFO reset bits as write-1-to-clear. + * We don't actually implement DMA, but the SPI master driver configures this. */ + s->dma_conf = wvalue & ~(R_GPSPI2_DMA_CONF_RX_AFIFO_RST_MASK | + R_GPSPI2_DMA_CONF_BUF_AFIFO_RST_MASK | + R_GPSPI2_DMA_CONF_DMA_AFIFO_RST_MASK); + break; + case A_GPSPI2_DMA_INT_ENA: + s->dma_int_ena = wvalue; + esp32c3_spi2_update_irq(s); + break; + case A_GPSPI2_DMA_INT_CLR: + /* Writing 1 to a bit clears the corresponding bit in DMA_INT_RAW */ + s->dma_int_raw &= ~wvalue; + esp32c3_spi2_update_irq(s); + break; + case A_GPSPI2_DMA_INT_RAW: + /* RAW register is typically not written by software, but accept writes */ + s->dma_int_raw = wvalue; + esp32c3_spi2_update_irq(s); + break; + case A_GPSPI2_W0 ... A_GPSPI2_W15: + s->data_reg[(addr - A_GPSPI2_W0) / sizeof(uint32_t)] = wvalue; + break; + /* QEMU extension: additional W registers for large CPU-mode transfers */ + case GPSPI2_W_EXT_BASE ... (GPSPI2_W_EXT_END - 1): { + uint32_t idx = 16 + (addr - GPSPI2_W_EXT_BASE) / sizeof(uint32_t); + if (idx < ESP32C3_SPI2_BUF_WORDS) { + s->data_reg[idx] = wvalue; + } + break; + } + case A_GPSPI2_SLAVE: + s->slave = wvalue; + break; + case A_GPSPI2_CLK_GATE: + s->clk_gate = wvalue; + break; + case A_GPSPI2_DATE: + s->date = wvalue; + break; + case A_GPSPI2_DMA_INT_ST: + /* DMA_INT_ST is read-only (computed from RAW & ENA) - ignore writes */ + break; + case 0x24: /* SPI_DIN_MODE_REG */ + case 0x28: /* SPI_DIN_NUM_REG */ + case 0x2C: /* SPI_DOUT_MODE_REG */ + case 0x44: /* Additional registers */ + case 0x48: + case 0x4C: + case 0x50: + case 0x54: + case 0x58: + case 0x5C: + case 0x60: + case 0x64: + case 0x68: + case 0x6C: + case 0x70: + case 0x74: + case 0x78: + case 0x7C: + case 0x80: + case 0x84: + case 0x88: + case 0x8C: + case 0x90: + case 0x94: + case 0xD8: + case 0xDC: + case 0xE4: /* SPI_SLAVE1_REG */ + /* Accept but ignore timing/mode/other registers */ + break; + default: +#if SPI2_DEBUG + warn_report("[SPI2] Unsupported write to 0x%lx (%08lx)", addr, value); +#endif + break; + } +} + + +static const MemoryRegionOps esp32c3_spi2_ops = { + .read = esp32c3_spi2_read, + .write = esp32c3_spi2_write, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + + +static void esp32c3_spi2_reset_hold(Object *obj, ResetType type) +{ + ESP32C3Spi2State *s = ESP32C3_SPI2(obj); + + s->cmd = 0; + s->addr = 0; + s->ctrl = 0; + s->clock = 0; + s->user = 0; + s->user1 = 0; + s->user2 = 0; + s->ms_dlen = 0; + s->misc = 0; + s->dma_conf = 0; + s->dma_int_ena = 0; + s->dma_int_clr = 0; + s->dma_int_raw = 0; + s->dma_int_st = 0; + s->slave = 0; + s->clk_gate = 0; + s->date = 0; + memset(s->data_reg, 0, ESP32C3_SPI2_BUF_WORDS * sizeof(uint32_t)); + /* CS starts high (deasserted) */ + for (int i = 0; i < ESP32C3_SPI2_CS_COUNT; i++) { + s->cs_state[i] = 1; + } +} + + +static void esp32c3_spi2_realize(DeviceState *dev, Error **errp) +{ + ESP32C3Spi2State *s = ESP32C3_SPI2(dev); + + /* GDMA is optional - if not set, only CPU mode will work */ + if (s->gdma == NULL) { + info_report("[SPI2] GDMA not configured, DMA mode disabled"); + } +} + + +static void esp32c3_spi2_init(Object *obj) +{ + ESP32C3Spi2State *s = ESP32C3_SPI2(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + memory_region_init_io(&s->iomem, obj, &esp32c3_spi2_ops, s, + TYPE_ESP32C3_SPI2, ESP32C3_SPI2_IO_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq); + + esp32c3_spi2_reset_hold(obj, RESET_TYPE_COLD); + + s->spi = ssi_create_bus(DEVICE(s), "spi"); + qdev_init_gpio_out_named(DEVICE(s), s->cs_gpio, SSI_GPIO_CS, + ESP32C3_SPI2_CS_COUNT); +} + + +static Property esp32c3_spi2_properties[] = { + DEFINE_PROP_END_OF_LIST(), +}; + + +static void esp32c3_spi2_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + rc->phases.hold = esp32c3_spi2_reset_hold; + dc->realize = esp32c3_spi2_realize; + device_class_set_props(dc, esp32c3_spi2_properties); +} + + +static const TypeInfo esp32c3_spi2_info = { + .name = TYPE_ESP32C3_SPI2, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(ESP32C3Spi2State), + .instance_init = esp32c3_spi2_init, + .class_init = esp32c3_spi2_class_init, +}; + + +static void esp32c3_spi2_register_types(void) +{ + type_register_static(&esp32c3_spi2_info); +} + +type_init(esp32c3_spi2_register_types) diff --git a/hw/ssi/meson.build b/hw/ssi/meson.build index 30f1a871af558..6beedc94dc7bc 100644 --- a/hw/ssi/meson.build +++ b/hw/ssi/meson.build @@ -12,6 +12,7 @@ system_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files('xlnx-versal-ospi.c')) system_ss.add(when: 'CONFIG_IMX', if_true: files('imx_spi.c')) system_ss.add(when: 'CONFIG_XTENSA_ESP32', if_true: files('esp32_spi.c')) system_ss.add(when: 'CONFIG_RISCV_ESP32C3', if_true: files('esp32c3_spi.c')) +system_ss.add(when: 'CONFIG_RISCV_ESP32C3', if_true: files('esp32c3_spi2.c')) system_ss.add(when: 'CONFIG_XTENSA_ESP32S3', if_true: files('esp32s3_spi.c')) system_ss.add(when: 'CONFIG_IBEX', if_true: files('ibex_spi_host.c')) system_ss.add(when: 'CONFIG_BCM2835_SPI', if_true: files('bcm2835_spi.c')) diff --git a/include/hw/gpio/esp32c3_gpio.h b/include/hw/gpio/esp32c3_gpio.h index 6fe8b72bd764a..d102f9397801f 100644 --- a/include/hw/gpio/esp32c3_gpio.h +++ b/include/hw/gpio/esp32c3_gpio.h @@ -15,8 +15,21 @@ #define ESP32C3_STRAP_MODE_UART_BOOT 0x2 /* Diagnostic Mode0+UART0 download Mode */ #define ESP32C3_STRAP_MODE_USB_BOOT 0x0 /* Diagnostic Mode1+USB download Mode */ -typedef struct ESP32C3State { +/* Number of GPIO pins on ESP32-C3 */ +#define ESP32C3_GPIO_COUNT 22 + +/* GPIO output IRQ name for connecting to external devices */ +#define ESP32C3_GPIO_OUT_IRQ "esp32c3-gpio-out" + +typedef struct ESP32C3GPIOState { Esp32GpioState parent; + + /* GPIO output state */ + uint32_t gpio_out; + uint32_t gpio_enable; + + /* Output IRQs - one per GPIO pin */ + qemu_irq gpio_out_irq[ESP32C3_GPIO_COUNT]; } ESP32C3GPIOState; typedef struct ESP32C3GPIOClass { diff --git a/include/hw/ssi/esp32c3_spi2.h b/include/hw/ssi/esp32c3_spi2.h new file mode 100644 index 0000000000000..105e3c2f56a88 --- /dev/null +++ b/include/hw/ssi/esp32c3_spi2.h @@ -0,0 +1,136 @@ +/* + * ESP32-C3 GPSPI2 (general-purpose SPI2) controller + * + * Copyright (c) 2026 Oroblanco Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 or + * (at your option) any later version. + */ + +#ifndef ESP32C3_SPI2_H +#define ESP32C3_SPI2_H + +#include "hw/sysbus.h" +#include "hw/ssi/ssi.h" +#include "hw/registerfields.h" +#include "hw/dma/esp_gdma.h" + +#define TYPE_ESP32C3_SPI2 "esp32c3-spi2" +#define ESP32C3_SPI2(obj) OBJECT_CHECK(ESP32C3Spi2State, (obj), TYPE_ESP32C3_SPI2) + +/* Number of chip select lines */ +#define ESP32C3_SPI2_CS_COUNT 3 + +/* Number of 32-bit data registers (W0-W15) + * Real hardware is 16 words (64 bytes), but for QEMU we increase this to + * support larger transfers without DMA (needed for SD card sector reads) */ +#define ESP32C3_SPI2_BUF_WORDS 256 /* 1024 bytes for QEMU testing */ + +/* IO region size */ +#define ESP32C3_SPI2_IO_SIZE 0x1000 + +/* Register field definitions using REG32 which creates A_* enum values */ +REG32(GPSPI2_CMD, 0x00) + FIELD(GPSPI2_CMD, USR, 24, 1) + FIELD(GPSPI2_CMD, UPDATE, 23, 1) + +REG32(GPSPI2_ADDR, 0x04) + +REG32(GPSPI2_CTRL, 0x08) + +REG32(GPSPI2_CLOCK, 0x0C) + +REG32(GPSPI2_USER, 0x10) + FIELD(GPSPI2_USER, USR_COMMAND, 31, 1) + FIELD(GPSPI2_USER, USR_ADDR, 30, 1) + FIELD(GPSPI2_USER, USR_DUMMY, 29, 1) + FIELD(GPSPI2_USER, USR_MISO, 28, 1) + FIELD(GPSPI2_USER, USR_MOSI, 27, 1) + FIELD(GPSPI2_USER, DOUTDIN, 0, 1) + +REG32(GPSPI2_USER1, 0x14) + FIELD(GPSPI2_USER1, USR_ADDR_BITLEN, 26, 5) + FIELD(GPSPI2_USER1, USR_DUMMY_CYCLELEN, 0, 8) + +REG32(GPSPI2_USER2, 0x18) + FIELD(GPSPI2_USER2, USR_COMMAND_BITLEN, 28, 4) + FIELD(GPSPI2_USER2, USR_COMMAND_VALUE, 0, 16) + +REG32(GPSPI2_MS_DLEN, 0x1C) + FIELD(GPSPI2_MS_DLEN, MS_DATA_BITLEN, 0, 18) + +REG32(GPSPI2_MISC, 0x20) + FIELD(GPSPI2_MISC, CS_KEEP_ACTIVE, 10, 1) + +/* Register addresses from ESP32-C3 Technical Reference Manual */ +REG32(GPSPI2_DMA_CONF, 0x30) + FIELD(GPSPI2_DMA_CONF, RX_AFIFO_RST, 30, 1) + FIELD(GPSPI2_DMA_CONF, BUF_AFIFO_RST, 29, 1) + FIELD(GPSPI2_DMA_CONF, DMA_AFIFO_RST, 28, 1) + +REG32(GPSPI2_DMA_INT_ENA, 0x34) + FIELD(GPSPI2_DMA_INT_ENA, TRANS_DONE, 12, 1) + +REG32(GPSPI2_DMA_INT_CLR, 0x38) + FIELD(GPSPI2_DMA_INT_CLR, TRANS_DONE, 12, 1) + +REG32(GPSPI2_DMA_INT_RAW, 0x3C) + FIELD(GPSPI2_DMA_INT_RAW, TRANS_DONE, 12, 1) + +REG32(GPSPI2_DMA_INT_ST, 0x40) + FIELD(GPSPI2_DMA_INT_ST, TRANS_DONE, 12, 1) + +/* Data registers W0-W15 at 0x98-0xD4 (real HW) + * For QEMU testing of large CPU-mode transfers, we extend the buffer + * and add additional W registers at 0x100-0x4FC (W16-W255). + * This is not real hardware - it's a QEMU extension for SD card testing. */ +REG32(GPSPI2_W0, 0x98) + +REG32(GPSPI2_W15, 0xD4) + +/* QEMU extension: additional W registers for large transfers */ +#define GPSPI2_W_EXT_BASE 0x100 +#define GPSPI2_W_EXT_END (GPSPI2_W_EXT_BASE + (ESP32C3_SPI2_BUF_WORDS - 16) * 4) + +REG32(GPSPI2_SLAVE, 0xE0) + +REG32(GPSPI2_CLK_GATE, 0xE8) + +REG32(GPSPI2_DATE, 0xF0) + +typedef struct ESP32C3Spi2State { + SysBusDevice parent_obj; + + MemoryRegion iomem; + qemu_irq irq; + SSIBus *spi; + qemu_irq cs_gpio[ESP32C3_SPI2_CS_COUNT]; + + /* Registers */ + uint32_t cmd; + uint32_t addr; + uint32_t ctrl; + uint32_t clock; + uint32_t user; + uint32_t user1; + uint32_t user2; + uint32_t ms_dlen; + uint32_t misc; + uint32_t dma_conf; + uint32_t dma_int_ena; + uint32_t dma_int_clr; + uint32_t dma_int_raw; + uint32_t dma_int_st; + uint32_t slave; + uint32_t clk_gate; + uint32_t date; + uint32_t data_reg[ESP32C3_SPI2_BUF_WORDS]; + /* Track CS line state to avoid spurious edges */ + int cs_state[ESP32C3_SPI2_CS_COUNT]; + + /* GDMA controller for DMA transfers - must be set by machine before realize */ + ESPGdmaState *gdma; +} ESP32C3Spi2State; + +#endif /* ESP32C3_SPI2_H */ From dd8af1de281aaf13479a3d9fb085bdbc0ca509f0 Mon Sep 17 00:00:00 2001 From: Gabriel Chang Date: Wed, 24 Jun 2026 11:20:03 -0700 Subject: [PATCH 2/3] Implement esp32-c3 I2S hardware emulation. Currently only verified support for 16 bit output, in mono and stereo, and at 44.1kHz and 48kHz. --- hw/audio/esp32c3_i2s.c | 331 +++++++++++++++++++++++++++++++++ hw/audio/meson.build | 1 + hw/dma/esp_gdma.c | 35 ++++ hw/riscv/esp32c3.c | 14 ++ include/hw/audio/esp32c3_i2s.h | 112 +++++++++++ include/hw/dma/esp_gdma.h | 16 ++ 6 files changed, 509 insertions(+) create mode 100644 hw/audio/esp32c3_i2s.c create mode 100644 include/hw/audio/esp32c3_i2s.h diff --git a/hw/audio/esp32c3_i2s.c b/hw/audio/esp32c3_i2s.c new file mode 100644 index 0000000000000..ef7f5b6f0e825 --- /dev/null +++ b/hw/audio/esp32c3_i2s.c @@ -0,0 +1,331 @@ +/* + * ESP32-C3 I2S controller emulation + * + * Copyright (c) 2026 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 version 2 or + * (at your option) any later version. + * + * The ESP-IDF I2S driver pushes audio buffers to the I2S peripheral through the + * shared GDMA controller and then blocks in i2s_channel_write() until the GDMA + * OUT_EOF interrupt signals that a buffer has been consumed. With no I2S hardware + * present, that interrupt never fires and the firmware hangs. The HAL also spins + * on the TX_UPDATE / RX_UPDATE register sync bits. + * + * This model fixes both: + * - TX_UPDATE / RX_UPDATE are auto-cleared, so i2s_ll_tx_update() returns. + * - When TX is started, a periodic timer drains one DMA buffer per tick via the + * existing esp_gdma_read_channel() (the same "pull" mechanism the SHA + * accelerator uses). That call raises OUT_EOF on the GDMA channel, which is + * exactly the interrupt the I2S driver's DMA callback waits on. + * + * Each drained PCM buffer is also forwarded to an optional chardev backend so the + * audio can be captured/streamed (e.g. over a websocket socket chardev). + * + * Pacing tracks real time: the data byte-rate is derived from the TX clock-divider + * registers so a buffer of N bytes takes ~N/byte_rate seconds to drain. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/error-report.h" +#include "qapi/error.h" +#include "hw/hw.h" +#include "hw/sysbus.h" +#include "hw/irq.h" +#include "hw/qdev-properties.h" +#include "hw/qdev-properties-system.h" +#include "hw/registerfields.h" +#include "hw/dma/esp_gdma.h" +#include "hw/audio/esp32c3_i2s.h" + +#define I2S_WARNING 0 +#define I2S_DEBUG 0 + +/* Mask of the interrupt bits we model (RX_DONE, TX_DONE, RX_HUNG, TX_HUNG). */ +#define ESP32C3_I2S_INT_MASK 0x0F + +/* Fallback tick period when the clock registers are not yet meaningfully + * configured (1 ms of virtual time). Keeps the drain loop alive without busy + * spinning. */ +#define ESP32C3_I2S_FALLBACK_NS (1 * 1000 * 1000) + + +static inline uint32_t *i2s_reg(Esp32C3I2SState *s, hwaddr addr) +{ + return &s->regs[addr / sizeof(uint32_t)]; +} + +/* Recompute the device IRQ level from INT_RAW & INT_ENA. */ +static void esp32c3_i2s_update_irq(Esp32C3I2SState *s) +{ + const uint32_t st = s->regs[R_I2S_INT_RAW] & s->regs[R_I2S_INT_ENA] & + ESP32C3_I2S_INT_MASK; + qemu_set_irq(s->irq, st ? 1 : 0); +} + +/* + * Derive the TX data byte-rate (bytes/second the FIFO drains) from the clock + * registers. byte_rate = bclk / 8, where bclk = mclk / bck_div and + * mclk = src_clk / clkm_div. For software-mono the DMA buffer carries a single + * channel that hardware duplicates across both slots, so the data rate is halved. + * + * The fractional part of the MCLK divider (x/y/z) is intentionally ignored: the + * integer divider already places pacing within ~1% of the true 44.1/48 kHz rate, + * and audio data correctness does not depend on pacing at all. + */ +static uint64_t esp32c3_i2s_tx_byte_rate(Esp32C3I2SState *s) +{ + const uint32_t clkm = s->regs[R_I2S_TX_CLKM_CONF]; + const uint32_t conf1 = s->regs[R_I2S_TX_CONF1]; + const uint32_t conf = s->regs[R_I2S_TX_CONF]; + + uint64_t src_hz; + switch (FIELD_EX32(clkm, I2S_TX_CLKM_CONF, TX_CLK_SEL)) { + case 0: src_hz = 40000000; break; /* XTAL */ + case 2: src_hz = 160000000; break; /* PLL_160M */ + default: src_hz = 160000000; break; /* external/unknown: assume PLL */ + } + + uint32_t clkm_div = FIELD_EX32(clkm, I2S_TX_CLKM_CONF, TX_CLKM_DIV_NUM); + uint32_t bck_div = FIELD_EX32(conf1, I2S_TX_CONF1, TX_BCK_DIV_NUM); + if (clkm_div == 0 || bck_div == 0) { + return 0; + } + + uint64_t mclk = src_hz / clkm_div; + uint64_t bclk = mclk / bck_div; + uint64_t byte_rate = bclk / 8; + + if (FIELD_EX32(conf, I2S_TX_CONF, TX_MONO)) { + byte_rate /= 2; + } + + return byte_rate; +} + +/* Schedule the next drain tick, pacing it to the real-time duration of `len` bytes. */ +static void esp32c3_i2s_schedule(Esp32C3I2SState *s, uint32_t len) +{ + int64_t delay_ns = ESP32C3_I2S_FALLBACK_NS; + const uint64_t byte_rate = esp32c3_i2s_tx_byte_rate(s); + + if (byte_rate != 0 && len != 0) { + delay_ns = (int64_t)(((uint64_t)len * 1000000000ULL) / byte_rate); + if (delay_ns <= 0) { + delay_ns = 1000; /* clamp to 1 us minimum */ + } + } + + timer_mod(&s->tx_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + delay_ns); +} + +/* + * Drain exactly one DMA buffer from the GDMA OUT channel bound to I2S0. Reading + * the channel walks the descriptor list, copies the PCM out and (when it crosses + * a suc_eof descriptor) raises OUT_EOF on the GDMA IRQ, which unblocks the I2S + * driver. Then forward the PCM to the chardev and reschedule. + */ +static void esp32c3_i2s_tx_tick(void *opaque) +{ + Esp32C3I2SState *s = ESP32C3_I2S(opaque); + uint32_t chan = 0; + uint32_t len = 0; + + if (!s->tx_running) { + return; + } + + /* The driver may flip tx_start before linking the GDMA channel; wait for it. */ + if (s->gdma == NULL || + !esp_gdma_get_channel_periph(s->gdma, GDMA_I2S0, ESP_GDMA_OUT_IDX, &chan) || + !esp_gdma_peek_length(s->gdma, chan, &len) || len == 0) { + timer_mod(&s->tx_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ESP32C3_I2S_FALLBACK_NS); + return; + } + + g_autofree uint8_t *buf = g_malloc(len); + if (esp_gdma_read_channel(s->gdma, chan, buf, len)) { + if (qemu_chr_fe_backend_connected(&s->chr)) { + /* Best-effort: drop the buffer if the sink can't take it all. */ + qemu_chr_fe_write_all(&s->chr, buf, len); + } + + /* Reflect a completed TX in the device's own interrupt status too. */ + s->regs[R_I2S_INT_RAW] |= R_I2S_INT_RAW_TX_DONE_MASK; + esp32c3_i2s_update_irq(s); + } else { +#if I2S_WARNING + warn_report("[I2S] GDMA read failed while draining TX"); +#endif + } + + esp32c3_i2s_schedule(s, len); +} + +static void esp32c3_i2s_tx_set_running(Esp32C3I2SState *s, bool running) +{ + if (running == s->tx_running) { + return; + } + + s->tx_running = running; + if (running) { + /* Kick off draining as soon as possible. */ + timer_mod(&s->tx_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)); + } else { + timer_del(&s->tx_timer); + } +} + + +static uint64_t esp32c3_i2s_read(void *opaque, hwaddr addr, unsigned int size) +{ + Esp32C3I2SState *s = ESP32C3_I2S(opaque); + uint64_t r; + + switch (addr) { + case A_I2S_INT_ST: + r = s->regs[R_I2S_INT_RAW] & s->regs[R_I2S_INT_ENA] & ESP32C3_I2S_INT_MASK; + break; + case A_I2S_DATE: + r = ESP32C3_I2S_DATE_VALUE; + break; + default: + r = (addr + size <= ESP32C3_I2S_MEM_SIZE) ? *i2s_reg(s, addr) : 0; + break; + } + +#if I2S_DEBUG + info_report("[I2S] read %04lx -> %08lx", (unsigned long)addr, (unsigned long)r); +#endif + return r; +} + +static void esp32c3_i2s_write(void *opaque, hwaddr addr, uint64_t value, + unsigned int size) +{ + Esp32C3I2SState *s = ESP32C3_I2S(opaque); + + if (addr + size > ESP32C3_I2S_MEM_SIZE) { + return; + } + +#if I2S_DEBUG + info_report("[I2S] write %04lx <- %08lx", (unsigned long)addr, (unsigned long)value); +#endif + + switch (addr) { + case A_I2S_TX_CONF: + /* Auto-clear TX_UPDATE: the HAL writes it then spins until hardware + * clears it (i2s_ll_tx_update). Store the rest verbatim. */ + s->regs[R_I2S_TX_CONF] = (uint32_t)value & ~R_I2S_TX_CONF_TX_UPDATE_MASK; + esp32c3_i2s_tx_set_running(s, + FIELD_EX32((uint32_t)value, I2S_TX_CONF, TX_START) != 0); + break; + + case A_I2S_RX_CONF: + /* Auto-clear RX_UPDATE for the same reason. RX is not otherwise modelled. */ + s->regs[R_I2S_RX_CONF] = (uint32_t)value & ~R_I2S_RX_CONF_RX_UPDATE_MASK; + break; + + case A_I2S_INT_ENA: + s->regs[R_I2S_INT_ENA] = (uint32_t)value; + esp32c3_i2s_update_irq(s); + break; + + case A_I2S_INT_CLR: + /* Write-1-to-clear the corresponding raw bits. */ + s->regs[R_I2S_INT_RAW] &= ~((uint32_t)value & ESP32C3_I2S_INT_MASK); + esp32c3_i2s_update_irq(s); + break; + + case A_I2S_INT_RAW: + s->regs[R_I2S_INT_RAW] = (uint32_t)value & ESP32C3_I2S_INT_MASK; + esp32c3_i2s_update_irq(s); + break; + + case A_I2S_INT_ST: + case A_I2S_DATE: + /* Read-only: ignore writes. */ + break; + + default: + *i2s_reg(s, addr) = (uint32_t)value; + break; + } +} + +static const MemoryRegionOps esp32c3_i2s_ops = { + .read = esp32c3_i2s_read, + .write = esp32c3_i2s_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, +}; + + +static void esp32c3_i2s_reset_hold(Object *obj, ResetType type) +{ + Esp32C3I2SState *s = ESP32C3_I2S(obj); + + timer_del(&s->tx_timer); + s->tx_running = false; + memset(s->regs, 0, sizeof(s->regs)); + qemu_irq_lower(s->irq); +} + +static void esp32c3_i2s_realize(DeviceState *dev, Error **errp) +{ + Esp32C3I2SState *s = ESP32C3_I2S(dev); + + if (s->gdma == NULL) { + error_setg(errp, "[I2S] GDMA controller must be set before realize"); + return; + } + + timer_init_ns(&s->tx_timer, QEMU_CLOCK_VIRTUAL, esp32c3_i2s_tx_tick, s); +} + +static void esp32c3_i2s_init(Object *obj) +{ + Esp32C3I2SState *s = ESP32C3_I2S(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + memory_region_init_io(&s->iomem, obj, &esp32c3_i2s_ops, s, + TYPE_ESP32C3_I2S, ESP32C3_I2S_MEM_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq); +} + +static Property esp32c3_i2s_properties[] = { + DEFINE_PROP_CHR("chardev", Esp32C3I2SState, chr), + DEFINE_PROP_END_OF_LIST(), +}; + +static void esp32c3_i2s_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + dc->realize = esp32c3_i2s_realize; + rc->phases.hold = esp32c3_i2s_reset_hold; + device_class_set_props(dc, esp32c3_i2s_properties); +} + +static const TypeInfo esp32c3_i2s_info = { + .name = TYPE_ESP32C3_I2S, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Esp32C3I2SState), + .instance_init = esp32c3_i2s_init, + .class_init = esp32c3_i2s_class_init, +}; + +static void esp32c3_i2s_register_types(void) +{ + type_register_static(&esp32c3_i2s_info); +} + +type_init(esp32c3_i2s_register_types) diff --git a/hw/audio/meson.build b/hw/audio/meson.build index 2990974449077..9af3ad6e209e3 100644 --- a/hw/audio/meson.build +++ b/hw/audio/meson.build @@ -1,4 +1,5 @@ system_ss.add(files('soundhw.c')) +system_ss.add(when: 'CONFIG_RISCV_ESP32C3', if_true: files('esp32c3_i2s.c')) system_ss.add(when: 'CONFIG_AC97', if_true: files('ac97.c')) system_ss.add(when: 'CONFIG_ADLIB', if_true: files('fmopl.c', 'adlib.c')) system_ss.add(when: 'CONFIG_ASC', if_true: files('asc.c')) diff --git a/hw/dma/esp_gdma.c b/hw/dma/esp_gdma.c index 6640fcacd07b6..ce16886d4616d 100644 --- a/hw/dma/esp_gdma.c +++ b/hw/dma/esp_gdma.c @@ -289,6 +289,32 @@ bool esp_gdma_get_channel_periph(ESPGdmaState *s, GdmaPeripheral periph, int dir } +/** + * Check the header file for more info about this function + */ +bool esp_gdma_peek_length(ESPGdmaState *s, uint32_t chan, uint32_t *len) +{ + if (s == NULL || len == NULL || chan >= ESP_GDMA_GET_CLASS(s)->m_channel_count) { + return false; + } + + DmaConfigState* state = &s->ch_conf[ESP_GDMA_OUT_IDX][chan]; + + /* Compute the head descriptor guest address the same way esp_gdma_read_channel() does, + * but without mutating any state register. */ + const uint32_t link = state->link & R_GDMA_OUT_LINK_ADDR_MASK; + const uint32_t out_addr = ((ESP_GDMA_RAM_ADDR >> 20) << 20) | FIELD_EX32(link, GDMA_OUT_LINK, ADDR); + + GdmaLinkedList head; + if (!esp_gdma_read_descr(s, out_addr, &head)) { + return false; + } + + *len = head.config.length; + return true; +} + + /** * @brief Read data from guest RAM pointed by the linked list configured in the given DmaConfigState index. * `size` bytes will be read and stored in `buffer`. @@ -366,6 +392,12 @@ bool esp_gdma_read_channel(ESPGdmaState *s, uint32_t chan, uint8_t* buffer, uint const bool eof_bit = out_list.config.suc_eof; + /* Guest address of the descriptor that has just been completed. When it + * carries the EOF flag, the firmware's TX EOF ISR reads it back through the + * GDMA_OUT_EOF_DES_ADDR register (mapped to suc_eof_desc_addr for the OUT + * direction), so capture it before advancing to the next node. */ + const uint32_t eof_desc_addr = out_addr; + /* Retrieve the next node while updating the virtual guest address */ out_addr = out_list.next_addr; valid = esp_gdma_next_list_node(s, chan, ESP_GDMA_OUT_IDX, &out_list); @@ -379,6 +411,9 @@ bool esp_gdma_read_channel(ESPGdmaState *s, uint32_t chan, uint8_t* buffer, uint /* If the EOF bit was set, the real controller doesn't stop the transfer, it simply * sets the status accordingly (and generates an interrupt if enabled) */ if (eof_bit) { + /* Expose the completed descriptor's address via the OUT EOF descriptor + * address register so the TX EOF ISR can recover the finished buffer. */ + state->suc_eof_desc_addr = eof_desc_addr; esp_gdma_set_status(&state->int_state, R_GDMA_INTERRUPT_OUT_EOF_MASK | R_GDMA_INTERRUPT_OUT_TOTAL_EOF_MASK); } diff --git a/hw/riscv/esp32c3.c b/hw/riscv/esp32c3.c index 8442304291f24..5d9b9e857e4c4 100644 --- a/hw/riscv/esp32c3.c +++ b/hw/riscv/esp32c3.c @@ -50,6 +50,7 @@ #include "hw/misc/esp32c3_xts_aes.h" #include "hw/misc/esp32c3_jtag.h" #include "hw/dma/esp32c3_gdma.h" +#include "hw/audio/esp32c3_i2s.h" #include "hw/display/esp_rgb.h" #include "hw/net/can/esp32c3_twai.h" @@ -93,6 +94,7 @@ struct Esp32C3MachineState { ESP32C3UsbJtagState jtag; ESPRgbState rgb; Esp32C3TWAIState twai; + Esp32C3I2SState i2s0; /* SD card ssi-sd device for GPIO CS routing */ DeviceState *sd_ssi_dev; @@ -441,6 +443,7 @@ static void esp32c3_machine_init(MachineState *machine) object_initialize_child(OBJECT(machine), "jtag", &ms->jtag, TYPE_ESP32C3_JTAG); object_initialize_child(OBJECT(machine), "rgb", &ms->rgb, TYPE_ESP_RGB); object_initialize_child(OBJECT(machine), "twai", &ms->twai, TYPE_ESP32C3_TWAI); + object_initialize_child(OBJECT(machine), "i2s0", &ms->i2s0, TYPE_ESP32C3_I2S); /* Realize all the I/O peripherals we depend on */ @@ -653,6 +656,17 @@ static void esp32c3_machine_init(MachineState *machine) } + /* I2S0 realization */ + { + ms->i2s0.gdma = ESP_GDMA(&ms->gdma); + sysbus_realize(SYS_BUS_DEVICE(&ms->i2s0), &error_fatal); + MemoryRegion *mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&ms->i2s0), 0); + memory_region_add_subregion_overlap(sys_mem, DR_REG_I2S0_BASE, mr, 0); + /* The C3 has a single I2S; its interrupt source is named I2S1 in the matrix. */ + sysbus_connect_irq(SYS_BUS_DEVICE(&ms->i2s0), 0, + qdev_get_gpio_in(intmatrix_dev, ETS_I2S1_INTR_SOURCE)); + } + /* SHA realization */ { ms->sha.parent.gdma = ESP_GDMA(&ms->gdma); diff --git a/include/hw/audio/esp32c3_i2s.h b/include/hw/audio/esp32c3_i2s.h new file mode 100644 index 0000000000000..b754917c1b8ce --- /dev/null +++ b/include/hw/audio/esp32c3_i2s.h @@ -0,0 +1,112 @@ +/* + * ESP32-C3 I2S controller emulation + * + * Copyright (c) 2026 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 version 2 or + * (at your option) any later version. + * + * This model emulates the ESP32-C3 I2S0 peripheral well enough for the ESP-IDF + * I2S driver (esp_driver_i2s, standard mode, TX) to run under QEMU without + * stalling. It does NOT emulate the audio serial protocol; instead it drains the + * GDMA OUT channel bound to I2S0 (raising OUT_EOF so the driver's DMA callback + * unblocks i2s_channel_write()) and optionally forwards the raw PCM to a chardev. + */ + +#pragma once + +#include "hw/sysbus.h" +#include "hw/registerfields.h" +#include "chardev/char-fe.h" +#include "qemu/timer.h" +#include "hw/dma/esp_gdma.h" + +#define TYPE_ESP32C3_I2S "esp32c3.i2s" +#define ESP32C3_I2S(obj) OBJECT_CHECK(Esp32C3I2SState, (obj), TYPE_ESP32C3_I2S) + +/* Size of the MMIO region: the peripheral occupies a 4 KB page. */ +#define ESP32C3_I2S_MEM_SIZE 0x1000 +#define ESP32C3_I2S_REG_CNT (ESP32C3_I2S_MEM_SIZE / sizeof(uint32_t)) + +/* DATE/version register value (mirrors real silicon closely enough for the driver). */ +#define ESP32C3_I2S_DATE_VALUE 0x1908140 + +typedef struct Esp32C3I2SState { + SysBusDevice parent_obj; + MemoryRegion iomem; + + /* Device interrupt line, wired to the interrupt matrix. Optional for the + * no-stall path (the GDMA OUT_EOF IRQ is what actually unblocks the driver). */ + qemu_irq irq; + + /* Public: must be set before realize. GDMA controller used to fetch audio buffers. */ + ESPGdmaState *gdma; + + /* Optional PCM output sink (raw interleaved 16-bit LE samples). */ + CharBackend chr; + + /* Periodic timer that drains one DMA buffer per tick while TX is running. */ + QEMUTimer tx_timer; + bool tx_running; + + /* Flat register backing store, indexed by (offset / 4). */ + uint32_t regs[ESP32C3_I2S_REG_CNT]; +} Esp32C3I2SState; + + +/* + * Register offsets and fields, from the ESP32-C3 TRM / i2s_reg.h. + * Only the registers the driver actually touches on the TX path are interpreted; + * everything else is stored/returned verbatim via the backing store. + */ + +REG32(I2S_INT_RAW, 0x00C) + FIELD(I2S_INT_RAW, RX_DONE, 0, 1) + FIELD(I2S_INT_RAW, TX_DONE, 1, 1) + FIELD(I2S_INT_RAW, RX_HUNG, 2, 1) + FIELD(I2S_INT_RAW, TX_HUNG, 3, 1) + +REG32(I2S_INT_ST, 0x010) +REG32(I2S_INT_ENA, 0x014) +REG32(I2S_INT_CLR, 0x018) + +REG32(I2S_RX_CONF, 0x020) + FIELD(I2S_RX_CONF, RX_RESET, 0, 1) + FIELD(I2S_RX_CONF, RX_FIFO_RESET, 1, 1) + FIELD(I2S_RX_CONF, RX_START, 2, 1) + FIELD(I2S_RX_CONF, RX_UPDATE, 8, 1) + +REG32(I2S_TX_CONF, 0x024) + FIELD(I2S_TX_CONF, TX_RESET, 0, 1) + FIELD(I2S_TX_CONF, TX_FIFO_RESET, 1, 1) + FIELD(I2S_TX_CONF, TX_START, 2, 1) + FIELD(I2S_TX_CONF, TX_MONO, 5, 1) + FIELD(I2S_TX_CONF, TX_UPDATE, 8, 1) + +REG32(I2S_RX_CONF1, 0x028) + +REG32(I2S_TX_CONF1, 0x02C) + FIELD(I2S_TX_CONF1, TX_BCK_DIV_NUM, 7, 6) + FIELD(I2S_TX_CONF1, TX_BITS_MOD, 13, 5) + +REG32(I2S_RX_CLKM_CONF, 0x030) + +REG32(I2S_TX_CLKM_CONF, 0x034) + FIELD(I2S_TX_CLKM_CONF, TX_CLKM_DIV_NUM, 0, 8) + FIELD(I2S_TX_CLKM_CONF, TX_CLK_ACTIVE, 26, 1) + FIELD(I2S_TX_CLKM_CONF, TX_CLK_SEL, 27, 2) + FIELD(I2S_TX_CLKM_CONF, TX_CLK_EN, 29, 1) + +REG32(I2S_RX_CLKM_DIV_CONF, 0x038) + +REG32(I2S_TX_CLKM_DIV_CONF, 0x03C) + FIELD(I2S_TX_CLKM_DIV_CONF, TX_CLKM_DIV_Z, 0, 9) + FIELD(I2S_TX_CLKM_DIV_CONF, TX_CLKM_DIV_Y, 9, 9) + FIELD(I2S_TX_CLKM_DIV_CONF, TX_CLKM_DIV_X, 18, 9) + FIELD(I2S_TX_CLKM_DIV_CONF, TX_CLKM_DIV_YN1, 27, 1) + +REG32(I2S_RX_TIMING, 0x058) +REG32(I2S_TX_TIMING, 0x05C) + +REG32(I2S_DATE, 0x080) diff --git a/include/hw/dma/esp_gdma.h b/include/hw/dma/esp_gdma.h index d55524900f23b..7a166ae3986a8 100644 --- a/include/hw/dma/esp_gdma.h +++ b/include/hw/dma/esp_gdma.h @@ -158,6 +158,22 @@ bool esp_gdma_get_channel_periph(ESPGdmaState *s, GdmaPeripheral periph, int dir bool esp_gdma_read_channel(ESPGdmaState *s, uint32_t chan, uint8_t* buffer, uint32_t size); bool esp_gdma_write_channel(ESPGdmaState *s, uint32_t chan, uint8_t* buffer, uint32_t size); +/** + * @brief Peek at the number of valid bytes (`length`) advertised by the head OUT descriptor + * of the given channel, without consuming/advancing it. + * + * Useful for a continuous peripheral (e.g. I2S TX) that wants to drain exactly one DMA + * buffer per step: read the length here, then call esp_gdma_read_channel() with it so that + * exactly one descriptor (and thus one OUT_EOF) is processed. + * + * @param s GDMA state + * @param chan OUT channel index + * @param len Filled with the head descriptor's `length` field on success + * + * @returns true if a descriptor could be read, false otherwise + */ +bool esp_gdma_peek_length(ESPGdmaState *s, uint32_t chan, uint32_t *len); + /** * @brief Function only meant to be used by inherited classes From 7f205648abe994f8d0415107c453508b4878060e Mon Sep 17 00:00:00 2001 From: Gabriel Chang Date: Wed, 24 Jun 2026 16:57:23 -0700 Subject: [PATCH 3/3] Fix poor SPI/SD card read perf - Enable SPI2 DMA for reading from SD card - fix issue with GDMA int register stride - fix issue with deferred delivery in intmatrix - Pull up SPI MISO line when idle, setting it to 0x0 caused driver to interpret as SD card being busy. --- hw/dma/esp32c3_gdma.c | 17 ++-- hw/dma/esp_gdma.c | 31 ++++++-- hw/riscv/esp32c3_intmatrix.c | 54 +++++++++++++ hw/sd/ssi-sd.c | 41 +++++++++- hw/ssi/esp32c3_spi2.c | 113 +++++++++++++++++---------- include/hw/riscv/esp32c3_intmatrix.h | 6 ++ include/hw/ssi/esp32c3_spi2.h | 22 +++++- 7 files changed, 223 insertions(+), 61 deletions(-) diff --git a/hw/dma/esp32c3_gdma.c b/hw/dma/esp32c3_gdma.c index 20bee36e8ffab..76b8b6e6e748d 100644 --- a/hw/dma/esp32c3_gdma.c +++ b/hw/dma/esp32c3_gdma.c @@ -144,9 +144,13 @@ static DmaRegister esp32c3_generic_reg(uint32_t reg) static uint32_t esp32c3_read_int_register(ESP32C3GdmaState *s, hwaddr addr) { - /* Check which channel and which direction is being written to */ - const uint32_t chan = addr / DMA_DIR_REGS_SIZE; - const uint32_t reg = addr % DMA_DIR_REGS_SIZE; + /* The interrupt registers are packed 4 per channel (RAW/ST/ENA/CLR), so the + * per-channel stride is DMA_INT_CHAN_REGS_SIZE (0x10), NOT DMA_DIR_REGS_SIZE + * (which is the much larger per-direction channel-config stride). Using the + * wrong stride keeps chan stuck at 0 and lets reg run past the 4-entry table + * for channels >= 1 (e.g. when SPI2 and I2S occupy different GDMA channels). */ + const uint32_t chan = addr / DMA_INT_CHAN_REGS_SIZE; + const uint32_t reg = addr % DMA_INT_CHAN_REGS_SIZE; const uint32_t generic_reg = esp32c3_generic_int_reg(reg); uint32_t in_value = esp_gdma_read_chan_register(&s->parent, ESP_GDMA_IN_IDX, chan, generic_reg); @@ -204,9 +208,10 @@ static uint64_t esp32c3_gdma_read(void *opaque, hwaddr addr, unsigned int size) static void esp32c3_write_int_register(ESP32C3GdmaState *s, hwaddr addr, uint32_t value) { - /* Check which channel and which direction is being written to */ - const uint32_t chan = addr / DMA_DIR_REGS_SIZE; - const uint32_t reg = addr % DMA_DIR_REGS_SIZE; + /* Interrupt registers are packed 4 per channel; use the 0x10 int stride + * (see esp32c3_read_int_register for the rationale). */ + const uint32_t chan = addr / DMA_INT_CHAN_REGS_SIZE; + const uint32_t reg = addr % DMA_INT_CHAN_REGS_SIZE; uint32_t in_value = 0; uint32_t out_value = 0; diff --git a/hw/dma/esp_gdma.c b/hw/dma/esp_gdma.c index ce16886d4616d..8e707657ddde3 100644 --- a/hw/dma/esp_gdma.c +++ b/hw/dma/esp_gdma.c @@ -131,7 +131,7 @@ static void esp_gdma_write_int_state(DmaIntState* state, DmaRegister reg, uint32 static void esp_gdma_reset_fifo(DmaConfigState* s) { #if GDMA_DEBUG - info_report("Resetting FIFO for chan %d, direction: %d", chan, in_out); + info_report("Resetting GDMA FIFO"); #endif /* Set the FIFO empty bit to 1, full bit to 0, and number of bytes of data to 0 */ s->status = R_GDMA_INFIFO_STATUS_FIFO_EMPTY_MASK; @@ -273,13 +273,20 @@ bool esp_gdma_get_channel_periph(ESPGdmaState *s, GdmaPeripheral periph, int dir return false; } - /* Check all the channels of the GDMA */ + /* Find the channel configured for this peripheral, in this direction. + * + * Match strictly on PERI_SEL == periph AND a non-zero descriptor link address. + * The link-address requirement is essential: GDMA_SPI2 == 0, which is also the + * default/reset value of an *unconfigured* channel's PERI_SEL, so a plain + * PERI_SEL match would alias unused channels. Only a channel the firmware has + * actually armed (gdma_start set a descriptor link) has a non-zero ADDR. This + * also keeps concurrent users distinct, e.g. SPI2 (SD) vs I2S (audio): when SPI2 + * allocates TX on one channel and RX on another, querying the IN direction must + * skip the TX channel's unused (link==0) IN side and find the real RX channel. + * (The IN/OUT LINK registers share the same ADDR field layout.) */ for (int i = 0; i < class->m_channel_count; i++) { - /* IN/OUT PERI registers have the same organization, can use any macro. - * Look for the channel that was configured with the given peripheral. It must be marked as "started" too */ - if ( FIELD_EX32(s->ch_conf[dir][i].peripheral, GDMA_PERI_SEL, PERI_SEL) == periph || - FIELD_EX32(s->ch_conf[dir][i].link, GDMA_OUT_LINK, START)) { - + if (FIELD_EX32(s->ch_conf[dir][i].peripheral, GDMA_PERI_SEL, PERI_SEL) == periph && + FIELD_EX32(s->ch_conf[dir][i].link, GDMA_OUT_LINK, ADDR) != 0) { *chan = i; return true; } @@ -332,6 +339,16 @@ bool esp_gdma_read_channel(ESPGdmaState *s, uint32_t chan, uint8_t* buffer, uint /* Get the guest DRAM address */ uint32_t out_addr = ((ESP_GDMA_RAM_ADDR >> 20) << 20) | FIELD_EX32(state->link, GDMA_OUT_LINK, ADDR); +#if GDMA_DEBUG + { + GdmaLinkedList head; + if (esp_gdma_read_descr(s, out_addr, &head)) { + info_report("[GDMA] read_channel chan=%u size=%u desc@0x%08x buf@0x%08x len=%u", + chan, size, out_addr, head.buf_addr, head.config.length); + } + } +#endif + /* Boolean to mark whether we need to check the owner for in and out buffers */ const bool owner_check_out = FIELD_EX32(state->conf1, GDMA_OUT_CONF1, CHECK_OWNER); diff --git a/hw/riscv/esp32c3_intmatrix.c b/hw/riscv/esp32c3_intmatrix.c index c02ccf8be4fe3..1ecfae67fe6b0 100644 --- a/hw/riscv/esp32c3_intmatrix.c +++ b/hw/riscv/esp32c3_intmatrix.c @@ -11,6 +11,7 @@ #include "qemu/osdep.h" #include "qemu/log.h" #include "qemu/queue.h" +#include "qemu/main-loop.h" #include "qemu/timer.h" #include "qemu/error-report.h" #include "qapi/error.h" @@ -175,6 +176,19 @@ static void esp32c3_intmatrix_core_prio_changed(ESP32C3IntMatrixState* s, uint64 } +/** + * Bottom-half: deferred re-evaluation/delivery of pending interrupts at a clean + * execution point (next main-loop iteration). Scheduled from a remap-while-asserted + * so the CPU takes the routed interrupt cleanly, instead of synchronously re-entering + * an ISR from inside the guest's own write to the interrupt-map register. + */ +static void esp32c3_intmatrix_reeval_bh(void *opaque) +{ + ESP32C3IntMatrixState *s = opaque; + esp32c3_intmatrix_core_prio_changed(s, s->irq_thres); +} + + /** * This function is called when the status (enabled/disabled) of a line has just been changed. * It will update the pending IRQ map. @@ -252,7 +266,44 @@ static void esp32c3_intmatrix_write(void* opaque, hwaddr addr, uint64_t value, u if (index < ESP32C3_INT_MATRIX_INPUTS) { + const int old_line = s->irq_map[index]; s->irq_map[index] = (value & 0x1f); + + /* When a source is remapped to a *different* CPU line, the OLD line may + * no longer have any asserted source. On real hardware a CPU line's + * level is the OR of the levels of all sources mapped to it, so once + * this source leaves, the old line de-asserts. Recompute the old line + * and clear a now-stale pending bit. Without this, an ISR that disables + * itself by remapping its source to a parked/disabled line — exactly + * what the SPI bus-lock's bg_disable()/esp_intr_disable() does at ISR + * entry — leaves the old line permanently pending, so it is re-delivered + * the instant the handler returns and the ISR re-fires forever. */ + if (old_line != s->irq_map[index] && old_line != 0 && + esp32c3_get_output_line_level(s, old_line) == 0) { + CLEAR_BIT(s->irq_pending, old_line); + } + + /* Re-evaluate routing on a remap. The source's asserted level is unchanged, + * but it now targets a (possibly different) CPU line. On real hardware the + * level-triggered matrix immediately routes the current level to the new + * line; without re-evaluating here, a source that is asserted *while* being + * remapped onto an enabled line would never reach the CPU. This is exactly + * what the SPI master does when it switches a completed/asserted transfer + * from polled to interrupt-driven completion (remapping ETS_SPI2 onto an + * active line while trans_done is still high) — without this it deadlocks. */ + { + const int new_line = s->irq_map[index]; + if ((s->irq_levels & BIT(index)) && (s->irq_enabled & BIT(new_line))) { + /* Mark the line pending and deliver it from a bottom-half on the + * next main-loop iteration. Delivering it synchronously here would + * pulse the CPU line from within the guest's interrupt-config write + * (e.g. esp_intr_enable's matrix-register store), re-entering an ISR + * at an unsafe point and leaving the CPU interrupt threshold stuck + * elevated. The BH re-evaluates once we're back at a clean point. */ + SET_BIT(s->irq_pending, new_line); + qemu_bh_schedule(s->reeval_bh); + } + } #if INTMATRIX_DEBUG info_report("\x1b[31m[INTMATRIX] Mapping interrupt %d to CPU line %d\x1b[0m\n", index, s->irq_map[index]); #endif @@ -355,6 +406,9 @@ static void esp32c3_intmatrix_realize(DeviceState *dev, Error **errp) esp32c3_intmatrix_reset_hold(OBJECT(dev), RESET_TYPE_COLD); + /* Bottom-half for deferred (safe-point) delivery of remap-while-asserted IRQs */ + s->reeval_bh = qemu_bh_new(esp32c3_intmatrix_reeval_bh, s); + /* Register MIE callback */ assert(cpu); cpu_klass->esp_cpu_register_mie_callback(cpu, esp32c3_intmatrix_mie_enabled, s); diff --git a/hw/sd/ssi-sd.c b/hw/sd/ssi-sd.c index 66291dae09914..e8c59c496dd7f 100644 --- a/hw/sd/ssi-sd.c +++ b/hw/sd/ssi-sd.c @@ -106,6 +106,23 @@ static uint32_t ssi_sd_transfer(SSIPeripheral *dev, uint32_t val) SDRequest request; uint8_t longresp[16]; + /* + * When the card is not selected (CS inactive), it does not drive MISO; the + * line floats to its pulled-up idle level, so the master clocks in 0xFF. The + * generic ssi_transfer_raw_default() would gate the call out and return 0x00 + * for a deselected device, which is wrong for SPI SD: the ESP-IDF SDSPI + * driver's poll_busy() reads the bus with the card deselected and treats a + * non-zero (0xFF) byte as "card ready". Returning 0x00 makes it spin until + * timeout (tens of thousands of 1-byte transfers per command). We therefore + * declare SSI_CS_NONE (always invoked) and emulate the idle pull-up here. + * + * CS is active-low on this board, so dev->cs == 1 means the card is + * deselected. + */ + if (dev->cs) { + return 0xFF; + } + /* * Special case: allow CMD12 (STOP TRANSMISSION) while reading data. * @@ -366,11 +383,30 @@ static const VMStateDescription vmstate_ssi_sd = { } }; +/* + * CS gpio handler. We declare SSI_CS_NONE so ssi_sd_transfer() is always invoked + * (to emulate the MISO idle pull-up when deselected), which means the generic + * ssi_peripheral_realize() does NOT install the default SSI_GPIO_CS input. So we + * register our own here, mirroring ssi_cs_default(): track the level in dev->cs + * and notify the set_cs() handler on a change. + */ +static void ssi_sd_cs_gpio(void *opaque, int n, int level) +{ + SSIPeripheral *s = SSI_PERIPHERAL(opaque); + bool cs = !!level; + + if (s->cs != cs && s->spc->set_cs) { + s->spc->set_cs(s, cs); + } + s->cs = cs; +} + static void ssi_sd_realize(SSIPeripheral *d, Error **errp) { ssi_sd_state *s = SSI_SD(d); qbus_init(&s->sdbus, sizeof(s->sdbus), TYPE_SD_BUS, DEVICE(d), "sd-bus"); + qdev_init_gpio_in_named(DEVICE(d), ssi_sd_cs_gpio, SSI_GPIO_CS, 1); } static void ssi_sd_reset(DeviceState *dev) @@ -431,7 +467,10 @@ static void ssi_sd_class_init(ObjectClass *klass, void *data) k->realize = ssi_sd_realize; k->transfer = ssi_sd_transfer; k->set_cs = ssi_sd_set_cs; - k->cs_polarity = SSI_CS_LOW; + /* SSI_CS_NONE: always invoke ssi_sd_transfer() so it can emulate the MISO + * idle pull-up (0xFF) when the card is deselected (see ssi_sd_transfer()). + * CS is still tracked via set_cs()/dev->cs for the active-low select. */ + k->cs_polarity = SSI_CS_NONE; dc->vmsd = &vmstate_ssi_sd; device_class_set_legacy_reset(dc, ssi_sd_reset); /* Reason: GPIO chip-select line should be wired up */ diff --git a/hw/ssi/esp32c3_spi2.c b/hw/ssi/esp32c3_spi2.c index cb020aef4fcb4..c5263f5c24b2c 100644 --- a/hw/ssi/esp32c3_spi2.c +++ b/hw/ssi/esp32c3_spi2.c @@ -20,11 +20,11 @@ #include "hw/ssi/ssi.h" #include "hw/ssi/esp32c3_spi2.h" #include "qemu/error-report.h" +#include "qemu/timer.h" #define SPI2_DEBUG 0 #define SPI2_WARNING 1 - static void esp32c3_spi2_update_irq(ESP32C3Spi2State *s) { /* @@ -53,6 +53,31 @@ static void esp32c3_spi2_update_irq(ESP32C3Spi2State *s) } } +/* Virtual-time delay between a SPI_USR trigger and trans_done. Just needs to be + * non-zero so completion is signalled from the timer callback (a clean async + * context) rather than synchronously from inside the triggering MMIO write. */ +#define ESP32C3_SPI2_COMPLETION_NS 1000 + +/* Timer callback: the transfer has "completed" — set TRANS_DONE and (if enabled) + * raise the completion interrupt. Running here, rather than inline in the CMD.USR + * write, ensures the IRQ latches into the CPU even for interrupt-driven transfers + * whose trigger executes from within the guest's SPI ISR. */ +static void esp32c3_spi2_completion_cb(void *opaque) +{ + ESP32C3Spi2State *s = opaque; + s->dma_int_raw |= R_GPSPI2_DMA_INT_RAW_TRANS_DONE_MASK; + esp32c3_spi2_update_irq(s); +} + +/* Mark the just-triggered transfer as finished and schedule its async completion. + * The data movement (SSI/GDMA) has already happened synchronously by this point. */ +static void esp32c3_spi2_finish_transaction(ESP32C3Spi2State *s) +{ + s->cmd &= ~R_GPSPI2_CMD_USR_MASK; + timer_mod(&s->completion_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ESP32C3_SPI2_COMPLETION_NS); +} + static uint64_t esp32c3_spi2_read(void *opaque, hwaddr addr, unsigned int size) { @@ -335,14 +360,8 @@ static void esp32c3_spi2_begin_transaction(ESP32C3Spi2State *s) } #endif - /* Clear the USR bit in CMD to indicate transaction complete */ - s->cmd &= ~R_GPSPI2_CMD_USR_MASK; - - /* Set TRANS_DONE in DMA_INT_RAW */ - s->dma_int_raw |= R_GPSPI2_DMA_INT_RAW_TRANS_DONE_MASK; - - /* Update interrupt status and possibly raise IRQ */ - esp32c3_spi2_update_irq(s); + /* Finish the transaction: clear USR now, signal completion asynchronously. */ + esp32c3_spi2_finish_transaction(s); } @@ -355,32 +374,25 @@ static void esp32c3_spi2_begin_transaction(ESP32C3Spi2State *s) */ static bool esp32c3_spi2_use_dma_mode(ESP32C3Spi2State *s) { - /* TEMPORARY: Force CPU mode for QEMU SD card testing. - * The GDMA integration needs more work to properly support SPI DMA. - * For now, using CPU mode (W0-W15 data registers) works correctly. */ - (void)s; - return false; - -#if 0 /* Disabled until GDMA integration is complete */ - uint32_t dummy; - - fprintf(stderr, "[SPI2] use_dma_mode: s->gdma=%p\n", (void*)s->gdma); - fflush(stderr); - - /* No GDMA controller linked, must use CPU mode */ + /* No GDMA controller linked: must use the CPU (W-register) path. */ if (s->gdma == NULL) { - fprintf(stderr, "[SPI2] use_dma_mode: GDMA is NULL, using CPU mode\n"); - fflush(stderr); return false; } - /* Check if any GDMA channel is configured for SPI2 */ - bool have_out = esp_gdma_get_channel_periph(s->gdma, GDMA_SPI2, ESP_GDMA_OUT_IDX, &dummy); - bool have_in = esp_gdma_get_channel_periph(s->gdma, GDMA_SPI2, ESP_GDMA_IN_IDX, &dummy); - - /* Use DMA mode if at least one channel is configured */ - return have_out || have_in; -#endif + /* Use DMA for a transaction iff the SPI2 GDMA channel is actually *armed* for + * this transfer (the driver called gdma_start(), so the channel's LINK has + * START set). The dma_conf DMA_RX/TX_ENA bits alone are NOT a reliable + * discriminator: the SD driver leaves them set on the DMA-capable bus but then + * issues tiny SPI_TRANS_USE_RXDATA transfers (e.g. poll_busy's 1-byte card-ready + * polls) that read their result from the W0 register, with no GDMA descriptor + * armed. Routing those through the DMA path would deposit the received byte in + * GDMA memory and leave W0 zero, so poll_busy would never see the card go ready + * and would spin until timeout — tens of thousands of single-byte transfers per + * command, saturating the core and starving the I2S ring (the audio stutter). + * Gating on an armed GDMA transfer sends real data blocks through DMA and these + * W-register polls through the CPU path, as on real hardware. */ + return FIELD_EX32(s->dma_conf, GPSPI2_DMA_CONF, DMA_RX_ENA) || + FIELD_EX32(s->dma_conf, GPSPI2_DMA_CONF, DMA_TX_ENA); } @@ -409,8 +421,9 @@ static void esp32c3_spi2_dma_transaction(ESP32C3Spi2State *s) uint32_t data_len = (data_bitlen + 1) / 8; #if SPI2_DEBUG - info_report("[SPI2] DMA TRANSACTION: data_len=%u have_out=%d have_in=%d", - data_len, have_out, have_in); + info_report("[SPI2] DMA TRANSACTION: data_len=%u have_out=%d (idx=%u) have_in=%d (idx=%u) " + "user=0x%08x ms_dlen=0x%x", + data_len, have_out, gdma_out_idx, have_in, gdma_in_idx, s->user, s->ms_dlen); #endif /* Assert CS low */ @@ -486,10 +499,14 @@ static void esp32c3_spi2_dma_transaction(ESP32C3Spi2State *s) memset(buffer, 0xFF, data_len); } #if SPI2_DEBUG - info_report("[SPI2] DMA TX: read %u bytes from GDMA, first: %02x %02x %02x %02x", - data_len, - data_len > 0 ? buffer[0] : 0, data_len > 1 ? buffer[1] : 0, - data_len > 2 ? buffer[2] : 0, data_len > 3 ? buffer[3] : 0); + { + char hex[3 * 32 + 1] = {0}; + uint32_t n = data_len < 32 ? data_len : 32; + for (uint32_t k = 0; k < n; k++) { + snprintf(hex + 3 * k, 4, "%02x ", buffer[k]); + } + info_report("[SPI2] DMA TX: %u bytes: %s", data_len, hex); + } #endif } else { /* Fill with 0xFF for RX-only transfers */ @@ -530,10 +547,8 @@ static void esp32c3_spi2_dma_transaction(ESP32C3Spi2State *s) } complete: - /* Clear USR bit and set TRANS_DONE */ - s->cmd &= ~R_GPSPI2_CMD_USR_MASK; - s->dma_int_raw |= R_GPSPI2_DMA_INT_RAW_TRANS_DONE_MASK; - esp32c3_spi2_update_irq(s); + /* Finish the transaction: clear USR now, signal completion asynchronously. */ + esp32c3_spi2_finish_transaction(s); } @@ -570,8 +585,16 @@ static void esp32c3_spi2_write(void *opaque, hwaddr addr, #endif /* Check if DMA mode should be used */ if (esp32c3_spi2_use_dma_mode(s)) { +#if SPI2_DEBUG + info_report("[SPI2] -> DMA path (dma_conf=0x%x int_ena=0x%x)", + s->dma_conf, s->dma_int_ena); +#endif esp32c3_spi2_dma_transaction(s); } else { +#if SPI2_DEBUG + info_report("[SPI2] -> CPU path (dma_conf=0x%x int_ena=0x%x)", + s->dma_conf, s->dma_int_ena); +#endif esp32c3_spi2_begin_transaction(s); } } @@ -601,8 +624,9 @@ static void esp32c3_spi2_write(void *opaque, hwaddr addr, s->misc = wvalue; break; case A_GPSPI2_DMA_CONF: - /* Accept DMA configuration writes; handle AFIFO reset bits as write-1-to-clear. - * We don't actually implement DMA, but the SPI master driver configures this. */ + /* Store DMA config; the AFIFO reset bits are write-1-pulse, so mask them + * out of the retained value. The DMA_RX_ENA/DMA_TX_ENA bits are kept and + * drive esp32c3_spi2_use_dma_mode(). */ s->dma_conf = wvalue & ~(R_GPSPI2_DMA_CONF_RX_AFIFO_RST_MASK | R_GPSPI2_DMA_CONF_BUF_AFIFO_RST_MASK | R_GPSPI2_DMA_CONF_DMA_AFIFO_RST_MASK); @@ -739,6 +763,9 @@ static void esp32c3_spi2_init(Object *obj) sysbus_init_mmio(sbd, &s->iomem); sysbus_init_irq(sbd, &s->irq); + timer_init_ns(&s->completion_timer, QEMU_CLOCK_VIRTUAL, + esp32c3_spi2_completion_cb, s); + esp32c3_spi2_reset_hold(obj, RESET_TYPE_COLD); s->spi = ssi_create_bus(DEVICE(s), "spi"); diff --git a/include/hw/riscv/esp32c3_intmatrix.h b/include/hw/riscv/esp32c3_intmatrix.h index 388a099779b66..ad94ec5a0a058 100644 --- a/include/hw/riscv/esp32c3_intmatrix.h +++ b/include/hw/riscv/esp32c3_intmatrix.h @@ -88,6 +88,12 @@ typedef struct ESP32C3IntMatrixState { /* Output IRQ used to notify the CPU, indexed from 1 to 31, so allocate one more */ qemu_irq out_irqs[ESP32C3_CPU_INT_COUNT + 1]; + + /* Bottom-half used to deliver a remap-while-asserted interrupt at a clean + * execution point (next main-loop iteration) instead of synchronously from + * inside the guest's write to the interrupt-map register. See the remap + * handling in esp32c3_intmatrix_write(). */ + QEMUBH *reeval_bh; } ESP32C3IntMatrixState; _Static_assert(sizeof(uint64_t) * 8 >= ESP32C3_INT_MATRIX_INPUTS, diff --git a/include/hw/ssi/esp32c3_spi2.h b/include/hw/ssi/esp32c3_spi2.h index 105e3c2f56a88..a3b7661f022b9 100644 --- a/include/hw/ssi/esp32c3_spi2.h +++ b/include/hw/ssi/esp32c3_spi2.h @@ -15,6 +15,7 @@ #include "hw/ssi/ssi.h" #include "hw/registerfields.h" #include "hw/dma/esp_gdma.h" +#include "qemu/timer.h" #define TYPE_ESP32C3_SPI2 "esp32c3-spi2" #define ESP32C3_SPI2(obj) OBJECT_CHECK(ESP32C3Spi2State, (obj), TYPE_ESP32C3_SPI2) @@ -63,11 +64,16 @@ REG32(GPSPI2_MS_DLEN, 0x1C) REG32(GPSPI2_MISC, 0x20) FIELD(GPSPI2_MISC, CS_KEEP_ACTIVE, 10, 1) -/* Register addresses from ESP32-C3 Technical Reference Manual */ +/* Register addresses from ESP32-C3 Technical Reference Manual. + * Bit positions per the C3 TRM (SPI_DMA_CONF): DMA_RX_ENA=27, DMA_TX_ENA=28, + * RX_AFIFO_RST=29, BUF_AFIFO_RST=30, DMA_AFIFO_RST=31. (The earlier header had + * the AFIFO reset bits one position too low, colliding with DMA_TX_ENA.) */ REG32(GPSPI2_DMA_CONF, 0x30) - FIELD(GPSPI2_DMA_CONF, RX_AFIFO_RST, 30, 1) - FIELD(GPSPI2_DMA_CONF, BUF_AFIFO_RST, 29, 1) - FIELD(GPSPI2_DMA_CONF, DMA_AFIFO_RST, 28, 1) + FIELD(GPSPI2_DMA_CONF, DMA_RX_ENA, 27, 1) + FIELD(GPSPI2_DMA_CONF, DMA_TX_ENA, 28, 1) + FIELD(GPSPI2_DMA_CONF, RX_AFIFO_RST, 29, 1) + FIELD(GPSPI2_DMA_CONF, BUF_AFIFO_RST, 30, 1) + FIELD(GPSPI2_DMA_CONF, DMA_AFIFO_RST, 31, 1) REG32(GPSPI2_DMA_INT_ENA, 0x34) FIELD(GPSPI2_DMA_INT_ENA, TRANS_DONE, 12, 1) @@ -131,6 +137,14 @@ typedef struct ESP32C3Spi2State { /* GDMA controller for DMA transfers - must be set by machine before realize */ ESPGdmaState *gdma; + + /* Transaction completion is signalled asynchronously (a short time after the + * SPI_USR trigger), mirroring real hardware: a transfer takes time, so + * trans_done fires later. Raising the completion IRQ synchronously from inside + * the CMD.USR MMIO write breaks the interrupt-driven path, because that write + * can execute from within the guest's own SPI ISR and the level-IRQ wouldn't + * latch into the CPU. The timer defers trans_done + IRQ to a clean context. */ + QEMUTimer completion_timer; } ESP32C3Spi2State; #endif /* ESP32C3_SPI2_H */