[PATCH 09/11] hw/misc: Add K230 HI_SYS SSI control
Kangjie Huang <[email protected]> Sun, 26 Jul 2026 20:28:27 +0800
| Newsgroups | org.nongnu.qemu-riscv,org.nongnu.qemu-devel |
|---|---|
| Message-ID | <4b46245962029fc7e4585aa225c28053929e8a88.1785064313.git.flamboyant.h.01@gmail.com> |
Model the HI_SYS SSI_CTRL wrapper register, including its reset value, write mask, and dynamic mode and sleep status for the three logical SSI controllers. K230 software uses this register to control XIP enable and observe the mode and sleep state of the SSI instances, so controller-local registers alone do not provide the complete guest-visible interface. Reuse the machine SSI routing table to associate logical controller numbers with physical instances. Keep the sleep indication synchronized when IDMA disables SSI after completion or an AXI error. Map the wrapper over the previously unimplemented HI_SYS region and migrate its writable state and the controller sleep state. Cover the register reset value, write mask, and read-back behaviour, three-instance mode routing, and sleep transitions in qtest. Signed-off-by: Kangjie Huang <[email protected]> --- hw/misc/k230_hi_sys.c | 164 +++++++++++++++++++++++++++++++++ hw/misc/meson.build | 1 + hw/riscv/k230.c | 19 +++- hw/ssi/k230_dw_ssi.c | 15 +++ include/hw/misc/k230_hi_sys.h | 54 +++++++++++ include/hw/riscv/k230.h | 2 + include/hw/ssi/k230_dw_ssi.h | 4 + tests/qtest/k230-dw-ssi-test.c | 60 ++++++++++++ 8 files changed, 316 insertions(+), 3 deletions(-) create mode 100644 hw/misc/k230_hi_sys.c create mode 100644 include/hw/misc/k230_hi_sys.h diff --git a/hw/misc/k230_hi_sys.c b/hw/misc/k230_hi_sys.c new file mode 100644 index 0000000000..44f5ac61de --- /dev/null +++ b/hw/misc/k230_hi_sys.c @@ -0,0 +1,164 @@ +/* + * Kendryte K230 HI_SYS + * + * Copyright (c) 2026 Kangjie Huang <[email protected]> + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Models the HI_SYS system control block, including the SSI_CTRL wrapper + * register that K230 firmware uses for XIP enable and SSI mode/sleep + * status. + * + * K230 Technical Reference Manual V0.3.1 (2024-11-18): + * https://github.com/revyos/external-docs/blob/master/K230/en-us/K230_Technical_Reference_Manual_V0.3.1_20241118.pdf + * + * For more information, see <https://www.kendryte.com/en/proDetail/230> + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/misc/k230_hi_sys.h" +#include "migration/vmstate.h" + +/* + * The TRM places SSI_CTRL at HI_SYS_CONFIG + 0x068, while the DWC SSI + * register table uses controller offset 0x068 for DR2. These are separate + * address spaces, so model SSI_CTRL in HI_SYS rather than as an SSI DR alias. + */ +static uint32_t k230_hi_sys_ssi_status(const K230HiSysState *s) +{ + static const unsigned int sleep_bits[] = { + K230_SSI_CTRL_SPI0_SLEEP, + K230_SSI_CTRL_SPI1_SLEEP, + K230_SSI_CTRL_SPI2_SLEEP, + }; + static const unsigned int mode_shifts[] = { + K230_SSI_CTRL_SPI0_MODE_SHIFT, + K230_SSI_CTRL_SPI1_MODE_SHIFT, + K230_SSI_CTRL_SPI2_MODE_SHIFT, + }; + uint32_t value = s->ssi_ctrl; + + for (unsigned int i = 0; i < ARRAY_SIZE(s->ssi); i++) { + if (!s->ssi[i]) { + continue; + } + + value |= k230_dw_ssi_get_spi_mode(s->ssi[i]) << mode_shifts[i]; + if (k230_dw_ssi_is_sleeping(s->ssi[i])) { + value |= sleep_bits[i]; + } + } + + return value; +} + +static uint64_t k230_hi_sys_read(void *opaque, hwaddr addr, unsigned size) +{ + K230HiSysState *s = opaque; + + if (addr == K230_HI_SYS_SSI_CTRL_OFFSET) { + return k230_hi_sys_ssi_status(s); + } + + if (addr < K230_HI_SYS_MMIO_SIZE) { + return 0; + } + + qemu_log_mask(LOG_GUEST_ERROR, + "%s: read from invalid offset 0x%" HWADDR_PRIx "\n", + TYPE_K230_HI_SYS, addr); + return 0; +} + +static void k230_hi_sys_write(void *opaque, hwaddr addr, uint64_t value, + unsigned size) +{ + K230HiSysState *s = opaque; + + if (addr == K230_HI_SYS_SSI_CTRL_OFFSET) { + s->ssi_ctrl = (s->ssi_ctrl & ~K230_SSI_CTRL_WRITABLE_MASK) | + ((uint32_t)value & K230_SSI_CTRL_WRITABLE_MASK); + s->ssi_ctrl &= K230_SSI_CTRL_IMPLEMENTED_MASK; + return; + } + + if (addr < K230_HI_SYS_MMIO_SIZE) { + return; + } + + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to invalid offset 0x%" HWADDR_PRIx "\n", + TYPE_K230_HI_SYS, addr); +} + +static const MemoryRegionOps k230_hi_sys_ops = { + .read = k230_hi_sys_read, + .write = k230_hi_sys_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +void k230_hi_sys_set_ssi(K230HiSysState *s, unsigned int index, + K230DwSsiState *ssi) +{ + g_assert(index < ARRAY_SIZE(s->ssi)); + s->ssi[index] = ssi; +} + +static void k230_hi_sys_reset(Object *obj, ResetType type) +{ + K230HiSysState *s = K230_HI_SYS(obj); + + s->ssi_ctrl = K230_SSI_CTRL_RESET; +} + +static void k230_hi_sys_init(Object *obj) +{ + K230HiSysState *s = K230_HI_SYS(obj); + + memory_region_init_io(&s->mmio, obj, &k230_hi_sys_ops, s, + TYPE_K230_HI_SYS, K230_HI_SYS_MMIO_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio); +} + +static const VMStateDescription vmstate_k230_hi_sys = { + .name = TYPE_K230_HI_SYS, + .fields = (const VMStateField[]) { + VMSTATE_UINT32(ssi_ctrl, K230HiSysState), + VMSTATE_END_OF_LIST() + }, +}; + +static void k230_hi_sys_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + dc->vmsd = &vmstate_k230_hi_sys; + rc->phases.enter = k230_hi_sys_reset; +} + +static const TypeInfo k230_hi_sys_type_info = { + .name = TYPE_K230_HI_SYS, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230HiSysState), + .instance_init = k230_hi_sys_init, + .class_init = k230_hi_sys_class_init, +}; + +static void k230_hi_sys_register_types(void) +{ + type_register_static(&k230_hi_sys_type_info); +} + +type_init(k230_hi_sys_register_types) diff --git a/hw/misc/meson.build b/hw/misc/meson.build index 23265f6035..aaae64b33c 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -28,6 +28,7 @@ system_ss.add(when: 'CONFIG_IOSB', if_true: files('iosb.c')) system_ss.add(when: 'CONFIG_VIRT_CTRL', if_true: files('virt_ctrl.c')) # RISC-V devices +system_ss.add(when: 'CONFIG_K230', if_true: files('k230_hi_sys.c')) system_ss.add(when: 'CONFIG_MCHP_PFSOC_DMC', if_true: files('mchp_pfsoc_dmc.c')) system_ss.add(when: 'CONFIG_MCHP_PFSOC_IOSCB', if_true: files('mchp_pfsoc_ioscb.c')) system_ss.add(when: 'CONFIG_MCHP_PFSOC_SYSREG', if_true: files('mchp_pfsoc_sysreg.c')) diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c index 36fd1eda22..a666a41c59 100644 --- a/hw/riscv/k230.c +++ b/hw/riscv/k230.c @@ -134,6 +134,8 @@ static void k230_soc_init(Object *obj) TYPE_K230_DW_SSI); object_initialize_child(obj, "k230-spi-opi", &s->dw_ssi[2], TYPE_K230_DW_SSI); + object_initialize_child(obj, "k230-hi-sys", &s->hi_sys, + TYPE_K230_HI_SYS); qdev_prop_set_uint32(DEVICE(cpu0), "hartid-base", 0); qdev_prop_set_string(DEVICE(cpu0), "cpu-type", TYPE_RISCV_CPU_THEAD_C908); @@ -254,6 +256,18 @@ static void k230_soc_realize(DeviceState *dev, Error **errp) } } + for (size_t logical_index = 0; + logical_index < ARRAY_SIZE(k230_ssi_routes); logical_index++) { + const K230SsiRoute *route = &k230_ssi_routes[logical_index]; + + k230_hi_sys_set_ssi(&s->hi_sys, logical_index, + &s->dw_ssi[route->ssi_index]); + } + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->hi_sys), errp)) { + return; + } + k230_connect_ssi_irqs(s); sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt[0]), 0, memmap[K230_DEV_WDT0].base); @@ -270,6 +284,8 @@ static void k230_soc_realize(DeviceState *dev, Error **errp) memmap[K230_DEV_QSPI1].base); sysbus_mmio_map(SYS_BUS_DEVICE(&s->dw_ssi[2]), 0, memmap[K230_DEV_SPI].base); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->hi_sys), 0, + memmap[K230_DEV_HI_SYS_CFG].base); /* unimplemented devices */ create_unimplemented_device("kpu.l2-cache", @@ -414,9 +430,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("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); diff --git a/hw/ssi/k230_dw_ssi.c b/hw/ssi/k230_dw_ssi.c index 65b2a8c245..c1c4f40e07 100644 --- a/hw/ssi/k230_dw_ssi.c +++ b/hw/ssi/k230_dw_ssi.c @@ -337,6 +337,16 @@ static bool k230_dw_ssi_enabled(const K230DwSsiState *s) return FIELD_EX32(s->regs[R_SSIENR], SSIENR, SSIC_EN); } +uint32_t k230_dw_ssi_get_spi_mode(const K230DwSsiState *s) +{ + return FIELD_EX32(s->regs[R_CTRLR0], CTRLR0, SPI_FRF); +} + +bool k230_dw_ssi_is_sleeping(const K230DwSsiState *s) +{ + return s->sleep_status; +} + static void k230_dw_ssi_deselect(K230DwSsiState *s) { if (s->active_cs < 0) { @@ -648,6 +658,7 @@ static bool k230_dw_ssi_idma_triggered(const K230DwSsiState *s) static void k230_dw_ssi_idma_end(K230DwSsiState *s, uint32_t cause) { s->regs[R_SSIENR] = 0; + s->sleep_status = true; s->phase = K230_DW_SSI_PHASE_IDLE; s->remaining_frames = 0; k230_dw_ssi_deselect(s); @@ -1202,9 +1213,11 @@ static void k230_dw_ssi_write(void *opaque, hwaddr addr, s->regs[R_SSIENR] = FIELD_DP32(0, SSIENR, SSIC_EN, new_enabled); if (!new_enabled) { k230_dw_ssi_abort_transfer(s); + s->sleep_status = true; return; } + s->sleep_status = false; k230_dw_ssi_update_cs(s); if (k230_dw_ssi_idma_enabled(s)) { k230_dw_ssi_try_idma(s); @@ -1371,6 +1384,7 @@ static void k230_dw_ssi_enter_reset(Object *obj, ResetType type) s->irq_latched = 0; s->idma_completed_frames = 0; memset(&s->enhanced, 0, sizeof(s->enhanced)); + s->sleep_status = false; s->regs[R_CTRLR0] = K230_DW_SSI_CTRLR0_RESET; s->regs[R_SR] = K230_DW_SSI_SR_RESET; @@ -1448,6 +1462,7 @@ static const VMStateDescription vmstate_k230_dw_ssi = { VMSTATE_UINT32(enhanced.tmod, K230DwSsiState), VMSTATE_BOOL(enhanced.mode_bits_enabled, K230DwSsiState), VMSTATE_INT32(active_cs, K230DwSsiState), + VMSTATE_BOOL(sleep_status, K230DwSsiState), VMSTATE_END_OF_LIST() }, }; diff --git a/include/hw/misc/k230_hi_sys.h b/include/hw/misc/k230_hi_sys.h new file mode 100644 index 0000000000..4c85da531e --- /dev/null +++ b/include/hw/misc/k230_hi_sys.h @@ -0,0 +1,54 @@ +/* + * Kendryte K230 HI_SYS + * + * Copyright (c) 2026 Kangjie Huang <[email protected]> + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Models the HI_SYS system control block, including the SSI_CTRL wrapper + * register that K230 firmware uses for XIP enable and SSI mode/sleep + * status. + * + * K230 Technical Reference Manual V0.3.1 (2024-11-18): + * https://github.com/revyos/external-docs/blob/master/K230/en-us/K230_Technical_Reference_Manual_V0.3.1_20241118.pdf + * + * For more information, see <https://www.kendryte.com/en/proDetail/230> + */ + +#ifndef HW_MISC_K230_HI_SYS_H +#define HW_MISC_K230_HI_SYS_H + +#include "hw/core/sysbus.h" +#include "hw/ssi/k230_dw_ssi.h" +#include "qom/object.h" + +#define TYPE_K230_HI_SYS "riscv.k230.hi-sys" +OBJECT_DECLARE_SIMPLE_TYPE(K230HiSysState, K230_HI_SYS) + +#define K230_HI_SYS_MMIO_SIZE 0x400 +#define K230_HI_SYS_SSI_CTRL_OFFSET 0x068 + +#define K230_SSI_CTRL_RESET 0x00004000U +#define K230_SSI_CTRL_IMPLEMENTED_MASK 0x0003fff1U +#define K230_SSI_CTRL_WRITABLE_MASK 0x0003e001U + +#define K230_SSI_CTRL_XIP_EN (1U << 0) +#define K230_SSI_CTRL_SPI0_SLEEP (1U << 4) +#define K230_SSI_CTRL_SPI0_MODE_SHIFT 5 +#define K230_SSI_CTRL_SPI1_SLEEP (1U << 7) +#define K230_SSI_CTRL_SPI1_MODE_SHIFT 8 +#define K230_SSI_CTRL_SPI2_SLEEP (1U << 10) +#define K230_SSI_CTRL_SPI2_MODE_SHIFT 11 + +struct K230HiSysState { + SysBusDevice parent_obj; + + MemoryRegion mmio; + uint32_t ssi_ctrl; + K230DwSsiState *ssi[3]; +}; + +void k230_hi_sys_set_ssi(K230HiSysState *s, unsigned int index, + K230DwSsiState *ssi); + +#endif /* HW_MISC_K230_HI_SYS_H */ diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h index 493d739aa4..f280c3bebe 100644 --- a/include/hw/riscv/k230.h +++ b/include/hw/riscv/k230.h @@ -16,6 +16,7 @@ #define HW_K230_H #include "hw/core/boards.h" +#include "hw/misc/k230_hi_sys.h" #include "hw/riscv/riscv_hart.h" #include "hw/ssi/k230_dw_ssi.h" #include "hw/watchdog/k230_wdt.h" @@ -35,6 +36,7 @@ typedef struct K230SoCState { K230WdtState wdt[2]; K230DwSsiState dw_ssi[3]; + K230HiSysState hi_sys; MemoryRegion sram; MemoryRegion bootrom; diff --git a/include/hw/ssi/k230_dw_ssi.h b/include/hw/ssi/k230_dw_ssi.h index 3a1b2c08cd..49c137d73f 100644 --- a/include/hw/ssi/k230_dw_ssi.h +++ b/include/hw/ssi/k230_dw_ssi.h @@ -96,6 +96,10 @@ struct K230DwSsiState { uint32_t num_cs; uint32_t max_lines; int active_cs; + bool sleep_status; }; +uint32_t k230_dw_ssi_get_spi_mode(const K230DwSsiState *s); +bool k230_dw_ssi_is_sleeping(const K230DwSsiState *s); + #endif /* HW_SSI_K230_DW_SSI_H */ diff --git a/tests/qtest/k230-dw-ssi-test.c b/tests/qtest/k230-dw-ssi-test.c index ddf5e13145..6bae56b1a8 100644 --- a/tests/qtest/k230-dw-ssi-test.c +++ b/tests/qtest/k230-dw-ssi-test.c @@ -14,6 +14,8 @@ #define K230_SPI0_BASE 0x91584000ULL #define K230_SPI1_BASE 0x91582000ULL #define K230_SPI2_BASE 0x91583000ULL +#define K230_HI_SYS_BASE 0x91585000ULL +#define K230_SSI_CTRL_ADDR (K230_HI_SYS_BASE + 0x68) #define K230_PLIC_BASE 0xf00000000ULL #define K230_PLIC_PENDING_BASE 0x1000 #define K230_SSI_CTRLR0 0x000 @@ -53,6 +55,15 @@ #define K230_SSI_CTRLR0_WRITABLE_MASK 0x01cf7f1fU #define K230_SSI_BAUDR_WRITABLE_MASK 0x0000fffeU +#define K230_SSI_CTRL_RESET 0x00004000U +#define K230_SSI_CTRL_IMPLEMENTED_MASK 0x0003fff1U +#define K230_SSI_CTRL_WRITABLE_MASK 0x0003e001U +#define K230_SSI_CTRL_XIP_EN BIT(0) +#define K230_SSI_CTRL_SPI0_SLEEP BIT(4) +#define K230_SSI_CTRL_SPI0_MODE_SHIFT 5 +#define K230_SSI_CTRL_SPI1_MODE_SHIFT 8 +#define K230_SSI_CTRL_SPI2_MODE_SHIFT 11 + #define K230_SSI_CTRLR0_DFS_MASK 0x1fU #define K230_SSI_CTRLR0_TMOD_SHIFT 10 #define K230_SSI_CTRLR0_SRL BIT(13) @@ -767,6 +778,54 @@ static void test_idma(void) k230_ssi_flash_image_clear(&image); } +static void test_hi_sys(void) +{ + QTestState *qts = k230_ssi_start(); + uint32_t ctrlr0; + uint32_t expected_modes; + + g_assert_cmphex(qtest_readl(qts, K230_SSI_CTRL_ADDR), + ==, K230_SSI_CTRL_RESET); + qtest_writel(qts, K230_SSI_CTRL_ADDR, UINT32_MAX); + g_assert_cmphex(qtest_readl(qts, K230_SSI_CTRL_ADDR) & + K230_SSI_CTRL_IMPLEMENTED_MASK, + ==, (K230_SSI_CTRL_RESET & + ~K230_SSI_CTRL_WRITABLE_MASK) | + K230_SSI_CTRL_WRITABLE_MASK); + qtest_system_reset(qts); + + ctrlr0 = k230_ssi_readl(qts, K230_SPI0_BASE, K230_SSI_CTRLR0); + k230_ssi_writel(qts, K230_SPI0_BASE, K230_SSI_CTRLR0, + ctrlr0 | (K230_SSI_FRF_QUAD << + K230_SSI_CTRLR0_SPI_FRF_SHIFT)); + ctrlr0 = k230_ssi_readl(qts, K230_SPI1_BASE, K230_SSI_CTRLR0); + k230_ssi_writel(qts, K230_SPI1_BASE, K230_SSI_CTRLR0, + ctrlr0 | (1U << K230_SSI_CTRLR0_SPI_FRF_SHIFT)); + ctrlr0 = k230_ssi_readl(qts, K230_SPI2_BASE, K230_SSI_CTRLR0); + k230_ssi_writel(qts, K230_SPI2_BASE, K230_SSI_CTRLR0, + ctrlr0 | (K230_SSI_FRF_OCTAL << + K230_SSI_CTRLR0_SPI_FRF_SHIFT)); + expected_modes = (K230_SSI_FRF_QUAD << + K230_SSI_CTRL_SPI0_MODE_SHIFT) | + (1U << K230_SSI_CTRL_SPI1_MODE_SHIFT) | + (K230_SSI_FRF_OCTAL << + K230_SSI_CTRL_SPI2_MODE_SHIFT); + g_assert_cmphex(qtest_readl(qts, K230_SSI_CTRL_ADDR) & + ((3U << K230_SSI_CTRL_SPI0_MODE_SHIFT) | + (3U << K230_SSI_CTRL_SPI1_MODE_SHIFT) | + (3U << K230_SSI_CTRL_SPI2_MODE_SHIFT)), + ==, expected_modes); + + k230_ssi_writel(qts, K230_SPI0_BASE, K230_SSI_SSIENR, 1); + g_assert_cmphex(qtest_readl(qts, K230_SSI_CTRL_ADDR) & + K230_SSI_CTRL_SPI0_SLEEP, ==, 0); + k230_ssi_disable(qts, K230_SPI0_BASE); + g_assert_cmphex(qtest_readl(qts, K230_SSI_CTRL_ADDR) & + K230_SSI_CTRL_SPI0_SLEEP, + ==, K230_SSI_CTRL_SPI0_SLEEP); + qtest_quit(qts); +} + int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); @@ -780,5 +839,6 @@ int main(int argc, char **argv) qtest_add_func("/k230-dw-ssi/spi-nor", test_spi_nor); qtest_add_func("/k230-dw-ssi/qspi-sdr", test_qspi_sdr); qtest_add_func("/k230-dw-ssi/idma", test_idma); + qtest_add_func("/k230-dw-ssi/hi-sys", test_hi_sys); return g_test_run(); } -- 2.43.0