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 | 78 +++++
hw/timer/qct-qtimer.c | 613 ++++++++++++++++++++++++++++++++++
hw/timer/Kconfig | 3 +
hw/timer/meson.build | 2 +
hw/timer/trace-events | 5 +
6 files changed, 703 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 a5df21aa12d..2c73705c727 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..d2ef9447b26
--- /dev/null
+++ b/include/hw/timer/qct-qtimer.h
@@ -0,0 +1,78 @@
+/*
+ * 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"
+
+#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)
+
+/* 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)(QctQtimerInterface *qtimer);
+ uint32_t (*get_timer_hi)(QctQtimerInterface *qtimer);
+} QctQtimerInterfaceClass;
+
+DECLARE_OBJ_CHECKERS(QctQtimerInterface, QctQtimerInterfaceClass,
+ QCT_QTIMER_INTERFACE, TYPE_QCT_QTIMER_INTERFACE);
+
+static inline uint32_t qct_qtimer_get_timer_lo(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(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..fd56a7118b1
--- /dev/null
+++ b/hw/timer/qct-qtimer.c
@@ -0,0 +1,613 @@
+/*
+ * 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
+
+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 freq;
+ 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;
+ 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 scaler;
+ uint64_t scaled_elapsed;
+
+ if (now <= s->offset_ns) {
+ return 0;
+ }
+ scaler = MAX(s->qtimer->freq_scale, 1u);
+ scaled_elapsed = (uint64_t)(now - s->offset_ns) / scaler;
+ return muldiv64(scaled_elapsed, s->freq, NANOSECONDS_PER_SECOND) &
+ QCT_QTIMER_CNT_MASK;
+}
+
+/* Arm (or disarm) the one-shot deadline timer. */
+static void hex_timer_rearm(QCTHextimerState *s)
+{
+ uint32_t scaler;
+ uint64_t base_ns;
+ int64_t deadline_ns;
+
+ if (!(s->control & QCT_QTIMER_CNTP_CTL_ENABLE)) {
+ timer_del(s->timer);
+ return;
+ }
+
+ scaler = MAX(s->qtimer->freq_scale, 1u);
+ base_ns = muldiv64(s->cntval, NANOSECONDS_PER_SECOND, s->freq);
+ if (base_ns >
+ ((uint64_t)INT64_MAX - (uint64_t)s->offset_ns - 1) / scaler) {
+ timer_del(s->timer);
+ return;
+ }
+ /*
+ * muldiv64() truncates, so the computed deadline can land up to 1 ns
+ * before the true cntval crossing. Add 1 ns so hex_timer_now() is
+ * guaranteed to be >= cntval when the QEMUTimer fires; without this,
+ * hex_timer_tick() would re-arm at the same deadline and loop.
+ */
+ deadline_ns = s->offset_ns + (int64_t)(base_ns * scaler) + 1;
+ 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;
+ 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:
+ s->freq = 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,
+ },
+};
+
+/*
+ * 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 & (0xf * stride)) >> 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->freq;
+ 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;
+ }
+}
+
+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_STRUCT_ARRAY(timer, QCTQtimerState,
QCT_QTIMER_TIMER_FRAME_ELTS,
+ 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;
+ }
+
+ 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;
+ t->freq = QTIMER_DEFAULT_FREQ_HZ;
+ 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_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->int_level = 0;
+ t->offset_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ timer_del(t->timer);
+ }
+}
+
+static const Property qct_qtimer_properties[] = {
+ DEFINE_PROP_UINT32("freq", QCTQtimerState, freq, 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->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(QctQtimerInterface *obj)
+{
+ QCTQtimerState *s = QCT_QTIMER(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(QctQtimerInterface *obj)
+{
+ QCTQtimerState *s = QCT_QTIMER(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_interface_info = {
+ .name = TYPE_QCT_QTIMER_INTERFACE,
+ .parent = TYPE_INTERFACE,
+ .class_size = sizeof(QctQtimerInterfaceClass),
+ .class_init = qct_qtimer_interface_class_init,
+};
+
+static const TypeInfo qct_qtimer_info = {
+ .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 },
+ { }
+ },
+};
+
+static void qct_qtimer_register_types(void)
+{
+ type_register_static(&qct_qtimer_interface_info);
+ type_register_static(&qct_qtimer_info);
+}
+
+type_init(qct_qtimer_register_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'))
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