[PATCH 02/11] hw/riscv/k230: Instantiate K230 SSI controllers

Kangjie Huang <[email protected]> Sun, 26 Jul 2026 20:28:20 +0800
Newsgroups org.nongnu.qemu-riscv,org.nongnu.qemu-devel
Message-ID <41750ee3b9b96b8f6caf234b0855f4cae79b761c.1785064313.git.flamboyant.h.01@gmail.com>
Instantiate the three SSI controller profiles used by K230 and map their
MMIO regions at the documented addresses.

Add the K230 SSI qtest target. Cover the three instance profiles, reset
state, chip-select masks, representative register masks, and system
reset behaviour.

Signed-off-by: Kangjie Huang <[email protected]>
---
 docs/system/riscv/k230.rst     |   1 +
 hw/riscv/Kconfig               |   1 +
 hw/riscv/k230.c                |  35 +++++--
 include/hw/riscv/k230.h        |   2 +
 tests/qtest/k230-dw-ssi-test.c | 178 +++++++++++++++++++++++++++++++++
 tests/qtest/meson.build        |   4 +-
 6 files changed, 211 insertions(+), 10 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..0358709d2c 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 and QSPI
 
 Boot options
 ------------
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index de37c08cae..69059ae044 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -162,3 +162,4 @@ config K230
     select SERIAL_MM
     select UNIMP
     select K230_WDT
+    select K230_DW_SSI
diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c
index 656f28190c..acd5445b31 100644
--- a/hw/riscv/k230.c
+++ b/hw/riscv/k230.c
@@ -110,6 +110,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_K230_DW_SSI);
+    object_initialize_child(obj, "k230-qspi1", &s->dw_ssi[1],
+                            TYPE_K230_DW_SSI);
+    object_initialize_child(obj, "k230-spi-opi", &s->dw_ssi[2],
+                            TYPE_K230_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 +204,19 @@ static void k230_soc_realize(DeviceState *dev, Error **errp)
         }
     }
 
+    qdev_prop_set_uint32(DEVICE(&s->dw_ssi[0]), "num-cs", 5);
+    qdev_prop_set_uint32(DEVICE(&s->dw_ssi[1]), "num-cs", 5);
+    qdev_prop_set_uint32(DEVICE(&s->dw_ssi[2]), "num-cs", 1);
+    qdev_prop_set_uint32(DEVICE(&s->dw_ssi[0]), "max-lines", 4);
+    qdev_prop_set_uint32(DEVICE(&s->dw_ssi[1]), "max-lines", 4);
+    qdev_prop_set_uint32(DEVICE(&s->dw_ssi[2]), "max-lines", 8);
+
+    for (int i = 0; i < ARRAY_SIZE(s->dw_ssi); 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 +225,13 @@ 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);
+
     /* unimplemented devices */
     create_unimplemented_device("kpu.l2-cache",
                                 memmap[K230_DEV_KPU_L2_CACHE].base,
@@ -349,15 +375,6 @@ 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);
 
diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h
index 592e1c26bf..4a79b7ed45 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/k230_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];
+    K230DwSsiState 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..dcd6cb1d6e
--- /dev/null
+++ b/tests/qtest/k230-dw-ssi-test.c
@@ -0,0 +1,178 @@
+/*
+ * 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"
+
+#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_DMACR           0x04c
+#define K230_SSI_IDR             0x058
+#define K230_SSI_VERSION_ID      0x05c
+#define K230_SSI_SPI_CTRLR0      0x0f4
+#define K230_SSI_XIP_MODE_BITS   0x0fc
+#define K230_SSI_XIP_INCR_INST   0x100
+#define K230_SSI_SPIDR           0x120
+#define K230_SSI_SPIAR           0x124
+#define K230_SSI_AXIAR0          0x128
+#define K230_SSI_AXIAR1          0x12c
+#define K230_SSI_AXIECR          0x130
+#define K230_SSI_DONECR          0x134
+
+#define K230_SSI_CTRLR0_RESET           0x00004007U
+#define K230_SSI_SPI_CTRLR0_SPI_RESET   0x04000200U
+#define K230_SSI_SPI_CTRLR0_FMC_RESET   0x28000200U
+#define K230_SSI_IMR_RESET              0x0000003fU
+#define K230_SSI_IDR_RESET              0xa1b2c3d5U
+#define K230_SSI_VERSION_RESET          0x3130332aU
+
+#define K230_SSI_CTRLR0_WRITABLE_MASK   0x01cf7f1fU
+#define K230_SSI_BAUDR_WRITABLE_MASK    0x0000fffeU
+
+#define K230_SSI_CTRLR0_DFS_MASK        0x1fU
+#define K230_SSI_CTRLR0_TMOD_SHIFT      10
+
+#define K230_SSI_TMOD_TR                0
+#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_AXIE               BIT(8)
+#define K230_SSI_INT_DONE               BIT(11)
+
+typedef struct K230SsiInstance {
+    uint64_t base;
+    uint32_t num_cs;
+    uint32_t spi_ctrlr0_reset;
+} K230SsiInstance;
+
+static const K230SsiInstance k230_ssi_instances[3] = {
+    {
+        .base = K230_SPI0_BASE,
+        .num_cs = 1,
+        .spi_ctrlr0_reset = K230_SSI_SPI_CTRLR0_FMC_RESET,
+    }, {
+        .base = K230_SPI1_BASE,
+        .num_cs = 5,
+        .spi_ctrlr0_reset = K230_SSI_SPI_CTRLR0_SPI_RESET,
+    }, {
+        .base = K230_SPI2_BASE,
+        .num_cs = 5,
+        .spi_ctrlr0_reset = K230_SSI_SPI_CTRLR0_SPI_RESET,
+    },
+};
+
+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 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),
+                        ==, inst->spi_ctrlr0_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);
+    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);
+    g_assert_cmphex(k230_ssi_readl(qts, K230_SPI0_BASE, K230_SSI_IMR),
+                    ==, K230_SSI_IMR_RESET);
+    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);
+    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