| /* | |
| * 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) |