* [PATCH 0/3] hw/misc: add K230 PMU device model
@ 2026-08-29 2:02 JunWei Li
2026-08-29 2:02 ` [PATCH 1/3] hw/misc: add K230 PMU device JunWei Li
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: JunWei Li @ 2026-08-29 2:02 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-riscv, chenhuacai, JunWei Li
This series adds a device model for the K230 power management unit (PMU)
and wires it into the K230 SoC.
The PMU (K230 TRM V0.3.1 chapter 14) controls the core power-on/off path
through two PMIC output pads and reports six PAD inputs plus RTC
interrupts to the CPU. This minimal model covers the APB register bank
with TRM reset values and write masks, interrupt detection state
(PAD64..PAD69 inputs setting PMU_INT_STATE, the interrupt line following
PMU_INT_STATE & PMU_INT0_TO_CPU, W1C clear) and the PAD70/PAD71 outputs
driven by PMU_SYSCTRL_REG. Long/short press timing, edge counting and
debounce are intentionally left for a follow-up.
Patch 1 adds the device model, patch 2 wires it into the K230 SoC
(MMIO 0x91000000, small-core PLIC source 81) and patch 3 adds qtest
coverage. The k230 qtests pass under qtest-riscv64.
JunWei Li (3):
hw/misc: add K230 PMU device
hw/riscv: wire up K230 PMU in the SoC
tests/qtest: add k230-pmu-test
MAINTAINERS | 3 +
hw/misc/Kconfig | 3 +
hw/misc/k230_pmu.c | 242 ++++++++++++++++++++++++++++++++++++
hw/misc/meson.build | 1 +
hw/riscv/Kconfig | 1 +
hw/riscv/k230.c | 12 +-
include/hw/misc/k230_pmu.h | 71 +++++++++++
include/hw/riscv/k230.h | 3 +
tests/qtest/k230-pmu-test.c | 136 ++++++++++++++++++++
tests/qtest/meson.build | 3 +-
10 files changed, 471 insertions(+), 4 deletions(-)
create mode 100644 hw/misc/k230_pmu.c
create mode 100644 include/hw/misc/k230_pmu.h
create mode 100644 tests/qtest/k230-pmu-test.c
--
2.20.1
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 1/3] hw/misc: add K230 PMU device
2026-08-29 2:02 [PATCH 0/3] hw/misc: add K230 PMU device model JunWei Li
@ 2026-08-29 2:02 ` JunWei Li
2026-08-29 2:02 ` [PATCH 2/3] hw/riscv: wire up K230 PMU in the SoC JunWei Li
2026-08-29 2:02 ` [PATCH 3/3] tests/qtest: add k230-pmu-test JunWei Li
2 siblings, 0 replies; 4+ messages in thread
From: JunWei Li @ 2026-08-29 2:02 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-riscv, chenhuacai, JunWei Li
Add a model of the K230 power management unit with the APB register bank, interrupt detection state and the two PMIC output pads, based on the K230 Technical Reference Manual V0.3.1 chapter 14.
The PMU registers keep their reset values and write masks from the TRM. PAD64..PAD69 inputs set the matching PMU_INT_STATE bit when the corresponding detection is enabled, and the interrupt line follows PMU_INT_STATE & PMU_INT0_TO_CPU. Writing PMU_SYSCTRL_REG drives the PAD70/PAD71 outputs. Long/short press timing, edge counting and debounce are not modelled yet.
Signed-off-by: JunWei Li <vae1207@gmail.com>
---
MAINTAINERS | 3 +
hw/misc/Kconfig | 3 +
hw/misc/k230_pmu.c | 242 +++++++++++++++++++++++++++++++++++++
hw/misc/meson.build | 1 +
include/hw/misc/k230_pmu.h | 71 +++++++++++
5 files changed, 320 insertions(+)
create mode 100644 hw/misc/k230_pmu.c
create mode 100644 include/hw/misc/k230_pmu.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 6171cc7494..49c165f71e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1824,10 +1824,13 @@ M: Chao Liu <chao.liu@processmission.com>
L: qemu-riscv@nongnu.org
S: Maintained
F: docs/system/riscv/k230.rst
+F: hw/misc/k230_pmu.c
F: hw/riscv/k230.c
F: hw/watchdog/k230_wdt.c
+F: include/hw/misc/k230_pmu.h
F: include/hw/riscv/k230.h
F: include/hw/watchdog/k230_wdt.h
+F: tests/qtest/k230-pmu-test.c
F: tests/qtest/k230-wdt-test.c
RX Machines
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 1543ee6653..98a2ab3709 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -135,6 +135,9 @@ config RISCV_MIPS_CPC
config RISCV_MIPS_CPS
bool
+config K230_PMU
+ bool
+
config MPS2_FPGAIO
bool
select LED
diff --git a/hw/misc/k230_pmu.c b/hw/misc/k230_pmu.c
new file mode 100644
index 0000000000..14a955dbd1
--- /dev/null
+++ b/hw/misc/k230_pmu.c
@@ -0,0 +1,242 @@
+/*
+ * K230 PMU device
+ *
+ * Copyright (c) 2026 LiJunWei <vae1207@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * 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
+ *
+ * Minimal model of the PMU (TRM chapter 14): the APB register bank,
+ * the interrupt detection state and the two PMIC output pads. The
+ * long/short press timing, edge counting and debounce units are not
+ * modelled yet.
+ */
+#include "qemu/osdep.h"
+
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "migration/vmstate.h"
+#include "hw/core/irq.h"
+#include "hw/core/sysbus.h"
+#include "hw/misc/k230_pmu.h"
+
+/* PMU_INT_DETECT_CLR bit i clears PMU_INT_STATE bit state_bit[i] */
+static const uint8_t state_bit[10] = {
+ [0] = 3, [1] = 4, [2] = 5, [3] = 6, [4] = 7,
+ [5] = 8, [6] = 9, [7] = 10, [8] = 0, [9] = 11,
+};
+
+/*
+ * PAD64..PAD69 edge/level detection. For these inputs the
+ * PMU_INT_DETECT_EN enable bit and the PMU_INT_STATE state bit
+ * coincide.
+ */
+static const uint8_t input_bit[K230_PMU_INPUTS] = { 9, 7, 6, 5, 4, 3 };
+
+static void k230_pmu_update_irq(K230PmuState *s)
+{
+ qemu_set_irq(s->irq,
+ (s->regs[R_INT_STATE] &
+ s->regs[R_INT0_TO_CPU] & 0xfff) != 0);
+}
+
+static void k230_pmu_gpio_in(void *opaque, int n, int level)
+{
+ K230PmuState *s = opaque;
+ uint32_t bit = BIT(input_bit[n]);
+
+ if (level && (s->regs[R_INT_DETECT_EN] & bit)) {
+ s->regs[R_INT_STATE] |= bit;
+ k230_pmu_update_irq(s);
+ }
+}
+
+static uint64_t k230_pmu_read(void *opaque, hwaddr offset, unsigned size)
+{
+ K230PmuState *s = opaque;
+
+ if (offset >= K230_PMU_REG_COUNT * 4) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: read from invalid offset 0x%" HWADDR_PRIx "\n",
+ TYPE_K230_PMU, offset);
+ return 0;
+ }
+
+ return s->regs[offset / 4];
+}
+
+static void k230_pmu_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ K230PmuState *s = opaque;
+ uint32_t mask;
+
+ if (offset >= K230_PMU_REG_COUNT * 4) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: write to invalid offset 0x%" HWADDR_PRIx "\n",
+ TYPE_K230_PMU, offset);
+ return;
+ }
+
+ switch (offset / 4) {
+ case R_INT_DETECT_CLR: /* W1C */
+ for (unsigned int i = 0; i < ARRAY_SIZE(state_bit); i++) {
+ if (value & BIT(i)) {
+ s->regs[R_INT_STATE] &= ~BIT(state_bit[i]);
+ }
+ }
+ k230_pmu_update_irq(s);
+ return;
+ case R_INT_STATE: /* RO */
+ return;
+ case R_SYSCTRL: /* drives PAD70/71 */
+ s->regs[R_SYSCTRL] = value & 0x3;
+ qemu_set_irq(s->pad[0], s->regs[R_SYSCTRL] & BIT(0));
+ qemu_set_irq(s->pad[1], s->regs[R_SYSCTRL] & BIT(1));
+ return;
+ case R_INT0_TO_CTL:
+ case R_INT1_TO_CTL:
+ case R_INT0_TO_CPU:
+ case R_INT_DETECT_EN:
+ case R_OUT_EVENT_CTRL:
+ mask = 0x1fff; /* [12:0] */
+ break;
+ case R_INT_DETECT_TYP:
+ mask = 0x1fffff; /* [20:0] */
+ break;
+ case R_LONG_PRESS_TRIGGER:
+ case R_LONG_PRESS_SHUTDOWN:
+ mask = 0xfffff; /* [19:0] */
+ break;
+ case R_SHORT_PRESS_TRIGGER:
+ case R_NORMAL_TIMER_VAL:
+ mask = 0xffff; /* [15:0] */
+ break;
+ case R_LEVEL_DEBOUNCE:
+ case R_INT1_LEVEL_DEBOUNCE:
+ case R_INT2_LEVEL_DEBOUNCE:
+ case R_INT3_LEVEL_DEBOUNCE:
+ mask = 0xfff; /* [11:0] */
+ break;
+ case R_EDGE_NUMBER:
+ mask = 0x3ff; /* [9:0] */
+ break;
+ case R_OUT_DELAY_SET:
+ mask = 0x3ffffff; /* [25:0] */
+ break;
+ case R_IO_CFG0:
+ case R_IO_CFG0 + 1:
+ case R_IO_CFG0 + 2:
+ case R_IO_CFG0 + 3:
+ case R_IO_CFG0 + 4:
+ case R_IO_CFG0 + 5:
+ case R_IO_CFG6:
+ case R_IO_CFG7:
+ mask = 0x3def; /* [9] and [4] are reserved */
+ break;
+ default:
+ mask = 0xffffffff;
+ break;
+ }
+
+ s->regs[offset / 4] = (s->regs[offset / 4] & ~mask) | (value & mask);
+ k230_pmu_update_irq(s);
+}
+
+static void k230_pmu_reset(Object *obj, ResetType type)
+{
+ K230PmuState *s = K230_PMU(obj);
+
+ memset(s->regs, 0, sizeof(s->regs));
+ s->regs[R_INT0_TO_CTL] = 0xfff;
+ s->regs[R_INT1_TO_CTL] = 0xfff;
+ s->regs[R_INT_DETECT_EN] = 0x810;
+ s->regs[R_LONG_PRESS_TRIGGER] = 0x17700;
+ s->regs[R_LONG_PRESS_SHUTDOWN] = 0x4e200;
+ s->regs[R_SHORT_PRESS_TRIGGER] = 0x3e800;
+ s->regs[R_LEVEL_DEBOUNCE] = 0x100;
+ s->regs[R_EDGE_NUMBER] = 0x1;
+ s->regs[R_INT1_LEVEL_DEBOUNCE] = 0x100;
+ s->regs[R_INT2_LEVEL_DEBOUNCE] = 0x100;
+ s->regs[R_INT3_LEVEL_DEBOUNCE] = 0x100;
+ s->regs[R_SYSCTRL] = 0x1;
+ s->regs[R_OUT_DELAY_SET] = 0x1100640;
+ for (unsigned int i = 0; i < 6; i++) {
+ s->regs[R_IO_CFG0 + i] = 0x1124;
+ }
+ s->regs[R_IO_CFG6] = 0x10a4;
+ s->regs[R_IO_CFG7] = 0x1084;
+ s->regs[R_NORMAL_TIMER_VAL] = 0x780;
+ s->regs[R_OUT_EVENT_CTRL] = 0x5c;
+ s->regs[R_OUT_LOGIC_CTRL] = 0x2;
+
+ k230_pmu_update_irq(s);
+ qemu_set_irq(s->pad[0], s->regs[R_SYSCTRL] & BIT(0));
+ qemu_set_irq(s->pad[1], s->regs[R_SYSCTRL] & BIT(1));
+}
+
+static const MemoryRegionOps k230_pmu_ops = {
+ .read = k230_pmu_read,
+ .write = k230_pmu_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const VMStateDescription vmstate_k230_pmu = {
+ .name = TYPE_K230_PMU,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, K230PmuState, K230_PMU_REG_COUNT),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void k230_pmu_init(Object *obj)
+{
+ K230PmuState *s = K230_PMU(obj);
+
+ memory_region_init_io(&s->iomem, obj, &k230_pmu_ops, s,
+ TYPE_K230_PMU, K230_PMU_MMIO_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->pad[0]);
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->pad[1]);
+
+ qdev_init_gpio_in(DEVICE(obj), k230_pmu_gpio_in, K230_PMU_INPUTS);
+}
+
+static void k230_pmu_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ dc->desc = "K230 PMU";
+ dc->vmsd = &vmstate_k230_pmu;
+ rc->phases.enter = k230_pmu_reset;
+}
+
+static const TypeInfo k230_pmu_type_info = {
+ .name = TYPE_K230_PMU,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(K230PmuState),
+ .instance_init = k230_pmu_init,
+ .class_init = k230_pmu_class_init,
+};
+
+static void k230_pmu_register_types(void)
+{
+ type_register_static(&k230_pmu_type_info);
+}
+
+type_init(k230_pmu_register_types)
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 23265f6035..7eaff69081 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_PMU', if_true: files('k230_pmu.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/include/hw/misc/k230_pmu.h b/include/hw/misc/k230_pmu.h
new file mode 100644
index 0000000000..48f5052f61
--- /dev/null
+++ b/include/hw/misc/k230_pmu.h
@@ -0,0 +1,71 @@
+/*
+ * K230 PMU device
+ *
+ * Copyright (c) 2026 LiJunWei <vae1207@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_K230_PMU_H
+#define HW_MISC_K230_PMU_H
+
+#include "hw/core/registerfields.h"
+#include "hw/core/sysbus.h"
+
+REG32(LOGGER, 0x000)
+REG32(INT0_TO_CTL, 0x040)
+REG32(INT1_TO_CTL, 0x044)
+REG32(INT0_TO_CPU, 0x048)
+REG32(INT_DETECT_EN, 0x04c)
+ FIELD(INT_DETECT_EN, INT1_EDGE_NUM_TICK_EN, 0, 1)
+ FIELD(INT_DETECT_EN, INT7_EN, 1, 1)
+ FIELD(INT_DETECT_EN, INT6_EN, 2, 1)
+ FIELD(INT_DETECT_EN, INT5_EDGE_LEVEL_EN, 3, 1)
+ FIELD(INT_DETECT_EN, INT4_EDGE_LEVEL_EN, 4, 1)
+ FIELD(INT_DETECT_EN, INT3_EDGE_LEVEL_EN, 5, 1)
+ FIELD(INT_DETECT_EN, INT2_EDGE_LEVEL_EN, 6, 1)
+ FIELD(INT_DETECT_EN, INT1_EDGE_LEVEL_EN, 7, 1)
+ FIELD(INT_DETECT_EN, INT1_EDGE_NUM_EN, 8, 1)
+ FIELD(INT_DETECT_EN, INT0_EDGE_LEVEL_EN, 9, 1)
+ FIELD(INT_DETECT_EN, INT0_SHORT_PRESS_EN, 10, 1)
+ FIELD(INT_DETECT_EN, INT0_LONG_PRESS_EN, 11, 1)
+ FIELD(INT_DETECT_EN, INT0_SHUTDOWN_PRESS_EN, 12, 1)
+REG32(INT_DETECT_TYP, 0x050)
+REG32(INT_DETECT_CLR, 0x054)
+REG32(LONG_PRESS_TRIGGER, 0x058)
+REG32(LONG_PRESS_SHUTDOWN, 0x05c)
+REG32(SHORT_PRESS_TRIGGER, 0x060)
+REG32(LEVEL_DEBOUNCE, 0x064)
+REG32(EDGE_NUMBER, 0x068)
+REG32(INT1_LEVEL_DEBOUNCE, 0x06c)
+REG32(INT2_LEVEL_DEBOUNCE, 0x070)
+REG32(INT3_LEVEL_DEBOUNCE, 0x074)
+REG32(SYSCTRL, 0x078)
+REG32(OUT_DELAY_SET, 0x07c)
+REG32(IO_CFG0, 0x080)
+REG32(IO_CFG6, 0x098)
+REG32(IO_CFG7, 0x09c)
+REG32(NORMAL_TIMER_VAL, 0x0a0)
+REG32(OUT_EVENT_CTRL, 0x0a4)
+REG32(OUT_LOGIC_CTRL, 0x0a8)
+REG32(INT_STATE, 0x0ac)
+
+#define K230_PMU_MMIO_SIZE 0xc00
+#define K230_PMU_REG_COUNT (R_INT_STATE + 1)
+#define K230_PMU_INPUTS 6
+#define K230_PMU_OUTPUTS 2
+
+#define TYPE_K230_PMU "riscv.k230.pmu"
+OBJECT_DECLARE_SIMPLE_TYPE(K230PmuState, K230_PMU)
+
+struct K230PmuState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq irq;
+ qemu_irq pad[K230_PMU_OUTPUTS];
+
+ uint32_t regs[K230_PMU_REG_COUNT];
+};
+
+#endif /* HW_MISC_K230_PMU_H */
--
2.20.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 2/3] hw/riscv: wire up K230 PMU in the SoC
2026-08-29 2:02 [PATCH 0/3] hw/misc: add K230 PMU device model JunWei Li
2026-08-29 2:02 ` [PATCH 1/3] hw/misc: add K230 PMU device JunWei Li
@ 2026-08-29 2:02 ` JunWei Li
2026-08-29 2:02 ` [PATCH 3/3] tests/qtest: add k230-pmu-test JunWei Li
2 siblings, 0 replies; 4+ messages in thread
From: JunWei Li @ 2026-08-29 2:02 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-riscv, chenhuacai, JunWei Li
Instantiate the PMU device, map its MMIO region at 0x91000000 and connect its interrupt line to the small-core PLIC source 81 (TRM 2.4 interrupt bit 65 + 16). Replace the previous unimplemented PMU window.
Signed-off-by: JunWei Li <vae1207@gmail.com>
---
hw/riscv/Kconfig | 1 +
hw/riscv/k230.c | 12 +++++++++---
include/hw/riscv/k230.h | 3 +++
3 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index de37c08cae..0e61e6be3d 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_PMU
diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c
index 656f28190c..906a5c1240 100644
--- a/hw/riscv/k230.c
+++ b/hw/riscv/k230.c
@@ -110,6 +110,7 @@ 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-pmu", &s->pmu, TYPE_K230_PMU);
qdev_prop_set_uint32(DEVICE(cpu0), "hartid-base", 0);
qdev_prop_set_string(DEVICE(cpu0), "cpu-type", TYPE_RISCV_CPU_THEAD_C908);
@@ -191,6 +192,14 @@ static void k230_soc_realize(DeviceState *dev, Error **errp)
k230_create_uart(sys_mem, DEVICE(s->c908_plic), i);
}
+ /* PMU */
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->pmu), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->pmu), 0, memmap[K230_DEV_PMU].base);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->pmu), 0,
+ qdev_get_gpio_in(DEVICE(s->c908_plic), K230_PMU_IRQ));
+
/* Watchdog */
for (int i = 0; i < 2; i++) {
if (!sysbus_realize(SYS_BUS_DEVICE(&s->wdt[i]), errp)) {
@@ -259,9 +268,6 @@ static void k230_soc_realize(DeviceState *dev, Error **errp)
create_unimplemented_device("3d-engine", memmap[K230_DEV_3D_ENGINE].base,
memmap[K230_DEV_3D_ENGINE].size);
- create_unimplemented_device("pmu", memmap[K230_DEV_PMU].base,
- memmap[K230_DEV_PMU].size);
-
create_unimplemented_device("rtc", memmap[K230_DEV_RTC].base,
memmap[K230_DEV_RTC].size);
diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h
index 592e1c26bf..5e4845a274 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_pmu.h"
#include "hw/riscv/riscv_hart.h"
#include "hw/watchdog/k230_wdt.h"
@@ -37,6 +38,7 @@ typedef struct K230SoCState {
MemoryRegion bootrom;
DeviceState *c908_plic;
+ K230PmuState pmu;
} K230SoCState;
#define TYPE_RISCV_K230_MACHINE MACHINE_TYPE_NAME("k230")
@@ -127,6 +129,7 @@ enum {
K230_UART2_IRQ = 18,
K230_UART3_IRQ = 19,
K230_UART4_IRQ = 20,
+ K230_PMU_IRQ = 81,
K230_WDT0_IRQ = 107,
K230_WDT1_IRQ = 108,
};
--
2.20.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 3/3] tests/qtest: add k230-pmu-test
2026-08-29 2:02 [PATCH 0/3] hw/misc: add K230 PMU device model JunWei Li
2026-08-29 2:02 ` [PATCH 1/3] hw/misc: add K230 PMU device JunWei Li
2026-08-29 2:02 ` [PATCH 2/3] hw/riscv: wire up K230 PMU in the SoC JunWei Li
@ 2026-08-29 2:02 ` JunWei Li
2 siblings, 0 replies; 4+ messages in thread
From: JunWei Li @ 2026-08-29 2:02 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-riscv, chenhuacai, JunWei Li
Add qtest coverage for the K230 PMU: register reset values, write masks, interrupt triggering and W1C clearing through the input pads, and PAD70/PAD71 output behaviour.
Signed-off-by: JunWei Li <vae1207@gmail.com>
---
tests/qtest/k230-pmu-test.c | 136 ++++++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 3 +-
2 files changed, 138 insertions(+), 1 deletion(-)
create mode 100644 tests/qtest/k230-pmu-test.c
diff --git a/tests/qtest/k230-pmu-test.c b/tests/qtest/k230-pmu-test.c
new file mode 100644
index 0000000000..9f24b18f19
--- /dev/null
+++ b/tests/qtest/k230-pmu-test.c
@@ -0,0 +1,136 @@
+/*
+ * QTest for the K230 PMU Unit.
+ *
+ * Copyright (c) 2026 LiJunWei <vae1207@gmail.com>
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * 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
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "hw/misc/k230_pmu.h"
+
+#define PMU_BASE 0x91000000
+
+static void test_reset_values(void)
+{
+ QTestState *qts = qtest_init("-machine k230");
+
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_INT0_TO_CTL * 4), ==,
+ 0xfff);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_INT1_TO_CTL * 4), ==,
+ 0xfff);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_INT0_TO_CPU * 4), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_INT_DETECT_EN * 4), ==,
+ 0x810);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_INT_DETECT_TYP * 4), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_INT_DETECT_CLR * 4), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_LONG_PRESS_TRIGGER * 4),
+ ==, 0x17700);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_LONG_PRESS_SHUTDOWN * 4),
+ ==, 0x4e200);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_SHORT_PRESS_TRIGGER * 4),
+ ==, 0x3e800);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_LEVEL_DEBOUNCE * 4), ==,
+ 0x100);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_EDGE_NUMBER * 4), ==, 0x1);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_SYSCTRL * 4), ==, 0x1);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_OUT_DELAY_SET * 4), ==,
+ 0x1100640);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_IO_CFG0 * 4), ==, 0x1124);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_IO_CFG6 * 4), ==, 0x10a4);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_IO_CFG7 * 4), ==, 0x1084);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_NORMAL_TIMER_VAL * 4), ==,
+ 0x780);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_OUT_EVENT_CTRL * 4), ==,
+ 0x5c);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_OUT_LOGIC_CTRL * 4), ==,
+ 0x2);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_INT_STATE * 4), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_write_masks(void)
+{
+ QTestState *qts = qtest_init("-machine k230");
+
+ /* Reserved bits are write-ignored and read as zero. */
+ qtest_writel(qts, PMU_BASE + R_INT_DETECT_EN * 4, 0xffffffff);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_INT_DETECT_EN * 4), ==,
+ 0x1fff);
+ qtest_writel(qts, PMU_BASE + R_INT_DETECT_TYP * 4, 0xffffffff);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_INT_DETECT_TYP * 4), ==,
+ 0x1fffff);
+ qtest_writel(qts, PMU_BASE + R_IO_CFG0 * 4, 0xffffffff);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_IO_CFG0 * 4), ==, 0x3def);
+ qtest_writel(qts, PMU_BASE + R_SYSCTRL * 4, 0xffffffff);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_SYSCTRL * 4), ==, 0x3);
+
+ /* PMU_INT_STATE_REG is read-only. */
+ qtest_writel(qts, PMU_BASE + R_INT_STATE * 4, 0xffffffff);
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_INT_STATE * 4), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_interrupt_clear(void)
+{
+ QTestState *qts = qtest_init("-machine k230");
+
+ qtest_irq_intercept_out_named(qts, "/machine/soc/k230-pmu",
+ "sysbus-irq");
+
+ /*
+ * PAD68 (input 4) edge/level detection is enabled by default
+ * (PMU_INT_DETECT_EN bit 4). Raise the input and gate the
+ * corresponding interrupt through PMU_INT0_TO_CPU.
+ */
+ qtest_writel(qts, PMU_BASE + R_INT0_TO_CPU * 4, BIT(4));
+ qtest_set_irq_in(qts, "/machine/soc/k230-pmu", NULL, 4, 1);
+
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_INT_STATE * 4) & BIT(4),
+ ==, BIT(4));
+ g_assert_cmpuint(qtest_get_irq(qts, 0), ==, 1);
+
+ /* CLR bit 1 clears the int4 edge/level state (W1C). */
+ qtest_writel(qts, PMU_BASE + R_INT_DETECT_CLR * 4, BIT(1));
+ g_assert_cmphex(qtest_readl(qts, PMU_BASE + R_INT_STATE * 4) & BIT(4),
+ ==, 0);
+ g_assert_cmpuint(qtest_get_irq(qts, 0), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_pad_outputs(void)
+{
+ QTestState *qts = qtest_init("-machine k230");
+
+ qtest_irq_intercept_out_named(qts, "/machine/soc/k230-pmu",
+ "sysbus-irq");
+
+ /* PAD70/PAD71 follow the PMU_SYSCTRL_REG to_ctrl bits. */
+ qtest_writel(qts, PMU_BASE + R_SYSCTRL * 4, 0x0);
+ g_assert_cmpuint(qtest_get_irq(qts, 1), ==, 0);
+ g_assert_cmpuint(qtest_get_irq(qts, 2), ==, 0);
+
+ qtest_writel(qts, PMU_BASE + R_SYSCTRL * 4, 0x3);
+ g_assert_cmpuint(qtest_get_irq(qts, 1), ==, 1);
+ g_assert_cmpuint(qtest_get_irq(qts, 2), ==, 1);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/k230/pmu/reset-values", test_reset_values);
+ qtest_add_func("/k230/pmu/write-masks", test_write_masks);
+ qtest_add_func("/k230/pmu/interrupt-clear", test_interrupt_clear);
+ qtest_add_func("/k230/pmu/pad-outputs", test_pad_outputs);
+
+ return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 56ff860e21..daa5b6a284 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-pmu-test'] : [])
qtests_hexagon = ['boot-serial-test']
--
2.20.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-08-29 4:21 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-29 2:02 [PATCH 0/3] hw/misc: add K230 PMU device model JunWei Li
2026-08-29 2:02 ` [PATCH 1/3] hw/misc: add K230 PMU device JunWei Li
2026-08-29 2:02 ` [PATCH 2/3] hw/riscv: wire up K230 PMU in the SoC JunWei Li
2026-08-29 2:02 ` [PATCH 3/3] tests/qtest: add k230-pmu-test JunWei Li
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.