* [PATCH v4 1/3] hw/timer: add DesignWare APB timer model
2026-08-13 13:36 [PATCH v4 0/3] hw/timer: add DesignWare APB timer raoyi
@ 2026-08-13 13:36 ` raoyi
2026-08-14 7:40 ` Bin Meng
2026-08-13 13:36 ` [PATCH v4 2/3] hw/riscv: add DesignWare APB timer to K230 board raoyi
2026-08-13 13:36 ` [PATCH v4 3/3] tests/qtest: add k230 dwapb timer test raoyi
2 siblings, 1 reply; 10+ messages in thread
From: raoyi @ 2026-08-13 13:36 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-riscv, alistair.francis, palmer, liwei1518, daniel.barboza,
zhiwei_liu, chao.liu, bmeng.cn, philmd, pbonzini, lvivier,
farosas, caojunze424, raoyi
Add generic DesignWare APB timer device model. The number
of timer channels is configurable via the num-timers property.
Add timer files to MAINTAINERS.
Signed-off-by: raoyi <rao232328@gmail.com>
---
MAINTAINERS | 2 +
hw/timer/Kconfig | 4 +
hw/timer/dw-apb-timer.c | 376 ++++++++++++++++++++++++++++++++
hw/timer/meson.build | 1 +
hw/timer/trace-events | 8 +
include/hw/timer/dw-apb-timer.h | 40 ++++
6 files changed, 431 insertions(+)
create mode 100644 hw/timer/dw-apb-timer.c
create mode 100644 include/hw/timer/dw-apb-timer.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 6171cc7494..cf689433ff 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1826,8 +1826,10 @@ S: Maintained
F: docs/system/riscv/k230.rst
F: hw/riscv/k230.c
F: hw/watchdog/k230_wdt.c
+F: hw/timer/dw-apb-timer.c
F: include/hw/riscv/k230.h
F: include/hw/watchdog/k230_wdt.h
+F: include/hw/timer/dw-apb-timer.h
F: tests/qtest/k230-wdt-test.c
RX Machines
diff --git a/hw/timer/Kconfig b/hw/timer/Kconfig
index b3d823ce2c..b533cf1ba9 100644
--- a/hw/timer/Kconfig
+++ b/hw/timer/Kconfig
@@ -65,3 +65,7 @@ config STELLARIS_GPTM
config AVR_TIMER16
bool
+
+config DW_APB_TIMER
+ bool
+ select PTIMER
diff --git a/hw/timer/dw-apb-timer.c b/hw/timer/dw-apb-timer.c
new file mode 100644
index 0000000000..0b994e1570
--- /dev/null
+++ b/hw/timer/dw-apb-timer.c
@@ -0,0 +1,376 @@
+/*
+ * Synopsys DesignWare APB timer
+ *
+ * Copyright (c) 2026 raoyi <rao232328@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bitops.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "migration/vmstate.h"
+#include "hw/core/ptimer.h"
+#include "hw/core/qdev-clock.h"
+#include "hw/core/sysbus.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/timer/dw-apb-timer.h"
+#include "trace.h"
+
+#define DW_APB_TIMER_STRIDE 0x14
+#define DW_APB_TIMER_MMIO_SIZE 0x100
+
+/* Per-timer register offsets */
+#define DW_APB_TIMER_N_LOAD_COUNT 0x00
+#define DW_APB_TIMER_N_CURRENT_VALUE 0x04
+#define DW_APB_TIMER_N_CONTROL 0x08
+#define DW_APB_TIMER_N_EOI 0x0c
+#define DW_APB_TIMER_N_INT_STATUS 0x10
+/* Global register offsets */
+#define DW_APB_TIMER_INT_STATUS 0xa0
+#define DW_APB_TIMER_EOI 0xa4
+#define DW_APB_TIMER_RAW_INT_STATUS 0xa8
+#define DW_APB_TIMER_COMP_VERSION 0xac
+
+/* Control register bits */
+#define DW_APB_TIMER_CONTROL_ENABLE BIT(0)
+/* 1: periodic, 0: free running. */
+#define DW_APB_TIMER_CONTROL_MODE_PERIODIC BIT(1)
+#define DW_APB_TIMER_CONTROL_INT BIT(2)
+#define DW_APB_TIMER_CONTROL_RW_MASK 0x7
+
+/*
+ * Component version of the DW_apb_timers IP, fixed in silicon.
+ * Decodes as the ASCII string "211*" (2.11a series); the model reports
+ * the version integrated in K230. Linux does not read this register.
+ */
+#define DW_APB_TIMER_COMP_VERSION_VAL 0x3231312A
+
+static void dw_apb_timer_update_irq(DWAPBTimerChannel *t)
+{
+ qemu_set_irq(t->irq, t->int_status &&
+ !(t->control & DW_APB_TIMER_CONTROL_INT));
+}
+
+static void dw_apb_timer_clk_update(void *opaque, ClockEvent event)
+{
+ DWAPBTimerChannel *t = opaque;
+
+ if (!t->ptimer) {
+ return;
+ }
+ ptimer_transaction_begin(t->ptimer);
+ ptimer_set_period_from_clock(t->ptimer, t->clk, 1);
+ ptimer_transaction_commit(t->ptimer);
+}
+
+static void dw_apb_timer_enable(DWAPBTimerChannel *t)
+{
+ trace_dw_apb_timer_enable(t->id, t->load);
+
+ ptimer_transaction_begin(t->ptimer);
+ ptimer_set_limit(t->ptimer, t->load ? t->load : 1, 1);
+ ptimer_run(t->ptimer, 1);
+ ptimer_transaction_commit(t->ptimer);
+}
+
+static void dw_apb_timer_disable(DWAPBTimerChannel *t)
+{
+ trace_dw_apb_timer_disable(t->id);
+
+ ptimer_transaction_begin(t->ptimer);
+ ptimer_stop(t->ptimer);
+ ptimer_transaction_commit(t->ptimer);
+
+ t->int_status = 0;
+ dw_apb_timer_update_irq(t);
+}
+
+static void dw_apb_timer_tick(void *opaque)
+{
+ DWAPBTimerChannel *t = opaque;
+ uint32_t reload;
+
+ trace_dw_apb_timer_tick(t->id);
+
+ t->int_status = 1;
+ dw_apb_timer_update_irq(t);
+
+ if (t->control & DW_APB_TIMER_CONTROL_MODE_PERIODIC) {
+ reload = t->load ? t->load : 1;
+ } else {
+ reload = UINT32_MAX;
+ }
+
+ ptimer_set_limit(t->ptimer, reload, 1);
+ ptimer_run(t->ptimer, 1);
+}
+
+static uint64_t dw_apb_timer_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ DWAPBTimerState *s = DW_APB_TIMER(opaque);
+ uint32_t value = 0;
+
+ if (addr < DW_APB_TIMER_INT_STATUS) {
+ unsigned int idx = addr / DW_APB_TIMER_STRIDE;
+ hwaddr reg = addr % DW_APB_TIMER_STRIDE;
+
+ if (idx < s->num_timers) {
+ DWAPBTimerChannel *t = &s->timers[idx];
+
+ switch (reg) {
+ case DW_APB_TIMER_N_LOAD_COUNT:
+ value = t->load;
+ break;
+ case DW_APB_TIMER_N_CURRENT_VALUE:
+ if (t->control & DW_APB_TIMER_CONTROL_ENABLE) {
+ value = ptimer_get_count(t->ptimer);
+ }
+ break;
+ case DW_APB_TIMER_N_CONTROL:
+ value = t->control;
+ break;
+ case DW_APB_TIMER_N_EOI:
+ t->int_status = 0;
+ dw_apb_timer_update_irq(t);
+ trace_dw_apb_timer_irq_clear(t->id);
+ break;
+ case DW_APB_TIMER_N_INT_STATUS:
+ value = t->int_status &&
+ !(t->control & DW_APB_TIMER_CONTROL_INT);
+ break;
+ default:
+ break;
+ }
+ }
+ } else {
+ switch (addr) {
+ case DW_APB_TIMER_INT_STATUS:
+ for (int i = 0; i < s->num_timers; i++) {
+ DWAPBTimerChannel *t = &s->timers[i];
+
+ value |= (t->int_status &&
+ !(t->control & DW_APB_TIMER_CONTROL_INT)) << i;
+ }
+ break;
+ case DW_APB_TIMER_EOI:
+ for (int i = 0; i < s->num_timers; i++) {
+ DWAPBTimerChannel *t = &s->timers[i];
+
+ t->int_status = 0;
+ dw_apb_timer_update_irq(t);
+ }
+ break;
+ case DW_APB_TIMER_RAW_INT_STATUS:
+ for (int i = 0; i < s->num_timers; i++) {
+ value |= s->timers[i].int_status << i;
+ }
+ break;
+ case DW_APB_TIMER_COMP_VERSION:
+ value = DW_APB_TIMER_COMP_VERSION_VAL;
+ break;
+ default:
+ break;
+ }
+ }
+
+ trace_dw_apb_timer_read(addr, value);
+ return value;
+}
+
+static void dw_apb_timer_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned int size)
+{
+ DWAPBTimerState *s = DW_APB_TIMER(opaque);
+
+ if (addr < DW_APB_TIMER_INT_STATUS) {
+ unsigned int idx = addr / DW_APB_TIMER_STRIDE;
+ hwaddr reg = addr % DW_APB_TIMER_STRIDE;
+
+ if (idx < s->num_timers) {
+ DWAPBTimerChannel *t = &s->timers[idx];
+
+ switch (reg) {
+ case DW_APB_TIMER_N_LOAD_COUNT:
+ t->load = value;
+ break;
+ case DW_APB_TIMER_N_CONTROL: {
+ uint32_t old_control = t->control;
+ uint32_t new_control = value & DW_APB_TIMER_CONTROL_RW_MASK;
+
+ t->control = new_control;
+ if ((new_control ^ old_control) &
+ DW_APB_TIMER_CONTROL_ENABLE) {
+ if (new_control & DW_APB_TIMER_CONTROL_ENABLE) {
+ dw_apb_timer_enable(t);
+ } else {
+ dw_apb_timer_disable(t);
+ }
+ }
+ if ((new_control ^ old_control) &
+ DW_APB_TIMER_CONTROL_INT) {
+ dw_apb_timer_update_irq(t);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+
+ trace_dw_apb_timer_write(addr, value);
+}
+
+static const MemoryRegionOps dw_apb_timer_ops = {
+ .read = dw_apb_timer_read,
+ .write = dw_apb_timer_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void dw_apb_timer_reset_hold(Object *obj, ResetType type)
+{
+ DWAPBTimerState *s = DW_APB_TIMER(obj);
+
+ for (int i = 0; i < s->num_timers; i++) {
+ DWAPBTimerChannel *t = &s->timers[i];
+
+ ptimer_transaction_begin(t->ptimer);
+ ptimer_stop(t->ptimer);
+ ptimer_transaction_commit(t->ptimer);
+
+ t->load = 0;
+ t->control = 0;
+ t->int_status = 0;
+ dw_apb_timer_update_irq(t);
+ }
+}
+
+static const VMStateDescription vmstate_dw_apb_timer_channel = {
+ .name = "dw-apb-timer-channel",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_PTIMER(ptimer, DWAPBTimerChannel),
+ VMSTATE_CLOCK(clk, DWAPBTimerChannel),
+ VMSTATE_UINT32(load, DWAPBTimerChannel),
+ VMSTATE_UINT32(control, DWAPBTimerChannel),
+ VMSTATE_UINT32(int_status, DWAPBTimerChannel),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_dw_apb_timer = {
+ .name = "dw-apb-timer",
+ .fields = (const VMStateField[]) {
+ VMSTATE_STRUCT_VARRAY_UINT32(timers, DWAPBTimerState,
+ num_timers, 0,
+ vmstate_dw_apb_timer_channel,
+ DWAPBTimerChannel),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const Property dw_apb_timer_properties[] = {
+ DEFINE_PROP_UINT32("num-timers", DWAPBTimerState, num_timers, 1),
+};
+
+static void dw_apb_timer_init(Object *obj)
+{
+ DWAPBTimerState *s = DW_APB_TIMER(obj);
+
+ for (int i = 0; i < DW_APB_TIMER_MAX_TIMERS; i++) {
+ DWAPBTimerChannel *t = &s->timers[i];
+ g_autofree char *name = g_strdup_printf("timer[%d]", i);
+
+ t->id = i;
+ t->clk = qdev_init_clock_in(DEVICE(obj), name,
+ dw_apb_timer_clk_update, t,
+ ClockUpdate);
+ }
+}
+
+static void dw_apb_timer_realize(DeviceState *dev, Error **errp)
+{
+ DWAPBTimerState *s = DW_APB_TIMER(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ if (s->num_timers == 0 || s->num_timers > DW_APB_TIMER_MAX_TIMERS) {
+ error_setg(errp, "dw-apb-timer: num-timers must be between 1 and %u",
+ DW_APB_TIMER_MAX_TIMERS);
+ return;
+ }
+
+ for (int i = 0; i < s->num_timers; i++) {
+ DWAPBTimerChannel *t = &s->timers[i];
+
+ if (!clock_has_source(t->clk)) {
+ error_setg(errp, "dw-apb-timer: timer[%u] clock must be connected",
+ i);
+ return;
+ }
+ }
+
+ for (int i = 0; i < s->num_timers; i++) {
+ DWAPBTimerChannel *t = &s->timers[i];
+
+ t->ptimer = ptimer_init(dw_apb_timer_tick, t,
+ PTIMER_POLICY_NO_IMMEDIATE_TRIGGER |
+ PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
+ PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
+ ptimer_transaction_begin(t->ptimer);
+ ptimer_set_limit(t->ptimer, UINT32_MAX, 1);
+ ptimer_transaction_commit(t->ptimer);
+ sysbus_init_irq(sbd, &t->irq);
+
+ /* The source may have been connected before the ptimer existed. */
+ dw_apb_timer_clk_update(t, ClockUpdate);
+ }
+
+ memory_region_init_io(&s->mmio, OBJECT(dev), &dw_apb_timer_ops,
+ s, TYPE_DW_APB_TIMER, DW_APB_TIMER_MMIO_SIZE);
+ sysbus_init_mmio(sbd, &s->mmio);
+}
+
+static void dw_apb_timer_unrealize(DeviceState *dev)
+{
+ DWAPBTimerState *s = DW_APB_TIMER(dev);
+
+ for (int i = 0; i < s->num_timers; i++) {
+ ptimer_free(s->timers[i].ptimer);
+ }
+}
+
+static void dw_apb_timer_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ dc->realize = dw_apb_timer_realize;
+ dc->unrealize = dw_apb_timer_unrealize;
+ dc->vmsd = &vmstate_dw_apb_timer;
+ dc->desc = "Synopsys DesignWare APB timer";
+ rc->phases.hold = dw_apb_timer_reset_hold;
+ device_class_set_props(dc, dw_apb_timer_properties);
+}
+
+static const TypeInfo dw_apb_timer_info = {
+ .name = TYPE_DW_APB_TIMER,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(DWAPBTimerState),
+ .instance_init = dw_apb_timer_init,
+ .class_init = dw_apb_timer_class_init,
+};
+
+static void dw_apb_timer_register_type(void)
+{
+ type_register_static(&dw_apb_timer_info);
+}
+
+type_init(dw_apb_timer_register_type)
diff --git a/hw/timer/meson.build b/hw/timer/meson.build
index 201b5d8316..fccc36540c 100644
--- a/hw/timer/meson.build
+++ b/hw/timer/meson.build
@@ -34,3 +34,4 @@ specific_ss.add(when: 'CONFIG_IBEX', if_true: files('ibex_timer.c'))
system_ss.add(when: 'CONFIG_SIFIVE_PWM', if_true: files('sifive_pwm.c'))
system_ss.add(when: 'CONFIG_AVR_TIMER16', if_true: files('avr_timer16.c'))
+system_ss.add(when: 'CONFIG_DW_APB_TIMER', if_true: files('dw-apb-timer.c'))
diff --git a/hw/timer/trace-events b/hw/timer/trace-events
index 634ba1da27..7f592a96c8 100644
--- a/hw/timer/trace-events
+++ b/hw/timer/trace-events
@@ -1,5 +1,13 @@
# See docs/devel/tracing.rst for syntax documentation.
+# dw-apb-timer.c
+dw_apb_timer_read(uint64_t addr, uint32_t val) "DW APB timer read: [0x%" PRIx64 "] -> 0x%" PRIx32
+dw_apb_timer_write(uint64_t addr, uint64_t val) "DW APB timer write: [0x%" PRIx64 "] <- 0x%" PRIx64
+dw_apb_timer_tick(int idx) "DW APB timer %d tick"
+dw_apb_timer_irq_clear(int idx) "DW APB timer %d IRQ cleared"
+dw_apb_timer_enable(int idx, uint32_t load) "DW APB timer %d enabled load=0x%" PRIx32
+dw_apb_timer_disable(int idx) "DW APB timer %d disabled"
+
# slavio_timer.c
slavio_timer_get_out(uint64_t limit, uint32_t counthigh, uint32_t count) "limit 0x%"PRIx64" count 0x%x0x%08x"
slavio_timer_irq(uint32_t counthigh, uint32_t count) "callback: count 0x%x0x%08x"
diff --git a/include/hw/timer/dw-apb-timer.h b/include/hw/timer/dw-apb-timer.h
new file mode 100644
index 0000000000..f6b83ae5ca
--- /dev/null
+++ b/include/hw/timer/dw-apb-timer.h
@@ -0,0 +1,40 @@
+/*
+ * Synopsys DesignWare APB timer
+ *
+ * Copyright (c) 2026 raoyi <rao232328@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef DW_APB_TIMER_H
+#define DW_APB_TIMER_H
+
+#include "hw/core/sysbus.h"
+#include "hw/core/irq.h"
+#include "hw/core/clock.h"
+#include "qom/object.h"
+
+#define TYPE_DW_APB_TIMER "dw-apb-timer"
+OBJECT_DECLARE_SIMPLE_TYPE(DWAPBTimerState, DW_APB_TIMER)
+
+#define DW_APB_TIMER_MAX_TIMERS 8
+
+typedef struct DWAPBTimerChannel {
+ struct ptimer_state *ptimer;
+ Clock *clk;
+ qemu_irq irq;
+ unsigned int id;
+ uint32_t load;
+ uint32_t control;
+ uint32_t int_status;
+} DWAPBTimerChannel;
+
+struct DWAPBTimerState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ uint32_t num_timers;
+ DWAPBTimerChannel timers[DW_APB_TIMER_MAX_TIMERS];
+};
+
+#endif
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH v4 1/3] hw/timer: add DesignWare APB timer model
2026-08-13 13:36 ` [PATCH v4 1/3] hw/timer: add DesignWare APB timer model raoyi
@ 2026-08-14 7:40 ` Bin Meng
2026-08-18 14:03 ` 回复: " 饶轶
0 siblings, 1 reply; 10+ messages in thread
From: Bin Meng @ 2026-08-14 7:40 UTC (permalink / raw)
To: raoyi
Cc: qemu-devel, qemu-riscv, alistair.francis, palmer, liwei1518,
daniel.barboza, zhiwei_liu, chao.liu, philmd, pbonzini, lvivier,
farosas, caojunze424
On Thu, Aug 13, 2026 at 9:37 PM raoyi <rao232328@gmail.com> wrote:
>
> Add generic DesignWare APB timer device model. The number
> of timer channels is configurable via the num-timers property.
>
> Add timer files to MAINTAINERS.
>
> Signed-off-by: raoyi <rao232328@gmail.com>
> ---
> MAINTAINERS | 2 +
> hw/timer/Kconfig | 4 +
> hw/timer/dw-apb-timer.c | 376 ++++++++++++++++++++++++++++++++
> hw/timer/meson.build | 1 +
> hw/timer/trace-events | 8 +
> include/hw/timer/dw-apb-timer.h | 40 ++++
> 6 files changed, 431 insertions(+)
> create mode 100644 hw/timer/dw-apb-timer.c
> create mode 100644 include/hw/timer/dw-apb-timer.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 6171cc7494..cf689433ff 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1826,8 +1826,10 @@ S: Maintained
> F: docs/system/riscv/k230.rst
> F: hw/riscv/k230.c
> F: hw/watchdog/k230_wdt.c
> +F: hw/timer/dw-apb-timer.c
> F: include/hw/riscv/k230.h
> F: include/hw/watchdog/k230_wdt.h
> +F: include/hw/timer/dw-apb-timer.h
> F: tests/qtest/k230-wdt-test.c
>
> RX Machines
> diff --git a/hw/timer/Kconfig b/hw/timer/Kconfig
> index b3d823ce2c..b533cf1ba9 100644
> --- a/hw/timer/Kconfig
> +++ b/hw/timer/Kconfig
> @@ -65,3 +65,7 @@ config STELLARIS_GPTM
>
> config AVR_TIMER16
> bool
> +
> +config DW_APB_TIMER
> + bool
> + select PTIMER
> diff --git a/hw/timer/dw-apb-timer.c b/hw/timer/dw-apb-timer.c
> new file mode 100644
> index 0000000000..0b994e1570
> --- /dev/null
> +++ b/hw/timer/dw-apb-timer.c
> @@ -0,0 +1,376 @@
> +/*
> + * Synopsys DesignWare APB timer
> + *
> + * Copyright (c) 2026 raoyi <rao232328@gmail.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/bitops.h"
> +#include "qemu/module.h"
> +#include "qapi/error.h"
> +#include "migration/vmstate.h"
> +#include "hw/core/ptimer.h"
> +#include "hw/core/qdev-clock.h"
> +#include "hw/core/sysbus.h"
> +#include "hw/core/qdev-properties.h"
> +#include "hw/timer/dw-apb-timer.h"
> +#include "trace.h"
> +
> +#define DW_APB_TIMER_STRIDE 0x14
> +#define DW_APB_TIMER_MMIO_SIZE 0x100
> +
> +/* Per-timer register offsets */
> +#define DW_APB_TIMER_N_LOAD_COUNT 0x00
> +#define DW_APB_TIMER_N_CURRENT_VALUE 0x04
> +#define DW_APB_TIMER_N_CONTROL 0x08
> +#define DW_APB_TIMER_N_EOI 0x0c
> +#define DW_APB_TIMER_N_INT_STATUS 0x10
> +/* Global register offsets */
> +#define DW_APB_TIMER_INT_STATUS 0xa0
> +#define DW_APB_TIMER_EOI 0xa4
> +#define DW_APB_TIMER_RAW_INT_STATUS 0xa8
> +#define DW_APB_TIMER_COMP_VERSION 0xac
> +
> +/* Control register bits */
> +#define DW_APB_TIMER_CONTROL_ENABLE BIT(0)
> +/* 1: periodic, 0: free running. */
> +#define DW_APB_TIMER_CONTROL_MODE_PERIODIC BIT(1)
> +#define DW_APB_TIMER_CONTROL_INT BIT(2)
The macro name is misleading, better to name it as:
DW_APB_TIMER_CONTROL_INT_MASK
> +#define DW_APB_TIMER_CONTROL_RW_MASK 0x7
> +
> +/*
> + * Component version of the DW_apb_timers IP, fixed in silicon.
> + * Decodes as the ASCII string "211*" (2.11a series); the model reports
> + * the version integrated in K230. Linux does not read this register.
> + */
> +#define DW_APB_TIMER_COMP_VERSION_VAL 0x3231312A
> +
> +static void dw_apb_timer_update_irq(DWAPBTimerChannel *t)
> +{
> + qemu_set_irq(t->irq, t->int_status &&
> + !(t->control & DW_APB_TIMER_CONTROL_INT));
> +}
> +
> +static void dw_apb_timer_clk_update(void *opaque, ClockEvent event)
> +{
> + DWAPBTimerChannel *t = opaque;
> +
> + if (!t->ptimer) {
> + return;
> + }
> + ptimer_transaction_begin(t->ptimer);
> + ptimer_set_period_from_clock(t->ptimer, t->clk, 1);
> + ptimer_transaction_commit(t->ptimer);
> +}
> +
> +static void dw_apb_timer_enable(DWAPBTimerChannel *t)
> +{
> + trace_dw_apb_timer_enable(t->id, t->load);
> +
> + ptimer_transaction_begin(t->ptimer);
> + ptimer_set_limit(t->ptimer, t->load ? t->load : 1, 1);
> + ptimer_run(t->ptimer, 1);
> + ptimer_transaction_commit(t->ptimer);
> +}
> +
> +static void dw_apb_timer_disable(DWAPBTimerChannel *t)
> +{
> + trace_dw_apb_timer_disable(t->id);
> +
> + ptimer_transaction_begin(t->ptimer);
> + ptimer_stop(t->ptimer);
> + ptimer_transaction_commit(t->ptimer);
> +
> + t->int_status = 0;
> + dw_apb_timer_update_irq(t);
> +}
> +
> +static void dw_apb_timer_tick(void *opaque)
> +{
> + DWAPBTimerChannel *t = opaque;
> + uint32_t reload;
> +
> + trace_dw_apb_timer_tick(t->id);
> +
> + t->int_status = 1;
> + dw_apb_timer_update_irq(t);
> +
> + if (t->control & DW_APB_TIMER_CONTROL_MODE_PERIODIC) {
> + reload = t->load ? t->load : 1;
> + } else {
> + reload = UINT32_MAX;
> + }
> +
> + ptimer_set_limit(t->ptimer, reload, 1);
> + ptimer_run(t->ptimer, 1);
> +}
> +
> +static uint64_t dw_apb_timer_read(void *opaque, hwaddr addr,
> + unsigned int size)
> +{
> + DWAPBTimerState *s = DW_APB_TIMER(opaque);
> + uint32_t value = 0;
> +
> + if (addr < DW_APB_TIMER_INT_STATUS) {
> + unsigned int idx = addr / DW_APB_TIMER_STRIDE;
> + hwaddr reg = addr % DW_APB_TIMER_STRIDE;
> +
> + if (idx < s->num_timers) {
> + DWAPBTimerChannel *t = &s->timers[idx];
> +
> + switch (reg) {
> + case DW_APB_TIMER_N_LOAD_COUNT:
> + value = t->load;
> + break;
> + case DW_APB_TIMER_N_CURRENT_VALUE:
> + if (t->control & DW_APB_TIMER_CONTROL_ENABLE) {
> + value = ptimer_get_count(t->ptimer);
> + }
> + break;
> + case DW_APB_TIMER_N_CONTROL:
> + value = t->control;
> + break;
> + case DW_APB_TIMER_N_EOI:
> + t->int_status = 0;
> + dw_apb_timer_update_irq(t);
> + trace_dw_apb_timer_irq_clear(t->id);
> + break;
> + case DW_APB_TIMER_N_INT_STATUS:
> + value = t->int_status &&
> + !(t->control & DW_APB_TIMER_CONTROL_INT);
> + break;
> + default:
> + break;
> + }
> + }
> + } else {
> + switch (addr) {
> + case DW_APB_TIMER_INT_STATUS:
> + for (int i = 0; i < s->num_timers; i++) {
> + DWAPBTimerChannel *t = &s->timers[i];
> +
> + value |= (t->int_status &&
> + !(t->control & DW_APB_TIMER_CONTROL_INT)) << i;
> + }
> + break;
> + case DW_APB_TIMER_EOI:
> + for (int i = 0; i < s->num_timers; i++) {
> + DWAPBTimerChannel *t = &s->timers[i];
> +
> + t->int_status = 0;
> + dw_apb_timer_update_irq(t);
> + }
> + break;
> + case DW_APB_TIMER_RAW_INT_STATUS:
> + for (int i = 0; i < s->num_timers; i++) {
> + value |= s->timers[i].int_status << i;
> + }
> + break;
> + case DW_APB_TIMER_COMP_VERSION:
> + value = DW_APB_TIMER_COMP_VERSION_VAL;
> + break;
> + default:
> + break;
> + }
> + }
> +
> + trace_dw_apb_timer_read(addr, value);
> + return value;
> +}
> +
> +static void dw_apb_timer_write(void *opaque, hwaddr addr,
> + uint64_t value, unsigned int size)
> +{
> + DWAPBTimerState *s = DW_APB_TIMER(opaque);
> +
> + if (addr < DW_APB_TIMER_INT_STATUS) {
> + unsigned int idx = addr / DW_APB_TIMER_STRIDE;
> + hwaddr reg = addr % DW_APB_TIMER_STRIDE;
> +
> + if (idx < s->num_timers) {
> + DWAPBTimerChannel *t = &s->timers[idx];
> +
> + switch (reg) {
> + case DW_APB_TIMER_N_LOAD_COUNT:
> + t->load = value;
> + break;
> + case DW_APB_TIMER_N_CONTROL: {
> + uint32_t old_control = t->control;
> + uint32_t new_control = value & DW_APB_TIMER_CONTROL_RW_MASK;
> +
> + t->control = new_control;
> + if ((new_control ^ old_control) &
> + DW_APB_TIMER_CONTROL_ENABLE) {
> + if (new_control & DW_APB_TIMER_CONTROL_ENABLE) {
> + dw_apb_timer_enable(t);
> + } else {
> + dw_apb_timer_disable(t);
> + }
> + }
> + if ((new_control ^ old_control) &
> + DW_APB_TIMER_CONTROL_INT) {
> + dw_apb_timer_update_irq(t);
> + }
> + break;
> + }
> + default:
> + break;
> + }
> + }
> + }
> +
> + trace_dw_apb_timer_write(addr, value);
> +}
> +
> +static const MemoryRegionOps dw_apb_timer_ops = {
> + .read = dw_apb_timer_read,
> + .write = dw_apb_timer_write,
> + .endianness = DEVICE_LITTLE_ENDIAN,
> + .impl = {
> + .min_access_size = 4,
> + .max_access_size = 4,
> + },
> +};
> +
> +static void dw_apb_timer_reset_hold(Object *obj, ResetType type)
> +{
> + DWAPBTimerState *s = DW_APB_TIMER(obj);
> +
> + for (int i = 0; i < s->num_timers; i++) {
> + DWAPBTimerChannel *t = &s->timers[i];
> +
> + ptimer_transaction_begin(t->ptimer);
> + ptimer_stop(t->ptimer);
> + ptimer_transaction_commit(t->ptimer);
> +
> + t->load = 0;
> + t->control = 0;
> + t->int_status = 0;
> + dw_apb_timer_update_irq(t);
> + }
> +}
> +
> +static const VMStateDescription vmstate_dw_apb_timer_channel = {
> + .name = "dw-apb-timer-channel",
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .fields = (const VMStateField[]) {
> + VMSTATE_PTIMER(ptimer, DWAPBTimerChannel),
> + VMSTATE_CLOCK(clk, DWAPBTimerChannel),
> + VMSTATE_UINT32(load, DWAPBTimerChannel),
> + VMSTATE_UINT32(control, DWAPBTimerChannel),
> + VMSTATE_UINT32(int_status, DWAPBTimerChannel),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +static const VMStateDescription vmstate_dw_apb_timer = {
> + .name = "dw-apb-timer",
> + .fields = (const VMStateField[]) {
> + VMSTATE_STRUCT_VARRAY_UINT32(timers, DWAPBTimerState,
> + num_timers, 0,
> + vmstate_dw_apb_timer_channel,
> + DWAPBTimerChannel),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +static const Property dw_apb_timer_properties[] = {
> + DEFINE_PROP_UINT32("num-timers", DWAPBTimerState, num_timers, 1),
> +};
> +
> +static void dw_apb_timer_init(Object *obj)
> +{
> + DWAPBTimerState *s = DW_APB_TIMER(obj);
> +
> + for (int i = 0; i < DW_APB_TIMER_MAX_TIMERS; i++) {
> + DWAPBTimerChannel *t = &s->timers[i];
> + g_autofree char *name = g_strdup_printf("timer[%d]", i);
> +
> + t->id = i;
> + t->clk = qdev_init_clock_in(DEVICE(obj), name,
> + dw_apb_timer_clk_update, t,
> + ClockUpdate);
> + }
> +}
> +
> +static void dw_apb_timer_realize(DeviceState *dev, Error **errp)
> +{
> + DWAPBTimerState *s = DW_APB_TIMER(dev);
> + SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
> +
> + if (s->num_timers == 0 || s->num_timers > DW_APB_TIMER_MAX_TIMERS) {
> + error_setg(errp, "dw-apb-timer: num-timers must be between 1 and %u",
> + DW_APB_TIMER_MAX_TIMERS);
> + return;
> + }
> +
> + for (int i = 0; i < s->num_timers; i++) {
> + DWAPBTimerChannel *t = &s->timers[i];
> +
> + if (!clock_has_source(t->clk)) {
> + error_setg(errp, "dw-apb-timer: timer[%u] clock must be connected",
> + i);
> + return;
> + }
> + }
> +
> + for (int i = 0; i < s->num_timers; i++) {
> + DWAPBTimerChannel *t = &s->timers[i];
> +
> + t->ptimer = ptimer_init(dw_apb_timer_tick, t,
> + PTIMER_POLICY_NO_IMMEDIATE_TRIGGER |
> + PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
> + PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
> + ptimer_transaction_begin(t->ptimer);
> + ptimer_set_limit(t->ptimer, UINT32_MAX, 1);
> + ptimer_transaction_commit(t->ptimer);
> + sysbus_init_irq(sbd, &t->irq);
> +
> + /* The source may have been connected before the ptimer existed. */
> + dw_apb_timer_clk_update(t, ClockUpdate);
> + }
> +
> + memory_region_init_io(&s->mmio, OBJECT(dev), &dw_apb_timer_ops,
> + s, TYPE_DW_APB_TIMER, DW_APB_TIMER_MMIO_SIZE);
> + sysbus_init_mmio(sbd, &s->mmio);
> +}
> +
> +static void dw_apb_timer_unrealize(DeviceState *dev)
> +{
> + DWAPBTimerState *s = DW_APB_TIMER(dev);
> +
> + for (int i = 0; i < s->num_timers; i++) {
> + ptimer_free(s->timers[i].ptimer);
> + }
> +}
> +
> +static void dw_apb_timer_class_init(ObjectClass *klass, const void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> + ResettableClass *rc = RESETTABLE_CLASS(klass);
> +
> + dc->realize = dw_apb_timer_realize;
> + dc->unrealize = dw_apb_timer_unrealize;
> + dc->vmsd = &vmstate_dw_apb_timer;
> + dc->desc = "Synopsys DesignWare APB timer";
> + rc->phases.hold = dw_apb_timer_reset_hold;
> + device_class_set_props(dc, dw_apb_timer_properties);
> +}
> +
> +static const TypeInfo dw_apb_timer_info = {
> + .name = TYPE_DW_APB_TIMER,
> + .parent = TYPE_SYS_BUS_DEVICE,
> + .instance_size = sizeof(DWAPBTimerState),
> + .instance_init = dw_apb_timer_init,
> + .class_init = dw_apb_timer_class_init,
> +};
> +
> +static void dw_apb_timer_register_type(void)
> +{
> + type_register_static(&dw_apb_timer_info);
> +}
> +
> +type_init(dw_apb_timer_register_type)
> diff --git a/hw/timer/meson.build b/hw/timer/meson.build
> index 201b5d8316..fccc36540c 100644
> --- a/hw/timer/meson.build
> +++ b/hw/timer/meson.build
> @@ -34,3 +34,4 @@ specific_ss.add(when: 'CONFIG_IBEX', if_true: files('ibex_timer.c'))
> system_ss.add(when: 'CONFIG_SIFIVE_PWM', if_true: files('sifive_pwm.c'))
>
> system_ss.add(when: 'CONFIG_AVR_TIMER16', if_true: files('avr_timer16.c'))
> +system_ss.add(when: 'CONFIG_DW_APB_TIMER', if_true: files('dw-apb-timer.c'))
> diff --git a/hw/timer/trace-events b/hw/timer/trace-events
> index 634ba1da27..7f592a96c8 100644
> --- a/hw/timer/trace-events
> +++ b/hw/timer/trace-events
> @@ -1,5 +1,13 @@
> # See docs/devel/tracing.rst for syntax documentation.
>
> +# dw-apb-timer.c
> +dw_apb_timer_read(uint64_t addr, uint32_t val) "DW APB timer read: [0x%" PRIx64 "] -> 0x%" PRIx32
> +dw_apb_timer_write(uint64_t addr, uint64_t val) "DW APB timer write: [0x%" PRIx64 "] <- 0x%" PRIx64
> +dw_apb_timer_tick(int idx) "DW APB timer %d tick"
> +dw_apb_timer_irq_clear(int idx) "DW APB timer %d IRQ cleared"
> +dw_apb_timer_enable(int idx, uint32_t load) "DW APB timer %d enabled load=0x%" PRIx32
> +dw_apb_timer_disable(int idx) "DW APB timer %d disabled"
> +
> # slavio_timer.c
> slavio_timer_get_out(uint64_t limit, uint32_t counthigh, uint32_t count) "limit 0x%"PRIx64" count 0x%x0x%08x"
> slavio_timer_irq(uint32_t counthigh, uint32_t count) "callback: count 0x%x0x%08x"
> diff --git a/include/hw/timer/dw-apb-timer.h b/include/hw/timer/dw-apb-timer.h
> new file mode 100644
> index 0000000000..f6b83ae5ca
> --- /dev/null
> +++ b/include/hw/timer/dw-apb-timer.h
> @@ -0,0 +1,40 @@
> +/*
> + * Synopsys DesignWare APB timer
> + *
> + * Copyright (c) 2026 raoyi <rao232328@gmail.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef DW_APB_TIMER_H
> +#define DW_APB_TIMER_H
> +
> +#include "hw/core/sysbus.h"
> +#include "hw/core/irq.h"
> +#include "hw/core/clock.h"
> +#include "qom/object.h"
> +
> +#define TYPE_DW_APB_TIMER "dw-apb-timer"
> +OBJECT_DECLARE_SIMPLE_TYPE(DWAPBTimerState, DW_APB_TIMER)
> +
> +#define DW_APB_TIMER_MAX_TIMERS 8
> +
> +typedef struct DWAPBTimerChannel {
> + struct ptimer_state *ptimer;
> + Clock *clk;
> + qemu_irq irq;
> + unsigned int id;
> + uint32_t load;
> + uint32_t control;
> + uint32_t int_status;
> +} DWAPBTimerChannel;
> +
> +struct DWAPBTimerState {
> + SysBusDevice parent_obj;
> +
> + MemoryRegion mmio;
> + uint32_t num_timers;
> + DWAPBTimerChannel timers[DW_APB_TIMER_MAX_TIMERS];
> +};
> +
> +#endif
> --
Other than that,
Reviewed-by: Bin Meng <bin.meng@processmission.com>
^ permalink raw reply [flat|nested] 10+ messages in thread* 回复: [PATCH v4 1/3] hw/timer: add DesignWare APB timer model
2026-08-14 7:40 ` Bin Meng
@ 2026-08-18 14:03 ` 饶轶
0 siblings, 0 replies; 10+ messages in thread
From: 饶轶 @ 2026-08-18 14:03 UTC (permalink / raw)
To: Bin Meng
Cc: qemu-devel@nongnu.org, qemu-riscv@nongnu.org,
alistair.francis@wdc.com, palmer@dabbelt.com, liwei1518@gmail.com,
daniel.barboza@oss.qualcomm.com, zhiwei_liu@linux.alibaba.com,
chao.liu@processmission.com, philmd@oss.qualcomm.com,
pbonzini@redhat.com, lvivier@redhat.com, farosas@suse.de,
caojunze424@gmail.com
[-- Attachment #1: Type: text/plain, Size: 18214 bytes --]
Thanks for the review.
Renamed DW_APB_TIMER_CONTROL_INT to
DW_APB_TIMER_CONTROL_INT_MASK in v5.
________________________________
发件人: Bin Meng <bmeng.cn@gmail.com>
发送时间: 2026年8月14日 15:40
收件人: raoyi <rao232328@gmail.com>
抄送: qemu-devel@nongnu.org <qemu-devel@nongnu.org>; qemu-riscv@nongnu.org <qemu-riscv@nongnu.org>; alistair.francis@wdc.com <alistair.francis@wdc.com>; palmer@dabbelt.com <palmer@dabbelt.com>; liwei1518@gmail.com <liwei1518@gmail.com>; daniel.barboza@oss.qualcomm.com <daniel.barboza@oss.qualcomm.com>; zhiwei_liu@linux.alibaba.com <zhiwei_liu@linux.alibaba.com>; chao.liu@processmission.com <chao.liu@processmission.com>; philmd@oss.qualcomm.com <philmd@oss.qualcomm.com>; pbonzini@redhat.com <pbonzini@redhat.com>; lvivier@redhat.com <lvivier@redhat.com>; farosas@suse.de <farosas@suse.de>; caojunze424@gmail.com <caojunze424@gmail.com>
主题: Re: [PATCH v4 1/3] hw/timer: add DesignWare APB timer model
On Thu, Aug 13, 2026 at 9:37 PM raoyi <rao232328@gmail.com> wrote:
>
> Add generic DesignWare APB timer device model. The number
> of timer channels is configurable via the num-timers property.
>
> Add timer files to MAINTAINERS.
>
> Signed-off-by: raoyi <rao232328@gmail.com>
> ---
> MAINTAINERS | 2 +
> hw/timer/Kconfig | 4 +
> hw/timer/dw-apb-timer.c | 376 ++++++++++++++++++++++++++++++++
> hw/timer/meson.build | 1 +
> hw/timer/trace-events | 8 +
> include/hw/timer/dw-apb-timer.h | 40 ++++
> 6 files changed, 431 insertions(+)
> create mode 100644 hw/timer/dw-apb-timer.c
> create mode 100644 include/hw/timer/dw-apb-timer.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 6171cc7494..cf689433ff 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1826,8 +1826,10 @@ S: Maintained
> F: docs/system/riscv/k230.rst
> F: hw/riscv/k230.c
> F: hw/watchdog/k230_wdt.c
> +F: hw/timer/dw-apb-timer.c
> F: include/hw/riscv/k230.h
> F: include/hw/watchdog/k230_wdt.h
> +F: include/hw/timer/dw-apb-timer.h
> F: tests/qtest/k230-wdt-test.c
>
> RX Machines
> diff --git a/hw/timer/Kconfig b/hw/timer/Kconfig
> index b3d823ce2c..b533cf1ba9 100644
> --- a/hw/timer/Kconfig
> +++ b/hw/timer/Kconfig
> @@ -65,3 +65,7 @@ config STELLARIS_GPTM
>
> config AVR_TIMER16
> bool
> +
> +config DW_APB_TIMER
> + bool
> + select PTIMER
> diff --git a/hw/timer/dw-apb-timer.c b/hw/timer/dw-apb-timer.c
> new file mode 100644
> index 0000000000..0b994e1570
> --- /dev/null
> +++ b/hw/timer/dw-apb-timer.c
> @@ -0,0 +1,376 @@
> +/*
> + * Synopsys DesignWare APB timer
> + *
> + * Copyright (c) 2026 raoyi <rao232328@gmail.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/bitops.h"
> +#include "qemu/module.h"
> +#include "qapi/error.h"
> +#include "migration/vmstate.h"
> +#include "hw/core/ptimer.h"
> +#include "hw/core/qdev-clock.h"
> +#include "hw/core/sysbus.h"
> +#include "hw/core/qdev-properties.h"
> +#include "hw/timer/dw-apb-timer.h"
> +#include "trace.h"
> +
> +#define DW_APB_TIMER_STRIDE 0x14
> +#define DW_APB_TIMER_MMIO_SIZE 0x100
> +
> +/* Per-timer register offsets */
> +#define DW_APB_TIMER_N_LOAD_COUNT 0x00
> +#define DW_APB_TIMER_N_CURRENT_VALUE 0x04
> +#define DW_APB_TIMER_N_CONTROL 0x08
> +#define DW_APB_TIMER_N_EOI 0x0c
> +#define DW_APB_TIMER_N_INT_STATUS 0x10
> +/* Global register offsets */
> +#define DW_APB_TIMER_INT_STATUS 0xa0
> +#define DW_APB_TIMER_EOI 0xa4
> +#define DW_APB_TIMER_RAW_INT_STATUS 0xa8
> +#define DW_APB_TIMER_COMP_VERSION 0xac
> +
> +/* Control register bits */
> +#define DW_APB_TIMER_CONTROL_ENABLE BIT(0)
> +/* 1: periodic, 0: free running. */
> +#define DW_APB_TIMER_CONTROL_MODE_PERIODIC BIT(1)
> +#define DW_APB_TIMER_CONTROL_INT BIT(2)
The macro name is misleading, better to name it as:
DW_APB_TIMER_CONTROL_INT_MASK
> +#define DW_APB_TIMER_CONTROL_RW_MASK 0x7
> +
> +/*
> + * Component version of the DW_apb_timers IP, fixed in silicon.
> + * Decodes as the ASCII string "211*" (2.11a series); the model reports
> + * the version integrated in K230. Linux does not read this register.
> + */
> +#define DW_APB_TIMER_COMP_VERSION_VAL 0x3231312A
> +
> +static void dw_apb_timer_update_irq(DWAPBTimerChannel *t)
> +{
> + qemu_set_irq(t->irq, t->int_status &&
> + !(t->control & DW_APB_TIMER_CONTROL_INT));
> +}
> +
> +static void dw_apb_timer_clk_update(void *opaque, ClockEvent event)
> +{
> + DWAPBTimerChannel *t = opaque;
> +
> + if (!t->ptimer) {
> + return;
> + }
> + ptimer_transaction_begin(t->ptimer);
> + ptimer_set_period_from_clock(t->ptimer, t->clk, 1);
> + ptimer_transaction_commit(t->ptimer);
> +}
> +
> +static void dw_apb_timer_enable(DWAPBTimerChannel *t)
> +{
> + trace_dw_apb_timer_enable(t->id, t->load);
> +
> + ptimer_transaction_begin(t->ptimer);
> + ptimer_set_limit(t->ptimer, t->load ? t->load : 1, 1);
> + ptimer_run(t->ptimer, 1);
> + ptimer_transaction_commit(t->ptimer);
> +}
> +
> +static void dw_apb_timer_disable(DWAPBTimerChannel *t)
> +{
> + trace_dw_apb_timer_disable(t->id);
> +
> + ptimer_transaction_begin(t->ptimer);
> + ptimer_stop(t->ptimer);
> + ptimer_transaction_commit(t->ptimer);
> +
> + t->int_status = 0;
> + dw_apb_timer_update_irq(t);
> +}
> +
> +static void dw_apb_timer_tick(void *opaque)
> +{
> + DWAPBTimerChannel *t = opaque;
> + uint32_t reload;
> +
> + trace_dw_apb_timer_tick(t->id);
> +
> + t->int_status = 1;
> + dw_apb_timer_update_irq(t);
> +
> + if (t->control & DW_APB_TIMER_CONTROL_MODE_PERIODIC) {
> + reload = t->load ? t->load : 1;
> + } else {
> + reload = UINT32_MAX;
> + }
> +
> + ptimer_set_limit(t->ptimer, reload, 1);
> + ptimer_run(t->ptimer, 1);
> +}
> +
> +static uint64_t dw_apb_timer_read(void *opaque, hwaddr addr,
> + unsigned int size)
> +{
> + DWAPBTimerState *s = DW_APB_TIMER(opaque);
> + uint32_t value = 0;
> +
> + if (addr < DW_APB_TIMER_INT_STATUS) {
> + unsigned int idx = addr / DW_APB_TIMER_STRIDE;
> + hwaddr reg = addr % DW_APB_TIMER_STRIDE;
> +
> + if (idx < s->num_timers) {
> + DWAPBTimerChannel *t = &s->timers[idx];
> +
> + switch (reg) {
> + case DW_APB_TIMER_N_LOAD_COUNT:
> + value = t->load;
> + break;
> + case DW_APB_TIMER_N_CURRENT_VALUE:
> + if (t->control & DW_APB_TIMER_CONTROL_ENABLE) {
> + value = ptimer_get_count(t->ptimer);
> + }
> + break;
> + case DW_APB_TIMER_N_CONTROL:
> + value = t->control;
> + break;
> + case DW_APB_TIMER_N_EOI:
> + t->int_status = 0;
> + dw_apb_timer_update_irq(t);
> + trace_dw_apb_timer_irq_clear(t->id);
> + break;
> + case DW_APB_TIMER_N_INT_STATUS:
> + value = t->int_status &&
> + !(t->control & DW_APB_TIMER_CONTROL_INT);
> + break;
> + default:
> + break;
> + }
> + }
> + } else {
> + switch (addr) {
> + case DW_APB_TIMER_INT_STATUS:
> + for (int i = 0; i < s->num_timers; i++) {
> + DWAPBTimerChannel *t = &s->timers[i];
> +
> + value |= (t->int_status &&
> + !(t->control & DW_APB_TIMER_CONTROL_INT)) << i;
> + }
> + break;
> + case DW_APB_TIMER_EOI:
> + for (int i = 0; i < s->num_timers; i++) {
> + DWAPBTimerChannel *t = &s->timers[i];
> +
> + t->int_status = 0;
> + dw_apb_timer_update_irq(t);
> + }
> + break;
> + case DW_APB_TIMER_RAW_INT_STATUS:
> + for (int i = 0; i < s->num_timers; i++) {
> + value |= s->timers[i].int_status << i;
> + }
> + break;
> + case DW_APB_TIMER_COMP_VERSION:
> + value = DW_APB_TIMER_COMP_VERSION_VAL;
> + break;
> + default:
> + break;
> + }
> + }
> +
> + trace_dw_apb_timer_read(addr, value);
> + return value;
> +}
> +
> +static void dw_apb_timer_write(void *opaque, hwaddr addr,
> + uint64_t value, unsigned int size)
> +{
> + DWAPBTimerState *s = DW_APB_TIMER(opaque);
> +
> + if (addr < DW_APB_TIMER_INT_STATUS) {
> + unsigned int idx = addr / DW_APB_TIMER_STRIDE;
> + hwaddr reg = addr % DW_APB_TIMER_STRIDE;
> +
> + if (idx < s->num_timers) {
> + DWAPBTimerChannel *t = &s->timers[idx];
> +
> + switch (reg) {
> + case DW_APB_TIMER_N_LOAD_COUNT:
> + t->load = value;
> + break;
> + case DW_APB_TIMER_N_CONTROL: {
> + uint32_t old_control = t->control;
> + uint32_t new_control = value & DW_APB_TIMER_CONTROL_RW_MASK;
> +
> + t->control = new_control;
> + if ((new_control ^ old_control) &
> + DW_APB_TIMER_CONTROL_ENABLE) {
> + if (new_control & DW_APB_TIMER_CONTROL_ENABLE) {
> + dw_apb_timer_enable(t);
> + } else {
> + dw_apb_timer_disable(t);
> + }
> + }
> + if ((new_control ^ old_control) &
> + DW_APB_TIMER_CONTROL_INT) {
> + dw_apb_timer_update_irq(t);
> + }
> + break;
> + }
> + default:
> + break;
> + }
> + }
> + }
> +
> + trace_dw_apb_timer_write(addr, value);
> +}
> +
> +static const MemoryRegionOps dw_apb_timer_ops = {
> + .read = dw_apb_timer_read,
> + .write = dw_apb_timer_write,
> + .endianness = DEVICE_LITTLE_ENDIAN,
> + .impl = {
> + .min_access_size = 4,
> + .max_access_size = 4,
> + },
> +};
> +
> +static void dw_apb_timer_reset_hold(Object *obj, ResetType type)
> +{
> + DWAPBTimerState *s = DW_APB_TIMER(obj);
> +
> + for (int i = 0; i < s->num_timers; i++) {
> + DWAPBTimerChannel *t = &s->timers[i];
> +
> + ptimer_transaction_begin(t->ptimer);
> + ptimer_stop(t->ptimer);
> + ptimer_transaction_commit(t->ptimer);
> +
> + t->load = 0;
> + t->control = 0;
> + t->int_status = 0;
> + dw_apb_timer_update_irq(t);
> + }
> +}
> +
> +static const VMStateDescription vmstate_dw_apb_timer_channel = {
> + .name = "dw-apb-timer-channel",
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .fields = (const VMStateField[]) {
> + VMSTATE_PTIMER(ptimer, DWAPBTimerChannel),
> + VMSTATE_CLOCK(clk, DWAPBTimerChannel),
> + VMSTATE_UINT32(load, DWAPBTimerChannel),
> + VMSTATE_UINT32(control, DWAPBTimerChannel),
> + VMSTATE_UINT32(int_status, DWAPBTimerChannel),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +static const VMStateDescription vmstate_dw_apb_timer = {
> + .name = "dw-apb-timer",
> + .fields = (const VMStateField[]) {
> + VMSTATE_STRUCT_VARRAY_UINT32(timers, DWAPBTimerState,
> + num_timers, 0,
> + vmstate_dw_apb_timer_channel,
> + DWAPBTimerChannel),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +static const Property dw_apb_timer_properties[] = {
> + DEFINE_PROP_UINT32("num-timers", DWAPBTimerState, num_timers, 1),
> +};
> +
> +static void dw_apb_timer_init(Object *obj)
> +{
> + DWAPBTimerState *s = DW_APB_TIMER(obj);
> +
> + for (int i = 0; i < DW_APB_TIMER_MAX_TIMERS; i++) {
> + DWAPBTimerChannel *t = &s->timers[i];
> + g_autofree char *name = g_strdup_printf("timer[%d]", i);
> +
> + t->id = i;
> + t->clk = qdev_init_clock_in(DEVICE(obj), name,
> + dw_apb_timer_clk_update, t,
> + ClockUpdate);
> + }
> +}
> +
> +static void dw_apb_timer_realize(DeviceState *dev, Error **errp)
> +{
> + DWAPBTimerState *s = DW_APB_TIMER(dev);
> + SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
> +
> + if (s->num_timers == 0 || s->num_timers > DW_APB_TIMER_MAX_TIMERS) {
> + error_setg(errp, "dw-apb-timer: num-timers must be between 1 and %u",
> + DW_APB_TIMER_MAX_TIMERS);
> + return;
> + }
> +
> + for (int i = 0; i < s->num_timers; i++) {
> + DWAPBTimerChannel *t = &s->timers[i];
> +
> + if (!clock_has_source(t->clk)) {
> + error_setg(errp, "dw-apb-timer: timer[%u] clock must be connected",
> + i);
> + return;
> + }
> + }
> +
> + for (int i = 0; i < s->num_timers; i++) {
> + DWAPBTimerChannel *t = &s->timers[i];
> +
> + t->ptimer = ptimer_init(dw_apb_timer_tick, t,
> + PTIMER_POLICY_NO_IMMEDIATE_TRIGGER |
> + PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
> + PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
> + ptimer_transaction_begin(t->ptimer);
> + ptimer_set_limit(t->ptimer, UINT32_MAX, 1);
> + ptimer_transaction_commit(t->ptimer);
> + sysbus_init_irq(sbd, &t->irq);
> +
> + /* The source may have been connected before the ptimer existed. */
> + dw_apb_timer_clk_update(t, ClockUpdate);
> + }
> +
> + memory_region_init_io(&s->mmio, OBJECT(dev), &dw_apb_timer_ops,
> + s, TYPE_DW_APB_TIMER, DW_APB_TIMER_MMIO_SIZE);
> + sysbus_init_mmio(sbd, &s->mmio);
> +}
> +
> +static void dw_apb_timer_unrealize(DeviceState *dev)
> +{
> + DWAPBTimerState *s = DW_APB_TIMER(dev);
> +
> + for (int i = 0; i < s->num_timers; i++) {
> + ptimer_free(s->timers[i].ptimer);
> + }
> +}
> +
> +static void dw_apb_timer_class_init(ObjectClass *klass, const void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> + ResettableClass *rc = RESETTABLE_CLASS(klass);
> +
> + dc->realize = dw_apb_timer_realize;
> + dc->unrealize = dw_apb_timer_unrealize;
> + dc->vmsd = &vmstate_dw_apb_timer;
> + dc->desc = "Synopsys DesignWare APB timer";
> + rc->phases.hold = dw_apb_timer_reset_hold;
> + device_class_set_props(dc, dw_apb_timer_properties);
> +}
> +
> +static const TypeInfo dw_apb_timer_info = {
> + .name = TYPE_DW_APB_TIMER,
> + .parent = TYPE_SYS_BUS_DEVICE,
> + .instance_size = sizeof(DWAPBTimerState),
> + .instance_init = dw_apb_timer_init,
> + .class_init = dw_apb_timer_class_init,
> +};
> +
> +static void dw_apb_timer_register_type(void)
> +{
> + type_register_static(&dw_apb_timer_info);
> +}
> +
> +type_init(dw_apb_timer_register_type)
> diff --git a/hw/timer/meson.build b/hw/timer/meson.build
> index 201b5d8316..fccc36540c 100644
> --- a/hw/timer/meson.build
> +++ b/hw/timer/meson.build
> @@ -34,3 +34,4 @@ specific_ss.add(when: 'CONFIG_IBEX', if_true: files('ibex_timer.c'))
> system_ss.add(when: 'CONFIG_SIFIVE_PWM', if_true: files('sifive_pwm.c'))
>
> system_ss.add(when: 'CONFIG_AVR_TIMER16', if_true: files('avr_timer16.c'))
> +system_ss.add(when: 'CONFIG_DW_APB_TIMER', if_true: files('dw-apb-timer.c'))
> diff --git a/hw/timer/trace-events b/hw/timer/trace-events
> index 634ba1da27..7f592a96c8 100644
> --- a/hw/timer/trace-events
> +++ b/hw/timer/trace-events
> @@ -1,5 +1,13 @@
> # See docs/devel/tracing.rst for syntax documentation.
>
> +# dw-apb-timer.c
> +dw_apb_timer_read(uint64_t addr, uint32_t val) "DW APB timer read: [0x%" PRIx64 "] -> 0x%" PRIx32
> +dw_apb_timer_write(uint64_t addr, uint64_t val) "DW APB timer write: [0x%" PRIx64 "] <- 0x%" PRIx64
> +dw_apb_timer_tick(int idx) "DW APB timer %d tick"
> +dw_apb_timer_irq_clear(int idx) "DW APB timer %d IRQ cleared"
> +dw_apb_timer_enable(int idx, uint32_t load) "DW APB timer %d enabled load=0x%" PRIx32
> +dw_apb_timer_disable(int idx) "DW APB timer %d disabled"
> +
> # slavio_timer.c
> slavio_timer_get_out(uint64_t limit, uint32_t counthigh, uint32_t count) "limit 0x%"PRIx64" count 0x%x0x%08x"
> slavio_timer_irq(uint32_t counthigh, uint32_t count) "callback: count 0x%x0x%08x"
> diff --git a/include/hw/timer/dw-apb-timer.h b/include/hw/timer/dw-apb-timer.h
> new file mode 100644
> index 0000000000..f6b83ae5ca
> --- /dev/null
> +++ b/include/hw/timer/dw-apb-timer.h
> @@ -0,0 +1,40 @@
> +/*
> + * Synopsys DesignWare APB timer
> + *
> + * Copyright (c) 2026 raoyi <rao232328@gmail.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef DW_APB_TIMER_H
> +#define DW_APB_TIMER_H
> +
> +#include "hw/core/sysbus.h"
> +#include "hw/core/irq.h"
> +#include "hw/core/clock.h"
> +#include "qom/object.h"
> +
> +#define TYPE_DW_APB_TIMER "dw-apb-timer"
> +OBJECT_DECLARE_SIMPLE_TYPE(DWAPBTimerState, DW_APB_TIMER)
> +
> +#define DW_APB_TIMER_MAX_TIMERS 8
> +
> +typedef struct DWAPBTimerChannel {
> + struct ptimer_state *ptimer;
> + Clock *clk;
> + qemu_irq irq;
> + unsigned int id;
> + uint32_t load;
> + uint32_t control;
> + uint32_t int_status;
> +} DWAPBTimerChannel;
> +
> +struct DWAPBTimerState {
> + SysBusDevice parent_obj;
> +
> + MemoryRegion mmio;
> + uint32_t num_timers;
> + DWAPBTimerChannel timers[DW_APB_TIMER_MAX_TIMERS];
> +};
> +
> +#endif
> --
Other than that,
Reviewed-by: Bin Meng <bin.meng@processmission.com>
[-- Attachment #2: Type: text/html, Size: 35739 bytes --]
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v4 2/3] hw/riscv: add DesignWare APB timer to K230 board
2026-08-13 13:36 [PATCH v4 0/3] hw/timer: add DesignWare APB timer raoyi
2026-08-13 13:36 ` [PATCH v4 1/3] hw/timer: add DesignWare APB timer model raoyi
@ 2026-08-13 13:36 ` raoyi
2026-08-14 7:40 ` Bin Meng
2026-08-13 13:36 ` [PATCH v4 3/3] tests/qtest: add k230 dwapb timer test raoyi
2 siblings, 1 reply; 10+ messages in thread
From: raoyi @ 2026-08-13 13:36 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-riscv, alistair.francis, palmer, liwei1518, daniel.barboza,
zhiwei_liu, chao.liu, bmeng.cn, philmd, pbonzini, lvivier,
farosas, caojunze424, raoyi
Wire up the DesignWare APB timer to the K230 SoC with
num-timers=6 and a 6.25 MHz clock. Connect timer IRQs
to the PLIC. Remove the previous unimplemented timer stub.
Signed-off-by: raoyi <rao232328@gmail.com>
---
hw/riscv/Kconfig | 1 +
hw/riscv/k230.c | 33 ++++++++++++++++++++++++++++++---
include/hw/riscv/k230.h | 8 ++++++++
3 files changed, 39 insertions(+), 3 deletions(-)
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index de37c08cae..c2c01af6ee 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_APB_TIMER
diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c
index 656f28190c..78a05492ad 100644
--- a/hw/riscv/k230.c
+++ b/hw/riscv/k230.c
@@ -24,6 +24,8 @@
#include "target/riscv/cpu.h"
#include "hw/core/loader.h"
#include "hw/core/sysbus.h"
+#include "hw/core/clock.h"
+#include "hw/core/qdev-clock.h"
#include "hw/riscv/k230.h"
#include "hw/riscv/boot.h"
#include "hw/riscv/machines-qom.h"
@@ -37,6 +39,9 @@
#define K230_DIRECT_KERNEL_ADDR 0x8200000
#define K230_DIRECT_DTB_ADDR 0xa000000
+#define K230_TIMER_NUM_CHANNELS 6
+#define K230_TIMER_DEFAULT_FREQ 6250000
+
static const MemMapEntry memmap[] = {
[K230_DEV_DDRC] = { 0x00000000, 0x80000000 },
[K230_DEV_KPU_L2_CACHE] = { 0x80000000, 0x00200000 },
@@ -110,6 +115,19 @@ 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, "dw-apb-timer", &s->timer,
+ TYPE_DW_APB_TIMER);
+
+ qdev_prop_set_uint32(DEVICE(&s->timer), "num-timers",
+ K230_TIMER_NUM_CHANNELS);
+
+ Clock *timer_clk = clock_new(OBJECT(s), "timer-clk");
+ clock_set_hz(timer_clk, K230_TIMER_DEFAULT_FREQ);
+ for (int i = 0; i < K230_TIMER_NUM_CHANNELS; i++) {
+ g_autofree char *clock_name = g_strdup_printf("timer[%d]", i);
+
+ qdev_connect_clock_in(DEVICE(&s->timer), clock_name, timer_clk);
+ }
qdev_prop_set_uint32(DEVICE(cpu0), "hartid-base", 0);
qdev_prop_set_string(DEVICE(cpu0), "cpu-type", TYPE_RISCV_CPU_THEAD_C908);
@@ -191,6 +209,18 @@ static void k230_soc_realize(DeviceState *dev, Error **errp)
k230_create_uart(sys_mem, DEVICE(s->c908_plic), i);
}
+ /* Timer */
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->timer), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->timer), 0, memmap[K230_DEV_TIMER].base);
+
+ for (int i = 0; i < K230_TIMER_NUM_CHANNELS; i++) {
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->timer), i,
+ qdev_get_gpio_in(DEVICE(s->c908_plic),
+ K230_TIMER0_IRQ + i));
+ }
+
/* Watchdog */
for (int i = 0; i < 2; i++) {
if (!sysbus_realize(SYS_BUS_DEVICE(&s->wdt[i]), errp)) {
@@ -283,9 +313,6 @@ static void k230_soc_realize(DeviceState *dev, Error **errp)
create_unimplemented_device("iomux", memmap[K230_DEV_IOMUX].base,
memmap[K230_DEV_IOMUX].size);
- create_unimplemented_device("timer", memmap[K230_DEV_TIMER].base,
- memmap[K230_DEV_TIMER].size);
-
create_unimplemented_device("wdt0", memmap[K230_DEV_WDT0].base,
memmap[K230_DEV_WDT0].size);
diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h
index 592e1c26bf..42a36664eb 100644
--- a/include/hw/riscv/k230.h
+++ b/include/hw/riscv/k230.h
@@ -18,6 +18,7 @@
#include "hw/core/boards.h"
#include "hw/riscv/riscv_hart.h"
#include "hw/watchdog/k230_wdt.h"
+#include "hw/timer/dw-apb-timer.h"
#define C908_CPU_HARTID (0)
@@ -33,6 +34,7 @@ typedef struct K230SoCState {
RISCVHartArrayState c908_cpu; /* Small core */
K230WdtState wdt[2];
+ DWAPBTimerState timer;
MemoryRegion sram;
MemoryRegion bootrom;
@@ -127,6 +129,12 @@ enum {
K230_UART2_IRQ = 18,
K230_UART3_IRQ = 19,
K230_UART4_IRQ = 20,
+ K230_TIMER0_IRQ = 101,
+ K230_TIMER1_IRQ = 102,
+ K230_TIMER2_IRQ = 103,
+ K230_TIMER3_IRQ = 104,
+ K230_TIMER4_IRQ = 105,
+ K230_TIMER5_IRQ = 106,
K230_WDT0_IRQ = 107,
K230_WDT1_IRQ = 108,
};
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH v4 2/3] hw/riscv: add DesignWare APB timer to K230 board
2026-08-13 13:36 ` [PATCH v4 2/3] hw/riscv: add DesignWare APB timer to K230 board raoyi
@ 2026-08-14 7:40 ` Bin Meng
2026-08-18 13:56 ` 回复: " 饶轶
0 siblings, 1 reply; 10+ messages in thread
From: Bin Meng @ 2026-08-14 7:40 UTC (permalink / raw)
To: raoyi
Cc: qemu-devel, qemu-riscv, alistair.francis, palmer, liwei1518,
daniel.barboza, zhiwei_liu, chao.liu, philmd, pbonzini, lvivier,
farosas, caojunze424
On Thu, Aug 13, 2026 at 9:37 PM raoyi <rao232328@gmail.com> wrote:
>
> Wire up the DesignWare APB timer to the K230 SoC with
> num-timers=6 and a 6.25 MHz clock. Connect timer IRQs
> to the PLIC. Remove the previous unimplemented timer stub.
>
> Signed-off-by: raoyi <rao232328@gmail.com>
> ---
> hw/riscv/Kconfig | 1 +
> hw/riscv/k230.c | 33 ++++++++++++++++++++++++++++++---
> include/hw/riscv/k230.h | 8 ++++++++
> 3 files changed, 39 insertions(+), 3 deletions(-)
>
> diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
> index de37c08cae..c2c01af6ee 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_APB_TIMER
> diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c
> index 656f28190c..78a05492ad 100644
> --- a/hw/riscv/k230.c
> +++ b/hw/riscv/k230.c
> @@ -24,6 +24,8 @@
> #include "target/riscv/cpu.h"
> #include "hw/core/loader.h"
> #include "hw/core/sysbus.h"
> +#include "hw/core/clock.h"
> +#include "hw/core/qdev-clock.h"
> #include "hw/riscv/k230.h"
> #include "hw/riscv/boot.h"
> #include "hw/riscv/machines-qom.h"
> @@ -37,6 +39,9 @@
> #define K230_DIRECT_KERNEL_ADDR 0x8200000
> #define K230_DIRECT_DTB_ADDR 0xa000000
>
> +#define K230_TIMER_NUM_CHANNELS 6
> +#define K230_TIMER_DEFAULT_FREQ 6250000
The TRM says the timer frequency is 50MHz. Where is this 6250000 coming from?
> +
> static const MemMapEntry memmap[] = {
> [K230_DEV_DDRC] = { 0x00000000, 0x80000000 },
> [K230_DEV_KPU_L2_CACHE] = { 0x80000000, 0x00200000 },
> @@ -110,6 +115,19 @@ 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, "dw-apb-timer", &s->timer,
> + TYPE_DW_APB_TIMER);
> +
> + qdev_prop_set_uint32(DEVICE(&s->timer), "num-timers",
> + K230_TIMER_NUM_CHANNELS);
> +
> + Clock *timer_clk = clock_new(OBJECT(s), "timer-clk");
> + clock_set_hz(timer_clk, K230_TIMER_DEFAULT_FREQ);
> + for (int i = 0; i < K230_TIMER_NUM_CHANNELS; i++) {
> + g_autofree char *clock_name = g_strdup_printf("timer[%d]", i);
> +
> + qdev_connect_clock_in(DEVICE(&s->timer), clock_name, timer_clk);
> + }
>
> qdev_prop_set_uint32(DEVICE(cpu0), "hartid-base", 0);
> qdev_prop_set_string(DEVICE(cpu0), "cpu-type", TYPE_RISCV_CPU_THEAD_C908);
> @@ -191,6 +209,18 @@ static void k230_soc_realize(DeviceState *dev, Error **errp)
> k230_create_uart(sys_mem, DEVICE(s->c908_plic), i);
> }
>
> + /* Timer */
> + if (!sysbus_realize(SYS_BUS_DEVICE(&s->timer), errp)) {
> + return;
> + }
> + sysbus_mmio_map(SYS_BUS_DEVICE(&s->timer), 0, memmap[K230_DEV_TIMER].base);
> +
> + for (int i = 0; i < K230_TIMER_NUM_CHANNELS; i++) {
> + sysbus_connect_irq(SYS_BUS_DEVICE(&s->timer), i,
> + qdev_get_gpio_in(DEVICE(s->c908_plic),
> + K230_TIMER0_IRQ + i));
> + }
> +
> /* Watchdog */
> for (int i = 0; i < 2; i++) {
> if (!sysbus_realize(SYS_BUS_DEVICE(&s->wdt[i]), errp)) {
> @@ -283,9 +313,6 @@ static void k230_soc_realize(DeviceState *dev, Error **errp)
> create_unimplemented_device("iomux", memmap[K230_DEV_IOMUX].base,
> memmap[K230_DEV_IOMUX].size);
>
> - create_unimplemented_device("timer", memmap[K230_DEV_TIMER].base,
> - memmap[K230_DEV_TIMER].size);
> -
> create_unimplemented_device("wdt0", memmap[K230_DEV_WDT0].base,
> memmap[K230_DEV_WDT0].size);
>
> diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h
> index 592e1c26bf..42a36664eb 100644
> --- a/include/hw/riscv/k230.h
> +++ b/include/hw/riscv/k230.h
> @@ -18,6 +18,7 @@
> #include "hw/core/boards.h"
> #include "hw/riscv/riscv_hart.h"
> #include "hw/watchdog/k230_wdt.h"
> +#include "hw/timer/dw-apb-timer.h"
>
> #define C908_CPU_HARTID (0)
>
> @@ -33,6 +34,7 @@ typedef struct K230SoCState {
> RISCVHartArrayState c908_cpu; /* Small core */
>
> K230WdtState wdt[2];
> + DWAPBTimerState timer;
> MemoryRegion sram;
> MemoryRegion bootrom;
>
> @@ -127,6 +129,12 @@ enum {
> K230_UART2_IRQ = 18,
> K230_UART3_IRQ = 19,
> K230_UART4_IRQ = 20,
> + K230_TIMER0_IRQ = 101,
> + K230_TIMER1_IRQ = 102,
> + K230_TIMER2_IRQ = 103,
> + K230_TIMER3_IRQ = 104,
> + K230_TIMER4_IRQ = 105,
> + K230_TIMER5_IRQ = 106,
> K230_WDT0_IRQ = 107,
> K230_WDT1_IRQ = 108,
> };
> --
Regards,
Bin
^ permalink raw reply [flat|nested] 10+ messages in thread* 回复: [PATCH v4 2/3] hw/riscv: add DesignWare APB timer to K230 board
2026-08-14 7:40 ` Bin Meng
@ 2026-08-18 13:56 ` 饶轶
0 siblings, 0 replies; 10+ messages in thread
From: 饶轶 @ 2026-08-18 13:56 UTC (permalink / raw)
To: Bin Meng
Cc: qemu-devel@nongnu.org, qemu-riscv@nongnu.org,
alistair.francis@wdc.com, palmer@dabbelt.com, liwei1518@gmail.com,
daniel.barboza@oss.qualcomm.com, zhiwei_liu@linux.alibaba.com,
chao.liu@processmission.com, philmd@oss.qualcomm.com,
pbonzini@redhat.com, lvivier@redhat.com, farosas@suse.de,
caojunze424@gmail.com
[-- Attachment #1: Type: text/plain, Size: 6124 bytes --]
Thanks for pointing this out.
You are right. According to the TRM, timerN_clk_sel
resets to 1 and selects the 50 MHz timerN_pulse_in clock.
I mistakenly applied the /8 divider and used 6.25 MHz.
I will fix the timer clock frequency to 50 MHz in v5.
________________________________
发件人: Bin Meng <bmeng.cn@gmail.com>
发送时间: 2026年8月14日 15:40
收件人: raoyi <rao232328@gmail.com>
抄送: qemu-devel@nongnu.org <qemu-devel@nongnu.org>; qemu-riscv@nongnu.org <qemu-riscv@nongnu.org>; alistair.francis@wdc.com <alistair.francis@wdc.com>; palmer@dabbelt.com <palmer@dabbelt.com>; liwei1518@gmail.com <liwei1518@gmail.com>; daniel.barboza@oss.qualcomm.com <daniel.barboza@oss.qualcomm.com>; zhiwei_liu@linux.alibaba.com <zhiwei_liu@linux.alibaba.com>; chao.liu@processmission.com <chao.liu@processmission.com>; philmd@oss.qualcomm.com <philmd@oss.qualcomm.com>; pbonzini@redhat.com <pbonzini@redhat.com>; lvivier@redhat.com <lvivier@redhat.com>; farosas@suse.de <farosas@suse.de>; caojunze424@gmail.com <caojunze424@gmail.com>
主题: Re: [PATCH v4 2/3] hw/riscv: add DesignWare APB timer to K230 board
On Thu, Aug 13, 2026 at 9:37 PM raoyi <rao232328@gmail.com> wrote:
>
> Wire up the DesignWare APB timer to the K230 SoC with
> num-timers=6 and a 6.25 MHz clock. Connect timer IRQs
> to the PLIC. Remove the previous unimplemented timer stub.
>
> Signed-off-by: raoyi <rao232328@gmail.com>
> ---
> hw/riscv/Kconfig | 1 +
> hw/riscv/k230.c | 33 ++++++++++++++++++++++++++++++---
> include/hw/riscv/k230.h | 8 ++++++++
> 3 files changed, 39 insertions(+), 3 deletions(-)
>
> diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
> index de37c08cae..c2c01af6ee 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_APB_TIMER
> diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c
> index 656f28190c..78a05492ad 100644
> --- a/hw/riscv/k230.c
> +++ b/hw/riscv/k230.c
> @@ -24,6 +24,8 @@
> #include "target/riscv/cpu.h"
> #include "hw/core/loader.h"
> #include "hw/core/sysbus.h"
> +#include "hw/core/clock.h"
> +#include "hw/core/qdev-clock.h"
> #include "hw/riscv/k230.h"
> #include "hw/riscv/boot.h"
> #include "hw/riscv/machines-qom.h"
> @@ -37,6 +39,9 @@
> #define K230_DIRECT_KERNEL_ADDR 0x8200000
> #define K230_DIRECT_DTB_ADDR 0xa000000
>
> +#define K230_TIMER_NUM_CHANNELS 6
> +#define K230_TIMER_DEFAULT_FREQ 6250000
The TRM says the timer frequency is 50MHz. Where is this 6250000 coming from?
> +
> static const MemMapEntry memmap[] = {
> [K230_DEV_DDRC] = { 0x00000000, 0x80000000 },
> [K230_DEV_KPU_L2_CACHE] = { 0x80000000, 0x00200000 },
> @@ -110,6 +115,19 @@ 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, "dw-apb-timer", &s->timer,
> + TYPE_DW_APB_TIMER);
> +
> + qdev_prop_set_uint32(DEVICE(&s->timer), "num-timers",
> + K230_TIMER_NUM_CHANNELS);
> +
> + Clock *timer_clk = clock_new(OBJECT(s), "timer-clk");
> + clock_set_hz(timer_clk, K230_TIMER_DEFAULT_FREQ);
> + for (int i = 0; i < K230_TIMER_NUM_CHANNELS; i++) {
> + g_autofree char *clock_name = g_strdup_printf("timer[%d]", i);
> +
> + qdev_connect_clock_in(DEVICE(&s->timer), clock_name, timer_clk);
> + }
>
> qdev_prop_set_uint32(DEVICE(cpu0), "hartid-base", 0);
> qdev_prop_set_string(DEVICE(cpu0), "cpu-type", TYPE_RISCV_CPU_THEAD_C908);
> @@ -191,6 +209,18 @@ static void k230_soc_realize(DeviceState *dev, Error **errp)
> k230_create_uart(sys_mem, DEVICE(s->c908_plic), i);
> }
>
> + /* Timer */
> + if (!sysbus_realize(SYS_BUS_DEVICE(&s->timer), errp)) {
> + return;
> + }
> + sysbus_mmio_map(SYS_BUS_DEVICE(&s->timer), 0, memmap[K230_DEV_TIMER].base);
> +
> + for (int i = 0; i < K230_TIMER_NUM_CHANNELS; i++) {
> + sysbus_connect_irq(SYS_BUS_DEVICE(&s->timer), i,
> + qdev_get_gpio_in(DEVICE(s->c908_plic),
> + K230_TIMER0_IRQ + i));
> + }
> +
> /* Watchdog */
> for (int i = 0; i < 2; i++) {
> if (!sysbus_realize(SYS_BUS_DEVICE(&s->wdt[i]), errp)) {
> @@ -283,9 +313,6 @@ static void k230_soc_realize(DeviceState *dev, Error **errp)
> create_unimplemented_device("iomux", memmap[K230_DEV_IOMUX].base,
> memmap[K230_DEV_IOMUX].size);
>
> - create_unimplemented_device("timer", memmap[K230_DEV_TIMER].base,
> - memmap[K230_DEV_TIMER].size);
> -
> create_unimplemented_device("wdt0", memmap[K230_DEV_WDT0].base,
> memmap[K230_DEV_WDT0].size);
>
> diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h
> index 592e1c26bf..42a36664eb 100644
> --- a/include/hw/riscv/k230.h
> +++ b/include/hw/riscv/k230.h
> @@ -18,6 +18,7 @@
> #include "hw/core/boards.h"
> #include "hw/riscv/riscv_hart.h"
> #include "hw/watchdog/k230_wdt.h"
> +#include "hw/timer/dw-apb-timer.h"
>
> #define C908_CPU_HARTID (0)
>
> @@ -33,6 +34,7 @@ typedef struct K230SoCState {
> RISCVHartArrayState c908_cpu; /* Small core */
>
> K230WdtState wdt[2];
> + DWAPBTimerState timer;
> MemoryRegion sram;
> MemoryRegion bootrom;
>
> @@ -127,6 +129,12 @@ enum {
> K230_UART2_IRQ = 18,
> K230_UART3_IRQ = 19,
> K230_UART4_IRQ = 20,
> + K230_TIMER0_IRQ = 101,
> + K230_TIMER1_IRQ = 102,
> + K230_TIMER2_IRQ = 103,
> + K230_TIMER3_IRQ = 104,
> + K230_TIMER4_IRQ = 105,
> + K230_TIMER5_IRQ = 106,
> K230_WDT0_IRQ = 107,
> K230_WDT1_IRQ = 108,
> };
> --
Regards,
Bin
[-- Attachment #2: Type: text/html, Size: 11795 bytes --]
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v4 3/3] tests/qtest: add k230 dwapb timer test
2026-08-13 13:36 [PATCH v4 0/3] hw/timer: add DesignWare APB timer raoyi
2026-08-13 13:36 ` [PATCH v4 1/3] hw/timer: add DesignWare APB timer model raoyi
2026-08-13 13:36 ` [PATCH v4 2/3] hw/riscv: add DesignWare APB timer to K230 board raoyi
@ 2026-08-13 13:36 ` raoyi
2026-08-14 7:40 ` Bin Meng
2 siblings, 1 reply; 10+ messages in thread
From: raoyi @ 2026-08-13 13:36 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-riscv, alistair.francis, palmer, liwei1518, daniel.barboza,
zhiwei_liu, chao.liu, bmeng.cn, philmd, pbonzini, lvivier,
farosas, caojunze424, raoyi
Add qtest coverage for K230 DW APB timer including
free-running, periodic, interrupt mask, disable
behavior, current value, dynamic reload, and
multi-channel scenarios.
Add timer test file to MAINTAINERS.
Signed-off-by: raoyi <rao232328@gmail.com>
---
MAINTAINERS | 1 +
tests/qtest/k230-dwapb-timer-test.c | 274 ++++++++++++++++++++++++++++
tests/qtest/meson.build | 4 +-
3 files changed, 278 insertions(+), 1 deletion(-)
create mode 100644 tests/qtest/k230-dwapb-timer-test.c
diff --git a/MAINTAINERS b/MAINTAINERS
index cf689433ff..82e5643a93 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1830,6 +1830,7 @@ F: hw/timer/dw-apb-timer.c
F: include/hw/riscv/k230.h
F: include/hw/watchdog/k230_wdt.h
F: include/hw/timer/dw-apb-timer.h
+F: tests/qtest/k230-dwapb-timer-test.c
F: tests/qtest/k230-wdt-test.c
RX Machines
diff --git a/tests/qtest/k230-dwapb-timer-test.c b/tests/qtest/k230-dwapb-timer-test.c
new file mode 100644
index 0000000000..a74f36999e
--- /dev/null
+++ b/tests/qtest/k230-dwapb-timer-test.c
@@ -0,0 +1,274 @@
+/*
+ * QTest testcase for the DesignWare APB timer through the K230 machine
+ *
+ * Copyright (c) 2026 raoyi <rao232328@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "qemu/osdep.h"
+#include "qemu/timer.h"
+#include "libqtest.h"
+
+#define TIMER_BASE 0x91105800
+#define K230_TIMER_NUM_CHANNELS 6
+#define K230_TIMER_DEFAULT_FREQ 6250000
+
+#define DW_APB_TIMER_STRIDE 0x14
+#define DW_APB_TIMER_N_LOAD_COUNT 0x00
+#define DW_APB_TIMER_N_CURRENT_VALUE 0x04
+#define DW_APB_TIMER_N_CONTROL 0x08
+#define DW_APB_TIMER_N_EOI 0x0c
+#define DW_APB_TIMER_N_INT_STATUS 0x10
+#define DW_APB_TIMER_INT_STATUS 0xa0
+#define DW_APB_TIMER_EOI 0xa4
+#define DW_APB_TIMER_RAW_INT_STATUS 0xa8
+#define DW_APB_TIMER_CONTROL_ENABLE BIT(0)
+#define DW_APB_TIMER_CONTROL_MODE_PERIODIC BIT(1)
+#define DW_APB_TIMER_CONTROL_INT BIT(2)
+
+#define TIMER_REG(n, reg) (TIMER_BASE + (n) * DW_APB_TIMER_STRIDE + (reg))
+#define TIMER_TICK_NS (NANOSECONDS_PER_SECOND / K230_TIMER_DEFAULT_FREQ)
+
+static void timer_load(QTestState *qts, int n, uint32_t value)
+{
+ qtest_writel(qts, TIMER_REG(n, DW_APB_TIMER_N_LOAD_COUNT), value);
+}
+
+static void timer_enable(QTestState *qts, int n, uint32_t ctrl)
+{
+ qtest_writel(qts, TIMER_REG(n, DW_APB_TIMER_N_CONTROL), ctrl);
+}
+
+static void timer_disable(QTestState *qts, int n)
+{
+ qtest_writel(qts, TIMER_REG(n, DW_APB_TIMER_N_CONTROL), 0);
+}
+
+static uint32_t timer_get_and_clear_status(QTestState *qts, int n)
+{
+ uint32_t int_status = qtest_readl(qts,
+ TIMER_REG(n, DW_APB_TIMER_N_INT_STATUS));
+
+ if (int_status) {
+ qtest_readl(qts, TIMER_REG(n, DW_APB_TIMER_N_EOI));
+ }
+
+ return int_status;
+}
+
+static void test_timer_free_running(void)
+{
+ QTestState *qts = qtest_init("-machine k230");
+
+ timer_load(qts, 0, 100);
+ timer_enable(qts, 0, DW_APB_TIMER_CONTROL_ENABLE);
+
+ qtest_clock_step(qts, 101ULL * TIMER_TICK_NS + 1);
+ g_assert_cmphex(timer_get_and_clear_status(qts, 0), ==, 1);
+
+ qtest_clock_step(qts, 10ULL * TIMER_TICK_NS);
+ uint32_t cur = qtest_readl(qts,
+ TIMER_REG(0, DW_APB_TIMER_N_CURRENT_VALUE));
+ g_assert_cmphex(cur, <, UINT32_MAX);
+ g_assert_cmphex(cur, >, 0);
+
+ g_assert_cmphex(qtest_readl(qts,
+ TIMER_REG(0, DW_APB_TIMER_N_INT_STATUS)), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_timer_periodic(void)
+{
+ QTestState *qts = qtest_init("-machine k230");
+ int i;
+
+ timer_load(qts, 0, 100);
+ timer_enable(qts, 0, DW_APB_TIMER_CONTROL_ENABLE |
+ DW_APB_TIMER_CONTROL_MODE_PERIODIC);
+
+ for (i = 0; i < 3; i++) {
+ qtest_clock_step(qts, 101ULL * TIMER_TICK_NS + 1);
+ g_assert_cmphex(timer_get_and_clear_status(qts, 0), ==, 1);
+ uint32_t cur = qtest_readl(qts,
+ TIMER_REG(0, DW_APB_TIMER_N_CURRENT_VALUE));
+ g_assert_cmphex(cur, <=, 100);
+ }
+
+ qtest_quit(qts);
+}
+
+static void test_timer_int_mask(void)
+{
+ QTestState *qts = qtest_init("-machine k230");
+
+ timer_load(qts, 0, 100);
+ timer_enable(qts, 0, DW_APB_TIMER_CONTROL_ENABLE |
+ DW_APB_TIMER_CONTROL_MODE_PERIODIC |
+ DW_APB_TIMER_CONTROL_INT);
+
+ qtest_clock_step(qts, 101ULL * TIMER_TICK_NS + 1);
+
+ g_assert_cmphex(qtest_readl(qts,
+ TIMER_REG(0, DW_APB_TIMER_N_INT_STATUS)) & 1, ==, 0);
+ g_assert_cmphex(qtest_readl(qts,
+ TIMER_BASE + DW_APB_TIMER_INT_STATUS), ==, 0);
+ g_assert_cmphex(qtest_readl(qts,
+ TIMER_BASE + DW_APB_TIMER_RAW_INT_STATUS) & 1, ==, 1);
+
+ /* Unmasking a pending interrupt must assert it immediately. */
+ timer_enable(qts, 0, DW_APB_TIMER_CONTROL_ENABLE |
+ DW_APB_TIMER_CONTROL_MODE_PERIODIC);
+ g_assert_cmphex(qtest_readl(qts,
+ TIMER_REG(0, DW_APB_TIMER_N_INT_STATUS)), ==, 1);
+ g_assert_cmphex(qtest_readl(qts,
+ TIMER_BASE + DW_APB_TIMER_INT_STATUS) & 1, ==, 1);
+
+ qtest_quit(qts);
+}
+
+static void test_timer_disable_clears_irq(void)
+{
+ QTestState *qts = qtest_init("-machine k230");
+
+ timer_load(qts, 0, 100);
+ timer_enable(qts, 0, DW_APB_TIMER_CONTROL_ENABLE |
+ DW_APB_TIMER_CONTROL_MODE_PERIODIC);
+
+ qtest_clock_step(qts, 101ULL * TIMER_TICK_NS + 1);
+ g_assert_cmphex(qtest_readl(qts,
+ TIMER_REG(0, DW_APB_TIMER_N_INT_STATUS)) & 1, ==, 1);
+
+ timer_disable(qts, 0);
+ g_assert_cmphex(qtest_readl(qts,
+ TIMER_REG(0, DW_APB_TIMER_N_INT_STATUS)) & 1, ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_timer_current_disabled(void)
+{
+ QTestState *qts = qtest_init("-machine k230");
+
+ /* TRM: "A '0' is always read back when the timer is not enabled" */
+ g_assert_cmphex(qtest_readl(qts,
+ TIMER_REG(0, DW_APB_TIMER_N_CURRENT_VALUE)), ==, 0);
+
+ timer_load(qts, 0, 9999);
+ g_assert_cmphex(qtest_readl(qts,
+ TIMER_REG(0, DW_APB_TIMER_N_CURRENT_VALUE)), ==, 0);
+
+ timer_enable(qts, 0, DW_APB_TIMER_CONTROL_ENABLE);
+ qtest_clock_step(qts, 10ULL * TIMER_TICK_NS);
+ uint32_t cur_enabled = qtest_readl(qts,
+ TIMER_REG(0, DW_APB_TIMER_N_CURRENT_VALUE));
+ g_assert_cmphex(cur_enabled, >, 0);
+ g_assert_cmphex(cur_enabled, <, UINT32_MAX);
+
+ timer_disable(qts, 0);
+ g_assert_cmphex(qtest_readl(qts,
+ TIMER_REG(0, DW_APB_TIMER_N_CURRENT_VALUE)), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_timer_dynamic_reload(void)
+{
+ QTestState *qts = qtest_init("-machine k230");
+ uint32_t cur;
+
+ timer_load(qts, 0, 1000);
+ timer_enable(qts, 0, DW_APB_TIMER_CONTROL_ENABLE |
+ DW_APB_TIMER_CONTROL_MODE_PERIODIC);
+
+ qtest_clock_step(qts, 100ULL * TIMER_TICK_NS);
+
+ timer_load(qts, 0, 200);
+
+ qtest_clock_step(qts, 901ULL * TIMER_TICK_NS + 1);
+ g_assert_cmphex(timer_get_and_clear_status(qts, 0), ==, 1);
+
+ qtest_clock_step(qts, 100ULL * TIMER_TICK_NS);
+ cur = qtest_readl(qts, TIMER_REG(0, DW_APB_TIMER_N_CURRENT_VALUE));
+ g_assert_cmphex(cur, ==, 99);
+
+ qtest_quit(qts);
+}
+
+static void test_timer_all_channels(void)
+{
+ QTestState *qts = qtest_init("-machine k230");
+ int i;
+
+ for (i = 0; i < K230_TIMER_NUM_CHANNELS; i++) {
+ timer_load(qts, i, 100 + i * 50);
+ timer_enable(qts, i, DW_APB_TIMER_CONTROL_ENABLE |
+ DW_APB_TIMER_CONTROL_MODE_PERIODIC);
+ }
+
+ /* Step past timer 0 expiry (load=100) - only timer 0 should fire */
+ qtest_clock_step(qts, 101ULL * TIMER_TICK_NS + 1);
+ uint32_t sts = qtest_readl(qts, TIMER_BASE + DW_APB_TIMER_INT_STATUS);
+ g_assert_cmphex(sts & 1, ==, 1);
+ g_assert_cmphex(sts & 0x3e, ==, 0);
+
+ /* Step to timer 1 expiry (another 50 ticks) */
+ qtest_clock_step(qts, 50ULL * TIMER_TICK_NS + 1);
+ sts = qtest_readl(qts, TIMER_BASE + DW_APB_TIMER_INT_STATUS);
+ g_assert_cmphex(sts & 0x3, ==, 0x3);
+ g_assert_cmphex(sts & 0x3c, ==, 0);
+
+ /* Step to expiry of all remaining timers (another 200 ticks) */
+ qtest_clock_step(qts, 200ULL * TIMER_TICK_NS + 1);
+ sts = qtest_readl(qts, TIMER_BASE + DW_APB_TIMER_INT_STATUS);
+ g_assert_cmphex(sts, ==, 0x3f);
+
+ /* EOI_ALL clears all */
+ qtest_readl(qts, TIMER_BASE + DW_APB_TIMER_EOI);
+ sts = qtest_readl(qts, TIMER_BASE + DW_APB_TIMER_INT_STATUS);
+ g_assert_cmphex(sts, ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_timer_reset(void)
+{
+ QTestState *qts = qtest_init("-machine k230");
+
+ timer_load(qts, 0, 100);
+ timer_enable(qts, 0, DW_APB_TIMER_CONTROL_ENABLE |
+ DW_APB_TIMER_CONTROL_MODE_PERIODIC);
+ qtest_clock_step(qts, 10ULL * TIMER_TICK_NS);
+
+ qtest_system_reset(qts);
+
+ g_assert_cmphex(qtest_readl(qts,
+ TIMER_REG(0, DW_APB_TIMER_N_LOAD_COUNT)), ==, 0);
+ g_assert_cmphex(qtest_readl(qts,
+ TIMER_REG(0, DW_APB_TIMER_N_CONTROL)), ==, 0);
+ g_assert_cmphex(qtest_readl(qts,
+ TIMER_REG(0, DW_APB_TIMER_N_CURRENT_VALUE)), ==, 0);
+ g_assert_cmphex(qtest_readl(qts,
+ TIMER_BASE + DW_APB_TIMER_RAW_INT_STATUS), ==, 0);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char *argv[])
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/dw-apb-timer/free_running", test_timer_free_running);
+ qtest_add_func("/dw-apb-timer/periodic", test_timer_periodic);
+ qtest_add_func("/dw-apb-timer/int_mask", test_timer_int_mask);
+ qtest_add_func("/dw-apb-timer/disable_clears_irq",
+ test_timer_disable_clears_irq);
+ qtest_add_func("/dw-apb-timer/current_disabled",
+ test_timer_current_disabled);
+ qtest_add_func("/dw-apb-timer/dynamic_reload",
+ test_timer_dynamic_reload);
+ qtest_add_func("/dw-apb-timer/all_channels", test_timer_all_channels);
+ qtest_add_func("/dw-apb-timer/reset", test_timer_reset);
+
+ return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 56ff860e21..295a724116 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -297,7 +297,9 @@ 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') and
+ config_all_devices.has_key('CONFIG_DW_APB_TIMER') ?
+ ['k230-wdt-test', 'k230-dwapb-timer-test'] : [])
qtests_hexagon = ['boot-serial-test']
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH v4 3/3] tests/qtest: add k230 dwapb timer test
2026-08-13 13:36 ` [PATCH v4 3/3] tests/qtest: add k230 dwapb timer test raoyi
@ 2026-08-14 7:40 ` Bin Meng
2026-08-18 14:00 ` 回复: " 饶轶
0 siblings, 1 reply; 10+ messages in thread
From: Bin Meng @ 2026-08-14 7:40 UTC (permalink / raw)
To: raoyi
Cc: qemu-devel, qemu-riscv, alistair.francis, palmer, liwei1518,
daniel.barboza, zhiwei_liu, chao.liu, philmd, pbonzini, lvivier,
farosas, caojunze424
On Thu, Aug 13, 2026 at 9:37 PM raoyi <rao232328@gmail.com> wrote:
>
> Add qtest coverage for K230 DW APB timer including
> free-running, periodic, interrupt mask, disable
> behavior, current value, dynamic reload, and
> multi-channel scenarios.
nits: please make sure the commit message width is about 70 characters per line.
>
> Add timer test file to MAINTAINERS.
>
> Signed-off-by: raoyi <rao232328@gmail.com>
> ---
> MAINTAINERS | 1 +
> tests/qtest/k230-dwapb-timer-test.c | 274 ++++++++++++++++++++++++++++
> tests/qtest/meson.build | 4 +-
> 3 files changed, 278 insertions(+), 1 deletion(-)
> create mode 100644 tests/qtest/k230-dwapb-timer-test.c
>
Other than that,
Reviewed-by: Bin Meng <bin.meng@processmission.com>
^ permalink raw reply [flat|nested] 10+ messages in thread
* 回复: [PATCH v4 3/3] tests/qtest: add k230 dwapb timer test
2026-08-14 7:40 ` Bin Meng
@ 2026-08-18 14:00 ` 饶轶
0 siblings, 0 replies; 10+ messages in thread
From: 饶轶 @ 2026-08-18 14:00 UTC (permalink / raw)
To: Bin Meng
Cc: qemu-devel@nongnu.org, qemu-riscv@nongnu.org,
alistair.francis@wdc.com, palmer@dabbelt.com, liwei1518@gmail.com,
daniel.barboza@oss.qualcomm.com, zhiwei_liu@linux.alibaba.com,
chao.liu@processmission.com, philmd@oss.qualcomm.com,
pbonzini@redhat.com, lvivier@redhat.com, farosas@suse.de,
caojunze424@gmail.com
[-- Attachment #1: Type: text/plain, Size: 1768 bytes --]
Thanks for the review.
Renamed DW_APB_TIMER_CONTROL_INT to
DW_APB_TIMER_CONTROL_INT_MASK in v5.
________________________________
发件人: Bin Meng <bmeng.cn@gmail.com>
发送时间: 2026年8月14日 15:40
收件人: raoyi <rao232328@gmail.com>
抄送: qemu-devel@nongnu.org <qemu-devel@nongnu.org>; qemu-riscv@nongnu.org <qemu-riscv@nongnu.org>; alistair.francis@wdc.com <alistair.francis@wdc.com>; palmer@dabbelt.com <palmer@dabbelt.com>; liwei1518@gmail.com <liwei1518@gmail.com>; daniel.barboza@oss.qualcomm.com <daniel.barboza@oss.qualcomm.com>; zhiwei_liu@linux.alibaba.com <zhiwei_liu@linux.alibaba.com>; chao.liu@processmission.com <chao.liu@processmission.com>; philmd@oss.qualcomm.com <philmd@oss.qualcomm.com>; pbonzini@redhat.com <pbonzini@redhat.com>; lvivier@redhat.com <lvivier@redhat.com>; farosas@suse.de <farosas@suse.de>; caojunze424@gmail.com <caojunze424@gmail.com>
主题: Re: [PATCH v4 3/3] tests/qtest: add k230 dwapb timer test
On Thu, Aug 13, 2026 at 9:37 PM raoyi <rao232328@gmail.com> wrote:
>
> Add qtest coverage for K230 DW APB timer including
> free-running, periodic, interrupt mask, disable
> behavior, current value, dynamic reload, and
> multi-channel scenarios.
nits: please make sure the commit message width is about 70 characters per line.
>
> Add timer test file to MAINTAINERS.
>
> Signed-off-by: raoyi <rao232328@gmail.com>
> ---
> MAINTAINERS | 1 +
> tests/qtest/k230-dwapb-timer-test.c | 274 ++++++++++++++++++++++++++++
> tests/qtest/meson.build | 4 +-
> 3 files changed, 278 insertions(+), 1 deletion(-)
> create mode 100644 tests/qtest/k230-dwapb-timer-test.c
>
Other than that,
Reviewed-by: Bin Meng <bin.meng@processmission.com>
[-- Attachment #2: Type: text/html, Size: 3523 bytes --]
^ permalink raw reply [flat|nested] 10+ messages in thread