Re: [PATCH v4 1/3] hw/timer: add DesignWare APB timer model
Bin Meng <[email protected]>
| Newsgroups | org.nongnu.qemu-riscv,org.nongnu.qemu-devel |
|---|---|
| Message-ID | <CAEUhbmW=TSD2duo6260kvj=t=+GmA31KVEHCJVppya3h8v7pSw@mail.gmail.com> |
On Thu, Aug 13, 2026 at 9:37 PM raoyi <[email protected]> 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 <[email protected]> > --- > 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 <[email protected]> > + * > + * 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 <[email protected]> > + * > + * 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 <[email protected]>