[PATCH v2 1/3] hw/timer: add k230 dwapb timer model
raoyi <[email protected]> Thu, 30 Jul 2026 21:37:09 +0800
| Newsgroups | org.nongnu.qemu-riscv,org.nongnu.qemu-devel |
|---|---|
| Message-ID | <[email protected]> |
Add DesignWare APB timer model for K230 machine. The timer uses the Clock framework for frequency input, preparing for future CMU integration. Signed-off-by: raoyi <[email protected]> --- hw/timer/Kconfig | 4 + hw/timer/k230_dwapb_timer.c | 317 ++++++++++++++++++++++++++++ hw/timer/meson.build | 1 + hw/timer/trace-events | 9 + include/hw/timer/k230_dwapb_timer.h | 72 +++++++ 5 files changed, 403 insertions(+) create mode 100644 hw/timer/k230_dwapb_timer.c create mode 100644 include/hw/timer/k230_dwapb_timer.h diff --git a/hw/timer/Kconfig b/hw/timer/Kconfig index b3d823ce2c..9d1f20f5cd 100644 --- a/hw/timer/Kconfig +++ b/hw/timer/Kconfig @@ -65,3 +65,7 @@ config STELLARIS_GPTM config AVR_TIMER16 bool + +config K230_TIMER + bool + select PTIMER diff --git a/hw/timer/k230_dwapb_timer.c b/hw/timer/k230_dwapb_timer.c new file mode 100644 index 0000000000..623f84cafd --- /dev/null +++ b/hw/timer/k230_dwapb_timer.c @@ -0,0 +1,317 @@ +/* + * K230 DW APB timer + * + * Copyright (c) 2026 raoyi <[email protected]> + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * K230 Technical Reference Manual V0.3.1 (2024-11-18): + * https://github.com/revyos/external-docs/blob/master/K230/en-us/K230_Technical_Reference_Manual_V0.3.1_20241118.pdf + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/module.h" +#include "qapi/error.h" +#include "migration/vmstate.h" +#include "hw/core/sysbus.h" +#include "hw/core/ptimer.h" +#include "hw/core/qdev-clock.h" +#include "hw/timer/k230_dwapb_timer.h" +#include "trace.h" + +static void k230_timer_clk_update(void *opaque, ClockEvent event) +{ + K230TimerState *s = K230_TIMER(opaque); + for (int i = 0; i < K230_APBTMR_NUM_TIMERS; i++) { + if (!s->timers[i].ptimer) { + continue; + } + ptimer_transaction_begin(s->timers[i].ptimer); + ptimer_set_period_from_clock(s->timers[i].ptimer, s->pclk, 1); + ptimer_transaction_commit(s->timers[i].ptimer); + } +} + +static void k230_timer_enable(K230Timer *t) +{ + 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); + + trace_k230_timer_enable(t->id, t->load); +} + +static void k230_timer_disable(K230Timer *t) +{ + ptimer_transaction_begin(t->ptimer); + ptimer_stop(t->ptimer); + ptimer_transaction_commit(t->ptimer); + + t->int_status = 0; + qemu_set_irq(t->irq, 0); + + trace_k230_timer_disable(t->id); +} + +static void k230_timer_tick(void *opaque) +{ + K230Timer *t = opaque; + uint32_t reload; + + trace_k230_timer_tick(t->id); + + t->int_status = 1; + + if (!(t->control & K230_APBTMR_CONTROL_INT)) { + qemu_set_irq(t->irq, 1); + trace_k230_timer_interrupt(t->id); + } + + if (t->control & K230_APBTMR_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 k230_timer_read(void *opaque, hwaddr addr, unsigned int size) +{ + K230TimerState *s = K230_TIMER(opaque); + uint32_t value = 0; + + addr &= 0xfff; + + if (addr < K230_APBTMRS_INT_STATUS) { + int idx = addr / K230_APBTMR_STRIDE; + int reg = addr % K230_APBTMR_STRIDE; + + if (idx < K230_APBTMR_NUM_TIMERS) { + switch (reg) { + case K230_APBTMR_N_LOAD_COUNT: + value = s->timers[idx].load; + break; + case K230_APBTMR_N_CURRENT_VALUE: + if (s->timers[idx].control & K230_APBTMR_CONTROL_ENABLE) { + value = ptimer_get_count(s->timers[idx].ptimer); + } else { + value = 0; + } + break; + case K230_APBTMR_N_CONTROL: + value = s->timers[idx].control; + break; + case K230_APBTMR_N_EOI: + s->timers[idx].int_status = 0; + qemu_set_irq(s->timers[idx].irq, 0); + trace_k230_timer_irq_clear(s->timers[idx].id); + value = 0; + break; + case K230_APBTMR_N_INT_STATUS: + if (s->timers[idx].control & K230_APBTMR_CONTROL_INT) { + value = 0; + } else { + value = s->timers[idx].int_status; + } + break; + } + } + } else if (addr >= K230_APBTMR_N_LOAD2 && + addr < K230_APBTMR_N_LOAD2 + + K230_APBTMR_NUM_TIMERS * K230_APBTMR_LOAD2_STRIDE) { + int idx = (addr - K230_APBTMR_N_LOAD2) / K230_APBTMR_LOAD2_STRIDE; + value = s->timers[idx].load2; + } else { + switch (addr) { + case K230_APBTMRS_INT_STATUS: + for (int i = 0; i < K230_APBTMR_NUM_TIMERS; i++) { + if (!(s->timers[i].control & K230_APBTMR_CONTROL_INT)) { + value |= s->timers[i].int_status << i; + } + } + break; + case K230_APBTMRS_EOI: + for (int i = 0; i < K230_APBTMR_NUM_TIMERS; i++) { + s->timers[i].int_status = 0; + qemu_set_irq(s->timers[i].irq, 0); + } + value = 0; + break; + case K230_APBTMRS_RAW_INT_STATUS: + for (int i = 0; i < K230_APBTMR_NUM_TIMERS; i++) { + value |= s->timers[i].int_status << i; + } + break; + case K230_APBTMRS_COMP_VERSION: + value = K230_APBTMR_COMP_VERSION_VAL; + break; + } + } + + trace_k230_timer_read(addr, value); + return value; +} + +static void k230_timer_write(void *opaque, hwaddr addr, + uint64_t value, unsigned int size) +{ + K230TimerState *s = K230_TIMER(opaque); + + addr &= 0xfff; + + if (addr < K230_APBTMRS_INT_STATUS) { + int idx = addr / K230_APBTMR_STRIDE; + int reg = addr % K230_APBTMR_STRIDE; + + if (idx < K230_APBTMR_NUM_TIMERS) { + switch (reg) { + case K230_APBTMR_N_LOAD_COUNT: + s->timers[idx].load = value; + break; + case K230_APBTMR_N_CONTROL: { + uint32_t old_ctrl = s->timers[idx].control; + s->timers[idx].control = value & K230_APBTMR_CONTROL_RW_MASK; + if ((value ^ old_ctrl) & K230_APBTMR_CONTROL_ENABLE) { + if (value & K230_APBTMR_CONTROL_ENABLE) { + k230_timer_enable(&s->timers[idx]); + } else { + k230_timer_disable(&s->timers[idx]); + } + } + if ((value ^ old_ctrl) & K230_APBTMR_CONTROL_INT) { + if (value & K230_APBTMR_CONTROL_INT) { + qemu_set_irq(s->timers[idx].irq, 0); + } else if (s->timers[idx].int_status) { + qemu_set_irq(s->timers[idx].irq, 1); + } + } + break; + } + default: + break; + } + } + } else if (addr >= K230_APBTMR_N_LOAD2 && + addr < K230_APBTMR_N_LOAD2 + + K230_APBTMR_NUM_TIMERS * K230_APBTMR_LOAD2_STRIDE) { + int idx = (addr - K230_APBTMR_N_LOAD2) / K230_APBTMR_LOAD2_STRIDE; + s->timers[idx].load2 = value; + } + + trace_k230_timer_write(addr, value); +} + +static const MemoryRegionOps k230_timer_ops = { + .read = k230_timer_read, + .write = k230_timer_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void k230_timer_realize(DeviceState *dev, Error **errp) +{ + K230TimerState *s = K230_TIMER(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + if (!clock_has_source(s->pclk)) { + error_setg(errp, "K230 DW APB timer: pclk clock must be connected"); + return; + } + + for (int i = 0; i < K230_APBTMR_NUM_TIMERS; i++) { + s->timers[i].id = i; + s->timers[i].ptimer = ptimer_init(k230_timer_tick, &s->timers[i], + PTIMER_POLICY_NO_IMMEDIATE_TRIGGER | + PTIMER_POLICY_NO_IMMEDIATE_RELOAD | + PTIMER_POLICY_NO_COUNTER_ROUND_DOWN); + ptimer_transaction_begin(s->timers[i].ptimer); + ptimer_set_limit(s->timers[i].ptimer, UINT32_MAX, 1); + ptimer_transaction_commit(s->timers[i].ptimer); + sysbus_init_irq(sbd, &s->timers[i].irq); + } + + k230_timer_clk_update(s, ClockUpdate); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_timer_ops, + s, TYPE_K230_TIMER, K230_APBTMR_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->mmio); +} + +static void k230_timer_reset(DeviceState *dev) +{ + K230TimerState *s = K230_TIMER(dev); + + for (int i = 0; i < K230_APBTMR_NUM_TIMERS; i++) { + K230Timer *t = &s->timers[i]; + + ptimer_transaction_begin(t->ptimer); + ptimer_stop(t->ptimer); + ptimer_transaction_commit(t->ptimer); + + t->load = 0; + t->load2 = 0; + t->control = 0; + t->int_status = 0; + qemu_set_irq(t->irq, 0); + } +} + +static const VMStateDescription vmstate_k230_timer_channel = { + .name = "k230-dwapb-timer-channel", + .fields = (const VMStateField[]) { + VMSTATE_PTIMER(ptimer, K230Timer), + VMSTATE_UINT32(load, K230Timer), + VMSTATE_UINT32(load2, K230Timer), + VMSTATE_UINT32(control, K230Timer), + VMSTATE_UINT32(int_status, K230Timer), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_k230_timer = { + .name = "k230-dwapb-timer", + .fields = (const VMStateField[]) { + VMSTATE_CLOCK(pclk, K230TimerState), + VMSTATE_STRUCT_ARRAY(timers, K230TimerState, + K230_APBTMR_NUM_TIMERS, 0, + vmstate_k230_timer_channel, K230Timer), + VMSTATE_END_OF_LIST() + } +}; + +static void k230_timer_init(Object *obj) +{ + K230TimerState *s = K230_TIMER(obj); + s->pclk = qdev_init_clock_in(DEVICE(obj), "pclk", k230_timer_clk_update, + s, ClockUpdate); +} + +static void k230_timer_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + dc->realize = k230_timer_realize; + dc->vmsd = &vmstate_k230_timer; + dc->desc = "K230 DW APB timer"; + device_class_set_legacy_reset(dc, k230_timer_reset); +} + +static const TypeInfo k230_timer_info = { + .name = TYPE_K230_TIMER, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(K230TimerState), + .instance_init = k230_timer_init, + .class_init = k230_timer_class_init, +}; + +static void k230_timer_register_type(void) +{ + type_register_static(&k230_timer_info); +} +type_init(k230_timer_register_type) diff --git a/hw/timer/meson.build b/hw/timer/meson.build index 201b5d8316..d0a834e127 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_K230_TIMER', if_true: files('k230_dwapb_timer.c')) diff --git a/hw/timer/trace-events b/hw/timer/trace-events index 634ba1da27..89c619bc87 100644 --- a/hw/timer/trace-events +++ b/hw/timer/trace-events @@ -1,5 +1,14 @@ # See docs/devel/tracing.rst for syntax documentation. +# k230_dwapb_timer.c +k230_timer_read(uint64_t addr, uint32_t val) "K230 timer read: [0x%" PRIx64 "] -> 0x%" PRIx32 +k230_timer_write(uint64_t addr, uint64_t val) "K230 timer write: [0x%" PRIx64 "] <- 0x%" PRIx64 +k230_timer_tick(int idx) "K230 timer %d tick" +k230_timer_interrupt(int idx) "K230 timer %d IRQ asserted" +k230_timer_irq_clear(int idx) "K230 timer %d IRQ cleared" +k230_timer_enable(int idx, uint32_t load) "K230 timer %d enabled load=0x%" PRIx32 +k230_timer_disable(int idx) "K230 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/k230_dwapb_timer.h b/include/hw/timer/k230_dwapb_timer.h new file mode 100644 index 0000000000..3aba178dfa --- /dev/null +++ b/include/hw/timer/k230_dwapb_timer.h @@ -0,0 +1,72 @@ +/* + * K230 DW APB timer + * + * Copyright (c) 2026 raoyi <[email protected]> + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * K230 Technical Reference Manual V0.3.1 (2024-11-18): + * https://github.com/revyos/external-docs/blob/master/K230/en-us/K230_Technical_Reference_Manual_V0.3.1_20241118.pdf + */ + +#ifndef K230_DWAPB_TIMER_H +#define K230_DWAPB_TIMER_H + +#include "qemu/bitops.h" +#include "hw/core/sysbus.h" +#include "hw/core/irq.h" +#include "hw/core/clock.h" +#include "qom/object.h" + +#define TYPE_K230_TIMER "riscv.k230.timer" +OBJECT_DECLARE_SIMPLE_TYPE(K230TimerState, K230_TIMER) + +#define K230_APBTMR_MMIO_SIZE 0x800 +#define K230_APBTMR_NUM_TIMERS 6 +#define K230_APBTMR_STRIDE 0x14 +#define K230_APBTMR_DEFAULT_FREQ 6250000 +#define K230_APBTMR_COMP_VERSION_VAL 0x3231312A +#define K230_APBTMR_LOAD2_STRIDE 0x04 + +/* Per-timer register offsets */ +#define K230_APBTMR_N_LOAD_COUNT 0x00 +#define K230_APBTMR_N_CURRENT_VALUE 0x04 +#define K230_APBTMR_N_CONTROL 0x08 +#define K230_APBTMR_N_EOI 0x0c +#define K230_APBTMR_N_INT_STATUS 0x10 +/* Global register offsets */ +#define K230_APBTMRS_INT_STATUS 0xa0 +#define K230_APBTMRS_EOI 0xa4 +#define K230_APBTMRS_RAW_INT_STATUS 0xa8 +#define K230_APBTMRS_COMP_VERSION 0xac +/* PWM LoadCount2 registers */ +#define K230_APBTMR_N_LOAD2 0xb0 + +/* Control register bits */ +#define K230_APBTMR_CONTROL_ENABLE BIT(0) +#define K230_APBTMR_CONTROL_MODE_PERIODIC BIT(1) +#define K230_APBTMR_CONTROL_INT BIT(2) +#define K230_APBTMR_CONTROL_PWM BIT(3) +#define K230_APBTMR_CONTROL_RW_MASK 0xf + +typedef struct K230Timer { + struct ptimer_state *ptimer; + qemu_irq irq; + int id; + uint32_t load; + uint32_t load2; + uint32_t control; + uint32_t int_status; +} K230Timer; + +struct K230TimerState { + /* <private> */ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + K230Timer timers[K230_APBTMR_NUM_TIMERS]; + Clock *pclk; +}; + +#endif -- 2.53.0