All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v4 0/3] hw/timer: add DesignWare APB timer
@ 2026-08-13 13:36 raoyi
  2026-08-13 13:36 ` [PATCH v4 1/3] hw/timer: add DesignWare APB timer model raoyi
                   ` (2 more replies)
  0 siblings, 3 replies; 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 a generic DesignWare APB timer device model, and wire it
up for the K230 machine with num-timers=6 and a 6.25 MHz clock.

Changes since v3 (address review comments from Philippe Mathieu-Daude):
- Drop Clock framework mention from the commit message
- Cover the device in MAINTAINERS (device files in patch 1/3,
  test file in patch 3/3)
- Move trace calls to function entry
- Drop the addr mask; the MMIO region stays at 0x100 (the IP
  register implementation range; the SoC aperture is board-level)
- Hard-code comp-version (0x3231312A, "211*", IP component version)
- Use the Resettable API (phases.hold) instead of legacy reset
- Add unrealize to free per-channel ptimers
- Keep register offsets and bit definitions in the C file
- Drop private/public comments in the header
- Move K230 timer macros into k230.c

raoyi (3):
  hw/timer: add DesignWare APB timer model
  hw/riscv: add DesignWare APB timer to K230 board
  tests/qtest: add k230 dwapb timer test

 MAINTAINERS                         |   3 +
 hw/riscv/Kconfig                    |   1 +
 hw/riscv/k230.c                     |  33 ++-
 hw/timer/Kconfig                    |   4 +
 hw/timer/dw-apb-timer.c             | 376 ++++++++++++++++++++++++++++
 hw/timer/meson.build                |   1 +
 hw/timer/trace-events               |   8 +
 include/hw/riscv/k230.h             |   8 +
 include/hw/timer/dw-apb-timer.h     |  40 +++
 tests/qtest/k230-dwapb-timer-test.c | 274 ++++++++++++++++++++
 tests/qtest/meson.build             |   4 +-
 11 files changed, 748 insertions(+), 4 deletions(-)
 create mode 100644 hw/timer/dw-apb-timer.c
 create mode 100644 include/hw/timer/dw-apb-timer.h
 create mode 100644 tests/qtest/k230-dwapb-timer-test.c

-- 
2.53.0



^ permalink raw reply	[flat|nested] 10+ messages in thread

* [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

* [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

* [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 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

* 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

* 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 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-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

* 回复: [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

end of thread, other threads:[~2026-08-18 14:04 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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-14  7:40   ` Bin Meng
2026-08-18 14:03     ` 回复: " 饶轶
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     ` 回复: " 饶轶
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     ` 回复: " 饶轶

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.