Re: [PATCH v4 15/18] hw/timer: Add QCT QTimer device model
Brian Cain <[email protected]> Wed, 5 Aug 2026 16:10:46 -0500
| Newsgroups | gmane.comp.emulators.qemu |
|---|---|
| Message-ID | <[email protected]> |
On 7/31/2026 12:59 PM, Pierrick Bouvier wrote: > On 7/29/2026 6:28 PM, Brian Cain wrote: >> Implement the QCT QTimer generic timer device used by Hexagon DSP >> systems. >> >> Co-authored-by: Damien Hedde <[email protected]> >> Co-authored-by: Tobias Röhmel <[email protected]> >> Co-authored-by: Sid Manning <[email protected]> >> Co-authored-by: Thomas Marceron <[email protected]> >> Co-authored-by: Mahmoud Kamel <[email protected]> >> Signed-off-by: Brian Cain <[email protected]> >> --- >> MAINTAINERS | 2 + >> include/hw/timer/qct-qtimer.h | 43 +++ >> hw/timer/qct-qtimer.c | 681 ++++++++++++++++++++++++++++++++++ >> hw/timer/Kconfig | 3 + >> hw/timer/meson.build | 2 + >> hw/timer/trace-events | 5 + >> 6 files changed, 736 insertions(+) >> create mode 100644 include/hw/timer/qct-qtimer.h >> create mode 100644 hw/timer/qct-qtimer.c >> >> diff --git a/MAINTAINERS b/MAINTAINERS >> index 114bbec45ad..de106416b64 100644 >> --- a/MAINTAINERS >> +++ b/MAINTAINERS >> @@ -253,6 +253,8 @@ F: target/hexagon/ >> F: hw/intc/hex-l2vic.c >> F: include/hw/intc/hex-l2vic.h >> F: tests/qtest/l2vic-test.c >> +F: hw/timer/qct-qtimer.c >> +F: include/hw/timer/qct-qtimer.h >> X: target/hexagon/idef-parser/ >> X: target/hexagon/gen_idef_parser_funcs.py >> F: linux-user/hexagon/ >> diff --git a/include/hw/timer/qct-qtimer.h b/include/hw/timer/qct-qtimer.h >> new file mode 100644 >> index 00000000000..53d8291472d >> --- /dev/null >> +++ b/include/hw/timer/qct-qtimer.h >> @@ -0,0 +1,43 @@ >> +/* >> + * Qualcomm QCT QTimer >> + * >> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. >> + * SPDX-License-Identifier: GPL-2.0-or-later >> + */ >> + >> +#ifndef HW_TIMER_QCT_QTIMER_H >> +#define HW_TIMER_QCT_QTIMER_H >> + >> +#include "qom/object.h" >> + >> +#define TYPE_QCT_QTIMER "qct-qtimer" >> + >> +/* QTimer interface for external access from hexagon_globalreg */ >> +#define TYPE_QCT_QTIMER_INTERFACE "qct-qtimer-if" >> + > Why not directly get access to TYPE_QCT_QTIMER? > Is it to ensure there is no other access than get_timer_lo/hi made from > there? The export for the sake of the globalreg only needs access to that timer count, so -- yes, I guess. And I think this also allows us to keep the state private? >> +typedef struct QctQtimerInterface QctQtimerInterface; >> + >> +typedef struct QctQtimerInterfaceClass { >> + InterfaceClass parent_class; >> + >> + /* Read the live physical counter, backing HEX_SREG_TIMERLO/TIMERHI */ >> + uint32_t (*get_timer_lo)(const QctQtimerInterface *qtimer); >> + uint32_t (*get_timer_hi)(const QctQtimerInterface *qtimer); >> +} QctQtimerInterfaceClass; >> + >> +DECLARE_OBJ_CHECKERS(QctQtimerInterface, QctQtimerInterfaceClass, >> + QCT_QTIMER_INTERFACE, TYPE_QCT_QTIMER_INTERFACE); >> + >> +static inline uint32_t qct_qtimer_get_timer_lo(const QctQtimerInterface *qtimer) >> +{ >> + QctQtimerInterfaceClass *k = QCT_QTIMER_INTERFACE_GET_CLASS(qtimer); >> + return k->get_timer_lo(qtimer); >> +} >> + >> +static inline uint32_t qct_qtimer_get_timer_hi(const QctQtimerInterface *qtimer) >> +{ >> + QctQtimerInterfaceClass *k = QCT_QTIMER_INTERFACE_GET_CLASS(qtimer); >> + return k->get_timer_hi(qtimer); >> +} >> + >> +#endif /* HW_TIMER_QCT_QTIMER_H */ >> diff --git a/hw/timer/qct-qtimer.c b/hw/timer/qct-qtimer.c >> new file mode 100644 >> index 00000000000..82c711000eb >> --- /dev/null >> +++ b/hw/timer/qct-qtimer.c >> @@ -0,0 +1,681 @@ >> +/* >> + * Qualcomm QCT QTimer >> + * >> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. >> + * SPDX-License-Identifier: GPL-2.0-or-later >> + */ >> + >> +#include "qemu/osdep.h" >> +#include "hw/core/irq.h" >> +#include "hw/core/qdev-properties.h" >> +#include "hw/core/sysbus.h" >> +#include "hw/timer/qct-qtimer.h" >> +#include "migration/vmstate.h" >> +#include "qemu/bitops.h" >> +#include "qemu/log.h" >> +#include "qemu/module.h" >> +#include "qemu/timer.h" >> +#include "qapi/error.h" >> +#include "trace.h" >> + >> +#define QTIMER_MEM_SIZE_BYTES 0x1000 >> +#define QTIMER_DEFAULT_FREQ_HZ 19200000ULL >> + >> +#define QCT_QTIMER_TIMER_FRAME_ELTS (16) >> +#define QCT_QTIMER_TIMER_VIEW_ELTS (2) >> + >> +#define QCT_QTIMER_AC_CNTFRQ (0x000) >> +#define QCT_QTIMER_AC_CNTSR (0x004) >> +#define QCT_QTIMER_AC_CNTTID_0 (0x08) >> +#define QCT_QTIMER_AC_CNTACR_START (0x40) >> +#define QCT_QTIMER_AC_CNTACR_END (0x5c) >> +#define QCT_QTIMER_AC_CNTTID_1 (0x108) >> +#define QCT_QTIMER_AC_CNTACR_RWPT (1 << 5) /* R/W of CNTP_* regs */ >> +#define QCT_QTIMER_AC_CNTACR_RWVT (1 << 4) /* R/W of CNTV_* regs */ >> +#define QCT_QTIMER_AC_CNTACR_RVOFF (1 << 3) /* R/W of CNTVOFF register */ >> +#define QCT_QTIMER_AC_CNTACR_RFRQ (1 << 2) /* R/W of CNTFRQ register */ >> +#define QCT_QTIMER_AC_CNTACR_RPVCT (1 << 1) /* R/W of CNTVCT register */ >> +#define QCT_QTIMER_AC_CNTACR_RPCT (1 << 0) /* R/W of CNTPCT register */ >> +#define QCT_QTIMER_VERSION (0x0fd0) >> + >> +#define QCT_QTIMER_CNTPCT_LO (0x000) >> +#define QCT_QTIMER_CNTPCT_HI (0x004) >> +#define QCT_QTIMER_CNT_FREQ (0x010) >> +#define QCT_QTIMER_CNTPL0ACR (0x014) >> +#define QCT_QTIMER_CNTPL0ACR_PL0CTEN (1 << 9) >> +#define QCT_QTIMER_CNTPL0ACR_PL0TVEN (1 << 8) >> +#define QCT_QTIMER_CNTPL0ACR_PL0VCTEN (1 << 1) >> +#define QCT_QTIMER_CNTPL0ACR_PL0PCTEN (1 << 0) >> +#define QCT_QTIMER_CNTP_CVAL_LO (0x020) >> +#define QCT_QTIMER_CNTP_CVAL_HI (0x024) >> +#define QCT_QTIMER_CNT_MASK 0x00ffffffffffffffULL >> +#define QCT_QTIMER_CNT_HI_BITS 24 >> +#define QCT_QTIMER_CNTP_TVAL (0x028) >> +#define QCT_QTIMER_CNTP_CTL (0x02c) >> +#define QCT_QTIMER_CNTP_CTL_ENABLE (1 << 0) >> +#define QCT_QTIMER_CNTP_CTL_INTEN (1 << 1) >> +#define QCT_QTIMER_CNTP_CTL_ISTAT (1 << 2) >> + >> +OBJECT_DECLARE_SIMPLE_TYPE(QCTQtimerState, QCT_QTIMER) >> + >> +typedef struct QCTHextimerState { >> + QCTQtimerState *qtimer; >> + QEMUTimer *timer; /* one-shot deadline timer */ >> + int64_t offset_ns; /* QEMU_CLOCK_VIRTUAL ns at which cntpct == 0 */ >> + uint64_t cntval; /* 64-bit physical timer compare value */ >> + uint32_t control; >> + uint32_t cnt_ctrl; >> + uint32_t cntpl0acr; >> + uint32_t int_level; >> + qemu_irq irq; >> +} QCTHextimerState; >> + >> +struct QCTQtimerState { >> + SysBusDevice parent_obj; >> + >> + MemoryRegion iomem; >> + MemoryRegion view_iomem; >> + uint32_t secure; >> + QCTHextimerState timer[QCT_QTIMER_TIMER_FRAME_ELTS]; >> + uint32_t freq_hz; >> + uint32_t nr_frames; >> + uint32_t nr_views; >> + uint32_t frame_stride; >> + uint32_t cnttid_0; >> + uint32_t cnttid_1; >> + uint32_t freq_scale; >> +}; >> + >> +/* >> + * QTimer version register: >> + * >> + * 3 2 1 >> + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 >> + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ >> + * | Major | Minor | Step | >> + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ >> + */ >> +#define QCT_QTIMER_VERSION_VALUE 0x20020000 >> + >> +/* Counter value derived on-demand from QEMU_CLOCK_VIRTUAL. */ >> +static uint64_t hex_timer_now(QCTHextimerState *s) >> +{ >> + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); >> + uint32_t scale; >> + uint64_t scaled_elapsed; >> + >> + if (now <= s->offset_ns) { >> + return 0; >> + } >> + scale = MAX(s->qtimer->freq_scale, 1u); >> + scaled_elapsed = (uint64_t)(now - s->offset_ns) / scale; >> + return muldiv64(scaled_elapsed, s->qtimer->freq_hz, >> + NANOSECONDS_PER_SECOND) & >> + QCT_QTIMER_CNT_MASK; >> +} >> + >> +/* Arm (or disarm) the one-shot deadline timer. */ >> +static void hex_timer_rearm(QCTHextimerState *s) >> +{ >> + uint32_t scale; >> + uint64_t base_ns; >> + int64_t deadline_ns; >> + >> + if (!(s->control & QCT_QTIMER_CNTP_CTL_ENABLE)) { >> + timer_del(s->timer); >> + return; >> + } >> + >> + scale = MAX(s->qtimer->freq_scale, 1u); >> + /* >> + * Round the ticks-to-ns conversion up so that hex_timer_now(), which >> + * truncates when it divides elapsed ns by scale, is guaranteed to >> + * report >= cntval once this deadline fires. A truncating conversion >> + * here could re-arm at the same deadline forever when scale > 1. >> + */ >> + base_ns = muldiv64_round_up(s->cntval, NANOSECONDS_PER_SECOND, >> + s->qtimer->freq_hz); >> + if (base_ns > >> + ((uint64_t)INT64_MAX - (uint64_t)s->offset_ns) / scale) { >> + timer_del(s->timer); >> + return; >> + } >> + deadline_ns = s->offset_ns + (int64_t)(base_ns * scale); >> + timer_mod(s->timer, deadline_ns); >> +} >> + >> +static void hex_timer_update(QCTHextimerState *s) >> +{ >> + int level = s->int_level && >> + (s->control & QCT_QTIMER_CNTP_CTL_ENABLE) && >> + !(s->control & QCT_QTIMER_CNTP_CTL_INTEN); >> + >> + trace_qtimer_interrupt(); >> + qemu_set_irq(s->irq, level); >> +} >> + >> +/* >> + * Access-control (AC) region: offsets below 0x1000, gates CNTFRQ/CNTSR/ >> + * CNTTID/CNTACR per frame plus the shared VERSION register. >> + */ >> +static uint64_t qct_qtimer_ac_read(void *opaque, hwaddr offset, unsigned size) >> +{ >> + QCTQtimerState *s = opaque; >> + uint32_t frame; >> + >> + switch (offset) { >> + case QCT_QTIMER_AC_CNTFRQ: >> + return s->freq_hz; >> + case QCT_QTIMER_AC_CNTSR: >> + return s->secure; >> + case QCT_QTIMER_AC_CNTTID_0: >> + return s->cnttid_0; >> + case QCT_QTIMER_AC_CNTTID_1: >> + return s->cnttid_1; >> + case QCT_QTIMER_AC_CNTACR_START ... QCT_QTIMER_AC_CNTACR_END: >> + frame = (offset - QCT_QTIMER_AC_CNTACR_START) / 4; >> + if (frame >= s->nr_frames) { >> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTACR offset 0x%x\n", >> + __func__, (int)offset); >> + return 0; >> + } >> + return s->timer[frame].cnt_ctrl; >> + case QCT_QTIMER_VERSION: >> + return QCT_QTIMER_VERSION_VALUE; >> + default: >> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__, >> + (int)offset); >> + return 0; >> + } >> +} >> + >> +static void qct_qtimer_ac_write(void *opaque, hwaddr offset, uint64_t value, >> + unsigned size) >> +{ >> + QCTQtimerState *s = opaque; >> + uint32_t frame; >> + >> + switch (offset) { >> + case QCT_QTIMER_AC_CNTFRQ: >> + if (value == 0) { >> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTFRQ value 0\n", >> + __func__); >> + return; >> + } >> + s->freq_hz = value; >> + return; >> + case QCT_QTIMER_AC_CNTSR: >> + if (value > 0xff) { >> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTSR value 0x%x\n", >> + __func__, (int)value); >> + return; >> + } >> + s->secure = value; >> + return; >> + case QCT_QTIMER_AC_CNTACR_START ... QCT_QTIMER_AC_CNTACR_END: >> + frame = (offset - QCT_QTIMER_AC_CNTACR_START) / 4; >> + if (frame >= s->nr_frames) { >> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTACR offset 0x%x\n", >> + __func__, (int)offset); >> + return; >> + } >> + s->timer[frame].cnt_ctrl = value; >> + return; >> + default: >> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__, >> + (int)offset); >> + return; >> + } >> +} >> + >> +static const MemoryRegionOps qct_qtimer_ac_ops = { >> + .read = qct_qtimer_ac_read, >> + .write = qct_qtimer_ac_write, >> + .endianness = DEVICE_LITTLE_ENDIAN, >> + .valid = { >> + .min_access_size = 4, >> + .max_access_size = 4, >> + .unaligned = false, >> + }, >> + .impl = { >> + .min_access_size = 4, >> + .max_access_size = 4, >> + }, >> +}; >> + >> +/* >> + * View region: a flat array of (frame, view) slots, each frame_stride >> + * bytes wide, holding the per-frame CNTPCT/CNTP_CVAL/CNTP_TVAL/CNTP_CTL >> + * register set. >> + */ >> +static QCTHextimerState *qct_qtimer_demux(QCTQtimerState *s, hwaddr offset, >> + uint32_t *reg_offset, >> + uint32_t *view) >> +{ >> + uint32_t stride = s->frame_stride; >> + uint32_t stride_shift = ctz32(stride); >> + uint32_t slot_nr = offset >> stride_shift; >> + uint32_t frame = slot_nr / s->nr_views; >> + >> + *reg_offset = offset & (stride - 1); >> + *view = slot_nr % s->nr_views; >> + if (frame >= s->nr_frames) { >> + return NULL; >> + } >> + return &s->timer[frame]; >> +} >> + >> +/* Frames 8+ share cnttid_1; each frame's second view is gated by a bit. */ >> +static bool qct_qtimer_view_visible(QCTQtimerState *s, uint32_t frame, >> + uint32_t view) >> +{ >> + uint32_t cnttid = frame < 8 ? s->cnttid_0 : s->cnttid_1; >> + uint32_t frame_idx = frame < 8 ? frame : frame - 8; >> + >> + return !view || (cnttid & (0x4 << (frame_idx * 4))); >> +} >> + >> +static MemTxResult hex_timer_read(void *opaque, hwaddr offset, uint64_t *data, >> + unsigned size, MemTxAttrs attrs) >> +{ >> + QCTQtimerState *qs = opaque; >> + uint32_t reg_offset; >> + uint32_t view; >> + QCTHextimerState *s = qct_qtimer_demux(qs, offset, ®_offset, &view); >> + uint32_t frame; >> + >> + if (!s) { >> + *data = 0; >> + return MEMTX_ACCESS_ERROR; >> + } >> + frame = s - qs->timer; >> + >> + trace_qtimer_read(offset); >> + >> + if (!qct_qtimer_view_visible(qs, frame, view)) { >> + *data = 0; >> + return MEMTX_OK; >> + } >> + >> + switch (reg_offset) { >> + case QCT_QTIMER_CNT_FREQ: >> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RFRQ)) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + if (view && !(s->cntpl0acr & (QCT_QTIMER_CNTPL0ACR_PL0PCTEN | >> + QCT_QTIMER_CNTPL0ACR_PL0VCTEN))) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + *data = s->qtimer->freq_hz; >> + return MEMTX_OK; >> + case QCT_QTIMER_CNTP_CVAL_LO: >> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + *data = extract64(s->cntval, 0, 32); >> + return MEMTX_OK; >> + case QCT_QTIMER_CNTP_CVAL_HI: >> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + /* HI half is 24-bit per TRM; bits [31:24] are reserved. */ >> + *data = extract64(s->cntval, 32, QCT_QTIMER_CNT_HI_BITS); >> + return MEMTX_OK; >> + case QCT_QTIMER_CNTPCT_LO: >> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RPCT)) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0PCTEN)) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + *data = extract64(hex_timer_now(s), 0, 32); >> + return MEMTX_OK; >> + case QCT_QTIMER_CNTPCT_HI: >> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RPCT)) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0PCTEN)) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + *data = extract64(hex_timer_now(s), 32, QCT_QTIMER_CNT_HI_BITS); >> + return MEMTX_OK; >> + case QCT_QTIMER_CNTP_TVAL: >> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + *data = (uint32_t)(int32_t)(int64_t)(s->cntval - hex_timer_now(s)); >> + return MEMTX_OK; >> + case QCT_QTIMER_CNTP_CTL: >> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + /* >> + * CNTP_CTL: bit 0 EN, bit 1 IMASK, bit 2 ISTAT (interrupt >> + * pending). ISTAT tracks int_level and is read-only. >> + */ >> + *data = s->control | ((s->int_level & 0x1) << 2); >> + return MEMTX_OK; >> + case QCT_QTIMER_CNTPL0ACR: >> + *data = view ? 0 : s->cntpl0acr; >> + return MEMTX_OK; >> + case QCT_QTIMER_VERSION: >> + *data = QCT_QTIMER_VERSION_VALUE; >> + return MEMTX_OK; >> + default: >> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__, >> + (int)offset); >> + *data = 0; >> + return MEMTX_ACCESS_ERROR; >> + } >> +} > Similar to a previous series, it seems like we could use a table (or at > least a CHECK macro to remove redundant code for each register. > Same for write function below. I'll add an access_ok() check to remove redundancies. >> + >> +static MemTxResult hex_timer_write(void *opaque, hwaddr offset, >> + uint64_t value, unsigned size, >> + MemTxAttrs attrs) >> +{ >> + QCTQtimerState *qs = opaque; >> + uint32_t reg_offset; >> + uint32_t view; >> + QCTHextimerState *s = qct_qtimer_demux(qs, offset, ®_offset, &view); >> + uint32_t frame; >> + >> + if (!s) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + frame = s - qs->timer; >> + >> + trace_qtimer_write(offset, value); >> + >> + if (!qct_qtimer_view_visible(qs, frame, view)) { >> + return MEMTX_OK; >> + } >> + >> + switch (reg_offset) { >> + case QCT_QTIMER_CNTP_CVAL_LO: >> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + s->int_level = 0; >> + s->cntval = deposit64(s->cntval, 0, 32, value); >> + hex_timer_rearm(s); >> + break; >> + case QCT_QTIMER_CNTP_CVAL_HI: >> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + s->int_level = 0; >> + /* HI half is 24-bit per TRM; bits [31:24] are reserved. */ >> + s->cntval = deposit64(s->cntval, 32, QCT_QTIMER_CNT_HI_BITS, value) & >> + QCT_QTIMER_CNT_MASK; >> + hex_timer_rearm(s); >> + break; >> + case QCT_QTIMER_CNTP_CTL: >> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + /* ISTAT (bit 2) is read-only; keep SW writes from polluting it. */ >> + s->control = value & ~QCT_QTIMER_CNTP_CTL_ISTAT; >> + hex_timer_rearm(s); >> + break; >> + case QCT_QTIMER_CNTP_TVAL: >> + if (!(s->cnt_ctrl & QCT_QTIMER_AC_CNTACR_RWPT)) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + if (view && !(s->cntpl0acr & QCT_QTIMER_CNTPL0ACR_PL0CTEN)) { >> + return MEMTX_ACCESS_ERROR; >> + } >> + /* TVAL write: CVAL = CNTPCT + TVAL (TVAL is signed 32-bit). */ >> + s->int_level = 0; >> + s->cntval = (hex_timer_now(s) + (int64_t)(int32_t)value) & >> + QCT_QTIMER_CNT_MASK; >> + hex_timer_rearm(s); >> + break; >> + case QCT_QTIMER_CNTPL0ACR: >> + if (!view) { >> + s->cntpl0acr = value; >> + } >> + break; >> + default: >> + qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__, >> + (int)offset); >> + return MEMTX_ACCESS_ERROR; >> + } >> + hex_timer_update(s); >> + return MEMTX_OK; >> +} >> + >> +static void hex_timer_tick(void *opaque) >> +{ >> + QCTHextimerState *s = opaque; >> + uint64_t now = hex_timer_now(s); >> + uint64_t diff56 = (now - s->cntval) & QCT_QTIMER_CNT_MASK; >> + int64_t signed_diff = (int64_t)(diff56 << 8) >> 8; >> + >> + if (signed_diff >= 0) { >> + s->int_level = 1; >> + hex_timer_update(s); >> + } else { >> + hex_timer_rearm(s); >> + } >> +} >> + >> +static const MemoryRegionOps hex_timer_ops = { >> + .read_with_attrs = hex_timer_read, >> + .write_with_attrs = hex_timer_write, >> + .endianness = DEVICE_LITTLE_ENDIAN, >> + .valid = { >> + .min_access_size = 4, >> + .max_access_size = 8, >> + .unaligned = false, >> + }, >> + .impl = { >> + .min_access_size = 4, >> + .max_access_size = 4, >> + }, >> +}; >> + >> +static const VMStateDescription vmstate_qct_hextimer = { >> + .name = "qct-hextimer", >> + .version_id = 1, >> + .minimum_version_id = 1, >> + .fields = (const VMStateField[]) { >> + VMSTATE_UINT32(control, QCTHextimerState), >> + VMSTATE_UINT32(cnt_ctrl, QCTHextimerState), >> + VMSTATE_INT64(offset_ns, QCTHextimerState), >> + VMSTATE_UINT64(cntval, QCTHextimerState), >> + VMSTATE_UINT32(cntpl0acr, QCTHextimerState), >> + VMSTATE_UINT32(int_level, QCTHextimerState), >> + VMSTATE_TIMER_PTR(timer, QCTHextimerState), >> + VMSTATE_END_OF_LIST() >> + } >> +}; >> + >> +static const VMStateDescription vmstate_qct_qtimer = { >> + .name = "qct-qtimer", >> + .version_id = 1, >> + .minimum_version_id = 1, >> + .fields = (const VMStateField[]) { >> + VMSTATE_UINT32(freq_hz, QCTQtimerState), >> + VMSTATE_UINT32(secure, QCTQtimerState), >> + VMSTATE_STRUCT_VARRAY_UINT32(timer, QCTQtimerState, nr_frames, >> + 1, vmstate_qct_hextimer, QCTHextimerState), >> + VMSTATE_END_OF_LIST() >> + } >> +}; >> + >> +static void qct_qtimer_realize(DeviceState *dev, Error **errp) >> +{ >> + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); >> + QCTQtimerState *s = QCT_QTIMER(dev); >> + unsigned int i; >> + >> + if (s->nr_frames > QCT_QTIMER_TIMER_FRAME_ELTS) { >> + error_setg(errp, "nr_frames too high"); >> + return; >> + } >> + if (s->nr_views > QCT_QTIMER_TIMER_VIEW_ELTS) { >> + error_setg(errp, "nr_views too high"); >> + return; >> + } >> + if (s->freq_hz == 0) { >> + error_setg(errp, "freq-hz must be nonzero"); >> + return; >> + } >> + if (s->frame_stride == 0 || !is_power_of_2(s->frame_stride)) { >> + error_setg(errp, "frame_stride must be a nonzero power of two"); >> + return; >> + } >> + >> + memory_region_init_io(&s->iomem, OBJECT(s), &qct_qtimer_ac_ops, s, >> + "qct-qtimer-ac", QTIMER_MEM_SIZE_BYTES); >> + sysbus_init_mmio(sbd, &s->iomem); >> + >> + memory_region_init_io(&s->view_iomem, OBJECT(s), &hex_timer_ops, s, >> + "qct-qtimer-view", >> + (uint64_t)s->frame_stride * s->nr_frames * >> + s->nr_views); >> + sysbus_init_mmio(sbd, &s->view_iomem); >> + >> + for (i = 0; i < s->nr_frames; i++) { >> + QCTHextimerState *t = &s->timer[i]; >> + >> + t->qtimer = s; >> + s->secure |= (1 << i); >> + >> + sysbus_init_irq(sbd, &t->irq); >> + t->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, hex_timer_tick, t); >> + } >> +} >> + >> +static void qct_qtimer_unrealize(DeviceState *dev) >> +{ >> + QCTQtimerState *s = QCT_QTIMER(dev); >> + unsigned int i; >> + >> + for (i = 0; i < s->nr_frames; i++) { >> + QCTHextimerState *t = &s->timer[i]; >> + >> + if (t->timer) { >> + timer_free(t->timer); >> + t->timer = NULL; >> + } >> + } >> +} >> + >> +static void qct_qtimer_reset_hold(Object *obj, ResetType type) >> +{ >> + QCTQtimerState *s = QCT_QTIMER(obj); >> + unsigned int i; >> + >> + for (i = 0; i < s->nr_frames; i++) { >> + QCTHextimerState *t = &s->timer[i]; >> + >> + /* >> + * Per TRM: CTL = 0 (EN=0, IMASK=0, ISTAT=0), CVAL = 0 so that >> + * TVAL (= CVAL - CNTPCT) also reads 0 at reset. The QEMUTimer is >> + * only armed when SW sets CTL.EN=1, so cntval=0 does not cause a >> + * spurious fire before SW programs the compare value. >> + */ >> + t->control = 0; >> + t->cnt_ctrl = QCT_QTIMER_AC_CNTACR_RWPT | QCT_QTIMER_AC_CNTACR_RWVT | >> + QCT_QTIMER_AC_CNTACR_RVOFF | QCT_QTIMER_AC_CNTACR_RFRQ | >> + QCT_QTIMER_AC_CNTACR_RPVCT | QCT_QTIMER_AC_CNTACR_RPCT; >> + t->cntval = 0; >> + t->cntpl0acr = 0; >> + t->int_level = 0; >> + t->offset_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); >> + timer_del(t->timer); >> + qemu_set_irq(t->irq, 0); >> + } >> +} >> + >> +static const Property qct_qtimer_properties[] = { >> + DEFINE_PROP_UINT32("freq-hz", QCTQtimerState, freq_hz, >> + QTIMER_DEFAULT_FREQ_HZ), >> + DEFINE_PROP_UINT32("freq-scale", QCTQtimerState, freq_scale, 1), >> + DEFINE_PROP_UINT32("nr_frames", QCTQtimerState, nr_frames, 2), >> + DEFINE_PROP_UINT32("nr_views", QCTQtimerState, nr_views, 1), >> + DEFINE_PROP_UINT32("frame_stride", QCTQtimerState, frame_stride, 0x1000), >> + DEFINE_PROP_UINT32("cnttid_0", QCTQtimerState, cnttid_0, 0x11), >> + DEFINE_PROP_UINT32("cnttid_1", QCTQtimerState, cnttid_1, 0x0), >> +}; >> + >> +static void qct_qtimer_class_init(ObjectClass *klass, const void *data) >> +{ >> + DeviceClass *dc = DEVICE_CLASS(klass); >> + ResettableClass *rc = RESETTABLE_CLASS(klass); >> + >> + device_class_set_props(dc, qct_qtimer_properties); >> + dc->realize = qct_qtimer_realize; >> + dc->unrealize = qct_qtimer_unrealize; >> + dc->vmsd = &vmstate_qct_qtimer; >> + rc->phases.hold = qct_qtimer_reset_hold; >> +} >> + >> +/* QTimer interface implementation, backing HEX_SREG_TIMERLO/TIMERHI */ >> +static uint32_t qct_qtimer_get_timer_lo_impl(const QctQtimerInterface *obj) >> +{ >> + QCTQtimerState *s = QCT_QTIMER((QctQtimerInterface *)obj); >> + >> + return s->nr_frames > 0 ? extract64(hex_timer_now(&s->timer[0]), 0, 32) >> + : 0; >> +} >> + >> +static uint32_t qct_qtimer_get_timer_hi_impl(const QctQtimerInterface *obj) >> +{ >> + QCTQtimerState *s = QCT_QTIMER((QctQtimerInterface *)obj); >> + >> + return s->nr_frames > 0 ? extract64(hex_timer_now(&s->timer[0]), 32, 32) >> + : 0; >> +} >> + >> +static void qct_qtimer_interface_class_init(ObjectClass *klass, >> + const void *data) >> +{ >> + QctQtimerInterfaceClass *k = QCT_QTIMER_INTERFACE_CLASS(klass); >> + >> + k->get_timer_lo = qct_qtimer_get_timer_lo_impl; >> + k->get_timer_hi = qct_qtimer_get_timer_hi_impl; >> +} >> + >> +static const TypeInfo qct_qtimer_types[] = { >> + { >> + .name = TYPE_QCT_QTIMER_INTERFACE, >> + .parent = TYPE_INTERFACE, >> + .class_size = sizeof(QctQtimerInterfaceClass), >> + .class_init = qct_qtimer_interface_class_init, >> + }, >> + { >> + .name = TYPE_QCT_QTIMER, >> + .parent = TYPE_SYS_BUS_DEVICE, >> + .instance_size = sizeof(QCTQtimerState), >> + .class_init = qct_qtimer_class_init, >> + .interfaces = (InterfaceInfo[]) { >> + { TYPE_QCT_QTIMER_INTERFACE }, >> + { } >> + }, >> + }, >> +}; >> + >> +DEFINE_TYPES(qct_qtimer_types) >> diff --git a/hw/timer/Kconfig b/hw/timer/Kconfig >> index b3d823ce2c3..e1b751a54a7 100644 >> --- a/hw/timer/Kconfig >> +++ b/hw/timer/Kconfig >> @@ -65,3 +65,6 @@ config STELLARIS_GPTM >> >> config AVR_TIMER16 >> bool >> + >> +config HEX_QTIMER >> + bool >> diff --git a/hw/timer/meson.build b/hw/timer/meson.build >> index 201b5d8316d..0e8326b0492 100644 >> --- a/hw/timer/meson.build >> +++ b/hw/timer/meson.build >> @@ -34,3 +34,5 @@ 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')) >> + >> +specific_ss.add(when: 'CONFIG_HEX_QTIMER', if_true: files('qct-qtimer.c')) > Is there something preventing it from being in system_ss? > The file itself can be conditionnally included by meson, but it does not > seem to have target specifics in itself (or maybe through one header). I'll change it to `system_ss`. > >> diff --git a/hw/timer/trace-events b/hw/timer/trace-events >> index 634ba1da27a..636310f8caa 100644 >> --- a/hw/timer/trace-events >> +++ b/hw/timer/trace-events >> @@ -128,3 +128,8 @@ imx_epit_get_freq(uint32_t freq) "ptimer frequency is %u" >> imx_epit_read(const char *name, uint32_t value) "(%s) = 0x%08x" >> imx_epit_write(const char *name, uint64_t value) "(%s, value = 0x%08" PRIx64 ")" >> imx_epit_cmp(uint32_t sr) "sr was %d" >> + >> +# qct-qtimer.c >> +qtimer_interrupt(void) "qtimer interrupt line updated" >> +qtimer_read(uint64_t offset) "offset 0x%" PRIx64 >> +qtimer_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 0x%" PRIx64