Upstream: https://gitlab.com/qemu-project/qemu.git Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
668 lines
20 KiB
C
668 lines
20 KiB
C
/*
|
|
* 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)
|