[PATCH v2 3/5] hw/riscv/k230: Instantiate DesignWare SSI controllers
Kangjie Huang <[email protected]> Sun, 2 Aug 2026 03:28:46 +0800
| Newsgroups | org.nongnu.qemu-riscv,org.nongnu.qemu-devel |
|---|---|
| Message-ID | <[email protected]> |
Instantiate the three DesignWare SSI profiles used by K230 and map their controller MMIO regions at the documented addresses. Configure the QSPI0, QSPI1, and SPI-OPI instances with their chip-select, FIFO-depth, and interrupt-mask reset profiles. Add K230 SSI qtests for Standard PIO transfers, register masks, FIFO behaviour, interrupt state, unsupported-register semantics, and reset profiles. K230 is used as the hardware carrier for the generic controller tests; PLIC routing is added by the next patch. Signed-off-by: Kangjie Huang <[email protected]> --- docs/system/riscv/k230.rst | 1 + hw/riscv/Kconfig | 1 + hw/riscv/k230.c | 79 ++++-- include/hw/riscv/k230.h | 2 + tests/qtest/k230-dw-ssi-test.c | 469 +++++++++++++++++++++++++++++++++ tests/qtest/meson.build | 4 +- 6 files changed, 540 insertions(+), 16 deletions(-) create mode 100644 tests/qtest/k230-dw-ssi-test.c diff --git a/docs/system/riscv/k230.rst b/docs/system/riscv/k230.rst index cea8202e55..7a49cac95b 100644 --- a/docs/system/riscv/k230.rst +++ b/docs/system/riscv/k230.rst @@ -20,6 +20,7 @@ The ``k230`` machine supports the following devices: * Platform-Level Interrupt Controller (PLIC) * 2 K230 Watchdog Timer * 5 UART +* 3 K230 SSI controllers for SPI only Boot options ------------ diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index de37c08cae..60a260691c 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -162,3 +162,4 @@ config K230 select SERIAL_MM select UNIMP select K230_WDT + select DW_SSI diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c index 656f28190c..4c321002cd 100644 --- a/hw/riscv/k230.c +++ b/hw/riscv/k230.c @@ -21,7 +21,9 @@ #include "system/device_tree.h" #include "system/system.h" #include "system/memory.h" +#include "system/blockdev.h" #include "target/riscv/cpu.h" +#include "hw/block/flash.h" #include "hw/core/loader.h" #include "hw/core/sysbus.h" #include "hw/riscv/k230.h" @@ -102,6 +104,40 @@ static const MemMapEntry memmap[] = { [K230_DEV_CLINT] = { 0xF04000000, 0x00400000 }, }; +typedef struct K230DwSsiProfile { + uint32_t num_cs; + uint32_t fifo_depth; + uint32_t imr_reset; +} K230DwSsiProfile; + +static const K230DwSsiProfile k230_dw_ssi_profiles[] = { + [0] = { /* QSPI0, SDK spi1 */ + .num_cs = 5, + .fifo_depth = 256, + .imr_reset = 0x0000001f, + }, + [1] = { /* QSPI1, SDK spi2 */ + .num_cs = 5, + .fifo_depth = 256, + .imr_reset = 0x0000001f, + }, + [2] = { /* SPI-OPI/FMC, SDK spi0 */ + .num_cs = 1, + .fifo_depth = 256, + .imr_reset = 0x0000003f, + }, +}; + +static void k230_configure_dw_ssi(DwSsiState *ssi, + const K230DwSsiProfile *profile) +{ + DeviceState *dev = DEVICE(ssi); + + qdev_prop_set_uint32(dev, "num-cs", profile->num_cs); + qdev_prop_set_uint32(dev, "fifo-depth", profile->fifo_depth); + qdev_prop_set_uint32(dev, "imr-reset", profile->imr_reset); +} + static void k230_soc_init(Object *obj) { K230SoCState *s = RISCV_K230_SOC(obj); @@ -110,6 +146,12 @@ static void k230_soc_init(Object *obj) object_initialize_child(obj, "c908-cpu", cpu0, TYPE_RISCV_HART_ARRAY); object_initialize_child(obj, "k230-wdt0", &s->wdt[0], TYPE_K230_WDT); object_initialize_child(obj, "k230-wdt1", &s->wdt[1], TYPE_K230_WDT); + object_initialize_child(obj, "k230-qspi0", &s->dw_ssi[0], + TYPE_DW_SSI); + object_initialize_child(obj, "k230-qspi1", &s->dw_ssi[1], + TYPE_DW_SSI); + object_initialize_child(obj, "k230-spi-opi", &s->dw_ssi[2], + TYPE_DW_SSI); qdev_prop_set_uint32(DEVICE(cpu0), "hartid-base", 0); qdev_prop_set_string(DEVICE(cpu0), "cpu-type", TYPE_RISCV_CPU_THEAD_C908); @@ -198,6 +240,13 @@ static void k230_soc_realize(DeviceState *dev, Error **errp) } } + for (int i = 0; i < ARRAY_SIZE(s->dw_ssi); i++) { + k230_configure_dw_ssi(&s->dw_ssi[i], &k230_dw_ssi_profiles[i]); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->dw_ssi[i]), errp)) { + return; + } + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt[0]), 0, memmap[K230_DEV_WDT0].base); sysbus_connect_irq(SYS_BUS_DEVICE(&s->wdt[0]), 0, qdev_get_gpio_in(DEVICE(s->c908_plic), K230_WDT0_IRQ)); @@ -206,6 +255,20 @@ static void k230_soc_realize(DeviceState *dev, Error **errp) sysbus_connect_irq(SYS_BUS_DEVICE(&s->wdt[1]), 0, qdev_get_gpio_in(DEVICE(s->c908_plic), K230_WDT1_IRQ)); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->dw_ssi[0]), 0, + memmap[K230_DEV_QSPI0].base); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->dw_ssi[1]), 0, + memmap[K230_DEV_QSPI1].base); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->dw_ssi[2]), 0, + memmap[K230_DEV_SPI].base); + + create_unimplemented_device("spi-flash-xip", + memmap[K230_DEV_FLASH].base, + memmap[K230_DEV_FLASH].size); + + create_unimplemented_device("hi_sys", memmap[K230_DEV_HI_SYS_CFG].base, + memmap[K230_DEV_HI_SYS_CFG].size); + /* unimplemented devices */ create_unimplemented_device("kpu.l2-cache", memmap[K230_DEV_KPU_L2_CACHE].base, @@ -349,23 +412,8 @@ static void k230_soc_realize(DeviceState *dev, Error **errp) create_unimplemented_device("sd1", memmap[K230_DEV_SD1].base, memmap[K230_DEV_SD1].size); - create_unimplemented_device("qspi0", memmap[K230_DEV_QSPI0].base, - memmap[K230_DEV_QSPI0].size); - - create_unimplemented_device("qspi1", memmap[K230_DEV_QSPI1].base, - memmap[K230_DEV_QSPI1].size); - - create_unimplemented_device("spi", memmap[K230_DEV_SPI].base, - memmap[K230_DEV_SPI].size); - - create_unimplemented_device("hi_sys_cfg", memmap[K230_DEV_HI_SYS_CFG].base, - memmap[K230_DEV_HI_SYS_CFG].size); - create_unimplemented_device("ddrc_cfg", memmap[K230_DEV_DDRC_CFG].base, memmap[K230_DEV_DDRC_CFG].size); - - create_unimplemented_device("flash", memmap[K230_DEV_FLASH].base, - memmap[K230_DEV_FLASH].size); } static void k230_soc_class_init(ObjectClass *oc, const void *data) @@ -511,6 +559,7 @@ static void k230_machine_class_init(ObjectClass *oc, const void *data) mc->default_cpus = 1; mc->default_ram_id = "riscv.K230.ram"; /* DDR */ mc->default_ram_size = memmap[K230_DEV_DDRC].size; + } static const TypeInfo k230_machine_typeinfo = { diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h index 592e1c26bf..99996a93b1 100644 --- a/include/hw/riscv/k230.h +++ b/include/hw/riscv/k230.h @@ -17,6 +17,7 @@ #include "hw/core/boards.h" #include "hw/riscv/riscv_hart.h" +#include "hw/ssi/dw_ssi.h" #include "hw/watchdog/k230_wdt.h" #define C908_CPU_HARTID (0) @@ -33,6 +34,7 @@ typedef struct K230SoCState { RISCVHartArrayState c908_cpu; /* Small core */ K230WdtState wdt[2]; + DwSsiState dw_ssi[3]; MemoryRegion sram; MemoryRegion bootrom; diff --git a/tests/qtest/k230-dw-ssi-test.c b/tests/qtest/k230-dw-ssi-test.c new file mode 100644 index 0000000000..6361c2b5ee --- /dev/null +++ b/tests/qtest/k230-dw-ssi-test.c @@ -0,0 +1,469 @@ +/* + * QTest for the Kendryte K230 DesignWare SSI + * + * Copyright (c) 2026 Kangjie Huang <[email protected]> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qemu/bitops.h" +#include "qemu/units.h" + +#define K230_SPI0_BASE 0x91584000ULL +#define K230_SPI1_BASE 0x91582000ULL +#define K230_SPI2_BASE 0x91583000ULL +#define K230_SSI_CTRLR0 0x000 +#define K230_SSI_CTRLR1 0x004 +#define K230_SSI_SSIENR 0x008 +#define K230_SSI_SER 0x010 +#define K230_SSI_BAUDR 0x014 +#define K230_SSI_TXFTLR 0x018 +#define K230_SSI_TXFLR 0x020 +#define K230_SSI_RXFLR 0x024 +#define K230_SSI_SR 0x028 +#define K230_SSI_IMR 0x02c +#define K230_SSI_ISR 0x030 +#define K230_SSI_RISR 0x034 +#define K230_SSI_RXUICR 0x040 +#define K230_SSI_IDR 0x058 +#define K230_SSI_VERSION_ID 0x05c +#define K230_SSI_DR0 0x060 +#define K230_SSI_SPI_CTRLR0 0x0f4 + +#define K230_SSI_CTRLR0_RESET 0x00004007U +#define K230_SSI_IDR_RESET 0xa1b2c3d5U +#define K230_SSI_VERSION_RESET 0x3130332aU + +#define K230_SSI_CTRLR0_WRITABLE_MASK 0x000f7f1fU +#define K230_SSI_BAUDR_WRITABLE_MASK 0x0000fffeU + +#define K230_SSI_CTRLR0_DFS_MASK 0x1fU +#define K230_SSI_CTRLR0_TMOD_SHIFT 10 +#define K230_SSI_CTRLR0_SRL BIT(13) +#define K230_SSI_TXFTLR_TXFTHR_SHIFT 16 +#define K230_SSI_TMOD_TR 0 +#define K230_SSI_TMOD_TO 1 +#define K230_SSI_TMOD_RO 2 +#define K230_SSI_TMOD_EEPROM_READ 3 + +#define K230_SSI_SR_BUSY BIT(0) +#define K230_SSI_SR_TFNF BIT(1) +#define K230_SSI_SR_TFE BIT(2) +#define K230_SSI_SR_RFNE BIT(3) + +#define K230_SSI_INT_TXE BIT(0) +#define K230_SSI_INT_TXO BIT(1) +#define K230_SSI_INT_RXO BIT(3) +#define K230_SSI_INT_RXU BIT(2) + +#define K230_SSI_RXFTLR 0x01c +#define K230_SSI_RXOICR 0x03c +#define K230_SSI_TXEICR 0x038 +#define K230_SSI_ICR 0x048 +#define K230_SSI_DMACR 0x04c +#define K230_SSI_XIP_MODE_BITS 0x0fc + +#define K230_SSI_FIFO_DEPTH 256 + +typedef struct K230SsiInstance { + uint64_t base; + uint32_t num_cs; + uint32_t imr_reset; +} K230SsiInstance; + +static const K230SsiInstance k230_ssi_instances[3] = { + { + .base = K230_SPI0_BASE, + .num_cs = 1, + .imr_reset = 0x0000003fU, + }, { + .base = K230_SPI1_BASE, + .num_cs = 5, + .imr_reset = 0x0000001fU, + }, { + .base = K230_SPI2_BASE, + .num_cs = 5, + .imr_reset = 0x0000001fU, + }, +}; + +static QTestState *k230_ssi_start(void) +{ + return qtest_init("-machine k230"); +} + +static uint32_t k230_ssi_readl(QTestState *qts, uint64_t base, + uint32_t offset) +{ + return qtest_readl(qts, base + offset); +} + +static void k230_ssi_writel(QTestState *qts, uint64_t base, + uint32_t offset, uint32_t value) +{ + qtest_writel(qts, base + offset, value); +} + +static void k230_ssi_disable(QTestState *qts, uint64_t base) +{ + k230_ssi_writel(qts, base, K230_SSI_SSIENR, 0); +} + +static void k230_ssi_configure(QTestState *qts, uint64_t base, + uint32_t tmod, uint32_t dfs_bits, + uint32_t ndf) +{ + uint32_t ctrlr0; + + g_assert_cmpuint(dfs_bits, >=, 4); + g_assert_cmpuint(dfs_bits, <=, 32); + g_assert_cmpuint(tmod, <=, K230_SSI_TMOD_EEPROM_READ); + + k230_ssi_disable(qts, base); + ctrlr0 = (dfs_bits - 1) & K230_SSI_CTRLR0_DFS_MASK; + ctrlr0 |= tmod << K230_SSI_CTRLR0_TMOD_SHIFT; + k230_ssi_writel(qts, base, K230_SSI_CTRLR0, ctrlr0); + k230_ssi_writel(qts, base, K230_SSI_CTRLR1, ndf); + k230_ssi_writel(qts, base, K230_SSI_BAUDR, 2); +} + +static void k230_ssi_enable_cs(QTestState *qts, uint64_t base, uint32_t ser) +{ + k230_ssi_writel(qts, base, K230_SSI_SER, ser); + k230_ssi_writel(qts, base, K230_SSI_SSIENR, 1); +} + +static void k230_ssi_write_frame(QTestState *qts, uint64_t base, + uint32_t value) +{ + k230_ssi_writel(qts, base, K230_SSI_DR0, value); +} + +static uint32_t k230_ssi_read_frame(QTestState *qts, uint64_t base) +{ + return k230_ssi_readl(qts, base, K230_SSI_DR0); +} + +static void k230_ssi_wait_mask(QTestState *qts, uint64_t base, + uint32_t offset, uint32_t mask, + uint32_t expected) +{ + for (int i = 0; i < 1000; i++) { + uint32_t value = k230_ssi_readl(qts, base, offset); + + if ((value & mask) == expected) { + return; + } + qtest_clock_step(qts, 1000); + } + + g_assert_cmphex(k230_ssi_readl(qts, base, offset) & mask, + ==, expected); +} + +static void configure_loopback(QTestState *qts, uint32_t tmod, + uint32_t ndf) +{ + uint32_t ctrlr0; + + k230_ssi_configure(qts, K230_SPI1_BASE, tmod, 8, ndf); + ctrlr0 = k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_CTRLR0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_CTRLR0, + ctrlr0 | K230_SSI_CTRLR0_SRL); +} + +static void test_register_contract(void) +{ + QTestState *qts = k230_ssi_start(); + + for (int i = 0; i < ARRAY_SIZE(k230_ssi_instances); i++) { + const K230SsiInstance *inst = &k230_ssi_instances[i]; + + g_assert_cmphex(k230_ssi_readl(qts, inst->base, K230_SSI_CTRLR0), + ==, K230_SSI_CTRLR0_RESET); + g_assert_cmphex(k230_ssi_readl(qts, inst->base, K230_SSI_SSIENR), + ==, 0); + g_assert_cmphex(k230_ssi_readl(qts, inst->base, + K230_SSI_SPI_CTRLR0), + ==, 0); + g_assert_cmphex(k230_ssi_readl(qts, inst->base, K230_SSI_IMR), + ==, inst->imr_reset); + k230_ssi_writel(qts, inst->base, K230_SSI_SER, UINT32_MAX); + g_assert_cmphex(k230_ssi_readl(qts, inst->base, K230_SSI_SER), + ==, MAKE_64BIT_MASK(0, inst->num_cs)); + } + + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI0_BASE, K230_SSI_IDR), + ==, K230_SSI_IDR_RESET); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI0_BASE, + K230_SSI_VERSION_ID), + ==, K230_SSI_VERSION_RESET); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_CTRLR0, UINT32_MAX); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_CTRLR0), + ==, K230_SSI_CTRLR0_WRITABLE_MASK); + /* SSTE is R/W per TRM; the frame-boundary toggle is not modelled. */ + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_CTRLR0, + K230_SSI_CTRLR0_RESET & ~BIT(14)); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_CTRLR0) & + BIT(14), ==, 0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_BAUDR, UINT32_MAX); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_BAUDR), + ==, K230_SSI_BAUDR_WRITABLE_MASK); + + qtest_system_reset(qts); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI0_BASE, K230_SSI_CTRLR0), + ==, K230_SSI_CTRLR0_RESET); + qtest_quit(qts); +} + +static void test_pio_data_path(void) +{ + QTestState *qts = k230_ssi_start(); + uint32_t status; + + configure_loopback(qts, K230_SSI_TMOD_TR, 0); + k230_ssi_enable_cs(qts, K230_SPI1_BASE, BIT(0)); + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0xa5); + k230_ssi_wait_mask(qts, K230_SPI1_BASE, K230_SSI_SR, + K230_SSI_SR_RFNE, K230_SSI_SR_RFNE); + g_assert_cmphex(k230_ssi_read_frame(qts, K230_SPI1_BASE), ==, 0xa5); + + configure_loopback(qts, K230_SSI_TMOD_RO, 3); + k230_ssi_enable_cs(qts, K230_SPI1_BASE, BIT(0)); + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0); + k230_ssi_wait_mask(qts, K230_SPI1_BASE, K230_SSI_RXFLR, + UINT32_MAX, 4); + for (int i = 0; i < 4; i++) { + g_assert_cmphex(k230_ssi_read_frame(qts, K230_SPI1_BASE), ==, 0); + } + + k230_ssi_disable(qts, K230_SPI1_BASE); + status = k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_SR); + g_assert_cmpuint(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXFLR), + ==, 0); + g_assert_cmphex(status & (K230_SSI_SR_BUSY | K230_SSI_SR_TFNF | + K230_SSI_SR_TFE | K230_SSI_SR_RFNE), + ==, K230_SSI_SR_TFNF | K230_SSI_SR_TFE); + qtest_quit(qts); +} + +static void test_interrupt_controller(void) +{ + QTestState *qts = k230_ssi_start(); + + k230_ssi_configure(qts, K230_SPI1_BASE, K230_SSI_TMOD_TR, 8, 0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_TXFTLR, 0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_IMR, 0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_SSIENR, 1); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_TXE, ==, K230_SSI_INT_TXE); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_ISR) & + K230_SSI_INT_TXE, ==, 0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_IMR, + K230_SSI_INT_TXE); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_ISR) & + K230_SSI_INT_TXE, ==, K230_SSI_INT_TXE); + + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_IMR, + K230_SSI_INT_RXU); + (void)k230_ssi_read_frame(qts, K230_SPI1_BASE); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_RXU, ==, K230_SSI_INT_RXU); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_ISR) & + K230_SSI_INT_RXU, ==, K230_SSI_INT_RXU); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXUICR), + ==, 1); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_RXU, ==, 0); + qtest_quit(qts); +} + + +static void test_tx_only_mode(void) +{ + QTestState *qts = k230_ssi_start(); + + configure_loopback(qts, K230_SSI_TMOD_TO, 0); + k230_ssi_enable_cs(qts, K230_SPI1_BASE, BIT(0)); + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0x55); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_SR) & + K230_SSI_SR_TFE, ==, K230_SSI_SR_TFE); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_TXFLR), + ==, 0); + qtest_quit(qts); +} + +static void test_eeprom_read_contract(void) +{ + QTestState *qts = k230_ssi_start(); + + configure_loopback(qts, K230_SSI_TMOD_EEPROM_READ, 3); + k230_ssi_enable_cs(qts, K230_SPI1_BASE, BIT(0)); + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0x03); + k230_ssi_wait_mask(qts, K230_SPI1_BASE, K230_SSI_RXFLR, + UINT32_MAX, 4); + for (int i = 0; i < 4; i++) { + g_assert_cmphex(k230_ssi_read_frame(qts, K230_SPI1_BASE), ==, 0); + } + + configure_loopback(qts, K230_SSI_TMOD_EEPROM_READ, 0); + k230_ssi_enable_cs(qts, K230_SPI1_BASE, BIT(0)); + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0x03); + k230_ssi_wait_mask(qts, K230_SPI1_BASE, K230_SSI_RXFLR, + UINT32_MAX, 1); + g_assert_cmphex(k230_ssi_read_frame(qts, K230_SPI1_BASE), ==, 0); + qtest_quit(qts); +} + +static void test_rx_fifo_overflow(void) +{ + QTestState *qts = k230_ssi_start(); + + configure_loopback(qts, K230_SSI_TMOD_TR, 0); + k230_ssi_enable_cs(qts, K230_SPI1_BASE, BIT(0)); + for (int i = 0; i < K230_SSI_FIFO_DEPTH + 1; i++) { + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0xa5); + } + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_RXO, ==, K230_SSI_INT_RXO); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, + K230_SSI_RXOICR), ==, 1); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_RXO, ==, 0); + qtest_quit(qts); +} + +static void test_txfthr_start_gate(void) +{ + QTestState *qts = k230_ssi_start(); + + /* TXFTHR=4: transfer starts only once 5+ frames are queued. */ + k230_ssi_configure(qts, K230_SPI1_BASE, K230_SSI_TMOD_TR, 8, 0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_TXFTLR, 4 << 16); + k230_ssi_enable_cs(qts, K230_SPI1_BASE, BIT(0)); + + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0x11); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_TXFLR), + ==, 1); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXFLR), + ==, 0); + + for (int i = 0; i < 4; i++) { + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0x22 + i); + } + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_TXFLR), + ==, 0); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXFLR), + ==, 5); + qtest_quit(qts); +} + +static void test_baudr_zero(void) +{ + QTestState *qts = k230_ssi_start(); + + /* SCKDV=0 disables sclk_out: queued frames must not be shifted out. */ + k230_ssi_configure(qts, K230_SPI1_BASE, K230_SSI_TMOD_TR, 8, 0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_BAUDR, 0); + k230_ssi_enable_cs(qts, K230_SPI1_BASE, BIT(0)); + + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0xa5); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_TXFLR), + ==, 1); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXFLR), + ==, 0); + qtest_quit(qts); +} + +static void test_ser_terminates_ro(void) +{ + QTestState *qts = k230_ssi_start(); + + /* + * RO transfer with NDF larger than the RX FIFO: after the FIFO + * fills, SER=0 must terminate the pending transaction. Re-selecting + * the slave must not resume receiving the stale remaining frames + * without a new dummy word. + */ + k230_ssi_configure(qts, K230_SPI1_BASE, K230_SSI_TMOD_RO, 8, 0xffff); + k230_ssi_enable_cs(qts, K230_SPI1_BASE, BIT(0)); + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0); + k230_ssi_wait_mask(qts, K230_SPI1_BASE, K230_SSI_RXFLR, + UINT32_MAX, K230_SSI_FIFO_DEPTH); + + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_SER, 0); + (void)k230_ssi_read_frame(qts, K230_SPI1_BASE); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXFLR), + ==, K230_SSI_FIFO_DEPTH - 1); + + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_SER, BIT(0)); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXFLR), + ==, K230_SSI_FIFO_DEPTH - 1); + + /* A direct BIT(0) -> BIT(1) switch must also terminate the tx. */ + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_SER, BIT(1)); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RXFLR), + ==, K230_SSI_FIFO_DEPTH - 1); + qtest_quit(qts); +} + +static void test_icr_total_clear(void) +{ + QTestState *qts = k230_ssi_start(); + + configure_loopback(qts, K230_SSI_TMOD_TR, 0); + k230_ssi_enable_cs(qts, K230_SPI1_BASE, BIT(0)); + (void)k230_ssi_read_frame(qts, K230_SPI1_BASE); /* latch RXU */ + for (int i = 0; i < K230_SSI_FIFO_DEPTH + 1; i++) { + k230_ssi_write_frame(qts, K230_SPI1_BASE, 0xa5); + } + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_RXU, ==, K230_SSI_INT_RXU); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, + K230_SSI_ICR), ==, 1); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_RXU, ==, 0); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_RISR) & + K230_SSI_INT_RXO, ==, 0); + qtest_quit(qts); +} + +static void test_unsupported_registers(void) +{ + QTestState *qts = k230_ssi_start(); + static const uint32_t unsupported[] = { + K230_SSI_DMACR, K230_SSI_XIP_MODE_BITS, K230_SSI_SPI_CTRLR0, + }; + + for (size_t i = 0; i < ARRAY_SIZE(unsupported); i++) { + uint32_t off = unsupported[i]; + + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, off), ==, 0); + k230_ssi_writel(qts, K230_SPI1_BASE, off, UINT32_MAX); + g_assert_cmphex(k230_ssi_readl(qts, K230_SPI1_BASE, off), ==, 0); + } + qtest_quit(qts); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/k230-dw-ssi/register-contract", test_register_contract); + qtest_add_func("/k230-dw-ssi/pio-data-path", test_pio_data_path); + qtest_add_func("/k230-dw-ssi/interrupt-controller", + test_interrupt_controller); + qtest_add_func("/k230-dw-ssi/tx-only-mode", test_tx_only_mode); + qtest_add_func("/k230-dw-ssi/eeprom-read-contract", + test_eeprom_read_contract); + qtest_add_func("/k230-dw-ssi/rx-fifo-overflow", test_rx_fifo_overflow); + qtest_add_func("/k230-dw-ssi/txfthr-start-gate", test_txfthr_start_gate); + qtest_add_func("/k230-dw-ssi/baudr-zero", test_baudr_zero); + qtest_add_func("/k230-dw-ssi/ser-terminates-ro", test_ser_terminates_ro); + qtest_add_func("/k230-dw-ssi/icr-total-clear", test_icr_total_clear); + qtest_add_func("/k230-dw-ssi/unsupported-registers", + test_unsupported_registers); + return g_test_run(); +} diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 56ff860e21..f5bcc7bece 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -297,7 +297,8 @@ qtests_riscv64 = ['riscv-csr-test'] + \ (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and config_all_devices.has_key('CONFIG_RISCV_IOMMU') ? ['iommu-riscv-test'] : []) + \ - (config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test'] : []) + (config_all_devices.has_key('CONFIG_K230') ? + ['k230-wdt-test', 'k230-dw-ssi-test'] : []) qtests_hexagon = ['boot-serial-test'] @@ -400,6 +401,7 @@ qtests = { [gio], 'erst-test': files('erst-test.c'), 'ivshmem-test': [rt, '../../contrib/ivshmem-server/ivshmem-server.c'], + 'k230-dw-ssi-test': files('k230-dw-ssi-test.c'), 'migration-test': test_migration_files + migration_tls_files + migration_colo_files, 'pxe-test': files('boot-sector.c'), 'pnv-xive2-test': files('pnv-xive2-common.c', 'pnv-xive2-flush-sync.c', -- 2.43.0