Re: [PATCH v4 15/18] hw/timer: Add QCT QTimer device model

Brian Cain <[email protected]>
Newsgroups org.nongnu.qemu-devel
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, &reg_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, &reg_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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.