d18fdd69d0
The stellaris-gptm timer currently uses system_clock_scale for one of its timer modes where the timer runs at the CPU clock rate. Make it use a Clock input instead. We don't try to make the timer handle changes in the clock frequency while the downcounter is running. This is not a change in behaviour from the previous system_clock_scale implementation -- we will pick up the new frequency only when the downcounter hits zero. Handling dynamic clock changes when the counter is running would require state that the current gptm implementation doesn't have. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Damien Hedde <damien.hedde@greensocs.com> Message-id: 20210812093356.1946-25-peter.maydell@linaro.org
333 lines
9.0 KiB
C
333 lines
9.0 KiB
C
/*
|
|
* Luminary Micro Stellaris General Purpose Timer Module
|
|
*
|
|
* Copyright (c) 2006 CodeSourcery.
|
|
* Written by Paul Brook
|
|
*
|
|
* This code is licensed under the GPL.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/log.h"
|
|
#include "qemu/timer.h"
|
|
#include "qapi/error.h"
|
|
#include "migration/vmstate.h"
|
|
#include "hw/qdev-clock.h"
|
|
#include "hw/timer/stellaris-gptm.h"
|
|
|
|
static void gptm_update_irq(gptm_state *s)
|
|
{
|
|
int level;
|
|
level = (s->state & s->mask) != 0;
|
|
qemu_set_irq(s->irq, level);
|
|
}
|
|
|
|
static void gptm_stop(gptm_state *s, int n)
|
|
{
|
|
timer_del(s->timer[n]);
|
|
}
|
|
|
|
static void gptm_reload(gptm_state *s, int n, int reset)
|
|
{
|
|
int64_t tick;
|
|
if (reset) {
|
|
tick = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
|
} else {
|
|
tick = s->tick[n];
|
|
}
|
|
|
|
if (s->config == 0) {
|
|
/* 32-bit CountDown. */
|
|
uint32_t count;
|
|
count = s->load[0] | (s->load[1] << 16);
|
|
tick += clock_ticks_to_ns(s->clk, count);
|
|
} else if (s->config == 1) {
|
|
/* 32-bit RTC. 1Hz tick. */
|
|
tick += NANOSECONDS_PER_SECOND;
|
|
} else if (s->mode[n] == 0xa) {
|
|
/* PWM mode. Not implemented. */
|
|
} else {
|
|
qemu_log_mask(LOG_UNIMP,
|
|
"GPTM: 16-bit timer mode unimplemented: 0x%x\n",
|
|
s->mode[n]);
|
|
return;
|
|
}
|
|
s->tick[n] = tick;
|
|
timer_mod(s->timer[n], tick);
|
|
}
|
|
|
|
static void gptm_tick(void *opaque)
|
|
{
|
|
gptm_state **p = (gptm_state **)opaque;
|
|
gptm_state *s;
|
|
int n;
|
|
|
|
s = *p;
|
|
n = p - s->opaque;
|
|
if (s->config == 0) {
|
|
s->state |= 1;
|
|
if ((s->control & 0x20)) {
|
|
/* Output trigger. */
|
|
qemu_irq_pulse(s->trigger);
|
|
}
|
|
if (s->mode[0] & 1) {
|
|
/* One-shot. */
|
|
s->control &= ~1;
|
|
} else {
|
|
/* Periodic. */
|
|
gptm_reload(s, 0, 0);
|
|
}
|
|
} else if (s->config == 1) {
|
|
/* RTC. */
|
|
uint32_t match;
|
|
s->rtc++;
|
|
match = s->match[0] | (s->match[1] << 16);
|
|
if (s->rtc > match)
|
|
s->rtc = 0;
|
|
if (s->rtc == 0) {
|
|
s->state |= 8;
|
|
}
|
|
gptm_reload(s, 0, 0);
|
|
} else if (s->mode[n] == 0xa) {
|
|
/* PWM mode. Not implemented. */
|
|
} else {
|
|
qemu_log_mask(LOG_UNIMP,
|
|
"GPTM: 16-bit timer mode unimplemented: 0x%x\n",
|
|
s->mode[n]);
|
|
}
|
|
gptm_update_irq(s);
|
|
}
|
|
|
|
static uint64_t gptm_read(void *opaque, hwaddr offset,
|
|
unsigned size)
|
|
{
|
|
gptm_state *s = (gptm_state *)opaque;
|
|
|
|
switch (offset) {
|
|
case 0x00: /* CFG */
|
|
return s->config;
|
|
case 0x04: /* TAMR */
|
|
return s->mode[0];
|
|
case 0x08: /* TBMR */
|
|
return s->mode[1];
|
|
case 0x0c: /* CTL */
|
|
return s->control;
|
|
case 0x18: /* IMR */
|
|
return s->mask;
|
|
case 0x1c: /* RIS */
|
|
return s->state;
|
|
case 0x20: /* MIS */
|
|
return s->state & s->mask;
|
|
case 0x24: /* CR */
|
|
return 0;
|
|
case 0x28: /* TAILR */
|
|
return s->load[0] | ((s->config < 4) ? (s->load[1] << 16) : 0);
|
|
case 0x2c: /* TBILR */
|
|
return s->load[1];
|
|
case 0x30: /* TAMARCHR */
|
|
return s->match[0] | ((s->config < 4) ? (s->match[1] << 16) : 0);
|
|
case 0x34: /* TBMATCHR */
|
|
return s->match[1];
|
|
case 0x38: /* TAPR */
|
|
return s->prescale[0];
|
|
case 0x3c: /* TBPR */
|
|
return s->prescale[1];
|
|
case 0x40: /* TAPMR */
|
|
return s->match_prescale[0];
|
|
case 0x44: /* TBPMR */
|
|
return s->match_prescale[1];
|
|
case 0x48: /* TAR */
|
|
if (s->config == 1) {
|
|
return s->rtc;
|
|
}
|
|
qemu_log_mask(LOG_UNIMP,
|
|
"GPTM: read of TAR but timer read not supported\n");
|
|
return 0;
|
|
case 0x4c: /* TBR */
|
|
qemu_log_mask(LOG_UNIMP,
|
|
"GPTM: read of TBR but timer read not supported\n");
|
|
return 0;
|
|
default:
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"GPTM: read at bad offset 0x02%" HWADDR_PRIx "\n",
|
|
offset);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void gptm_write(void *opaque, hwaddr offset,
|
|
uint64_t value, unsigned size)
|
|
{
|
|
gptm_state *s = (gptm_state *)opaque;
|
|
uint32_t oldval;
|
|
|
|
/*
|
|
* The timers should be disabled before changing the configuration.
|
|
* We take advantage of this and defer everything until the timer
|
|
* is enabled.
|
|
*/
|
|
switch (offset) {
|
|
case 0x00: /* CFG */
|
|
s->config = value;
|
|
break;
|
|
case 0x04: /* TAMR */
|
|
s->mode[0] = value;
|
|
break;
|
|
case 0x08: /* TBMR */
|
|
s->mode[1] = value;
|
|
break;
|
|
case 0x0c: /* CTL */
|
|
oldval = s->control;
|
|
s->control = value;
|
|
/* TODO: Implement pause. */
|
|
if ((oldval ^ value) & 1) {
|
|
if (value & 1) {
|
|
gptm_reload(s, 0, 1);
|
|
} else {
|
|
gptm_stop(s, 0);
|
|
}
|
|
}
|
|
if (((oldval ^ value) & 0x100) && s->config >= 4) {
|
|
if (value & 0x100) {
|
|
gptm_reload(s, 1, 1);
|
|
} else {
|
|
gptm_stop(s, 1);
|
|
}
|
|
}
|
|
break;
|
|
case 0x18: /* IMR */
|
|
s->mask = value & 0x77;
|
|
gptm_update_irq(s);
|
|
break;
|
|
case 0x24: /* CR */
|
|
s->state &= ~value;
|
|
break;
|
|
case 0x28: /* TAILR */
|
|
s->load[0] = value & 0xffff;
|
|
if (s->config < 4) {
|
|
s->load[1] = value >> 16;
|
|
}
|
|
break;
|
|
case 0x2c: /* TBILR */
|
|
s->load[1] = value & 0xffff;
|
|
break;
|
|
case 0x30: /* TAMARCHR */
|
|
s->match[0] = value & 0xffff;
|
|
if (s->config < 4) {
|
|
s->match[1] = value >> 16;
|
|
}
|
|
break;
|
|
case 0x34: /* TBMATCHR */
|
|
s->match[1] = value >> 16;
|
|
break;
|
|
case 0x38: /* TAPR */
|
|
s->prescale[0] = value;
|
|
break;
|
|
case 0x3c: /* TBPR */
|
|
s->prescale[1] = value;
|
|
break;
|
|
case 0x40: /* TAPMR */
|
|
s->match_prescale[0] = value;
|
|
break;
|
|
case 0x44: /* TBPMR */
|
|
s->match_prescale[0] = value;
|
|
break;
|
|
default:
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"GPTM: write at bad offset 0x02%" HWADDR_PRIx "\n",
|
|
offset);
|
|
}
|
|
gptm_update_irq(s);
|
|
}
|
|
|
|
static const MemoryRegionOps gptm_ops = {
|
|
.read = gptm_read,
|
|
.write = gptm_write,
|
|
.endianness = DEVICE_NATIVE_ENDIAN,
|
|
};
|
|
|
|
static const VMStateDescription vmstate_stellaris_gptm = {
|
|
.name = "stellaris_gptm",
|
|
.version_id = 2,
|
|
.minimum_version_id = 2,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_UINT32(config, gptm_state),
|
|
VMSTATE_UINT32_ARRAY(mode, gptm_state, 2),
|
|
VMSTATE_UINT32(control, gptm_state),
|
|
VMSTATE_UINT32(state, gptm_state),
|
|
VMSTATE_UINT32(mask, gptm_state),
|
|
VMSTATE_UNUSED(8),
|
|
VMSTATE_UINT32_ARRAY(load, gptm_state, 2),
|
|
VMSTATE_UINT32_ARRAY(match, gptm_state, 2),
|
|
VMSTATE_UINT32_ARRAY(prescale, gptm_state, 2),
|
|
VMSTATE_UINT32_ARRAY(match_prescale, gptm_state, 2),
|
|
VMSTATE_UINT32(rtc, gptm_state),
|
|
VMSTATE_INT64_ARRAY(tick, gptm_state, 2),
|
|
VMSTATE_TIMER_PTR_ARRAY(timer, gptm_state, 2),
|
|
VMSTATE_CLOCK(clk, gptm_state),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static void stellaris_gptm_init(Object *obj)
|
|
{
|
|
DeviceState *dev = DEVICE(obj);
|
|
gptm_state *s = STELLARIS_GPTM(obj);
|
|
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
|
|
|
sysbus_init_irq(sbd, &s->irq);
|
|
qdev_init_gpio_out(dev, &s->trigger, 1);
|
|
|
|
memory_region_init_io(&s->iomem, obj, &gptm_ops, s,
|
|
"gptm", 0x1000);
|
|
sysbus_init_mmio(sbd, &s->iomem);
|
|
|
|
s->opaque[0] = s->opaque[1] = s;
|
|
|
|
/*
|
|
* TODO: in an ideal world we would model the effects of changing
|
|
* the input clock frequency while the countdown timer is active.
|
|
* The best way to do this would be to convert the device to use
|
|
* ptimer instead of hand-rolling its own timer. This would also
|
|
* make it easy to implement reading the current count from the
|
|
* TAR and TBR registers.
|
|
*/
|
|
s->clk = qdev_init_clock_in(dev, "clk", NULL, NULL, 0);
|
|
}
|
|
|
|
static void stellaris_gptm_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
gptm_state *s = STELLARIS_GPTM(dev);
|
|
|
|
if (!clock_has_source(s->clk)) {
|
|
error_setg(errp, "stellaris-gptm: clk must be connected");
|
|
return;
|
|
}
|
|
|
|
s->timer[0] = timer_new_ns(QEMU_CLOCK_VIRTUAL, gptm_tick, &s->opaque[0]);
|
|
s->timer[1] = timer_new_ns(QEMU_CLOCK_VIRTUAL, gptm_tick, &s->opaque[1]);
|
|
}
|
|
|
|
static void stellaris_gptm_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
|
|
dc->vmsd = &vmstate_stellaris_gptm;
|
|
dc->realize = stellaris_gptm_realize;
|
|
}
|
|
|
|
static const TypeInfo stellaris_gptm_info = {
|
|
.name = TYPE_STELLARIS_GPTM,
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
.instance_size = sizeof(gptm_state),
|
|
.instance_init = stellaris_gptm_init,
|
|
.class_init = stellaris_gptm_class_init,
|
|
};
|
|
|
|
static void stellaris_gptm_register_types(void)
|
|
{
|
|
type_register_static(&stellaris_gptm_info);
|
|
}
|
|
|
|
type_init(stellaris_gptm_register_types)
|