[PATCH v5 15/18] hw/timer: Add QCT QTimer device model
Brian Cain <[email protected]> Wed, 5 Aug 2026 21:27:20 -0700
| Newsgroups | gmane.comp.emulators.qemu |
|---|---|
| Message-ID | <[email protected]> |
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 | 667 ++++++++++++++++++++++++++++++++++ hw/timer/Kconfig | 3 + hw/timer/meson.build | 2 + hw/timer/trace-events | 5 + 6 files changed, 722 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 678b7bacd79..ae623a7e924 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" + +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..f628e8d263a --- /dev/null +++ b/hw/timer/qct-qtimer.c @@ -0,0 +1,667 @@ +/* + * 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 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 + +static uint32_t qct_qtimer_cnttid(QCTQtimerState *s, unsigned int half) +{ + uint32_t nibble = 0x1 | (s->nr_views > 1 ? 0x4 : 0x0); + uint32_t base = half * 8; + uint32_t cnttid = 0; + unsigned int i; + + for (i = 0; i < 8; i++) { + if (base + i < s->nr_frames) { + cnttid |= nibble << (i * 4); + } + } + return cnttid; +} + +/* 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 qct_qtimer_cnttid(s, 0); + case QCT_QTIMER_AC_CNTTID_1: + return qct_qtimer_cnttid(s, 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+ are described by CNTTID_1; each frame's 2nd view is a gated bit. */ +static bool qct_qtimer_view_visible(QCTQtimerState *s, uint32_t frame, + uint32_t view) +{ + uint32_t cnttid = qct_qtimer_cnttid(s, frame < 8 ? 0 : 1); + uint32_t frame_idx = frame < 8 ? frame : frame - 8; + + return !view || (cnttid & (0x4 << (frame_idx * 4))); +} + +static bool access_ok(QCTHextimerState *s, uint32_t reg_offset, uint32_t view) +{ + uint32_t acr; + uint32_t pl0acr; + + switch (reg_offset) { + case QCT_QTIMER_CNT_FREQ: + acr = QCT_QTIMER_AC_CNTACR_RFRQ; + pl0acr = QCT_QTIMER_CNTPL0ACR_PL0PCTEN | + QCT_QTIMER_CNTPL0ACR_PL0VCTEN; + break; + case QCT_QTIMER_CNTPCT_LO: + case QCT_QTIMER_CNTPCT_HI: + acr = QCT_QTIMER_AC_CNTACR_RPCT; + pl0acr = QCT_QTIMER_CNTPL0ACR_PL0PCTEN; + break; + case QCT_QTIMER_CNTP_CVAL_LO: + case QCT_QTIMER_CNTP_CVAL_HI: + case QCT_QTIMER_CNTP_TVAL: + case QCT_QTIMER_CNTP_CTL: + acr = QCT_QTIMER_AC_CNTACR_RWPT; + pl0acr = QCT_QTIMER_CNTPL0ACR_PL0CTEN; + break; + default: + /* CNTPL0ACR and VERSION are ungated. */ + return true; + } + + if (!(s->cnt_ctrl & acr)) { + return false; + } + return !view || (s->cntpl0acr & pl0acr); +} + +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; + } + + if (!access_ok(s, reg_offset, view)) { + return MEMTX_ACCESS_ERROR; + } + + switch (reg_offset) { + case QCT_QTIMER_CNT_FREQ: + *data = s->qtimer->freq_hz; + return MEMTX_OK; + case QCT_QTIMER_CNTP_CVAL_LO: + *data = extract64(s->cntval, 0, 32); + return MEMTX_OK; + case QCT_QTIMER_CNTP_CVAL_HI: + /* 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: + *data = extract64(hex_timer_now(s), 0, 32); + return MEMTX_OK; + case QCT_QTIMER_CNTPCT_HI: + *data = extract64(hex_timer_now(s), 32, QCT_QTIMER_CNT_HI_BITS); + return MEMTX_OK; + case QCT_QTIMER_CNTP_TVAL: + *data = (uint32_t)(int32_t)(int64_t)(s->cntval - hex_timer_now(s)); + return MEMTX_OK; + case QCT_QTIMER_CNTP_CTL: + /* + * 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; + } +} + +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; + } + + if (!access_ok(s, reg_offset, view)) { + return MEMTX_ACCESS_ERROR; + } + + switch (reg_offset) { + case QCT_QTIMER_CNTP_CVAL_LO: + s->int_level = 0; + s->cntval = deposit64(s->cntval, 0, 32, value); + hex_timer_rearm(s); + break; + case QCT_QTIMER_CNTP_CVAL_HI: + 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: + /* 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: + /* 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), +}; + +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..8323efaf46b 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')) + +system_ss.add(when: 'CONFIG_HEX_QTIMER', if_true: files('qct-qtimer.c')) 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 -- 2.34.1