From: JunWei Li <vae1207@gmail.com>
To: qemu-devel@nongnu.org
Cc: qemu-riscv@nongnu.org, chenhuacai@canaan-creative.com,
JunWei Li <vae1207@gmail.com>
Subject: [PATCH 1/3] hw/misc: add K230 PMU device
Date: Sat, 29 Aug 2026 10:02:27 +0800 [thread overview]
Message-ID: <20260829020229.389742-2-vae1207@gmail.com> (raw)
In-Reply-To: <20260829020229.389742-1-vae1207@gmail.com>
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
next prev parent reply other threads:[~2026-08-29 4:21 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-29 2:02 [PATCH 0/3] hw/misc: add K230 PMU device model JunWei Li
2026-08-29 2:02 ` JunWei Li [this message]
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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260829020229.389742-2-vae1207@gmail.com \
--to=vae1207@gmail.com \
--cc=chenhuacai@canaan-creative.com \
--cc=qemu-devel@nongnu.org \
--cc=qemu-riscv@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.