1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
|
/*
* BCM2835 SYS timer emulation
*
* Copyright (C) 2019 Philippe Mathieu-Daudé <f4bug@amsat.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Datasheet: BCM2835 ARM Peripherals (C6357-M-1398)
* https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf
*
* Only the free running 64-bit counter is implemented.
* The 4 COMPARE registers and the interruption are not implemented.
*/
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "qemu/timer.h"
#include "hw/timer/bcm2835_systmr.h"
#include "hw/registerfields.h"
#include "migration/vmstate.h"
#include "trace.h"
REG32(CTRL_STATUS, 0x00)
REG32(COUNTER_LOW, 0x04)
REG32(COUNTER_HIGH, 0x08)
REG32(COMPARE0, 0x0c)
REG32(COMPARE1, 0x10)
REG32(COMPARE2, 0x14)
REG32(COMPARE3, 0x18)
static void bcm2835_systmr_timer_expire(void *opaque)
{
BCM2835SystemTimerCompare *tmr = opaque;
trace_bcm2835_systmr_timer_expired(tmr->id);
tmr->state->reg.ctrl_status |= 1 << tmr->id;
qemu_set_irq(tmr->irq, 1);
}
static uint64_t bcm2835_systmr_read(void *opaque, hwaddr offset,
unsigned size)
{
BCM2835SystemTimerState *s = BCM2835_SYSTIMER(opaque);
uint64_t r = 0;
switch (offset) {
case A_CTRL_STATUS:
r = s->reg.ctrl_status;
break;
case A_COMPARE0 ... A_COMPARE3:
r = s->reg.compare[(offset - A_COMPARE0) >> 2];
break;
case A_COUNTER_LOW:
case A_COUNTER_HIGH:
/* Free running counter at 1MHz */
r = qemu_clock_get_us(QEMU_CLOCK_VIRTUAL);
r >>= 8 * (offset - A_COUNTER_LOW);
r &= UINT32_MAX;
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%" HWADDR_PRIx "\n",
__func__, offset);
break;
}
trace_bcm2835_systmr_read(offset, r);
return r;
}
static void bcm2835_systmr_write(void *opaque, hwaddr offset,
uint64_t value64, unsigned size)
{
BCM2835SystemTimerState *s = BCM2835_SYSTIMER(opaque);
int index;
uint32_t value = value64;
uint32_t triggers_delay_us;
uint64_t now;
trace_bcm2835_systmr_write(offset, value);
switch (offset) {
case A_CTRL_STATUS:
s->reg.ctrl_status &= ~value; /* Ack */
for (index = 0; index < ARRAY_SIZE(s->tmr); index++) {
if (extract32(value, index, 1)) {
trace_bcm2835_systmr_irq_ack(index);
qemu_set_irq(s->tmr[index].irq, 0);
}
}
break;
case A_COMPARE0 ... A_COMPARE3:
index = (offset - A_COMPARE0) >> 2;
s->reg.compare[index] = value;
now = qemu_clock_get_us(QEMU_CLOCK_VIRTUAL);
/* Compare lower 32-bits of the free-running counter. */
triggers_delay_us = value - now;
trace_bcm2835_systmr_run(index, triggers_delay_us);
timer_mod(&s->tmr[index].timer, now + triggers_delay_us);
break;
case A_COUNTER_LOW:
case A_COUNTER_HIGH:
qemu_log_mask(LOG_GUEST_ERROR, "%s: read-only ofs 0x%" HWADDR_PRIx "\n",
__func__, offset);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%" HWADDR_PRIx "\n",
__func__, offset);
break;
}
}
static const MemoryRegionOps bcm2835_systmr_ops = {
.read = bcm2835_systmr_read,
.write = bcm2835_systmr_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.impl = {
.min_access_size = 4,
.max_access_size = 4,
},
};
static void bcm2835_systmr_reset(DeviceState *dev)
{
BCM2835SystemTimerState *s = BCM2835_SYSTIMER(dev);
memset(&s->reg, 0, sizeof(s->reg));
}
static void bcm2835_systmr_realize(DeviceState *dev, Error **errp)
{
BCM2835SystemTimerState *s = BCM2835_SYSTIMER(dev);
memory_region_init_io(&s->iomem, OBJECT(dev), &bcm2835_systmr_ops,
s, "bcm2835-sys-timer", 0x20);
sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
for (size_t i = 0; i < ARRAY_SIZE(s->tmr); i++) {
s->tmr[i].id = i;
s->tmr[i].state = s;
sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->tmr[i].irq);
timer_init_us(&s->tmr[i].timer, QEMU_CLOCK_VIRTUAL,
bcm2835_systmr_timer_expire, &s->tmr[i]);
}
}
static const VMStateDescription bcm2835_systmr_vmstate = {
.name = "bcm2835_sys_timer",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(reg.ctrl_status, BCM2835SystemTimerState),
VMSTATE_UINT32_ARRAY(reg.compare, BCM2835SystemTimerState,
BCM2835_SYSTIMER_COUNT),
VMSTATE_END_OF_LIST()
}
};
static void bcm2835_systmr_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = bcm2835_systmr_realize;
device_class_set_legacy_reset(dc, bcm2835_systmr_reset);
dc->vmsd = &bcm2835_systmr_vmstate;
}
static const TypeInfo bcm2835_systmr_info = {
.name = TYPE_BCM2835_SYSTIMER,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(BCM2835SystemTimerState),
.class_init = bcm2835_systmr_class_init,
};
static void bcm2835_systmr_register_types(void)
{
type_register_static(&bcm2835_systmr_info);
}
type_init(bcm2835_systmr_register_types);
|