Import QEMU upstream snapshot d2e570c

Upstream: https://gitlab.com/qemu-project/qemu.git

Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
This commit is contained in:
2026-08-31 02:15:30 +02:00
commit cf256aa081
11315 changed files with 3598369 additions and 0 deletions
+70
View File
@@ -0,0 +1,70 @@
config ARM_TIMER
bool
select PTIMER
config ARM_MPTIMER
bool
select PTIMER
config A9_GTIMER
bool
config HPET
bool
default y if PC
# The HPET has both a Rust and a C implementation
select HPET_C if !HAVE_RUST
select X_HPET_RUST if HAVE_RUST
config HPET_C
bool
config I8254
bool
depends on ISA_BUS
config ALLWINNER_A10_PIT
bool
select PTIMER
config PXA2XX_TIMER
bool
config SIFIVE_PWM
bool
config STM32F2XX_TIMER
bool
config CMSDK_APB_TIMER
bool
select PTIMER
config CMSDK_APB_DUALTIMER
bool
select PTIMER
config SH_TIMER
bool
select PTIMER
config RENESAS_TMR
bool
config RENESAS_CMT
bool
config SSE_COUNTER
bool
config SSE_TIMER
bool
config STELLARIS_GPTM
bool
config AVR_TIMER16
bool
config HEX_QTIMER
bool
+402
View File
@@ -0,0 +1,402 @@
/*
* Global peripheral timer block for ARM A9MP
*
* (C) 2013 Xilinx Inc.
*
* Written by François LEGAL
* Written by Peter Crosthwaite <[email protected]>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "hw/core/hw-error.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-properties.h"
#include "hw/timer/a9gtimer.h"
#include "migration/vmstate.h"
#include "qapi/error.h"
#include "qemu/timer.h"
#include "qemu/bitops.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "hw/core/cpu.h"
#include "system/qtest.h"
#ifndef A9_GTIMER_ERR_DEBUG
#define A9_GTIMER_ERR_DEBUG 0
#endif
#define DB_PRINT_L(level, ...) do { \
if (A9_GTIMER_ERR_DEBUG > (level)) { \
fprintf(stderr, ": %s: ", __func__); \
fprintf(stderr, ## __VA_ARGS__); \
} \
} while (0)
#define DB_PRINT(...) DB_PRINT_L(0, ## __VA_ARGS__)
static inline int a9_gtimer_get_current_cpu(A9GTimerState *s)
{
if (qtest_enabled()) {
return 0;
}
if (current_cpu->cpu_index >= s->num_cpu) {
hw_error("a9gtimer: num-cpu %d but this cpu is %d!\n",
s->num_cpu, current_cpu->cpu_index);
}
return current_cpu->cpu_index;
}
static inline uint64_t a9_gtimer_get_conv(A9GTimerState *s)
{
uint64_t prescale = extract32(s->control, R_CONTROL_PRESCALER_SHIFT,
R_CONTROL_PRESCALER_LEN);
return (prescale + 1) * 10;
}
static A9GTimerUpdate a9_gtimer_get_update(A9GTimerState *s)
{
A9GTimerUpdate ret;
ret.now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
ret.new = s->ref_counter +
(ret.now - s->cpu_ref_time) / a9_gtimer_get_conv(s);
return ret;
}
static void a9_gtimer_update(A9GTimerState *s, bool sync)
{
A9GTimerUpdate update = a9_gtimer_get_update(s);
int i;
int64_t next_cdiff = 0;
for (i = 0; i < s->num_cpu; ++i) {
A9GTimerPerCPU *gtb = &s->per_cpu[i];
int64_t cdiff = 0;
if ((s->control & R_CONTROL_TIMER_ENABLE) &&
(gtb->control & R_CONTROL_COMP_ENABLE)) {
/* R2p0+, where the compare function is >= */
if (gtb->compare < update.new) {
DB_PRINT("Compare event happened for CPU %d\n", i);
gtb->status = 1;
if (gtb->control & R_CONTROL_AUTO_INCREMENT && gtb->inc) {
uint64_t inc =
QEMU_ALIGN_UP(update.new - gtb->compare, gtb->inc);
DB_PRINT("Auto incrementing timer compare by %"
PRId64 "\n", inc);
gtb->compare += inc;
}
}
cdiff = (int64_t)gtb->compare - (int64_t)update.new + 1;
if (cdiff > 0 && (cdiff < next_cdiff || !next_cdiff)) {
next_cdiff = cdiff;
}
}
qemu_set_irq(gtb->irq,
gtb->status && (gtb->control & R_CONTROL_IRQ_ENABLE));
}
timer_del(s->timer);
if (next_cdiff) {
DB_PRINT("scheduling qemu_timer to fire again in %"
PRIx64 " cycles\n", next_cdiff);
timer_mod(s->timer, update.now + next_cdiff * a9_gtimer_get_conv(s));
}
if (s->control & R_CONTROL_TIMER_ENABLE) {
s->counter = update.new;
}
if (sync) {
s->cpu_ref_time = update.now;
s->ref_counter = s->counter;
}
}
static void a9_gtimer_update_no_sync(void *opaque)
{
A9GTimerState *s = A9_GTIMER(opaque);
a9_gtimer_update(s, false);
}
static uint64_t a9_gtimer_read(void *opaque, hwaddr addr, unsigned size)
{
A9GTimerPerCPU *gtb = (A9GTimerPerCPU *)opaque;
A9GTimerState *s = gtb->parent;
A9GTimerUpdate update;
uint64_t ret = 0;
int shift = 0;
switch (addr) {
case R_COUNTER_HI:
shift = 32;
/* fallthrough */
case R_COUNTER_LO:
update = a9_gtimer_get_update(s);
ret = extract64(update.new, shift, 32);
break;
case R_CONTROL:
ret = s->control | gtb->control;
break;
case R_INTERRUPT_STATUS:
ret = gtb->status;
break;
case R_COMPARATOR_HI:
shift = 32;
/* fallthrough */
case R_COMPARATOR_LO:
ret = extract64(gtb->compare, shift, 32);
break;
case R_AUTO_INCREMENT:
ret = gtb->inc;
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "bad a9gtimer register: %x\n",
(unsigned)addr);
return 0;
}
DB_PRINT("addr:%#x data:%#08" PRIx64 "\n", (unsigned)addr, ret);
return ret;
}
static void a9_gtimer_write(void *opaque, hwaddr addr, uint64_t value,
unsigned size)
{
A9GTimerPerCPU *gtb = (A9GTimerPerCPU *)opaque;
A9GTimerState *s = gtb->parent;
int shift = 0;
DB_PRINT("addr:%#x data:%#08" PRIx64 "\n", (unsigned)addr, value);
switch (addr) {
case R_COUNTER_HI:
shift = 32;
/* fallthrough */
case R_COUNTER_LO:
/*
* Keep it simple - ARM docco explicitly says to disable timer before
* modding it, so don't bother trying to do all the difficult on the fly
* timer modifications - (if they even work in real hardware??).
*/
if (s->control & R_CONTROL_TIMER_ENABLE) {
qemu_log_mask(LOG_GUEST_ERROR, "Cannot mod running ARM gtimer\n");
return;
}
s->counter = deposit64(s->counter, shift, 32, value);
return;
case R_CONTROL:
a9_gtimer_update(s, (value ^ s->control) & R_CONTROL_NEEDS_SYNC);
gtb->control = value & R_CONTROL_BANKED;
s->control = value & ~R_CONTROL_BANKED;
break;
case R_INTERRUPT_STATUS:
a9_gtimer_update(s, false);
gtb->status &= ~value;
break;
case R_COMPARATOR_HI:
shift = 32;
/* fallthrough */
case R_COMPARATOR_LO:
a9_gtimer_update(s, false);
gtb->compare = deposit64(gtb->compare, shift, 32, value);
break;
case R_AUTO_INCREMENT:
gtb->inc = value;
return;
default:
return;
}
a9_gtimer_update(s, false);
}
/* Wrapper functions to implement the "read global timer for
* the current CPU" memory regions.
*/
static uint64_t a9_gtimer_this_read(void *opaque, hwaddr addr,
unsigned size)
{
A9GTimerState *s = A9_GTIMER(opaque);
int id = a9_gtimer_get_current_cpu(s);
/* no \n so concatenates with message from read fn */
DB_PRINT("CPU:%d:", id);
return a9_gtimer_read(&s->per_cpu[id], addr, size);
}
static void a9_gtimer_this_write(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
A9GTimerState *s = A9_GTIMER(opaque);
int id = a9_gtimer_get_current_cpu(s);
/* no \n so concatenates with message from write fn */
DB_PRINT("CPU:%d:", id);
a9_gtimer_write(&s->per_cpu[id], addr, value, size);
}
static const MemoryRegionOps a9_gtimer_this_ops = {
.read = a9_gtimer_this_read,
.write = a9_gtimer_this_write,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
.endianness = DEVICE_NATIVE_ENDIAN,
};
static const MemoryRegionOps a9_gtimer_ops = {
.read = a9_gtimer_read,
.write = a9_gtimer_write,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
.endianness = DEVICE_NATIVE_ENDIAN,
};
static void a9_gtimer_reset(DeviceState *dev)
{
A9GTimerState *s = A9_GTIMER(dev);
int i;
s->counter = 0;
s->control = 0;
for (i = 0; i < s->num_cpu; i++) {
A9GTimerPerCPU *gtb = &s->per_cpu[i];
gtb->control = 0;
gtb->status = 0;
gtb->compare = 0;
gtb->inc = 0;
}
a9_gtimer_update(s, false);
}
static void a9_gtimer_realize(DeviceState *dev, Error **errp)
{
A9GTimerState *s = A9_GTIMER(dev);
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
int i;
if (s->num_cpu < 1 || s->num_cpu > A9_GTIMER_MAX_CPUS) {
error_setg(errp, "%s: num-cpu must be between 1 and %d",
__func__, A9_GTIMER_MAX_CPUS);
return;
}
memory_region_init_io(&s->iomem, OBJECT(dev), &a9_gtimer_this_ops, s,
"a9gtimer shared", 0x20);
sysbus_init_mmio(sbd, &s->iomem);
s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, a9_gtimer_update_no_sync, s);
for (i = 0; i < s->num_cpu; i++) {
A9GTimerPerCPU *gtb = &s->per_cpu[i];
gtb->parent = s;
sysbus_init_irq(sbd, &gtb->irq);
memory_region_init_io(&gtb->iomem, OBJECT(dev), &a9_gtimer_ops, gtb,
"a9gtimer per cpu", 0x20);
sysbus_init_mmio(sbd, &gtb->iomem);
}
}
static bool vmstate_a9_gtimer_control_needed(void *opaque)
{
A9GTimerState *s = opaque;
return s->control != 0;
}
static const VMStateDescription vmstate_a9_gtimer_per_cpu = {
.name = "arm.cortex-a9-global-timer.percpu",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(control, A9GTimerPerCPU),
VMSTATE_UINT64(compare, A9GTimerPerCPU),
VMSTATE_UINT32(status, A9GTimerPerCPU),
VMSTATE_UINT32(inc, A9GTimerPerCPU),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription vmstate_a9_gtimer_control = {
.name = "arm.cortex-a9-global-timer.control",
.version_id = 1,
.minimum_version_id = 1,
.needed = vmstate_a9_gtimer_control_needed,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(control, A9GTimerState),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription vmstate_a9_gtimer = {
.name = "arm.cortex-a9-global-timer",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_TIMER_PTR(timer, A9GTimerState),
VMSTATE_UINT64(counter, A9GTimerState),
VMSTATE_UINT64(ref_counter, A9GTimerState),
VMSTATE_UINT64(cpu_ref_time, A9GTimerState),
VMSTATE_STRUCT_VARRAY_UINT32(per_cpu, A9GTimerState, num_cpu,
1, vmstate_a9_gtimer_per_cpu,
A9GTimerPerCPU),
VMSTATE_END_OF_LIST()
},
.subsections = (const VMStateDescription * const []) {
&vmstate_a9_gtimer_control,
NULL
}
};
static const Property a9_gtimer_properties[] = {
DEFINE_PROP_UINT32("num-cpu", A9GTimerState, num_cpu, 0),
};
static void a9_gtimer_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = a9_gtimer_realize;
dc->vmsd = &vmstate_a9_gtimer;
device_class_set_legacy_reset(dc, a9_gtimer_reset);
device_class_set_props(dc, a9_gtimer_properties);
}
static const TypeInfo a9_gtimer_info = {
.name = TYPE_A9_GTIMER,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(A9GTimerState),
.class_init = a9_gtimer_class_init,
};
static void a9_gtimer_register_types(void)
{
type_register_static(&a9_gtimer_info);
}
type_init(a9_gtimer_register_types)
+315
View File
@@ -0,0 +1,315 @@
/*
* Allwinner A10 timer device emulation
*
* Copyright (C) 2013 Li Guang
* Written by Li Guang <[email protected]>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "qemu/osdep.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/sysbus.h"
#include "hw/timer/allwinner-a10-pit.h"
#include "migration/vmstate.h"
#include "qemu/log.h"
#include "qemu/module.h"
static void a10_pit_update_irq(AwA10PITState *s)
{
int i;
for (i = 0; i < AW_A10_PIT_TIMER_NR; i++) {
qemu_set_irq(s->irq[i], !!(s->irq_status & s->irq_enable & (1 << i)));
}
}
static uint64_t a10_pit_read(void *opaque, hwaddr offset, unsigned size)
{
AwA10PITState *s = AW_A10_PIT(opaque);
uint8_t index;
switch (offset) {
case AW_A10_PIT_TIMER_IRQ_EN:
return s->irq_enable;
case AW_A10_PIT_TIMER_IRQ_ST:
return s->irq_status;
case AW_A10_PIT_TIMER_BASE ... AW_A10_PIT_TIMER_BASE_END:
index = offset & 0xf0;
index >>= 4;
index -= 1;
switch (offset & 0x0f) {
case AW_A10_PIT_TIMER_CONTROL:
return s->control[index];
case AW_A10_PIT_TIMER_INTERVAL:
return s->interval[index];
case AW_A10_PIT_TIMER_COUNT:
s->count[index] = ptimer_get_count(s->timer[index]);
return s->count[index];
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%x\n", __func__, (int)offset);
break;
}
case AW_A10_PIT_WDOG_CONTROL:
break;
case AW_A10_PIT_WDOG_MODE:
break;
case AW_A10_PIT_COUNT_LO:
return s->count_lo;
case AW_A10_PIT_COUNT_HI:
return s->count_hi;
case AW_A10_PIT_COUNT_CTL:
return s->count_ctl;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%x\n", __func__, (int)offset);
break;
}
return 0;
}
/* Must be called inside a ptimer transaction block for s->timer[index] */
static void a10_pit_set_freq(AwA10PITState *s, int index)
{
uint32_t prescaler, source, source_freq;
prescaler = 1 << extract32(s->control[index], 4, 3);
source = extract32(s->control[index], 2, 2);
source_freq = s->clk_freq[source];
if (source_freq) {
ptimer_set_freq(s->timer[index], source_freq / prescaler);
} else {
qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid clock source %u\n",
__func__, source);
}
}
static void a10_pit_write(void *opaque, hwaddr offset, uint64_t value,
unsigned size)
{
AwA10PITState *s = AW_A10_PIT(opaque);
uint8_t index;
switch (offset) {
case AW_A10_PIT_TIMER_IRQ_EN:
s->irq_enable = value;
a10_pit_update_irq(s);
break;
case AW_A10_PIT_TIMER_IRQ_ST:
s->irq_status &= ~value;
a10_pit_update_irq(s);
break;
case AW_A10_PIT_TIMER_BASE ... AW_A10_PIT_TIMER_BASE_END:
index = offset & 0xf0;
index >>= 4;
index -= 1;
switch (offset & 0x0f) {
case AW_A10_PIT_TIMER_CONTROL:
s->control[index] = value;
ptimer_transaction_begin(s->timer[index]);
a10_pit_set_freq(s, index);
if (s->control[index] & AW_A10_PIT_TIMER_RELOAD) {
ptimer_set_count(s->timer[index], s->interval[index]);
}
if (s->control[index] & AW_A10_PIT_TIMER_EN) {
int oneshot = 0;
if (s->control[index] & AW_A10_PIT_TIMER_MODE) {
oneshot = 1;
}
ptimer_run(s->timer[index], oneshot);
} else {
ptimer_stop(s->timer[index]);
}
ptimer_transaction_commit(s->timer[index]);
break;
case AW_A10_PIT_TIMER_INTERVAL:
s->interval[index] = value;
ptimer_transaction_begin(s->timer[index]);
ptimer_set_limit(s->timer[index], s->interval[index], 1);
ptimer_transaction_commit(s->timer[index]);
break;
case AW_A10_PIT_TIMER_COUNT:
s->count[index] = value;
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%x\n", __func__, (int)offset);
}
break;
case AW_A10_PIT_WDOG_CONTROL:
s->watch_dog_control = value;
break;
case AW_A10_PIT_WDOG_MODE:
s->watch_dog_mode = value;
break;
case AW_A10_PIT_COUNT_LO:
s->count_lo = value;
break;
case AW_A10_PIT_COUNT_HI:
s->count_hi = value;
break;
case AW_A10_PIT_COUNT_CTL:
s->count_ctl = value;
if (s->count_ctl & AW_A10_PIT_COUNT_RL_EN) {
uint64_t tmp_count = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
s->count_lo = tmp_count;
s->count_hi = tmp_count >> 32;
s->count_ctl &= ~AW_A10_PIT_COUNT_RL_EN;
}
if (s->count_ctl & AW_A10_PIT_COUNT_CLR_EN) {
s->count_lo = 0;
s->count_hi = 0;
s->count_ctl &= ~AW_A10_PIT_COUNT_CLR_EN;
}
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%x\n", __func__, (int)offset);
break;
}
}
static const MemoryRegionOps a10_pit_ops = {
.read = a10_pit_read,
.write = a10_pit_write,
.endianness = DEVICE_LITTLE_ENDIAN,
};
static const Property a10_pit_properties[] = {
DEFINE_PROP_UINT32("clk0-freq", AwA10PITState, clk_freq[0], 0),
DEFINE_PROP_UINT32("clk1-freq", AwA10PITState, clk_freq[1], 0),
DEFINE_PROP_UINT32("clk2-freq", AwA10PITState, clk_freq[2], 0),
DEFINE_PROP_UINT32("clk3-freq", AwA10PITState, clk_freq[3], 0),
};
static const VMStateDescription vmstate_a10_pit = {
.name = "a10.pit",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(irq_enable, AwA10PITState),
VMSTATE_UINT32(irq_status, AwA10PITState),
VMSTATE_UINT32_ARRAY(control, AwA10PITState, AW_A10_PIT_TIMER_NR),
VMSTATE_UINT32_ARRAY(interval, AwA10PITState, AW_A10_PIT_TIMER_NR),
VMSTATE_UINT32_ARRAY(count, AwA10PITState, AW_A10_PIT_TIMER_NR),
VMSTATE_UINT32(watch_dog_mode, AwA10PITState),
VMSTATE_UINT32(watch_dog_control, AwA10PITState),
VMSTATE_UINT32(count_lo, AwA10PITState),
VMSTATE_UINT32(count_hi, AwA10PITState),
VMSTATE_UINT32(count_ctl, AwA10PITState),
VMSTATE_PTIMER_ARRAY(timer, AwA10PITState, AW_A10_PIT_TIMER_NR),
VMSTATE_END_OF_LIST()
}
};
static void a10_pit_reset(DeviceState *dev)
{
AwA10PITState *s = AW_A10_PIT(dev);
uint8_t i;
s->irq_enable = 0;
s->irq_status = 0;
a10_pit_update_irq(s);
for (i = 0; i < 6; i++) {
s->control[i] = AW_A10_PIT_DEFAULT_CLOCK;
s->interval[i] = 0;
s->count[i] = 0;
ptimer_transaction_begin(s->timer[i]);
ptimer_stop(s->timer[i]);
a10_pit_set_freq(s, i);
ptimer_transaction_commit(s->timer[i]);
}
s->watch_dog_mode = 0;
s->watch_dog_control = 0;
s->count_lo = 0;
s->count_hi = 0;
s->count_ctl = 0;
}
static void a10_pit_timer_cb(void *opaque)
{
AwA10TimerContext *tc = opaque;
AwA10PITState *s = tc->container;
uint8_t i = tc->index;
if (s->control[i] & AW_A10_PIT_TIMER_EN) {
s->irq_status |= 1 << i;
if (s->control[i] & AW_A10_PIT_TIMER_MODE) {
ptimer_stop(s->timer[i]);
s->control[i] &= ~AW_A10_PIT_TIMER_EN;
}
a10_pit_update_irq(s);
}
}
static void a10_pit_init(Object *obj)
{
AwA10PITState *s = AW_A10_PIT(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
uint8_t i;
for (i = 0; i < AW_A10_PIT_TIMER_NR; i++) {
sysbus_init_irq(sbd, &s->irq[i]);
}
memory_region_init_io(&s->iomem, OBJECT(s), &a10_pit_ops, s,
TYPE_AW_A10_PIT, 0x400);
sysbus_init_mmio(sbd, &s->iomem);
for (i = 0; i < AW_A10_PIT_TIMER_NR; i++) {
AwA10TimerContext *tc = &s->timer_context[i];
tc->container = s;
tc->index = i;
s->timer[i] = ptimer_init(a10_pit_timer_cb, tc, PTIMER_POLICY_LEGACY);
}
}
static void a10_pit_finalize(Object *obj)
{
AwA10PITState *s = AW_A10_PIT(obj);
int i;
for (i = 0; i < AW_A10_PIT_TIMER_NR; i++) {
ptimer_free(s->timer[i]);
}
}
static void a10_pit_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, a10_pit_reset);
device_class_set_props(dc, a10_pit_properties);
dc->desc = "allwinner a10 timer";
dc->vmsd = &vmstate_a10_pit;
}
static const TypeInfo a10_pit_info = {
.name = TYPE_AW_A10_PIT,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(AwA10PITState),
.instance_init = a10_pit_init,
.instance_finalize = a10_pit_finalize,
.class_init = a10_pit_class_init,
};
static void a10_register_types(void)
{
type_register_static(&a10_pit_info);
}
type_init(a10_register_types);
+330
View File
@@ -0,0 +1,330 @@
/*
* Private peripheral timer/watchdog blocks for ARM 11MPCore and A9MP
*
* Copyright (c) 2006-2007 CodeSourcery.
* Copyright (c) 2011 Linaro Limited
* Written by Paul Brook, Peter Maydell
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "hw/core/hw-error.h"
#include "hw/core/irq.h"
#include "hw/core/ptimer.h"
#include "hw/core/qdev-properties.h"
#include "hw/timer/arm_mptimer.h"
#include "migration/vmstate.h"
#include "qapi/error.h"
#include "qemu/module.h"
#include "hw/core/cpu.h"
#define PTIMER_POLICY \
(PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD | \
PTIMER_POLICY_CONTINUOUS_TRIGGER | \
PTIMER_POLICY_NO_IMMEDIATE_TRIGGER | \
PTIMER_POLICY_NO_IMMEDIATE_RELOAD | \
PTIMER_POLICY_NO_COUNTER_ROUND_DOWN)
/* This device implements the per-cpu private timer and watchdog block
* which is used in both the ARM11MPCore and Cortex-A9MP.
*/
static inline int get_current_cpu(ARMMPTimerState *s)
{
int cpu_id = current_cpu ? current_cpu->cpu_index : 0;
if (cpu_id >= s->num_cpu) {
hw_error("arm_mptimer: num-cpu %d but this cpu is %d!\n",
s->num_cpu, cpu_id);
}
return cpu_id;
}
static inline void timerblock_update_irq(TimerBlock *tb)
{
qemu_set_irq(tb->irq, tb->status && (tb->control & 4));
}
/* Return conversion factor from mpcore timer ticks to qemu timer ticks. */
static inline uint32_t timerblock_scale(uint32_t control)
{
return (((control >> 8) & 0xff) + 1) * 10;
}
/* Must be called within a ptimer transaction block */
static inline void timerblock_set_count(struct ptimer_state *timer,
uint32_t control, uint64_t *count)
{
/* PTimer would trigger interrupt for periodic timer when counter set
* to 0, MPtimer under certain condition only.
*/
if ((control & 3) == 3 && (control & 0xff00) == 0 && *count == 0) {
*count = ptimer_get_limit(timer);
}
ptimer_set_count(timer, *count);
}
/* Must be called within a ptimer transaction block */
static inline void timerblock_run(struct ptimer_state *timer,
uint32_t control, uint32_t load)
{
if ((control & 1) && ((control & 0xff00) || load != 0)) {
ptimer_run(timer, !(control & 2));
}
}
static void timerblock_tick(void *opaque)
{
TimerBlock *tb = (TimerBlock *)opaque;
/* Periodic timer with load = 0 and prescaler != 0 would re-trigger
* IRQ after one period, otherwise it either stops or wraps around.
*/
if ((tb->control & 2) && (tb->control & 0xff00) == 0 &&
ptimer_get_limit(tb->timer) == 0) {
ptimer_stop(tb->timer);
}
tb->status = 1;
timerblock_update_irq(tb);
}
static uint64_t timerblock_read(void *opaque, hwaddr addr,
unsigned size)
{
TimerBlock *tb = (TimerBlock *)opaque;
switch (addr) {
case 0: /* Load */
return ptimer_get_limit(tb->timer);
case 4: /* Counter. */
return ptimer_get_count(tb->timer);
case 8: /* Control. */
return tb->control;
case 12: /* Interrupt status. */
return tb->status;
default:
return 0;
}
}
static void timerblock_write(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
TimerBlock *tb = (TimerBlock *)opaque;
uint32_t control = tb->control;
switch (addr) {
case 0: /* Load */
ptimer_transaction_begin(tb->timer);
/* Setting load to 0 stops the timer without doing the tick if
* prescaler = 0.
*/
if ((control & 1) && (control & 0xff00) == 0 && value == 0) {
ptimer_stop(tb->timer);
}
ptimer_set_limit(tb->timer, value, 1);
timerblock_run(tb->timer, control, value);
ptimer_transaction_commit(tb->timer);
break;
case 4: /* Counter. */
ptimer_transaction_begin(tb->timer);
/* Setting counter to 0 stops the one-shot timer, or periodic with
* load = 0, without doing the tick if prescaler = 0.
*/
if ((control & 1) && (control & 0xff00) == 0 && value == 0 &&
(!(control & 2) || ptimer_get_limit(tb->timer) == 0)) {
ptimer_stop(tb->timer);
}
timerblock_set_count(tb->timer, control, &value);
timerblock_run(tb->timer, control, value);
ptimer_transaction_commit(tb->timer);
break;
case 8: /* Control. */
ptimer_transaction_begin(tb->timer);
if ((control & 3) != (value & 3)) {
ptimer_stop(tb->timer);
}
if ((control & 0xff00) != (value & 0xff00)) {
ptimer_set_period(tb->timer, timerblock_scale(value));
}
if (value & 1) {
uint64_t count = ptimer_get_count(tb->timer);
/* Re-load periodic timer counter if needed. */
if ((value & 2) && count == 0) {
timerblock_set_count(tb->timer, value, &count);
}
timerblock_run(tb->timer, value, count);
}
tb->control = value;
ptimer_transaction_commit(tb->timer);
break;
case 12: /* Interrupt status. */
tb->status &= ~value;
timerblock_update_irq(tb);
break;
}
}
/* Wrapper functions to implement the "read timer/watchdog for
* the current CPU" memory regions.
*/
static uint64_t arm_thistimer_read(void *opaque, hwaddr addr,
unsigned size)
{
ARMMPTimerState *s = (ARMMPTimerState *)opaque;
int id = get_current_cpu(s);
return timerblock_read(&s->timerblock[id], addr, size);
}
static void arm_thistimer_write(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
ARMMPTimerState *s = (ARMMPTimerState *)opaque;
int id = get_current_cpu(s);
timerblock_write(&s->timerblock[id], addr, value, size);
}
static const MemoryRegionOps arm_thistimer_ops = {
.read = arm_thistimer_read,
.write = arm_thistimer_write,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
.endianness = DEVICE_NATIVE_ENDIAN,
};
static const MemoryRegionOps timerblock_ops = {
.read = timerblock_read,
.write = timerblock_write,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
.endianness = DEVICE_NATIVE_ENDIAN,
};
static void timerblock_reset(TimerBlock *tb)
{
tb->control = 0;
tb->status = 0;
if (tb->timer) {
ptimer_transaction_begin(tb->timer);
ptimer_stop(tb->timer);
ptimer_set_limit(tb->timer, 0, 1);
ptimer_set_period(tb->timer, timerblock_scale(0));
ptimer_transaction_commit(tb->timer);
}
}
static void arm_mptimer_reset(DeviceState *dev)
{
ARMMPTimerState *s = ARM_MPTIMER(dev);
int i;
for (i = 0; i < ARRAY_SIZE(s->timerblock); i++) {
timerblock_reset(&s->timerblock[i]);
}
}
static void arm_mptimer_init(Object *obj)
{
ARMMPTimerState *s = ARM_MPTIMER(obj);
memory_region_init_io(&s->iomem, obj, &arm_thistimer_ops, s,
"arm_mptimer_timer", 0x20);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
}
static void arm_mptimer_realize(DeviceState *dev, Error **errp)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
ARMMPTimerState *s = ARM_MPTIMER(dev);
int i;
if (s->num_cpu < 1 || s->num_cpu > ARM_MPTIMER_MAX_CPUS) {
error_setg(errp, "num-cpu must be between 1 and %d",
ARM_MPTIMER_MAX_CPUS);
return;
}
/* We implement one timer block per CPU, and expose multiple MMIO regions:
* * region 0 is "timer for this core"
* * region 1 is "timer for core 0"
* * region 2 is "timer for core 1"
* and so on.
* The outgoing interrupt lines are
* * timer for core 0
* * timer for core 1
* and so on.
*/
for (i = 0; i < s->num_cpu; i++) {
TimerBlock *tb = &s->timerblock[i];
tb->timer = ptimer_init(timerblock_tick, tb, PTIMER_POLICY);
sysbus_init_irq(sbd, &tb->irq);
memory_region_init_io(&tb->iomem, OBJECT(s), &timerblock_ops, tb,
"arm_mptimer_timerblock", 0x20);
sysbus_init_mmio(sbd, &tb->iomem);
}
}
static const VMStateDescription vmstate_timerblock = {
.name = "arm_mptimer_timerblock",
.version_id = 3,
.minimum_version_id = 3,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(control, TimerBlock),
VMSTATE_UINT32(status, TimerBlock),
VMSTATE_PTIMER(timer, TimerBlock),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription vmstate_arm_mptimer = {
.name = "arm_mptimer",
.version_id = 3,
.minimum_version_id = 3,
.fields = (const VMStateField[]) {
VMSTATE_STRUCT_VARRAY_UINT32(timerblock, ARMMPTimerState, num_cpu,
3, vmstate_timerblock, TimerBlock),
VMSTATE_END_OF_LIST()
}
};
static const Property arm_mptimer_properties[] = {
DEFINE_PROP_UINT32("num-cpu", ARMMPTimerState, num_cpu, 0),
};
static void arm_mptimer_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = arm_mptimer_realize;
dc->vmsd = &vmstate_arm_mptimer;
device_class_set_legacy_reset(dc, arm_mptimer_reset);
device_class_set_props(dc, arm_mptimer_properties);
}
static const TypeInfo arm_mptimer_info = {
.name = TYPE_ARM_MPTIMER,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(ARMMPTimerState),
.instance_init = arm_mptimer_init,
.class_init = arm_mptimer_class_init,
};
static void arm_mptimer_register_types(void)
{
type_register_static(&arm_mptimer_info);
}
type_init(arm_mptimer_register_types)
+418
View File
@@ -0,0 +1,418 @@
/*
* ARM PrimeCell Timer modules.
*
* Copyright (c) 2005-2006 CodeSourcery.
* Written by Paul Brook
*
* This code is licensed under the GPL.
*/
#include "qemu/osdep.h"
#include "hw/core/sysbus.h"
#include "migration/vmstate.h"
#include "qemu/timer.h"
#include "hw/core/irq.h"
#include "hw/core/ptimer.h"
#include "hw/core/qdev-properties.h"
#include "qemu/module.h"
#include "qemu/log.h"
#include "qom/object.h"
/* Common timer implementation. */
#define TIMER_CTRL_ONESHOT (1 << 0)
#define TIMER_CTRL_32BIT (1 << 1)
#define TIMER_CTRL_DIV1 (0 << 2)
#define TIMER_CTRL_DIV16 (1 << 2)
#define TIMER_CTRL_DIV256 (2 << 2)
#define TIMER_CTRL_IE (1 << 5)
#define TIMER_CTRL_PERIODIC (1 << 6)
#define TIMER_CTRL_ENABLE (1 << 7)
typedef struct {
ptimer_state *timer;
uint32_t control;
uint32_t limit;
int freq;
int int_level;
qemu_irq irq;
} arm_timer_state;
/* Check all active timers, and schedule the next timer interrupt. */
static void arm_timer_update(arm_timer_state *s)
{
/* Update interrupts. */
if (s->int_level && (s->control & TIMER_CTRL_IE)) {
qemu_irq_raise(s->irq);
} else {
qemu_irq_lower(s->irq);
}
}
static uint32_t arm_timer_read(void *opaque, hwaddr offset)
{
arm_timer_state *s = (arm_timer_state *)opaque;
switch (offset >> 2) {
case 0: /* TimerLoad */
case 6: /* TimerBGLoad */
return s->limit;
case 1: /* TimerValue */
return ptimer_get_count(s->timer);
case 2: /* TimerControl */
return s->control;
case 4: /* TimerRIS */
return s->int_level;
case 5: /* TimerMIS */
if ((s->control & TIMER_CTRL_IE) == 0)
return 0;
return s->int_level;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset %x\n", __func__, (int)offset);
return 0;
}
}
/*
* Reset the timer limit after settings have changed.
* May only be called from inside a ptimer transaction block.
*/
static void arm_timer_recalibrate(arm_timer_state *s, int reload)
{
uint32_t limit;
if ((s->control & (TIMER_CTRL_PERIODIC | TIMER_CTRL_ONESHOT)) == 0) {
/* Free running. */
if (s->control & TIMER_CTRL_32BIT)
limit = 0xffffffff;
else
limit = 0xffff;
} else {
/* Periodic. */
limit = s->limit;
}
ptimer_set_limit(s->timer, limit, reload);
}
static void arm_timer_write(void *opaque, hwaddr offset,
uint32_t value)
{
arm_timer_state *s = (arm_timer_state *)opaque;
int freq;
switch (offset >> 2) {
case 0: /* TimerLoad */
s->limit = value;
ptimer_transaction_begin(s->timer);
arm_timer_recalibrate(s, 1);
ptimer_transaction_commit(s->timer);
break;
case 1: /* TimerValue */
/* ??? Linux seems to want to write to this readonly register.
Ignore it. */
break;
case 2: /* TimerControl */
ptimer_transaction_begin(s->timer);
if (s->control & TIMER_CTRL_ENABLE) {
/* Pause the timer if it is running. This may cause some
inaccuracy dure to rounding, but avoids a whole lot of other
messyness. */
ptimer_stop(s->timer);
}
s->control = value;
freq = s->freq;
/* ??? Need to recalculate expiry time after changing divisor. */
switch ((value >> 2) & 3) {
case 1: freq >>= 4; break;
case 2: freq >>= 8; break;
}
arm_timer_recalibrate(s, s->control & TIMER_CTRL_ENABLE);
ptimer_set_freq(s->timer, freq);
if (s->control & TIMER_CTRL_ENABLE) {
/* Restart the timer if still enabled. */
ptimer_run(s->timer, (s->control & TIMER_CTRL_ONESHOT) != 0);
}
ptimer_transaction_commit(s->timer);
break;
case 3: /* TimerIntClr */
s->int_level = 0;
break;
case 6: /* TimerBGLoad */
s->limit = value;
ptimer_transaction_begin(s->timer);
arm_timer_recalibrate(s, 0);
ptimer_transaction_commit(s->timer);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset %x\n", __func__, (int)offset);
}
arm_timer_update(s);
}
static void arm_timer_tick(void *opaque)
{
arm_timer_state *s = (arm_timer_state *)opaque;
s->int_level = 1;
arm_timer_update(s);
}
static const VMStateDescription vmstate_arm_timer = {
.name = "arm_timer",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(control, arm_timer_state),
VMSTATE_UINT32(limit, arm_timer_state),
VMSTATE_INT32(int_level, arm_timer_state),
VMSTATE_PTIMER(timer, arm_timer_state),
VMSTATE_END_OF_LIST()
}
};
static arm_timer_state *arm_timer_init(uint32_t freq)
{
arm_timer_state *s;
s = g_new0(arm_timer_state, 1);
s->freq = freq;
s->control = TIMER_CTRL_IE;
s->timer = ptimer_init(arm_timer_tick, s, PTIMER_POLICY_LEGACY);
vmstate_register_any(NULL, &vmstate_arm_timer, s);
return s;
}
/*
* ARM PrimeCell SP804 dual timer module.
* Docs at
* https://developer.arm.com/documentation/ddi0271/latest/
*/
#define TYPE_SP804 "sp804"
OBJECT_DECLARE_SIMPLE_TYPE(SP804State, SP804)
struct SP804State {
SysBusDevice parent_obj;
MemoryRegion iomem;
arm_timer_state *timer[2];
uint32_t freq0, freq1;
int level[2];
qemu_irq irq;
};
static const uint8_t sp804_ids[] = {
/* Timer ID */
0x04, 0x18, 0x14, 0,
/* PrimeCell ID */
0xd, 0xf0, 0x05, 0xb1
};
/* Merge the IRQs from the two component devices. */
static void sp804_set_irq(void *opaque, int irq, int level)
{
SP804State *s = (SP804State *)opaque;
s->level[irq] = level;
qemu_set_irq(s->irq, s->level[0] || s->level[1]);
}
static uint64_t sp804_read(void *opaque, hwaddr offset,
unsigned size)
{
SP804State *s = (SP804State *)opaque;
if (offset < 0x20) {
return arm_timer_read(s->timer[0], offset);
}
if (offset < 0x40) {
return arm_timer_read(s->timer[1], offset - 0x20);
}
/* TimerPeriphID */
if (offset >= 0xfe0 && offset <= 0xffc) {
return sp804_ids[(offset - 0xfe0) >> 2];
}
switch (offset) {
/* Integration Test control registers, which we won't support */
case 0xf00: /* TimerITCR */
case 0xf04: /* TimerITOP (strictly write only but..) */
qemu_log_mask(LOG_UNIMP,
"%s: integration test registers unimplemented\n",
__func__);
return 0;
}
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset %x\n", __func__, (int)offset);
return 0;
}
static void sp804_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
SP804State *s = (SP804State *)opaque;
if (offset < 0x20) {
arm_timer_write(s->timer[0], offset, value);
return;
}
if (offset < 0x40) {
arm_timer_write(s->timer[1], offset - 0x20, value);
return;
}
/* Technically we could be writing to the Test Registers, but not likely */
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %x\n",
__func__, (int)offset);
}
static const MemoryRegionOps sp804_ops = {
.read = sp804_read,
.write = sp804_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
static const VMStateDescription vmstate_sp804 = {
.name = "sp804",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_INT32_ARRAY(level, SP804State, 2),
VMSTATE_END_OF_LIST()
}
};
static void sp804_init(Object *obj)
{
SP804State *s = SP804(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
sysbus_init_irq(sbd, &s->irq);
memory_region_init_io(&s->iomem, obj, &sp804_ops, s,
"sp804", 0x1000);
sysbus_init_mmio(sbd, &s->iomem);
}
static void sp804_realize(DeviceState *dev, Error **errp)
{
SP804State *s = SP804(dev);
s->timer[0] = arm_timer_init(s->freq0);
s->timer[1] = arm_timer_init(s->freq1);
s->timer[0]->irq = qemu_allocate_irq(sp804_set_irq, s, 0);
s->timer[1]->irq = qemu_allocate_irq(sp804_set_irq, s, 1);
}
/* Integrator/CP timer module. */
#define TYPE_INTEGRATOR_PIT "integrator_pit"
OBJECT_DECLARE_SIMPLE_TYPE(icp_pit_state, INTEGRATOR_PIT)
struct icp_pit_state {
SysBusDevice parent_obj;
MemoryRegion iomem;
arm_timer_state *timer[3];
};
static uint64_t icp_pit_read(void *opaque, hwaddr offset,
unsigned size)
{
icp_pit_state *s = (icp_pit_state *)opaque;
int n;
/* ??? Don't know the PrimeCell ID for this device. */
n = offset >> 8;
if (n > 2) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad timer %d\n", __func__, n);
return 0;
}
return arm_timer_read(s->timer[n], offset & 0xff);
}
static void icp_pit_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
icp_pit_state *s = (icp_pit_state *)opaque;
int n;
n = offset >> 8;
if (n > 2) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad timer %d\n", __func__, n);
return;
}
arm_timer_write(s->timer[n], offset & 0xff, value);
}
static const MemoryRegionOps icp_pit_ops = {
.read = icp_pit_read,
.write = icp_pit_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
static void icp_pit_init(Object *obj)
{
icp_pit_state *s = INTEGRATOR_PIT(obj);
SysBusDevice *dev = SYS_BUS_DEVICE(obj);
/* Timer 0 runs at the system clock speed (40MHz). */
s->timer[0] = arm_timer_init(40000000);
/* The other two timers run at 1MHz. */
s->timer[1] = arm_timer_init(1000000);
s->timer[2] = arm_timer_init(1000000);
sysbus_init_irq(dev, &s->timer[0]->irq);
sysbus_init_irq(dev, &s->timer[1]->irq);
sysbus_init_irq(dev, &s->timer[2]->irq);
memory_region_init_io(&s->iomem, obj, &icp_pit_ops, s,
"icp_pit", 0x1000);
sysbus_init_mmio(dev, &s->iomem);
/* This device has no state to save/restore. The component timers will
save themselves. */
}
static const TypeInfo icp_pit_info = {
.name = TYPE_INTEGRATOR_PIT,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(icp_pit_state),
.instance_init = icp_pit_init,
};
static const Property sp804_properties[] = {
DEFINE_PROP_UINT32("freq0", SP804State, freq0, 1000000),
DEFINE_PROP_UINT32("freq1", SP804State, freq1, 1000000),
};
static void sp804_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *k = DEVICE_CLASS(klass);
k->realize = sp804_realize;
device_class_set_props(k, sp804_properties);
k->vmsd = &vmstate_sp804;
}
static const TypeInfo sp804_info = {
.name = TYPE_SP804,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(SP804State),
.instance_init = sp804_init,
.class_init = sp804_class_init,
};
static void arm_timer_register_types(void)
{
type_register_static(&icp_pit_info);
type_register_static(&sp804_info);
}
type_init(arm_timer_register_types)
+310
View File
@@ -0,0 +1,310 @@
/*
* ARMv7M SysTick timer
*
* Copyright (c) 2006-2007 CodeSourcery.
* Written by Paul Brook
* Copyright (c) 2017 Linaro Ltd
* Written by Peter Maydell
*
* This code is licensed under the GPL (version 2 or later).
*/
#include "qemu/osdep.h"
#include "hw/timer/armv7m_systick.h"
#include "migration/vmstate.h"
#include "hw/core/irq.h"
#include "hw/core/sysbus.h"
#include "hw/core/qdev-clock.h"
#include "qemu/timer.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "qapi/error.h"
#include "trace.h"
#define SYSTICK_ENABLE (1 << 0)
#define SYSTICK_TICKINT (1 << 1)
#define SYSTICK_CLKSOURCE (1 << 2)
#define SYSTICK_COUNTFLAG (1 << 16)
#define SYSCALIB_NOREF (1U << 31)
#define SYSCALIB_SKEW (1U << 30)
#define SYSCALIB_TENMS ((1U << 24) - 1)
static void systick_set_period_from_clock(SysTickState *s)
{
/*
* Set the ptimer period from whichever clock is selected.
* Must be called from within a ptimer transaction block.
*/
if (s->control & SYSTICK_CLKSOURCE) {
ptimer_set_period_from_clock(s->ptimer, s->cpuclk, 1);
} else {
ptimer_set_period_from_clock(s->ptimer, s->refclk, 1);
}
}
static void systick_timer_tick(void *opaque)
{
SysTickState *s = (SysTickState *)opaque;
trace_systick_timer_tick();
s->control |= SYSTICK_COUNTFLAG;
if (s->control & SYSTICK_TICKINT) {
/* Tell the NVIC to pend the SysTick exception */
qemu_irq_pulse(s->irq);
}
if (ptimer_get_limit(s->ptimer) == 0) {
/*
* Timer expiry with SYST_RVR zero disables the timer
* (but doesn't clear SYST_CSR.ENABLE)
*/
ptimer_stop(s->ptimer);
}
}
static MemTxResult systick_read(void *opaque, hwaddr addr, uint64_t *data,
unsigned size, MemTxAttrs attrs)
{
SysTickState *s = opaque;
uint32_t val;
if (attrs.user) {
/* Generate BusFault for unprivileged accesses */
return MEMTX_ERROR;
}
switch (addr) {
case 0x0: /* SysTick Control and Status. */
val = s->control;
s->control &= ~SYSTICK_COUNTFLAG;
break;
case 0x4: /* SysTick Reload Value. */
val = ptimer_get_limit(s->ptimer);
break;
case 0x8: /* SysTick Current Value. */
val = ptimer_get_count(s->ptimer);
break;
case 0xc: /* SysTick Calibration Value. */
/*
* In real hardware it is possible to make this register report
* a different value from what the reference clock is actually
* running at. We don't model that (which usually happens due
* to integration errors in the real hardware) and instead always
* report the theoretical correct value as described in the
* knowledgebase article at
* https://developer.arm.com/documentation/ka001325/latest
* If necessary, we could implement an extra QOM property on this
* device to force the STCALIB value to something different from
* the "correct" value.
*/
if (!clock_has_source(s->refclk)) {
val = SYSCALIB_NOREF;
break;
}
val = clock_ns_to_ticks(s->refclk, 10 * SCALE_MS) - 1;
val &= SYSCALIB_TENMS;
if (clock_ticks_to_ns(s->refclk, val + 1) != 10 * SCALE_MS) {
/* report that tick count does not yield exactly 10ms */
val |= SYSCALIB_SKEW;
}
break;
default:
val = 0;
qemu_log_mask(LOG_GUEST_ERROR,
"SysTick: Bad read offset 0x%" HWADDR_PRIx "\n", addr);
break;
}
trace_systick_read(addr, val, size);
*data = val;
return MEMTX_OK;
}
static MemTxResult systick_write(void *opaque, hwaddr addr,
uint64_t value, unsigned size,
MemTxAttrs attrs)
{
SysTickState *s = opaque;
if (attrs.user) {
/* Generate BusFault for unprivileged accesses */
return MEMTX_ERROR;
}
trace_systick_write(addr, value, size);
switch (addr) {
case 0x0: /* SysTick Control and Status. */
{
uint32_t oldval;
if (!clock_has_source(s->refclk)) {
/* This bit is always 1 if there is no external refclk */
value |= SYSTICK_CLKSOURCE;
}
ptimer_transaction_begin(s->ptimer);
oldval = s->control;
s->control &= 0xfffffff8;
s->control |= value & 7;
if ((oldval ^ value) & SYSTICK_CLKSOURCE) {
systick_set_period_from_clock(s);
}
if ((oldval ^ value) & SYSTICK_ENABLE) {
if (value & SYSTICK_ENABLE) {
ptimer_run(s->ptimer, 0);
} else {
ptimer_stop(s->ptimer);
}
}
ptimer_transaction_commit(s->ptimer);
break;
}
case 0x4: /* SysTick Reload Value. */
ptimer_transaction_begin(s->ptimer);
ptimer_set_limit(s->ptimer, value & 0xffffff, 0);
ptimer_transaction_commit(s->ptimer);
break;
case 0x8: /* SysTick Current Value. */
/*
* Writing any value clears SYST_CVR to zero and clears
* SYST_CSR.COUNTFLAG. The counter will then reload from SYST_RVR
* on the next clock edge unless SYST_RVR is zero.
*/
ptimer_transaction_begin(s->ptimer);
if (ptimer_get_limit(s->ptimer) == 0) {
ptimer_stop(s->ptimer);
}
ptimer_set_count(s->ptimer, 0);
s->control &= ~SYSTICK_COUNTFLAG;
ptimer_transaction_commit(s->ptimer);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"SysTick: Bad write offset 0x%" HWADDR_PRIx "\n", addr);
}
return MEMTX_OK;
}
static const MemoryRegionOps systick_ops = {
.read_with_attrs = systick_read,
.write_with_attrs = systick_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.valid.min_access_size = 4,
.valid.max_access_size = 4,
};
static void systick_reset(DeviceState *dev)
{
SysTickState *s = SYSTICK(dev);
ptimer_transaction_begin(s->ptimer);
s->control = 0;
if (!clock_has_source(s->refclk)) {
/* This bit is always 1 if there is no external refclk */
s->control |= SYSTICK_CLKSOURCE;
}
ptimer_stop(s->ptimer);
ptimer_set_count(s->ptimer, 0);
ptimer_set_limit(s->ptimer, 0, 0);
systick_set_period_from_clock(s);
ptimer_transaction_commit(s->ptimer);
}
static void systick_cpuclk_update(void *opaque, ClockEvent event)
{
SysTickState *s = SYSTICK(opaque);
if (!(s->control & SYSTICK_CLKSOURCE)) {
/* currently using refclk, we can ignore cpuclk changes */
}
ptimer_transaction_begin(s->ptimer);
ptimer_set_period_from_clock(s->ptimer, s->cpuclk, 1);
ptimer_transaction_commit(s->ptimer);
}
static void systick_refclk_update(void *opaque, ClockEvent event)
{
SysTickState *s = SYSTICK(opaque);
if (s->control & SYSTICK_CLKSOURCE) {
/* currently using cpuclk, we can ignore refclk changes */
}
ptimer_transaction_begin(s->ptimer);
ptimer_set_period_from_clock(s->ptimer, s->refclk, 1);
ptimer_transaction_commit(s->ptimer);
}
static void systick_instance_init(Object *obj)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
SysTickState *s = SYSTICK(obj);
memory_region_init_io(&s->iomem, obj, &systick_ops, s, "systick", 0xe0);
sysbus_init_mmio(sbd, &s->iomem);
sysbus_init_irq(sbd, &s->irq);
s->refclk = qdev_init_clock_in(DEVICE(obj), "refclk",
systick_refclk_update, s, ClockUpdate);
s->cpuclk = qdev_init_clock_in(DEVICE(obj), "cpuclk",
systick_cpuclk_update, s, ClockUpdate);
}
static void systick_realize(DeviceState *dev, Error **errp)
{
SysTickState *s = SYSTICK(dev);
s->ptimer = ptimer_init(systick_timer_tick, s,
PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD |
PTIMER_POLICY_NO_COUNTER_ROUND_DOWN |
PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
PTIMER_POLICY_TRIGGER_ONLY_ON_DECREMENT);
if (!clock_has_source(s->cpuclk)) {
error_setg(errp, "systick: cpuclk must be connected");
return;
}
/* It's OK not to connect the refclk */
}
static const VMStateDescription vmstate_systick = {
.name = "armv7m_systick",
.version_id = 3,
.minimum_version_id = 3,
.fields = (const VMStateField[]) {
VMSTATE_CLOCK(refclk, SysTickState),
VMSTATE_CLOCK(cpuclk, SysTickState),
VMSTATE_UINT32(control, SysTickState),
VMSTATE_INT64(tick, SysTickState),
VMSTATE_PTIMER(ptimer, SysTickState),
VMSTATE_END_OF_LIST()
}
};
static void systick_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->vmsd = &vmstate_systick;
device_class_set_legacy_reset(dc, systick_reset);
dc->realize = systick_realize;
}
static const TypeInfo armv7m_systick_info = {
.name = TYPE_SYSTICK,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_init = systick_instance_init,
.instance_size = sizeof(SysTickState),
.class_init = systick_class_init,
};
static void armv7m_systick_register_types(void)
{
type_register_static(&armv7m_systick_info);
}
type_init(armv7m_systick_register_types)
+996
View File
@@ -0,0 +1,996 @@
/*
* ASPEED AST2400 Timer
*
* Andrew Jeffery <[email protected]>
*
* Copyright (C) 2016 IBM Corp.
*
* This code is licensed under the GPL version 2 or later. See
* the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "hw/core/irq.h"
#include "hw/core/sysbus.h"
#include "hw/timer/aspeed_timer.h"
#include "migration/vmstate.h"
#include "qemu/bitops.h"
#include "qemu/timer.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "hw/core/qdev-properties.h"
#include "trace.h"
#define TIMER_NR_REGS 4
#define TIMER_CTRL_BITS 4
#define TIMER_CTRL_MASK ((1 << TIMER_CTRL_BITS) - 1)
#define TIMER_CLOCK_USE_EXT true
#define TIMER_CLOCK_EXT_HZ 1000000
#define TIMER_CLOCK_USE_APB false
#define TIMER_REG_STATUS 0
#define TIMER_REG_RELOAD 1
#define TIMER_REG_MATCH_FIRST 2
#define TIMER_REG_MATCH_SECOND 3
#define TIMER_FIRST_CAP_PULSE 4
enum timer_ctrl_op {
op_enable = 0,
op_external_clock,
op_overflow_interrupt,
op_pulse_enable
};
/*
* Minimum value of the reload register to filter out short period
* timers which have a noticeable impact in emulation. 5us should be
* enough, use 20us for "safety".
*/
#define TIMER_MIN_NS (20 * SCALE_US)
/**
* Avoid mutual references between AspeedTimerCtrlState and AspeedTimer
* structs, as it's a waste of memory. The ptimer BH callback needs to know
* whether a specific AspeedTimer is enabled, but this information is held in
* AspeedTimerCtrlState. So, provide a helper to hoist ourselves from an
* arbitrary AspeedTimer to AspeedTimerCtrlState.
*/
static inline AspeedTimerCtrlState *timer_to_ctrl(AspeedTimer *t)
{
const AspeedTimer (*timers)[] = (void *)t - (t->id * sizeof(*t));
return container_of(timers, AspeedTimerCtrlState, timers);
}
static inline bool timer_ctrl_status(AspeedTimer *t, enum timer_ctrl_op op)
{
return !!(timer_to_ctrl(t)->ctrl & BIT(t->id * TIMER_CTRL_BITS + op));
}
static inline bool timer_enabled(AspeedTimer *t)
{
return timer_ctrl_status(t, op_enable);
}
static inline bool timer_overflow_interrupt(AspeedTimer *t)
{
return timer_ctrl_status(t, op_overflow_interrupt);
}
static inline bool timer_can_pulse(AspeedTimer *t)
{
return t->id >= TIMER_FIRST_CAP_PULSE;
}
static inline bool timer_external_clock(AspeedTimer *t)
{
return timer_ctrl_status(t, op_external_clock);
}
static inline uint32_t calculate_rate(struct AspeedTimer *t)
{
AspeedTimerCtrlState *s = timer_to_ctrl(t);
return timer_external_clock(t) ? TIMER_CLOCK_EXT_HZ :
aspeed_scu_get_apb_freq(s->scu);
}
static inline uint32_t calculate_ticks(struct AspeedTimer *t, uint64_t now_ns)
{
uint64_t delta_ns = now_ns - MIN(now_ns, t->start);
uint32_t rate = calculate_rate(t);
uint64_t ticks = muldiv64(delta_ns, rate, NANOSECONDS_PER_SECOND);
return t->reload - MIN(t->reload, ticks);
}
static uint32_t calculate_min_ticks(AspeedTimer *t, uint32_t value)
{
uint32_t rate = calculate_rate(t);
uint32_t min_ticks = muldiv64(TIMER_MIN_NS, rate, NANOSECONDS_PER_SECOND);
return value < min_ticks ? min_ticks : value;
}
static inline uint64_t calculate_time(struct AspeedTimer *t, uint32_t ticks)
{
uint64_t delta_ns;
uint64_t delta_ticks;
delta_ticks = t->reload - MIN(t->reload, ticks);
delta_ns = muldiv64(delta_ticks, NANOSECONDS_PER_SECOND, calculate_rate(t));
return t->start + delta_ns;
}
static inline uint32_t calculate_match(struct AspeedTimer *t, int i)
{
return t->match[i] < t->reload ? t->match[i] : 0;
}
static uint64_t calculate_next(struct AspeedTimer *t)
{
uint64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
uint64_t next;
/*
* We don't know the relationship between the values in the match
* registers, so sort using MAX/MIN/zero. We sort in that order as
* the timer counts down to zero.
*/
next = calculate_time(t, MAX(calculate_match(t, 0), calculate_match(t, 1)));
if (now < next) {
return next;
}
next = calculate_time(t, MIN(calculate_match(t, 0), calculate_match(t, 1)));
if (now < next) {
return next;
}
next = calculate_time(t, 0);
if (now < next) {
return next;
}
/* We've missed all deadlines, fire interrupt and try again */
timer_del(&t->timer);
if (timer_overflow_interrupt(t)) {
AspeedTimerCtrlState *s = timer_to_ctrl(t);
t->level = !t->level;
s->irq_sts |= BIT(t->id);
qemu_set_irq(t->irq, t->level);
}
next = MAX(calculate_match(t, 0), calculate_match(t, 1));
t->start = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
return calculate_time(t, next);
}
static void aspeed_timer_mod(AspeedTimer *t)
{
uint64_t next = calculate_next(t);
if (next) {
timer_mod(&t->timer, next);
}
}
static void aspeed_timer_expire(void *opaque)
{
AspeedTimer *t = opaque;
bool interrupt = false;
uint32_t ticks;
if (!timer_enabled(t)) {
return;
}
ticks = calculate_ticks(t, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
if (!ticks) {
interrupt = timer_overflow_interrupt(t) || !t->match[0] || !t->match[1];
} else if (ticks <= MIN(t->match[0], t->match[1])) {
interrupt = true;
} else if (ticks <= MAX(t->match[0], t->match[1])) {
interrupt = true;
}
if (interrupt) {
AspeedTimerCtrlState *s = timer_to_ctrl(t);
t->level = !t->level;
s->irq_sts |= BIT(t->id);
qemu_set_irq(t->irq, t->level);
}
aspeed_timer_mod(t);
}
static uint64_t aspeed_timer_get_value(AspeedTimer *t, int reg)
{
uint64_t value;
switch (reg) {
case TIMER_REG_STATUS:
if (timer_enabled(t)) {
value = calculate_ticks(t, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
} else {
value = t->reload;
}
break;
case TIMER_REG_RELOAD:
value = t->reload;
break;
case TIMER_REG_MATCH_FIRST:
case TIMER_REG_MATCH_SECOND:
value = t->match[reg - 2];
break;
default:
qemu_log_mask(LOG_UNIMP, "%s: Programming error: unexpected reg: %d\n",
__func__, reg);
value = 0;
break;
}
return value;
}
static uint64_t aspeed_timer_read_common(AspeedTimerCtrlState *s, hwaddr offset)
{
const int reg = (offset & 0xf) / 4;
uint64_t value;
switch (offset) {
case 0x30: /* Control Register */
value = s->ctrl;
break;
case 0x00 ... 0x2c: /* Timers 1 - 4 */
value = aspeed_timer_get_value(&s->timers[(offset >> 4)], reg);
break;
case 0x40 ... 0x8c: /* Timers 5 - 8 */
value = aspeed_timer_get_value(&s->timers[(offset >> 4) - 1], reg);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
__func__, offset);
value = 0;
break;
}
return value;
}
static void aspeed_timer_set_value(AspeedTimerCtrlState *s, int timer, int reg,
uint32_t value)
{
AspeedTimer *t;
uint32_t old_reload;
trace_aspeed_timer_set_value(timer, reg, value);
t = &s->timers[timer];
switch (reg) {
case TIMER_REG_RELOAD:
old_reload = t->reload;
t->reload = calculate_min_ticks(t, value);
/*
* If the reload value was not previously set, or zero, and
* the current value is valid, try to start the timer if it is
* enabled.
*/
if (old_reload || !t->reload) {
break;
}
/* fall through to re-enable */
case TIMER_REG_STATUS:
if (timer_enabled(t)) {
uint64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
int64_t delta = (int64_t) value - (int64_t) calculate_ticks(t, now);
uint32_t rate = calculate_rate(t);
if (delta >= 0) {
t->start += muldiv64(delta, NANOSECONDS_PER_SECOND, rate);
} else {
t->start -= muldiv64(-delta, NANOSECONDS_PER_SECOND, rate);
}
aspeed_timer_mod(t);
}
break;
case TIMER_REG_MATCH_FIRST:
case TIMER_REG_MATCH_SECOND:
t->match[reg - 2] = value;
if (timer_enabled(t)) {
aspeed_timer_mod(t);
}
break;
default:
qemu_log_mask(LOG_UNIMP, "%s: Programming error: unexpected reg: %d\n",
__func__, reg);
break;
}
}
/*
* Control register operations are broken out into helpers that can be
* explicitly called on aspeed_timer_reset(), but also from
* aspeed_timer_ctrl_op().
*/
static void aspeed_timer_ctrl_enable(AspeedTimer *t, bool enable)
{
trace_aspeed_timer_ctrl_enable(t->id, enable);
if (enable) {
t->start = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
aspeed_timer_mod(t);
} else {
timer_del(&t->timer);
}
}
static void aspeed_timer_ctrl_external_clock(AspeedTimer *t, bool enable)
{
trace_aspeed_timer_ctrl_external_clock(t->id, enable);
}
static void aspeed_timer_ctrl_overflow_interrupt(AspeedTimer *t, bool enable)
{
trace_aspeed_timer_ctrl_overflow_interrupt(t->id, enable);
}
static void aspeed_timer_ctrl_pulse_enable(AspeedTimer *t, bool enable)
{
if (timer_can_pulse(t)) {
trace_aspeed_timer_ctrl_pulse_enable(t->id, enable);
} else {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Timer does not support pulse mode\n", __func__);
}
}
/**
* Given the actions are fixed in number and completely described in helper
* functions, dispatch with a lookup table rather than manage control flow with
* a switch statement.
*/
static void (*const ctrl_ops[])(AspeedTimer *, bool) = {
[op_enable] = aspeed_timer_ctrl_enable,
[op_external_clock] = aspeed_timer_ctrl_external_clock,
[op_overflow_interrupt] = aspeed_timer_ctrl_overflow_interrupt,
[op_pulse_enable] = aspeed_timer_ctrl_pulse_enable,
};
/**
* Conditionally affect changes chosen by a timer's control bit.
*
* The aspeed_timer_ctrl_op() interface is convenient for the
* aspeed_timer_set_ctrl() function as the "no change" early exit can be
* calculated for all operations, which cleans up the caller code. However the
* interface isn't convenient for the reset function where we want to enter a
* specific state without artificially constructing old and new values that
* will fall through the change guard (and motivates extracting the actions
* out to helper functions).
*
* @t: The timer to manipulate
* @op: The type of operation to be performed
* @old: The old state of the timer's control bits
* @new: The incoming state for the timer's control bits
*/
static void aspeed_timer_ctrl_op(AspeedTimer *t, enum timer_ctrl_op op,
uint8_t old, uint8_t new)
{
const uint8_t mask = BIT(op);
const bool enable = !!(new & mask);
const bool changed = ((old ^ new) & mask);
if (!changed) {
return;
}
ctrl_ops[op](t, enable);
}
static void aspeed_timer_set_ctrl(AspeedTimerCtrlState *s, uint32_t reg)
{
int i;
int shift;
uint8_t t_old, t_new;
AspeedTimer *t;
const uint8_t enable_mask = BIT(op_enable);
/*
* Handle a dependency between the 'enable' and remaining three
* configuration bits - i.e. if more than one bit in the control set has
* changed, including the 'enable' bit, then we want either disable the
* timer and perform configuration, or perform configuration and then
* enable the timer
*/
for (i = 0; i < ASPEED_TIMER_NR_TIMERS; i++) {
t = &s->timers[i];
shift = (i * TIMER_CTRL_BITS);
t_old = (s->ctrl >> shift) & TIMER_CTRL_MASK;
t_new = (reg >> shift) & TIMER_CTRL_MASK;
/* If we are disabling, do so first */
if ((t_old & enable_mask) && !(t_new & enable_mask)) {
aspeed_timer_ctrl_enable(t, false);
}
aspeed_timer_ctrl_op(t, op_external_clock, t_old, t_new);
aspeed_timer_ctrl_op(t, op_overflow_interrupt, t_old, t_new);
aspeed_timer_ctrl_op(t, op_pulse_enable, t_old, t_new);
/* If we are enabling, do so last */
if (!(t_old & enable_mask) && (t_new & enable_mask)) {
aspeed_timer_ctrl_enable(t, true);
}
}
s->ctrl = reg;
}
static void aspeed_timer_set_ctrl2(AspeedTimerCtrlState *s, uint32_t value)
{
trace_aspeed_timer_set_ctrl2(value);
}
static void aspeed_timer_write_common(AspeedTimerCtrlState *s, hwaddr offset,
uint64_t value)
{
const uint32_t tv = (uint32_t)(value & 0xFFFFFFFF);
const int reg = (offset & 0xf) / 4;
switch (offset) {
/* Control Registers */
case 0x30:
aspeed_timer_set_ctrl(s, tv);
break;
/* Timer Registers */
case 0x00 ... 0x2c:
aspeed_timer_set_value(s, (offset >> TIMER_NR_REGS), reg, tv);
break;
case 0x40 ... 0x8c:
aspeed_timer_set_value(s, (offset >> TIMER_NR_REGS) - 1, reg, tv);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
__func__, offset);
break;
}
}
static uint64_t aspeed_timer_read(void *opaque, hwaddr offset, unsigned size)
{
AspeedTimerCtrlState *s = ASPEED_TIMER(opaque);
return ASPEED_TIMER_GET_CLASS(s)->read(s, offset);
}
static void aspeed_timer_write(void *opaque, hwaddr offset, uint64_t value,
unsigned size)
{
AspeedTimerCtrlState *s = ASPEED_TIMER(opaque);
ASPEED_TIMER_GET_CLASS(s)->write(s, offset, value);
}
static const MemoryRegionOps aspeed_timer_ops = {
.read = aspeed_timer_read,
.write = aspeed_timer_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid.min_access_size = 4,
.valid.max_access_size = 4,
.valid.unaligned = false,
};
static uint64_t aspeed_2400_timer_read(AspeedTimerCtrlState *s, hwaddr offset)
{
uint64_t value;
switch (offset) {
case 0x34:
value = s->ctrl2;
break;
case 0x38:
case 0x3C:
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
__func__, offset);
value = 0;
break;
default:
value = aspeed_timer_read_common(s, offset);
break;
}
trace_aspeed_timer_read(offset, value);
return value;
}
static void aspeed_2400_timer_write(AspeedTimerCtrlState *s, hwaddr offset,
uint64_t value)
{
const uint32_t tv = (uint32_t)(value & 0xFFFFFFFF);
switch (offset) {
case 0x34:
aspeed_timer_set_ctrl2(s, tv);
break;
case 0x38:
case 0x3C:
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
__func__, offset);
break;
default:
aspeed_timer_write_common(s, offset, value);
break;
}
}
static uint64_t aspeed_2500_timer_read(AspeedTimerCtrlState *s, hwaddr offset)
{
uint64_t value;
switch (offset) {
case 0x34:
value = s->ctrl2;
break;
case 0x38:
value = s->ctrl3 & BIT(0);
break;
case 0x3C:
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
__func__, offset);
value = 0;
break;
default:
value = aspeed_timer_read_common(s, offset);
break;
}
trace_aspeed_timer_read(offset, value);
return value;
}
static void aspeed_2500_timer_write(AspeedTimerCtrlState *s, hwaddr offset,
uint64_t value)
{
const uint32_t tv = (uint32_t)(value & 0xFFFFFFFF);
uint8_t command;
switch (offset) {
case 0x34:
aspeed_timer_set_ctrl2(s, tv);
break;
case 0x38:
command = (value >> 1) & 0xFF;
if (command == 0xAE) {
s->ctrl3 = 0x1;
} else if (command == 0xEA) {
s->ctrl3 = 0x0;
}
break;
case 0x3C:
if (s->ctrl3 & BIT(0)) {
aspeed_timer_set_ctrl(s, s->ctrl & ~tv);
}
break;
default:
aspeed_timer_write_common(s, offset, value);
break;
}
}
static uint64_t aspeed_2600_timer_read(AspeedTimerCtrlState *s, hwaddr offset)
{
uint64_t value;
switch (offset) {
case 0x34:
value = s->irq_sts;
break;
case 0x38:
case 0x3C:
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
__func__, offset);
value = 0;
break;
default:
value = aspeed_timer_read_common(s, offset);
break;
}
trace_aspeed_timer_read(offset, value);
return value;
}
static void aspeed_2600_timer_write(AspeedTimerCtrlState *s, hwaddr offset,
uint64_t value)
{
const uint32_t tv = (uint32_t)(value & 0xFFFFFFFF);
switch (offset) {
case 0x34:
s->irq_sts &= ~tv;
break;
case 0x3C:
aspeed_timer_set_ctrl(s, s->ctrl & ~tv);
break;
case 0x38:
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
__func__, offset);
break;
default:
aspeed_timer_write_common(s, offset, value);
break;
}
}
static void aspeed_2700_timer_set_ctrl(AspeedTimerCtrlState *s, int index,
uint32_t reg)
{
const uint8_t overflow_interrupt_mask = BIT(op_overflow_interrupt);
const uint8_t external_clock_mask = BIT(op_external_clock);
const uint8_t pulse_enable_mask = BIT(op_pulse_enable);
const uint8_t enable_mask = BIT(op_enable);
AspeedTimer *t;
uint8_t t_old;
uint8_t t_new;
int shift;
/*
* Only 1 will set the specific bits to 1
* Handle a dependency between the 'enable' and remaining three
* configuration bits - i.e. if more than one bit in the control set has
* set, including the 'enable' bit, perform configuration and then
* enable the timer.
* Interrupt Status bit should not be set.
*/
t = &s->timers[index];
shift = index * TIMER_CTRL_BITS;
t_old = (s->ctrl >> shift) & TIMER_CTRL_MASK;
t_new = reg & TIMER_CTRL_MASK;
if (!(t_old & external_clock_mask) &&
(t_new & external_clock_mask)) {
aspeed_timer_ctrl_external_clock(t, true);
s->ctrl = deposit32(s->ctrl, shift + op_external_clock, 1, 1);
}
if (!(t_old & overflow_interrupt_mask) &&
(t_new & overflow_interrupt_mask)) {
aspeed_timer_ctrl_overflow_interrupt(t, true);
s->ctrl = deposit32(s->ctrl, shift + op_overflow_interrupt, 1, 1);
}
if (!(t_old & pulse_enable_mask) &&
(t_new & pulse_enable_mask)) {
aspeed_timer_ctrl_pulse_enable(t, true);
s->ctrl = deposit32(s->ctrl, shift + op_pulse_enable, 1, 1);
}
/* If we are enabling, do so last */
if (!(t_old & enable_mask) &&
(t_new & enable_mask)) {
aspeed_timer_ctrl_enable(t, true);
s->ctrl = deposit32(s->ctrl, shift + op_enable, 1, 1);
}
}
static void aspeed_2700_timer_clear_ctrl(AspeedTimerCtrlState *s, int index,
uint32_t reg)
{
const uint8_t overflow_interrupt_mask = BIT(op_overflow_interrupt);
const uint8_t external_clock_mask = BIT(op_external_clock);
const uint8_t pulse_enable_mask = BIT(op_pulse_enable);
const uint8_t enable_mask = BIT(op_enable);
AspeedTimer *t;
uint8_t t_old;
uint8_t t_new;
int shift;
/*
* Only 1 will clear the specific bits to 0
* Handle a dependency between the 'enable' and remaining three
* configuration bits - i.e. if more than one bit in the control set has
* clear, including the 'enable' bit, then disable the timer and perform
* configuration
*/
t = &s->timers[index];
shift = index * TIMER_CTRL_BITS;
t_old = (s->ctrl >> shift) & TIMER_CTRL_MASK;
t_new = reg & TIMER_CTRL_MASK;
/* If we are disabling, do so first */
if ((t_old & enable_mask) &&
(t_new & enable_mask)) {
aspeed_timer_ctrl_enable(t, false);
s->ctrl = deposit32(s->ctrl, shift + op_enable, 1, 0);
}
if ((t_old & external_clock_mask) &&
(t_new & external_clock_mask)) {
aspeed_timer_ctrl_external_clock(t, false);
s->ctrl = deposit32(s->ctrl, shift + op_external_clock, 1, 0);
}
if ((t_old & overflow_interrupt_mask) &&
(t_new & overflow_interrupt_mask)) {
aspeed_timer_ctrl_overflow_interrupt(t, false);
s->ctrl = deposit32(s->ctrl, shift + op_overflow_interrupt, 1, 0);
}
if ((t_old & pulse_enable_mask) &&
(t_new & pulse_enable_mask)) {
aspeed_timer_ctrl_pulse_enable(t, false);
s->ctrl = deposit32(s->ctrl, shift + op_pulse_enable, 1, 0);
}
/* Clear interrupt status */
if (reg & 0x10000) {
s->irq_sts = deposit32(s->irq_sts, index, 1, 0);
}
}
static uint64_t aspeed_2700_timer_read(AspeedTimerCtrlState *s, hwaddr offset)
{
uint32_t timer_offset = offset & 0x3f;
int timer_index = offset >> 6;
uint64_t value = 0;
if (timer_index >= ASPEED_TIMER_NR_TIMERS) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: offset 0x%" PRIx64 " out of bounds\n",
__func__, offset);
return 0;
}
switch (timer_offset) {
/*
* Counter Status
* Counter Reload
* Counter First Matching
* Counter Second Matching
*/
case 0x00 ... 0x0C:
value = aspeed_timer_get_value(&s->timers[timer_index],
timer_offset >> 2);
break;
/* Counter Control and Interrupt Status */
case 0x10:
value = deposit64(value, 0, 4,
extract32(s->ctrl, timer_index * 4, 4));
value = deposit64(value, 16, 1,
extract32(s->irq_sts, timer_index, 1));
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "%s: no getter for offset 0x%"
PRIx64"\n", __func__, offset);
value = 0;
break;
}
trace_aspeed_timer_read(offset, value);
return value;
}
static void aspeed_2700_timer_write(AspeedTimerCtrlState *s, hwaddr offset,
uint64_t value)
{
const uint32_t timer_value = (uint32_t)(value & 0xFFFFFFFF);
uint32_t timer_offset = offset & 0x3f;
int timer_index = offset >> 6;
if (timer_index >= ASPEED_TIMER_NR_TIMERS) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: offset 0x%" PRIx64 " out of bounds\n",
__func__, offset);
}
switch (timer_offset) {
/*
* Counter Status
* Counter Reload
* Counter First Matching
* Counter Second Matching
*/
case 0x00 ... 0x0C:
aspeed_timer_set_value(s, timer_index, timer_offset >> 2,
timer_value);
break;
/* Counter Control Set and Interrupt Status */
case 0x10:
aspeed_2700_timer_set_ctrl(s, timer_index, timer_value);
break;
/* Counter Control Clear and Interrupr Status */
case 0x14:
aspeed_2700_timer_clear_ctrl(s, timer_index, timer_value);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "%s: no setter for offset 0x%"
PRIx64"\n", __func__, offset);
break;
}
}
static void aspeed_init_one_timer(AspeedTimerCtrlState *s, uint8_t id)
{
AspeedTimer *t = &s->timers[id];
t->id = id;
timer_init_ns(&t->timer, QEMU_CLOCK_VIRTUAL, aspeed_timer_expire, t);
}
static void aspeed_timer_realize(DeviceState *dev, Error **errp)
{
int i;
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
AspeedTimerCtrlState *s = ASPEED_TIMER(dev);
assert(s->scu);
for (i = 0; i < ASPEED_TIMER_NR_TIMERS; i++) {
aspeed_init_one_timer(s, i);
sysbus_init_irq(sbd, &s->timers[i].irq);
}
memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_timer_ops, s,
TYPE_ASPEED_TIMER, 0x1000);
sysbus_init_mmio(sbd, &s->iomem);
}
static void aspeed_timer_reset_hold(Object *obj, ResetType type)
{
int i;
AspeedTimerCtrlState *s = ASPEED_TIMER(obj);
for (i = 0; i < ASPEED_TIMER_NR_TIMERS; i++) {
AspeedTimer *t = &s->timers[i];
/*
* Explicitly call helpers to avoid any conditional behaviour through
* aspeed_timer_set_ctrl().
*/
aspeed_timer_ctrl_enable(t, false);
aspeed_timer_ctrl_external_clock(t, TIMER_CLOCK_USE_APB);
aspeed_timer_ctrl_overflow_interrupt(t, false);
aspeed_timer_ctrl_pulse_enable(t, false);
t->level = 0;
t->reload = 0;
t->match[0] = 0;
t->match[1] = 0;
}
s->ctrl = 0;
s->ctrl2 = 0;
s->ctrl3 = 0;
s->irq_sts = 0;
}
static const VMStateDescription vmstate_aspeed_timer = {
.name = "aspeed.timer",
.version_id = 2,
.minimum_version_id = 2,
.fields = (const VMStateField[]) {
VMSTATE_UINT8(id, AspeedTimer),
VMSTATE_INT32(level, AspeedTimer),
VMSTATE_TIMER(timer, AspeedTimer),
VMSTATE_UINT32(reload, AspeedTimer),
VMSTATE_UINT32_ARRAY(match, AspeedTimer, 2),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription vmstate_aspeed_timer_state = {
.name = "aspeed.timerctrl",
.version_id = 2,
.minimum_version_id = 2,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(ctrl, AspeedTimerCtrlState),
VMSTATE_UINT32(ctrl2, AspeedTimerCtrlState),
VMSTATE_UINT32(ctrl3, AspeedTimerCtrlState),
VMSTATE_UINT32(irq_sts, AspeedTimerCtrlState),
VMSTATE_STRUCT_ARRAY(timers, AspeedTimerCtrlState,
ASPEED_TIMER_NR_TIMERS, 1, vmstate_aspeed_timer,
AspeedTimer),
VMSTATE_END_OF_LIST()
}
};
static const Property aspeed_timer_properties[] = {
DEFINE_PROP_LINK("scu", AspeedTimerCtrlState, scu, TYPE_ASPEED_SCU,
AspeedSCUState *),
};
static void timer_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
ResettableClass *rc = RESETTABLE_CLASS(klass);
dc->realize = aspeed_timer_realize;
rc->phases.hold = aspeed_timer_reset_hold;
dc->desc = "ASPEED Timer";
dc->vmsd = &vmstate_aspeed_timer_state;
device_class_set_props(dc, aspeed_timer_properties);
}
static void aspeed_2400_timer_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
AspeedTimerClass *awc = ASPEED_TIMER_CLASS(klass);
dc->desc = "ASPEED 2400 Timer";
awc->read = aspeed_2400_timer_read;
awc->write = aspeed_2400_timer_write;
}
static void aspeed_2500_timer_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
AspeedTimerClass *awc = ASPEED_TIMER_CLASS(klass);
dc->desc = "ASPEED 2500 Timer";
awc->read = aspeed_2500_timer_read;
awc->write = aspeed_2500_timer_write;
}
static void aspeed_2600_timer_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
AspeedTimerClass *awc = ASPEED_TIMER_CLASS(klass);
dc->desc = "ASPEED 2600 Timer";
awc->read = aspeed_2600_timer_read;
awc->write = aspeed_2600_timer_write;
}
static void aspeed_1030_timer_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
AspeedTimerClass *awc = ASPEED_TIMER_CLASS(klass);
dc->desc = "ASPEED 1030 Timer";
awc->read = aspeed_2600_timer_read;
awc->write = aspeed_2600_timer_write;
}
static void aspeed_2700_timer_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
AspeedTimerClass *awc = ASPEED_TIMER_CLASS(klass);
dc->desc = "ASPEED 2700 Timer";
awc->read = aspeed_2700_timer_read;
awc->write = aspeed_2700_timer_write;
}
static const TypeInfo aspeed_timer_types[] = {
{
.name = TYPE_ASPEED_TIMER,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(AspeedTimerCtrlState),
.class_init = timer_class_init,
.class_size = sizeof(AspeedTimerClass),
.abstract = true,
},
{
.name = TYPE_ASPEED_1030_TIMER,
.parent = TYPE_ASPEED_TIMER,
.class_init = aspeed_1030_timer_class_init,
},
{
.name = TYPE_ASPEED_2400_TIMER,
.parent = TYPE_ASPEED_TIMER,
.class_init = aspeed_2400_timer_class_init,
},
{
.name = TYPE_ASPEED_2500_TIMER,
.parent = TYPE_ASPEED_TIMER,
.class_init = aspeed_2500_timer_class_init,
},
{
.name = TYPE_ASPEED_2600_TIMER,
.parent = TYPE_ASPEED_TIMER,
.class_init = aspeed_2600_timer_class_init,
},
{
.name = TYPE_ASPEED_2700_TIMER,
.parent = TYPE_ASPEED_TIMER,
.class_init = aspeed_2700_timer_class_init,
}
};
DEFINE_TYPES(aspeed_timer_types)
+620
View File
@@ -0,0 +1,620 @@
/*
* AVR 16-bit timer
*
* Copyright (c) 2018 University of Kent
* Author: Ed Robbins
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <http://www.gnu.org/licenses/lgpl-2.1.html>
*/
/*
* Driver for 16 bit timers on 8 bit AVR devices.
* Note:
* ATmega640/V-1280/V-1281/V-2560/V-2561/V timers 1, 3, 4 and 5 are 16 bit
*/
/*
* XXX TODO: Power Reduction Register support
* prescaler pause support
* PWM modes, GPIO, output capture pins, input compare pin
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/log.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-properties.h"
#include "hw/timer/avr_timer16.h"
#include "trace.h"
/* Register offsets */
#define T16_CRA 0x0
#define T16_CRB 0x1
#define T16_CRC 0x2
#define T16_CNTL 0x4
#define T16_CNTH 0x5
#define T16_ICRL 0x6
#define T16_ICRH 0x7
#define T16_OCRAL 0x8
#define T16_OCRAH 0x9
#define T16_OCRBL 0xa
#define T16_OCRBH 0xb
#define T16_OCRCL 0xc
#define T16_OCRCH 0xd
/* Field masks */
#define T16_CRA_WGM01 0x3
#define T16_CRA_COMC 0xc
#define T16_CRA_COMB 0x30
#define T16_CRA_COMA 0xc0
#define T16_CRA_OC_CONF \
(T16_CRA_COMA | T16_CRA_COMB | T16_CRA_COMC)
#define T16_CRB_CS 0x7
#define T16_CRB_WGM23 0x18
#define T16_CRB_ICES 0x40
#define T16_CRB_ICNC 0x80
#define T16_CRC_FOCC 0x20
#define T16_CRC_FOCB 0x40
#define T16_CRC_FOCA 0x80
/* Fields masks both TIMSK and TIFR (interrupt mask/flag registers) */
#define T16_INT_TOV 0x1 /* Timer overflow */
#define T16_INT_OCA 0x2 /* Output compare A */
#define T16_INT_OCB 0x4 /* Output compare B */
#define T16_INT_OCC 0x8 /* Output compare C */
#define T16_INT_IC 0x20 /* Input capture */
/* Clock source values */
#define T16_CLKSRC_STOPPED 0
#define T16_CLKSRC_DIV1 1
#define T16_CLKSRC_DIV8 2
#define T16_CLKSRC_DIV64 3
#define T16_CLKSRC_DIV256 4
#define T16_CLKSRC_DIV1024 5
#define T16_CLKSRC_EXT_FALLING 6
#define T16_CLKSRC_EXT_RISING 7
/* Timer mode values (not including PWM modes) */
#define T16_MODE_NORMAL 0
#define T16_MODE_CTC_OCRA 4
#define T16_MODE_CTC_ICR 12
/* Accessors */
#define CLKSRC(t16) (t16->crb & T16_CRB_CS)
#define MODE(t16) (((t16->crb & T16_CRB_WGM23) >> 1) | \
(t16->cra & T16_CRA_WGM01))
#define CNT(t16) VAL16(t16->cntl, t16->cnth)
#define OCRA(t16) VAL16(t16->ocral, t16->ocrah)
#define OCRB(t16) VAL16(t16->ocrbl, t16->ocrbh)
#define OCRC(t16) VAL16(t16->ocrcl, t16->ocrch)
#define ICR(t16) VAL16(t16->icrl, t16->icrh)
/* Helper macros */
#define VAL16(l, h) ((h << 8) | l)
#define DB_PRINT(fmt, args...) /* Nothing */
static inline int64_t avr_timer16_ns_to_ticks(AVRTimer16State *t16, int64_t t)
{
if (t16->period_ns == 0) {
return 0;
}
return t / t16->period_ns;
}
static void avr_timer16_update_cnt(AVRTimer16State *t16)
{
uint16_t cnt;
cnt = avr_timer16_ns_to_ticks(t16, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) -
t16->reset_time_ns);
t16->cntl = (uint8_t)(cnt & 0xff);
t16->cnth = (uint8_t)((cnt & 0xff00) >> 8);
}
static inline void avr_timer16_recalc_reset_time(AVRTimer16State *t16)
{
t16->reset_time_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) -
CNT(t16) * t16->period_ns;
}
static void avr_timer16_clock_reset(AVRTimer16State *t16)
{
t16->cntl = 0;
t16->cnth = 0;
t16->reset_time_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
}
static void avr_timer16_clksrc_update(AVRTimer16State *t16)
{
uint16_t divider = 0;
switch (CLKSRC(t16)) {
case T16_CLKSRC_EXT_FALLING:
case T16_CLKSRC_EXT_RISING:
qemu_log_mask(LOG_UNIMP, "%s: external clock source unsupported\n",
__func__);
break;
case T16_CLKSRC_STOPPED:
break;
case T16_CLKSRC_DIV1:
divider = 1;
break;
case T16_CLKSRC_DIV8:
divider = 8;
break;
case T16_CLKSRC_DIV64:
divider = 64;
break;
case T16_CLKSRC_DIV256:
divider = 256;
break;
case T16_CLKSRC_DIV1024:
divider = 1024;
break;
default:
break;
}
if (divider) {
t16->freq_hz = t16->cpu_freq_hz / divider;
t16->period_ns = NANOSECONDS_PER_SECOND / t16->freq_hz;
trace_avr_timer16_clksrc_update(t16->freq_hz, t16->period_ns,
(uint64_t)(1e6 / t16->freq_hz));
}
}
static void avr_timer16_set_alarm(AVRTimer16State *t16)
{
if (CLKSRC(t16) == T16_CLKSRC_EXT_FALLING ||
CLKSRC(t16) == T16_CLKSRC_EXT_RISING ||
CLKSRC(t16) == T16_CLKSRC_STOPPED) {
/* Timer is disabled or set to external clock source (unsupported) */
return;
}
uint64_t alarm_offset = 0xffff;
enum NextInterrupt next_interrupt = OVERFLOW;
switch (MODE(t16)) {
case T16_MODE_NORMAL:
/* Normal mode */
if (OCRA(t16) < alarm_offset && OCRA(t16) > CNT(t16) &&
(t16->imsk & T16_INT_OCA)) {
alarm_offset = OCRA(t16);
next_interrupt = COMPA;
}
break;
case T16_MODE_CTC_OCRA:
/* CTC mode, top = ocra */
if (OCRA(t16) < alarm_offset && OCRA(t16) > CNT(t16)) {
alarm_offset = OCRA(t16);
next_interrupt = COMPA;
}
break;
case T16_MODE_CTC_ICR:
/* CTC mode, top = icr */
if (ICR(t16) < alarm_offset && ICR(t16) > CNT(t16)) {
alarm_offset = ICR(t16);
next_interrupt = CAPT;
}
if (OCRA(t16) < alarm_offset && OCRA(t16) > CNT(t16) &&
(t16->imsk & T16_INT_OCA)) {
alarm_offset = OCRA(t16);
next_interrupt = COMPA;
}
break;
default:
qemu_log_mask(LOG_UNIMP, "%s: pwm modes are unsupported\n",
__func__);
return;
}
if (OCRB(t16) < alarm_offset && OCRB(t16) > CNT(t16) &&
(t16->imsk & T16_INT_OCB)) {
alarm_offset = OCRB(t16);
next_interrupt = COMPB;
}
if (OCRC(t16) < alarm_offset && OCRB(t16) > CNT(t16) &&
(t16->imsk & T16_INT_OCC)) {
alarm_offset = OCRB(t16);
next_interrupt = COMPC;
}
alarm_offset -= CNT(t16);
t16->next_interrupt = next_interrupt;
uint64_t alarm_ns =
t16->reset_time_ns + ((CNT(t16) + alarm_offset) * t16->period_ns);
timer_mod(t16->timer, alarm_ns);
trace_avr_timer16_next_alarm(alarm_offset * t16->period_ns);
}
static void avr_timer16_interrupt(void *opaque)
{
AVRTimer16State *t16 = opaque;
uint8_t mode = MODE(t16);
avr_timer16_update_cnt(t16);
if (CLKSRC(t16) == T16_CLKSRC_EXT_FALLING ||
CLKSRC(t16) == T16_CLKSRC_EXT_RISING ||
CLKSRC(t16) == T16_CLKSRC_STOPPED) {
/* Timer is disabled or set to external clock source (unsupported) */
return;
}
trace_avr_timer16_interrupt_count(CNT(t16));
/* Counter overflow */
if (t16->next_interrupt == OVERFLOW) {
trace_avr_timer16_interrupt_overflow("counter 0xffff");
avr_timer16_clock_reset(t16);
if (t16->imsk & T16_INT_TOV) {
t16->ifr |= T16_INT_TOV;
qemu_set_irq(t16->ovf_irq, 1);
}
}
/* Check for ocra overflow in CTC mode */
if (mode == T16_MODE_CTC_OCRA && t16->next_interrupt == COMPA) {
trace_avr_timer16_interrupt_overflow("CTC OCRA");
avr_timer16_clock_reset(t16);
}
/* Check for icr overflow in CTC mode */
if (mode == T16_MODE_CTC_ICR && t16->next_interrupt == CAPT) {
trace_avr_timer16_interrupt_overflow("CTC ICR");
avr_timer16_clock_reset(t16);
if (t16->imsk & T16_INT_IC) {
t16->ifr |= T16_INT_IC;
qemu_set_irq(t16->capt_irq, 1);
}
}
/* Check for output compare interrupts */
if (t16->imsk & T16_INT_OCA && t16->next_interrupt == COMPA) {
t16->ifr |= T16_INT_OCA;
qemu_set_irq(t16->compa_irq, 1);
}
if (t16->imsk & T16_INT_OCB && t16->next_interrupt == COMPB) {
t16->ifr |= T16_INT_OCB;
qemu_set_irq(t16->compb_irq, 1);
}
if (t16->imsk & T16_INT_OCC && t16->next_interrupt == COMPC) {
t16->ifr |= T16_INT_OCC;
qemu_set_irq(t16->compc_irq, 1);
}
avr_timer16_set_alarm(t16);
}
static void avr_timer16_reset(DeviceState *dev)
{
AVRTimer16State *t16 = AVR_TIMER16(dev);
avr_timer16_clock_reset(t16);
avr_timer16_clksrc_update(t16);
avr_timer16_set_alarm(t16);
qemu_set_irq(t16->capt_irq, 0);
qemu_set_irq(t16->compa_irq, 0);
qemu_set_irq(t16->compb_irq, 0);
qemu_set_irq(t16->compc_irq, 0);
qemu_set_irq(t16->ovf_irq, 0);
}
static uint64_t avr_timer16_read(void *opaque, hwaddr offset, unsigned size)
{
assert(size == 1);
AVRTimer16State *t16 = opaque;
uint8_t retval = 0;
switch (offset) {
case T16_CRA:
retval = t16->cra;
break;
case T16_CRB:
retval = t16->crb;
break;
case T16_CRC:
retval = t16->crc;
break;
case T16_CNTL:
avr_timer16_update_cnt(t16);
t16->rtmp = t16->cnth;
retval = t16->cntl;
break;
case T16_CNTH:
retval = t16->rtmp;
break;
case T16_ICRL:
/*
* The timer copies cnt to icr when the input capture pin changes
* state or when the analog comparator has a match. We don't
* emulate this behaviour. We do support it's use for defining a
* TOP value in T16_MODE_CTC_ICR
*/
t16->rtmp = t16->icrh;
retval = t16->icrl;
break;
case T16_ICRH:
retval = t16->rtmp;
break;
case T16_OCRAL:
retval = t16->ocral;
break;
case T16_OCRAH:
retval = t16->ocrah;
break;
case T16_OCRBL:
retval = t16->ocrbl;
break;
case T16_OCRBH:
retval = t16->ocrbh;
break;
case T16_OCRCL:
retval = t16->ocrcl;
break;
case T16_OCRCH:
retval = t16->ocrch;
break;
default:
break;
}
trace_avr_timer16_read(offset, retval);
return (uint64_t)retval;
}
static void avr_timer16_write(void *opaque, hwaddr offset,
uint64_t val64, unsigned size)
{
assert(size == 1);
AVRTimer16State *t16 = opaque;
uint8_t val8 = (uint8_t)val64;
uint8_t prev_clk_src = CLKSRC(t16);
trace_avr_timer16_write(offset, val8);
switch (offset) {
case T16_CRA:
t16->cra = val8;
if (t16->cra & T16_CRA_OC_CONF) {
qemu_log_mask(LOG_UNIMP, "%s: output compare pins unsupported\n",
__func__);
}
break;
case T16_CRB:
t16->crb = val8;
if (t16->crb & T16_CRB_ICNC) {
qemu_log_mask(LOG_UNIMP,
"%s: input capture noise canceller unsupported\n",
__func__);
}
if (t16->crb & T16_CRB_ICES) {
qemu_log_mask(LOG_UNIMP, "%s: input capture unsupported\n",
__func__);
}
if (CLKSRC(t16) != prev_clk_src) {
avr_timer16_clksrc_update(t16);
if (prev_clk_src == T16_CLKSRC_STOPPED) {
t16->reset_time_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
}
}
break;
case T16_CRC:
t16->crc = val8;
qemu_log_mask(LOG_UNIMP, "%s: output compare pins unsupported\n",
__func__);
break;
case T16_CNTL:
/*
* CNT is the 16-bit counter value, it must be read/written via
* a temporary register (rtmp) to make the read/write atomic.
*/
/* ICR also has this behaviour, and shares rtmp */
/*
* Writing CNT blocks compare matches for one clock cycle.
* Writing CNT to TOP or to an OCR value (if in use) will
* skip the relevant interrupt
*/
t16->cntl = val8;
t16->cnth = t16->rtmp;
avr_timer16_recalc_reset_time(t16);
break;
case T16_CNTH:
t16->rtmp = val8;
break;
case T16_ICRL:
/* ICR can only be written in mode T16_MODE_CTC_ICR */
if (MODE(t16) == T16_MODE_CTC_ICR) {
t16->icrl = val8;
t16->icrh = t16->rtmp;
}
break;
case T16_ICRH:
if (MODE(t16) == T16_MODE_CTC_ICR) {
t16->rtmp = val8;
}
break;
case T16_OCRAL:
/*
* OCRn cause the relevant output compare flag to be raised, and
* trigger an interrupt, when CNT is equal to the value here
*/
t16->ocral = val8;
break;
case T16_OCRAH:
t16->ocrah = val8;
break;
case T16_OCRBL:
t16->ocrbl = val8;
break;
case T16_OCRBH:
t16->ocrbh = val8;
break;
case T16_OCRCL:
t16->ocrcl = val8;
break;
case T16_OCRCH:
t16->ocrch = val8;
break;
default:
break;
}
avr_timer16_set_alarm(t16);
}
static uint64_t avr_timer16_imsk_read(void *opaque,
hwaddr offset,
unsigned size)
{
assert(size == 1);
AVRTimer16State *t16 = opaque;
trace_avr_timer16_read_imsk(offset ? 0 : t16->imsk);
if (offset != 0) {
return 0;
}
return t16->imsk;
}
static void avr_timer16_imsk_write(void *opaque, hwaddr offset,
uint64_t val64, unsigned size)
{
assert(size == 1);
AVRTimer16State *t16 = opaque;
trace_avr_timer16_write_imsk(val64);
if (offset != 0) {
return;
}
t16->imsk = (uint8_t)val64;
}
static uint64_t avr_timer16_ifr_read(void *opaque,
hwaddr offset,
unsigned size)
{
assert(size == 1);
AVRTimer16State *t16 = opaque;
trace_avr_timer16_read_ifr(offset ? 0 : t16->ifr);
if (offset != 0) {
return 0;
}
return t16->ifr;
}
static void avr_timer16_ifr_write(void *opaque, hwaddr offset,
uint64_t val64, unsigned size)
{
assert(size == 1);
AVRTimer16State *t16 = opaque;
trace_avr_timer16_write_imsk(val64);
if (offset != 0) {
return;
}
t16->ifr = (uint8_t)val64;
}
static const MemoryRegionOps avr_timer16_ops = {
.read = avr_timer16_read,
.write = avr_timer16_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.impl = {.max_access_size = 1}
};
static const MemoryRegionOps avr_timer16_imsk_ops = {
.read = avr_timer16_imsk_read,
.write = avr_timer16_imsk_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.impl = {.max_access_size = 1}
};
static const MemoryRegionOps avr_timer16_ifr_ops = {
.read = avr_timer16_ifr_read,
.write = avr_timer16_ifr_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.impl = {.max_access_size = 1}
};
static const Property avr_timer16_properties[] = {
DEFINE_PROP_UINT8("id", struct AVRTimer16State, id, 0),
DEFINE_PROP_UINT64("cpu-frequency-hz", struct AVRTimer16State,
cpu_freq_hz, 0),
};
static void avr_timer16_pr(void *opaque, int irq, int level)
{
AVRTimer16State *s = AVR_TIMER16(opaque);
s->enabled = !level;
if (!s->enabled) {
avr_timer16_reset(DEVICE(s));
}
}
static void avr_timer16_init(Object *obj)
{
AVRTimer16State *s = AVR_TIMER16(obj);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->capt_irq);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->compa_irq);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->compb_irq);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->compc_irq);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->ovf_irq);
memory_region_init_io(&s->iomem, obj, &avr_timer16_ops,
s, "avr-timer16", 0xe);
memory_region_init_io(&s->imsk_iomem, obj, &avr_timer16_imsk_ops,
s, "avr-timer16-intmask", 0x1);
memory_region_init_io(&s->ifr_iomem, obj, &avr_timer16_ifr_ops,
s, "avr-timer16-intflag", 0x1);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->imsk_iomem);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->ifr_iomem);
qdev_init_gpio_in(DEVICE(s), avr_timer16_pr, 1);
}
static void avr_timer16_realize(DeviceState *dev, Error **errp)
{
AVRTimer16State *s = AVR_TIMER16(dev);
if (s->cpu_freq_hz == 0) {
error_setg(errp, "AVR timer16: cpu-frequency-hz property must be set");
return;
}
s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, avr_timer16_interrupt, s);
s->enabled = true;
}
static void avr_timer16_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, avr_timer16_reset);
dc->realize = avr_timer16_realize;
device_class_set_props(dc, avr_timer16_properties);
}
static const TypeInfo avr_timer16_info = {
.name = TYPE_AVR_TIMER16,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(AVRTimer16State),
.instance_init = avr_timer16_init,
.class_init = avr_timer16_class_init,
};
static void avr_timer16_register_types(void)
{
type_register_static(&avr_timer16_info);
}
type_init(avr_timer16_register_types)
+178
View File
@@ -0,0 +1,178 @@
/*
* BCM2835 SYS timer emulation
*
* Copyright (C) 2019 Philippe Mathieu-Daudé
*
* 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/core/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, const 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);
+475
View File
@@ -0,0 +1,475 @@
/*
* Xilinx Zynq cadence TTC model
*
* Copyright (c) 2011 Xilinx Inc.
* Copyright (c) 2012 Peter A.G. Crosthwaite ([email protected])
* Copyright (c) 2012 PetaLogix Pty Ltd.
* Written By Haibing Ma
* M. Habib
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "hw/core/irq.h"
#include "hw/core/sysbus.h"
#include "migration/vmstate.h"
#include "qemu/module.h"
#include "qemu/timer.h"
#include "qom/object.h"
#include "hw/timer/cadence_ttc.h"
#ifdef CADENCE_TTC_ERR_DEBUG
#define DB_PRINT(...) do { \
fprintf(stderr, ": %s: ", __func__); \
fprintf(stderr, ## __VA_ARGS__); \
} while (0)
#else
#define DB_PRINT(...)
#endif
#define COUNTER_INTR_IV 0x00000001
#define COUNTER_INTR_M1 0x00000002
#define COUNTER_INTR_M2 0x00000004
#define COUNTER_INTR_M3 0x00000008
#define COUNTER_INTR_OV 0x00000010
#define COUNTER_INTR_EV 0x00000020
#define COUNTER_CTRL_DIS 0x00000001
#define COUNTER_CTRL_INT 0x00000002
#define COUNTER_CTRL_DEC 0x00000004
#define COUNTER_CTRL_MATCH 0x00000008
#define COUNTER_CTRL_RST 0x00000010
#define CLOCK_CTRL_PS_EN 0x00000001
#define CLOCK_CTRL_PS_V 0x0000001e
static void cadence_timer_update(CadenceTimerState *s)
{
qemu_set_irq(s->irq, !!(s->reg_intr & s->reg_intr_en));
}
static CadenceTimerState *cadence_timer_from_addr(void *opaque,
hwaddr offset)
{
unsigned int index;
CadenceTTCState *s = (CadenceTTCState *)opaque;
index = (offset >> 2) % 3;
return &s->timer[index];
}
static uint64_t cadence_timer_get_ns(CadenceTimerState *s, uint64_t timer_steps)
{
/* timer_steps has max value of 0x100000000. double check it
* (or overflow can happen below) */
assert(timer_steps <= 1ULL << 32);
uint64_t r = timer_steps * 1000000000ULL;
if (s->reg_clock & CLOCK_CTRL_PS_EN) {
r >>= 16 - (((s->reg_clock & CLOCK_CTRL_PS_V) >> 1) + 1);
} else {
r >>= 16;
}
r /= (uint64_t)s->freq;
return r;
}
static uint64_t cadence_timer_get_steps(CadenceTimerState *s, uint64_t ns)
{
uint64_t to_divide = 1000000000ULL;
uint64_t r = ns;
/* for very large intervals (> 8s) do some division first to stop
* overflow (costs some prescision) */
while (r >= 8ULL << 30 && to_divide > 1) {
r /= 1000;
to_divide /= 1000;
}
r <<= 16;
/* keep early-dividing as needed */
while (r >= 8ULL << 30 && to_divide > 1) {
r /= 1000;
to_divide /= 1000;
}
r *= (uint64_t)s->freq;
if (s->reg_clock & CLOCK_CTRL_PS_EN) {
r /= 1 << (((s->reg_clock & CLOCK_CTRL_PS_V) >> 1) + 1);
}
r /= to_divide;
return r;
}
/* determine if x is in between a and b, exclusive of a, inclusive of b */
static inline int64_t is_between(int64_t x, int64_t a, int64_t b)
{
if (a < b) {
return x > a && x <= b;
}
return x < a && x >= b;
}
static void cadence_timer_run(CadenceTimerState *s)
{
int i;
int64_t event_interval, next_value;
assert(s->cpu_time_valid); /* cadence_timer_sync must be called first */
if (s->reg_count & COUNTER_CTRL_DIS) {
s->cpu_time_valid = 0;
return;
}
{ /* figure out what's going to happen next (rollover or match) */
int64_t interval = (uint64_t)((s->reg_count & COUNTER_CTRL_INT) ?
(int64_t)s->reg_interval + 1 : 0x10000ULL) << 16;
next_value = (s->reg_count & COUNTER_CTRL_DEC) ? -1ULL : interval;
for (i = 0; i < 3; ++i) {
int64_t cand = (uint64_t)s->reg_match[i] << 16;
if (is_between(cand, (uint64_t)s->reg_value, next_value)) {
next_value = cand;
}
}
}
DB_PRINT("next timer event value: %09llx\n",
(unsigned long long)next_value);
event_interval = next_value - (int64_t)s->reg_value;
event_interval = (event_interval < 0) ? -event_interval : event_interval;
timer_mod(s->timer, s->cpu_time +
cadence_timer_get_ns(s, event_interval));
}
static void cadence_timer_sync(CadenceTimerState *s)
{
int i;
int64_t r, x;
int64_t interval = ((s->reg_count & COUNTER_CTRL_INT) ?
(int64_t)s->reg_interval + 1 : 0x10000ULL) << 16;
uint64_t old_time = s->cpu_time;
s->cpu_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
DB_PRINT("cpu time: %lld ns\n", (long long)old_time);
if (!s->cpu_time_valid || old_time == s->cpu_time) {
s->cpu_time_valid = 1;
return;
}
r = (int64_t)cadence_timer_get_steps(s, s->cpu_time - old_time);
x = (int64_t)s->reg_value + ((s->reg_count & COUNTER_CTRL_DEC) ? -r : r);
for (i = 0; i < 3; ++i) {
int64_t m = (int64_t)s->reg_match[i] << 16;
if (m > interval) {
continue;
}
/* check to see if match event has occurred. check m +/- interval
* to account for match events in wrap around cases */
if (is_between(m, s->reg_value, x) ||
is_between(m + interval, s->reg_value, x) ||
is_between(m - interval, s->reg_value, x)) {
s->reg_intr |= (2 << i);
}
}
if ((x < 0) || (x >= interval)) {
s->reg_intr |= (s->reg_count & COUNTER_CTRL_INT) ?
COUNTER_INTR_IV : COUNTER_INTR_OV;
}
while (x < 0) {
x += interval;
}
s->reg_value = (uint32_t)(x % interval);
cadence_timer_update(s);
}
static void cadence_timer_tick(void *opaque)
{
CadenceTimerState *s = opaque;
DB_PRINT("\n");
cadence_timer_sync(s);
cadence_timer_run(s);
}
static uint32_t cadence_ttc_read_imp(void *opaque, hwaddr offset)
{
CadenceTimerState *s = cadence_timer_from_addr(opaque, offset);
uint32_t value;
cadence_timer_sync(s);
cadence_timer_run(s);
switch (offset) {
case 0x00: /* clock control */
case 0x04:
case 0x08:
return s->reg_clock;
case 0x0c: /* counter control */
case 0x10:
case 0x14:
return s->reg_count;
case 0x18: /* counter value */
case 0x1c:
case 0x20:
return (uint16_t)(s->reg_value >> 16);
case 0x24: /* reg_interval counter */
case 0x28:
case 0x2c:
return s->reg_interval;
case 0x30: /* match 1 counter */
case 0x34:
case 0x38:
return s->reg_match[0];
case 0x3c: /* match 2 counter */
case 0x40:
case 0x44:
return s->reg_match[1];
case 0x48: /* match 3 counter */
case 0x4c:
case 0x50:
return s->reg_match[2];
case 0x54: /* interrupt register */
case 0x58:
case 0x5c:
/* cleared after read */
value = s->reg_intr;
s->reg_intr = 0;
cadence_timer_update(s);
return value;
case 0x60: /* interrupt enable */
case 0x64:
case 0x68:
return s->reg_intr_en;
case 0x6c:
case 0x70:
case 0x74:
return s->reg_event_ctrl;
case 0x78:
case 0x7c:
case 0x80:
return s->reg_event;
default:
return 0;
}
}
static uint64_t cadence_ttc_read(void *opaque, hwaddr offset,
unsigned size)
{
uint32_t ret = cadence_ttc_read_imp(opaque, offset);
DB_PRINT("addr: %08x data: %08x\n", (unsigned)offset, (unsigned)ret);
return ret;
}
static void cadence_ttc_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
CadenceTimerState *s = cadence_timer_from_addr(opaque, offset);
DB_PRINT("addr: %08x data %08x\n", (unsigned)offset, (unsigned)value);
cadence_timer_sync(s);
switch (offset) {
case 0x00: /* clock control */
case 0x04:
case 0x08:
s->reg_clock = value & 0x3F;
break;
case 0x0c: /* counter control */
case 0x10:
case 0x14:
if (value & COUNTER_CTRL_RST) {
s->reg_value = 0;
}
s->reg_count = value & 0x3f & ~COUNTER_CTRL_RST;
break;
case 0x24: /* interval register */
case 0x28:
case 0x2c:
s->reg_interval = value & 0xffff;
break;
case 0x30: /* match register */
case 0x34:
case 0x38:
s->reg_match[0] = value & 0xffff;
break;
case 0x3c: /* match register */
case 0x40:
case 0x44:
s->reg_match[1] = value & 0xffff;
break;
case 0x48: /* match register */
case 0x4c:
case 0x50:
s->reg_match[2] = value & 0xffff;
break;
case 0x54: /* interrupt register */
case 0x58:
case 0x5c:
break;
case 0x60: /* interrupt enable */
case 0x64:
case 0x68:
s->reg_intr_en = value & 0x3f;
break;
case 0x6c: /* event control */
case 0x70:
case 0x74:
s->reg_event_ctrl = value & 0x07;
break;
default:
return;
}
cadence_timer_run(s);
cadence_timer_update(s);
}
static const MemoryRegionOps cadence_ttc_ops = {
.read = cadence_ttc_read,
.write = cadence_ttc_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
static void cadence_timer_reset(CadenceTimerState *s)
{
s->reg_count = 0x21;
}
static void cadence_timer_init(uint32_t freq, CadenceTimerState *s)
{
memset(s, 0, sizeof(CadenceTimerState));
s->freq = freq;
cadence_timer_reset(s);
s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, cadence_timer_tick, s);
}
static void cadence_ttc_init(Object *obj)
{
CadenceTTCState *s = CADENCE_TTC(obj);
memory_region_init_io(&s->iomem, obj, &cadence_ttc_ops, s,
"timer", 0x1000);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
}
static void cadence_ttc_realize(DeviceState *dev, Error **errp)
{
CadenceTTCState *s = CADENCE_TTC(dev);
int i;
for (i = 0; i < 3; ++i) {
cadence_timer_init(133000000, &s->timer[i]);
sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->timer[i].irq);
}
}
static int cadence_timer_pre_save(void *opaque)
{
cadence_timer_sync((CadenceTimerState *)opaque);
return 0;
}
static int cadence_timer_post_load(void *opaque, int version_id)
{
CadenceTimerState *s = opaque;
s->cpu_time_valid = 0;
cadence_timer_sync(s);
cadence_timer_run(s);
cadence_timer_update(s);
return 0;
}
static const VMStateDescription vmstate_cadence_timer = {
.name = "cadence_timer",
.version_id = 1,
.minimum_version_id = 1,
.pre_save = cadence_timer_pre_save,
.post_load = cadence_timer_post_load,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(reg_clock, CadenceTimerState),
VMSTATE_UINT32(reg_count, CadenceTimerState),
VMSTATE_UINT32(reg_value, CadenceTimerState),
VMSTATE_UINT16(reg_interval, CadenceTimerState),
VMSTATE_UINT16_ARRAY(reg_match, CadenceTimerState, 3),
VMSTATE_UINT32(reg_intr, CadenceTimerState),
VMSTATE_UINT32(reg_intr_en, CadenceTimerState),
VMSTATE_UINT32(reg_event_ctrl, CadenceTimerState),
VMSTATE_UINT32(reg_event, CadenceTimerState),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription vmstate_cadence_ttc = {
.name = "cadence_TTC",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_STRUCT_ARRAY(timer, CadenceTTCState, 3, 0,
vmstate_cadence_timer,
CadenceTimerState),
VMSTATE_END_OF_LIST()
}
};
static void cadence_ttc_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->vmsd = &vmstate_cadence_ttc;
dc->realize = cadence_ttc_realize;
}
static const TypeInfo cadence_ttc_info = {
.name = TYPE_CADENCE_TTC,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(CadenceTTCState),
.instance_init = cadence_ttc_init,
.class_init = cadence_ttc_class_init,
};
static void cadence_ttc_register_types(void)
{
type_register_static(&cadence_ttc_info);
}
type_init(cadence_ttc_register_types)
+559
View File
@@ -0,0 +1,559 @@
/*
* ARM CMSDK APB dual-timer emulation
*
* Copyright (c) 2018 Linaro Limited
* Written by Peter Maydell
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 or
* (at your option) any later version.
*/
/*
* This is a model of the "APB dual-input timer" which is part of the Cortex-M
* System Design Kit (CMSDK) and documented in the Cortex-M System
* Design Kit Technical Reference Manual (ARM DDI0479C):
* https://developer.arm.com/products/system-design/system-design-kits/cortex-m-system-design-kit
*/
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "trace.h"
#include "qapi/error.h"
#include "qemu/module.h"
#include "hw/core/sysbus.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/registerfields.h"
#include "hw/core/qdev-clock.h"
#include "hw/timer/cmsdk-apb-dualtimer.h"
#include "migration/vmstate.h"
REG32(TIMER1LOAD, 0x0)
REG32(TIMER1VALUE, 0x4)
REG32(TIMER1CONTROL, 0x8)
FIELD(CONTROL, ONESHOT, 0, 1)
FIELD(CONTROL, SIZE, 1, 1)
FIELD(CONTROL, PRESCALE, 2, 2)
FIELD(CONTROL, INTEN, 5, 1)
FIELD(CONTROL, MODE, 6, 1)
FIELD(CONTROL, ENABLE, 7, 1)
#define R_CONTROL_VALID_MASK (R_CONTROL_ONESHOT_MASK | R_CONTROL_SIZE_MASK | \
R_CONTROL_PRESCALE_MASK | R_CONTROL_INTEN_MASK | \
R_CONTROL_MODE_MASK | R_CONTROL_ENABLE_MASK)
REG32(TIMER1INTCLR, 0xc)
REG32(TIMER1RIS, 0x10)
REG32(TIMER1MIS, 0x14)
REG32(TIMER1BGLOAD, 0x18)
REG32(TIMER2LOAD, 0x20)
REG32(TIMER2VALUE, 0x24)
REG32(TIMER2CONTROL, 0x28)
REG32(TIMER2INTCLR, 0x2c)
REG32(TIMER2RIS, 0x30)
REG32(TIMER2MIS, 0x34)
REG32(TIMER2BGLOAD, 0x38)
REG32(TIMERITCR, 0xf00)
FIELD(TIMERITCR, ENABLE, 0, 1)
#define R_TIMERITCR_VALID_MASK R_TIMERITCR_ENABLE_MASK
REG32(TIMERITOP, 0xf04)
FIELD(TIMERITOP, TIMINT1, 0, 1)
FIELD(TIMERITOP, TIMINT2, 1, 1)
#define R_TIMERITOP_VALID_MASK (R_TIMERITOP_TIMINT1_MASK | \
R_TIMERITOP_TIMINT2_MASK)
REG32(PID4, 0xfd0)
REG32(PID5, 0xfd4)
REG32(PID6, 0xfd8)
REG32(PID7, 0xfdc)
REG32(PID0, 0xfe0)
REG32(PID1, 0xfe4)
REG32(PID2, 0xfe8)
REG32(PID3, 0xfec)
REG32(CID0, 0xff0)
REG32(CID1, 0xff4)
REG32(CID2, 0xff8)
REG32(CID3, 0xffc)
/* PID/CID values */
static const int timer_id[] = {
0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
0x23, 0xb8, 0x1b, 0x00, /* PID0..PID3 */
0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
};
static bool cmsdk_dualtimermod_intstatus(CMSDKAPBDualTimerModule *m)
{
/* Return masked interrupt status for the timer module */
return m->intstatus && (m->control & R_CONTROL_INTEN_MASK);
}
static void cmsdk_apb_dualtimer_update(CMSDKAPBDualTimer *s)
{
bool timint1, timint2, timintc;
if (s->timeritcr) {
/* Integration test mode: outputs driven directly from TIMERITOP bits */
timint1 = s->timeritop & R_TIMERITOP_TIMINT1_MASK;
timint2 = s->timeritop & R_TIMERITOP_TIMINT2_MASK;
} else {
timint1 = cmsdk_dualtimermod_intstatus(&s->timermod[0]);
timint2 = cmsdk_dualtimermod_intstatus(&s->timermod[1]);
}
timintc = timint1 || timint2;
qemu_set_irq(s->timermod[0].timerint, timint1);
qemu_set_irq(s->timermod[1].timerint, timint2);
qemu_set_irq(s->timerintc, timintc);
}
static int cmsdk_dualtimermod_divisor(CMSDKAPBDualTimerModule *m)
{
/* Return the divisor set by the current CONTROL.PRESCALE value */
switch (FIELD_EX32(m->control, CONTROL, PRESCALE)) {
case 0:
return 1;
case 1:
return 16;
case 2:
case 3: /* UNDEFINED, we treat like 2 (and complained when it was set) */
return 256;
default:
g_assert_not_reached();
}
}
static void cmsdk_dualtimermod_write_control(CMSDKAPBDualTimerModule *m,
uint32_t newctrl)
{
/* Handle a write to the CONTROL register */
uint32_t changed;
ptimer_transaction_begin(m->timer);
newctrl &= R_CONTROL_VALID_MASK;
changed = m->control ^ newctrl;
if (changed & ~newctrl & R_CONTROL_ENABLE_MASK) {
/* ENABLE cleared, stop timer before any further changes */
ptimer_stop(m->timer);
}
if (changed & R_CONTROL_PRESCALE_MASK) {
int divisor;
switch (FIELD_EX32(newctrl, CONTROL, PRESCALE)) {
case 0:
divisor = 1;
break;
case 1:
divisor = 16;
break;
case 2:
divisor = 256;
break;
case 3:
/* UNDEFINED; complain, and arbitrarily treat like 2 */
qemu_log_mask(LOG_GUEST_ERROR,
"CMSDK APB dual-timer: CONTROL.PRESCALE==0b11"
" is undefined behaviour\n");
divisor = 256;
break;
default:
g_assert_not_reached();
}
ptimer_set_period_from_clock(m->timer, m->parent->timclk, divisor);
}
if (changed & R_CONTROL_MODE_MASK) {
uint32_t load;
if (newctrl & R_CONTROL_MODE_MASK) {
/* Periodic: the limit is the LOAD register value */
load = m->load;
} else {
/* Free-running: counter wraps around */
load = ptimer_get_limit(m->timer);
if (!(m->control & R_CONTROL_SIZE_MASK)) {
load = deposit32(m->load, 0, 16, load);
}
m->load = load;
load = 0xffffffff;
}
if (!(m->control & R_CONTROL_SIZE_MASK)) {
load &= 0xffff;
}
ptimer_set_limit(m->timer, load, 0);
}
if (changed & R_CONTROL_SIZE_MASK) {
/* Timer switched between 16 and 32 bit count */
uint32_t value, load;
value = ptimer_get_count(m->timer);
load = ptimer_get_limit(m->timer);
if (newctrl & R_CONTROL_SIZE_MASK) {
/* 16 -> 32, top half of VALUE is in struct field */
value = deposit32(m->value, 0, 16, value);
} else {
/* 32 -> 16: save top half to struct field and truncate */
m->value = value;
value &= 0xffff;
}
if (newctrl & R_CONTROL_MODE_MASK) {
/* Periodic, timer limit has LOAD value */
if (newctrl & R_CONTROL_SIZE_MASK) {
load = deposit32(m->load, 0, 16, load);
} else {
m->load = load;
load &= 0xffff;
}
} else {
/* Free-running, timer limit is set to give wraparound */
if (newctrl & R_CONTROL_SIZE_MASK) {
load = 0xffffffff;
} else {
load = 0xffff;
}
}
ptimer_set_count(m->timer, value);
ptimer_set_limit(m->timer, load, 0);
}
if (newctrl & R_CONTROL_ENABLE_MASK) {
/*
* ENABLE is set; start the timer after all other changes.
* We start it even if the ENABLE bit didn't actually change,
* in case the timer was an expired one-shot timer that has
* now been changed into a free-running or periodic timer.
*/
ptimer_run(m->timer, !!(newctrl & R_CONTROL_ONESHOT_MASK));
}
m->control = newctrl;
ptimer_transaction_commit(m->timer);
}
static uint64_t cmsdk_apb_dualtimer_read(void *opaque, hwaddr offset,
unsigned size)
{
CMSDKAPBDualTimer *s = CMSDK_APB_DUALTIMER(opaque);
uint64_t r;
if (offset >= A_TIMERITCR) {
switch (offset) {
case A_TIMERITCR:
r = s->timeritcr;
break;
case A_PID4 ... A_CID3:
r = timer_id[(offset - A_PID4) / 4];
break;
default:
bad_offset:
qemu_log_mask(LOG_GUEST_ERROR,
"CMSDK APB dual-timer read: bad offset %x\n",
(int) offset);
r = 0;
break;
}
} else {
int timer = offset >> 5;
CMSDKAPBDualTimerModule *m;
if (timer >= ARRAY_SIZE(s->timermod)) {
goto bad_offset;
}
m = &s->timermod[timer];
switch (offset & 0x1F) {
case A_TIMER1LOAD:
case A_TIMER1BGLOAD:
if (m->control & R_CONTROL_MODE_MASK) {
/*
* Periodic: the ptimer limit is the LOAD register value, (or
* just the low 16 bits of it if the timer is in 16-bit mode)
*/
r = ptimer_get_limit(m->timer);
if (!(m->control & R_CONTROL_SIZE_MASK)) {
r = deposit32(m->load, 0, 16, r);
}
} else {
/* Free-running: LOAD register value is just in m->load */
r = m->load;
}
break;
case A_TIMER1VALUE:
r = ptimer_get_count(m->timer);
if (!(m->control & R_CONTROL_SIZE_MASK)) {
r = deposit32(m->value, 0, 16, r);
}
break;
case A_TIMER1CONTROL:
r = m->control;
break;
case A_TIMER1RIS:
r = m->intstatus;
break;
case A_TIMER1MIS:
r = cmsdk_dualtimermod_intstatus(m);
break;
default:
goto bad_offset;
}
}
trace_cmsdk_apb_dualtimer_read(offset, r, size);
return r;
}
static void cmsdk_apb_dualtimer_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
CMSDKAPBDualTimer *s = CMSDK_APB_DUALTIMER(opaque);
trace_cmsdk_apb_dualtimer_write(offset, value, size);
if (offset >= A_TIMERITCR) {
switch (offset) {
case A_TIMERITCR:
s->timeritcr = value & R_TIMERITCR_VALID_MASK;
cmsdk_apb_dualtimer_update(s);
break;
case A_TIMERITOP:
s->timeritop = value & R_TIMERITOP_VALID_MASK;
cmsdk_apb_dualtimer_update(s);
break;
default:
bad_offset:
qemu_log_mask(LOG_GUEST_ERROR,
"CMSDK APB dual-timer write: bad offset %x\n",
(int) offset);
break;
}
} else {
int timer = offset >> 5;
CMSDKAPBDualTimerModule *m;
if (timer >= ARRAY_SIZE(s->timermod)) {
goto bad_offset;
}
m = &s->timermod[timer];
switch (offset & 0x1F) {
case A_TIMER1LOAD:
/* Set the limit, and immediately reload the count from it */
m->load = value;
m->value = value;
if (!(m->control & R_CONTROL_SIZE_MASK)) {
value &= 0xffff;
}
ptimer_transaction_begin(m->timer);
if (!(m->control & R_CONTROL_MODE_MASK)) {
/*
* In free-running mode this won't set the limit but will
* still change the current count value.
*/
ptimer_set_count(m->timer, value);
} else {
if (!value) {
ptimer_stop(m->timer);
}
ptimer_set_limit(m->timer, value, 1);
if (value && (m->control & R_CONTROL_ENABLE_MASK)) {
/* Force possibly-expired oneshot timer to restart */
ptimer_run(m->timer, 1);
}
}
ptimer_transaction_commit(m->timer);
break;
case A_TIMER1BGLOAD:
/* Set the limit, but not the current count */
m->load = value;
if (!(m->control & R_CONTROL_MODE_MASK)) {
/* In free-running mode there is no limit */
break;
}
if (!(m->control & R_CONTROL_SIZE_MASK)) {
value &= 0xffff;
}
ptimer_transaction_begin(m->timer);
ptimer_set_limit(m->timer, value, 0);
ptimer_transaction_commit(m->timer);
break;
case A_TIMER1CONTROL:
cmsdk_dualtimermod_write_control(m, value);
cmsdk_apb_dualtimer_update(s);
break;
case A_TIMER1INTCLR:
m->intstatus = 0;
cmsdk_apb_dualtimer_update(s);
break;
default:
goto bad_offset;
}
}
}
static const MemoryRegionOps cmsdk_apb_dualtimer_ops = {
.read = cmsdk_apb_dualtimer_read,
.write = cmsdk_apb_dualtimer_write,
.endianness = DEVICE_LITTLE_ENDIAN,
/* byte/halfword accesses are just zero-padded on reads and writes */
.impl.min_access_size = 4,
.impl.max_access_size = 4,
.valid.min_access_size = 1,
.valid.max_access_size = 4,
};
static void cmsdk_dualtimermod_tick(void *opaque)
{
CMSDKAPBDualTimerModule *m = opaque;
m->intstatus = 1;
cmsdk_apb_dualtimer_update(m->parent);
}
static void cmsdk_dualtimermod_reset(CMSDKAPBDualTimerModule *m)
{
m->control = R_CONTROL_INTEN_MASK;
m->intstatus = 0;
m->load = 0;
m->value = 0xffffffff;
ptimer_transaction_begin(m->timer);
ptimer_stop(m->timer);
/*
* We start in free-running mode, with VALUE at 0xffffffff, and
* in 16-bit counter mode. This means that the ptimer count and
* limit must both be set to 0xffff, so we wrap at 16 bits.
*/
ptimer_set_limit(m->timer, 0xffff, 1);
ptimer_set_period_from_clock(m->timer, m->parent->timclk,
cmsdk_dualtimermod_divisor(m));
ptimer_transaction_commit(m->timer);
}
static void cmsdk_apb_dualtimer_reset(DeviceState *dev)
{
CMSDKAPBDualTimer *s = CMSDK_APB_DUALTIMER(dev);
int i;
trace_cmsdk_apb_dualtimer_reset();
for (i = 0; i < ARRAY_SIZE(s->timermod); i++) {
cmsdk_dualtimermod_reset(&s->timermod[i]);
}
s->timeritcr = 0;
s->timeritop = 0;
}
static void cmsdk_apb_dualtimer_clk_update(void *opaque, ClockEvent event)
{
CMSDKAPBDualTimer *s = CMSDK_APB_DUALTIMER(opaque);
int i;
for (i = 0; i < ARRAY_SIZE(s->timermod); i++) {
CMSDKAPBDualTimerModule *m = &s->timermod[i];
ptimer_transaction_begin(m->timer);
ptimer_set_period_from_clock(m->timer, m->parent->timclk,
cmsdk_dualtimermod_divisor(m));
ptimer_transaction_commit(m->timer);
}
}
static void cmsdk_apb_dualtimer_init(Object *obj)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
CMSDKAPBDualTimer *s = CMSDK_APB_DUALTIMER(obj);
int i;
memory_region_init_io(&s->iomem, obj, &cmsdk_apb_dualtimer_ops,
s, "cmsdk-apb-dualtimer", 0x1000);
sysbus_init_mmio(sbd, &s->iomem);
sysbus_init_irq(sbd, &s->timerintc);
for (i = 0; i < ARRAY_SIZE(s->timermod); i++) {
sysbus_init_irq(sbd, &s->timermod[i].timerint);
}
s->timclk = qdev_init_clock_in(DEVICE(s), "TIMCLK",
cmsdk_apb_dualtimer_clk_update, s,
ClockUpdate);
}
static void cmsdk_apb_dualtimer_realize(DeviceState *dev, Error **errp)
{
CMSDKAPBDualTimer *s = CMSDK_APB_DUALTIMER(dev);
int i;
if (!clock_has_source(s->timclk)) {
error_setg(errp, "CMSDK APB dualtimer: TIMCLK clock must be connected");
return;
}
for (i = 0; i < ARRAY_SIZE(s->timermod); i++) {
CMSDKAPBDualTimerModule *m = &s->timermod[i];
m->parent = s;
m->timer = ptimer_init(cmsdk_dualtimermod_tick, m,
PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD |
PTIMER_POLICY_TRIGGER_ONLY_ON_DECREMENT |
PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
}
}
static const VMStateDescription cmsdk_dualtimermod_vmstate = {
.name = "cmsdk-apb-dualtimer-module",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_PTIMER(timer, CMSDKAPBDualTimerModule),
VMSTATE_UINT32(load, CMSDKAPBDualTimerModule),
VMSTATE_UINT32(value, CMSDKAPBDualTimerModule),
VMSTATE_UINT32(control, CMSDKAPBDualTimerModule),
VMSTATE_UINT32(intstatus, CMSDKAPBDualTimerModule),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription cmsdk_apb_dualtimer_vmstate = {
.name = "cmsdk-apb-dualtimer",
.version_id = 2,
.minimum_version_id = 2,
.fields = (const VMStateField[]) {
VMSTATE_CLOCK(timclk, CMSDKAPBDualTimer),
VMSTATE_STRUCT_ARRAY(timermod, CMSDKAPBDualTimer,
CMSDK_APB_DUALTIMER_NUM_MODULES,
1, cmsdk_dualtimermod_vmstate,
CMSDKAPBDualTimerModule),
VMSTATE_UINT32(timeritcr, CMSDKAPBDualTimer),
VMSTATE_UINT32(timeritop, CMSDKAPBDualTimer),
VMSTATE_END_OF_LIST()
}
};
static void cmsdk_apb_dualtimer_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = cmsdk_apb_dualtimer_realize;
dc->vmsd = &cmsdk_apb_dualtimer_vmstate;
device_class_set_legacy_reset(dc, cmsdk_apb_dualtimer_reset);
}
static const TypeInfo cmsdk_apb_dualtimer_info = {
.name = TYPE_CMSDK_APB_DUALTIMER,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(CMSDKAPBDualTimer),
.instance_init = cmsdk_apb_dualtimer_init,
.class_init = cmsdk_apb_dualtimer_class_init,
};
static void cmsdk_apb_dualtimer_register_types(void)
{
type_register_static(&cmsdk_apb_dualtimer_info);
}
type_init(cmsdk_apb_dualtimer_register_types);
+286
View File
@@ -0,0 +1,286 @@
/*
* ARM CMSDK APB timer emulation
*
* Copyright (c) 2017 Linaro Limited
* Written by Peter Maydell
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 or
* (at your option) any later version.
*/
/* This is a model of the "APB timer" which is part of the Cortex-M
* System Design Kit (CMSDK) and documented in the Cortex-M System
* Design Kit Technical Reference Manual (ARM DDI0479C):
* https://developer.arm.com/products/system-design/system-design-kits/cortex-m-system-design-kit
*
* The hardware has an EXTIN input wire, which can be configured
* by the guest to act either as a 'timer enable' (timer does not run
* when EXTIN is low), or as a 'timer clock' (timer runs at frequency
* of EXTIN clock, not PCLK frequency). We don't model this.
*
* The documentation is not very clear about the exact behaviour;
* we choose to implement that the interrupt is triggered when
* the counter goes from 1 to 0, that the counter then holds at 0
* for one clock cycle before reloading from the RELOAD register,
* and that if the RELOAD register is 0 this does not cause an
* interrupt (as there is no further 1->0 transition).
*/
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "qapi/error.h"
#include "trace.h"
#include "hw/core/sysbus.h"
#include "hw/core/irq.h"
#include "hw/core/registerfields.h"
#include "hw/core/qdev-clock.h"
#include "hw/timer/cmsdk-apb-timer.h"
#include "migration/vmstate.h"
REG32(CTRL, 0)
FIELD(CTRL, EN, 0, 1)
FIELD(CTRL, SELEXTEN, 1, 1)
FIELD(CTRL, SELEXTCLK, 2, 1)
FIELD(CTRL, IRQEN, 3, 1)
REG32(VALUE, 4)
REG32(RELOAD, 8)
REG32(INTSTATUS, 0xc)
FIELD(INTSTATUS, IRQ, 0, 1)
REG32(PID4, 0xFD0)
REG32(PID5, 0xFD4)
REG32(PID6, 0xFD8)
REG32(PID7, 0xFDC)
REG32(PID0, 0xFE0)
REG32(PID1, 0xFE4)
REG32(PID2, 0xFE8)
REG32(PID3, 0xFEC)
REG32(CID0, 0xFF0)
REG32(CID1, 0xFF4)
REG32(CID2, 0xFF8)
REG32(CID3, 0xFFC)
/* PID/CID values */
static const int timer_id[] = {
0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
0x22, 0xb8, 0x1b, 0x00, /* PID0..PID3 */
0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
};
static void cmsdk_apb_timer_update(CMSDKAPBTimer *s)
{
qemu_set_irq(s->timerint, !!(s->intstatus & R_INTSTATUS_IRQ_MASK));
}
static uint64_t cmsdk_apb_timer_read(void *opaque, hwaddr offset, unsigned size)
{
CMSDKAPBTimer *s = CMSDK_APB_TIMER(opaque);
uint64_t r;
switch (offset) {
case A_CTRL:
r = s->ctrl;
break;
case A_VALUE:
r = ptimer_get_count(s->timer);
break;
case A_RELOAD:
r = ptimer_get_limit(s->timer);
break;
case A_INTSTATUS:
r = s->intstatus;
break;
case A_PID4 ... A_CID3:
r = timer_id[(offset - A_PID4) / 4];
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"CMSDK APB timer read: bad offset %x\n", (int) offset);
r = 0;
break;
}
trace_cmsdk_apb_timer_read(offset, r, size);
return r;
}
static void cmsdk_apb_timer_write(void *opaque, hwaddr offset, uint64_t value,
unsigned size)
{
CMSDKAPBTimer *s = CMSDK_APB_TIMER(opaque);
trace_cmsdk_apb_timer_write(offset, value, size);
switch (offset) {
case A_CTRL:
if (value & 6) {
/* Bits [1] and [2] enable using EXTIN as either clock or
* an enable line. We don't model this.
*/
qemu_log_mask(LOG_UNIMP,
"CMSDK APB timer: EXTIN input not supported\n");
}
s->ctrl = value & 0xf;
ptimer_transaction_begin(s->timer);
if (s->ctrl & R_CTRL_EN_MASK) {
ptimer_run(s->timer, ptimer_get_limit(s->timer) == 0);
} else {
ptimer_stop(s->timer);
}
ptimer_transaction_commit(s->timer);
break;
case A_RELOAD:
/* Writing to reload also sets the current timer value */
ptimer_transaction_begin(s->timer);
if (!value) {
ptimer_stop(s->timer);
}
ptimer_set_limit(s->timer, value, 1);
if (value && (s->ctrl & R_CTRL_EN_MASK)) {
/*
* Make sure timer is running (it might have stopped if this
* was an expired one-shot timer)
*/
ptimer_run(s->timer, 0);
}
ptimer_transaction_commit(s->timer);
break;
case A_VALUE:
ptimer_transaction_begin(s->timer);
if (!value && !ptimer_get_limit(s->timer)) {
ptimer_stop(s->timer);
}
ptimer_set_count(s->timer, value);
if (value && (s->ctrl & R_CTRL_EN_MASK)) {
ptimer_run(s->timer, ptimer_get_limit(s->timer) == 0);
}
ptimer_transaction_commit(s->timer);
break;
case A_INTSTATUS:
/* Just one bit, which is W1C. */
value &= 1;
s->intstatus &= ~value;
cmsdk_apb_timer_update(s);
break;
case A_PID4 ... A_CID3:
qemu_log_mask(LOG_GUEST_ERROR,
"CMSDK APB timer write: write to RO offset 0x%x\n",
(int)offset);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"CMSDK APB timer write: bad offset 0x%x\n", (int) offset);
break;
}
}
static const MemoryRegionOps cmsdk_apb_timer_ops = {
.read = cmsdk_apb_timer_read,
.write = cmsdk_apb_timer_write,
.endianness = DEVICE_LITTLE_ENDIAN,
};
static void cmsdk_apb_timer_tick(void *opaque)
{
CMSDKAPBTimer *s = CMSDK_APB_TIMER(opaque);
if (s->ctrl & R_CTRL_IRQEN_MASK) {
s->intstatus |= R_INTSTATUS_IRQ_MASK;
cmsdk_apb_timer_update(s);
}
}
static void cmsdk_apb_timer_reset(DeviceState *dev)
{
CMSDKAPBTimer *s = CMSDK_APB_TIMER(dev);
trace_cmsdk_apb_timer_reset();
s->ctrl = 0;
s->intstatus = 0;
ptimer_transaction_begin(s->timer);
ptimer_stop(s->timer);
/* Set the limit and the count */
ptimer_set_limit(s->timer, 0, 1);
ptimer_transaction_commit(s->timer);
}
static void cmsdk_apb_timer_clk_update(void *opaque, ClockEvent event)
{
CMSDKAPBTimer *s = CMSDK_APB_TIMER(opaque);
ptimer_transaction_begin(s->timer);
ptimer_set_period_from_clock(s->timer, s->pclk, 1);
ptimer_transaction_commit(s->timer);
}
static void cmsdk_apb_timer_init(Object *obj)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
CMSDKAPBTimer *s = CMSDK_APB_TIMER(obj);
memory_region_init_io(&s->iomem, obj, &cmsdk_apb_timer_ops,
s, "cmsdk-apb-timer", 0x1000);
sysbus_init_mmio(sbd, &s->iomem);
sysbus_init_irq(sbd, &s->timerint);
s->pclk = qdev_init_clock_in(DEVICE(s), "pclk",
cmsdk_apb_timer_clk_update, s, ClockUpdate);
}
static void cmsdk_apb_timer_realize(DeviceState *dev, Error **errp)
{
CMSDKAPBTimer *s = CMSDK_APB_TIMER(dev);
if (!clock_has_source(s->pclk)) {
error_setg(errp, "CMSDK APB timer: pclk clock must be connected");
return;
}
s->timer = ptimer_init(cmsdk_apb_timer_tick, s,
PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD |
PTIMER_POLICY_TRIGGER_ONLY_ON_DECREMENT |
PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
ptimer_transaction_begin(s->timer);
ptimer_set_period_from_clock(s->timer, s->pclk, 1);
ptimer_transaction_commit(s->timer);
}
static const VMStateDescription cmsdk_apb_timer_vmstate = {
.name = "cmsdk-apb-timer",
.version_id = 2,
.minimum_version_id = 2,
.fields = (const VMStateField[]) {
VMSTATE_PTIMER(timer, CMSDKAPBTimer),
VMSTATE_CLOCK(pclk, CMSDKAPBTimer),
VMSTATE_UINT32(ctrl, CMSDKAPBTimer),
VMSTATE_UINT32(value, CMSDKAPBTimer),
VMSTATE_UINT32(reload, CMSDKAPBTimer),
VMSTATE_UINT32(intstatus, CMSDKAPBTimer),
VMSTATE_END_OF_LIST()
}
};
static void cmsdk_apb_timer_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = cmsdk_apb_timer_realize;
dc->vmsd = &cmsdk_apb_timer_vmstate;
device_class_set_legacy_reset(dc, cmsdk_apb_timer_reset);
}
static const TypeInfo cmsdk_apb_timer_info = {
.name = TYPE_CMSDK_APB_TIMER,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(CMSDKAPBTimer),
.instance_init = cmsdk_apb_timer_init,
.class_init = cmsdk_apb_timer_class_init,
};
static void cmsdk_apb_timer_register_types(void)
{
type_register_static(&cmsdk_apb_timer_info);
}
type_init(cmsdk_apb_timer_register_types);
+186
View File
@@ -0,0 +1,186 @@
/*
* QEMU model of the Canon DIGIC timer block.
*
* Copyright (C) 2013 Antony Pavlov <[email protected]>
*
* This model is based on reverse engineering efforts
* made by CHDK (http://chdk.wikia.com) and
* Magic Lantern (http://www.magiclantern.fm) projects
* contributors.
*
* See "Timer/Clock Module" docs here:
* http://magiclantern.wikia.com/wiki/Register_Map
*
* The QEMU model of the OSTimer in PKUnity SoC by Guan Xuetao
* is used as a template.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include "qemu/osdep.h"
#include "hw/core/sysbus.h"
#include "hw/core/ptimer.h"
#include "qemu/module.h"
#include "qemu/log.h"
#include "hw/timer/digic-timer.h"
#include "migration/vmstate.h"
static const VMStateDescription vmstate_digic_timer = {
.name = "digic.timer",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_PTIMER(ptimer, DigicTimerState),
VMSTATE_UINT32(control, DigicTimerState),
VMSTATE_UINT32(relvalue, DigicTimerState),
VMSTATE_END_OF_LIST()
}
};
static void digic_timer_reset(DeviceState *dev)
{
DigicTimerState *s = DIGIC_TIMER(dev);
ptimer_transaction_begin(s->ptimer);
ptimer_stop(s->ptimer);
ptimer_transaction_commit(s->ptimer);
s->control = 0;
s->relvalue = 0;
}
static uint64_t digic_timer_read(void *opaque, hwaddr offset, unsigned size)
{
DigicTimerState *s = opaque;
uint64_t ret = 0;
switch (offset) {
case DIGIC_TIMER_CONTROL:
ret = s->control;
break;
case DIGIC_TIMER_RELVALUE:
ret = s->relvalue;
break;
case DIGIC_TIMER_VALUE:
ret = ptimer_get_count(s->ptimer) & 0xffff;
break;
default:
qemu_log_mask(LOG_UNIMP,
"digic-timer: read access to unknown register 0x"
HWADDR_FMT_plx "\n", offset);
}
return ret;
}
static void digic_timer_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
DigicTimerState *s = opaque;
switch (offset) {
case DIGIC_TIMER_CONTROL:
if (value & DIGIC_TIMER_CONTROL_RST) {
digic_timer_reset((DeviceState *)s);
break;
}
ptimer_transaction_begin(s->ptimer);
if (value & DIGIC_TIMER_CONTROL_EN) {
ptimer_run(s->ptimer, 0);
}
s->control = (uint32_t)value;
ptimer_transaction_commit(s->ptimer);
break;
case DIGIC_TIMER_RELVALUE:
s->relvalue = extract32(value, 0, 16);
ptimer_transaction_begin(s->ptimer);
ptimer_set_limit(s->ptimer, s->relvalue, 1);
ptimer_transaction_commit(s->ptimer);
break;
case DIGIC_TIMER_VALUE:
break;
default:
qemu_log_mask(LOG_UNIMP,
"digic-timer: read access to unknown register 0x"
HWADDR_FMT_plx "\n", offset);
}
}
static const MemoryRegionOps digic_timer_ops = {
.read = digic_timer_read,
.write = digic_timer_write,
.impl = {
.min_access_size = 4,
.max_access_size = 4,
},
.endianness = DEVICE_NATIVE_ENDIAN,
};
static void digic_timer_tick(void *opaque)
{
/* Nothing to do on timer rollover */
}
static void digic_timer_init(Object *obj)
{
DigicTimerState *s = DIGIC_TIMER(obj);
s->ptimer = ptimer_init(digic_timer_tick, NULL, PTIMER_POLICY_LEGACY);
/*
* FIXME: there is no documentation on Digic timer
* frequency setup so let it always run at 1 MHz
*/
ptimer_transaction_begin(s->ptimer);
ptimer_set_freq(s->ptimer, 1 * 1000 * 1000);
ptimer_transaction_commit(s->ptimer);
memory_region_init_io(&s->iomem, OBJECT(s), &digic_timer_ops, s,
TYPE_DIGIC_TIMER, 0x100);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
}
static void digic_timer_finalize(Object *obj)
{
DigicTimerState *s = DIGIC_TIMER(obj);
ptimer_free(s->ptimer);
}
static void digic_timer_class_init(ObjectClass *klass, const void *class_data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, digic_timer_reset);
dc->vmsd = &vmstate_digic_timer;
}
static const TypeInfo digic_timer_info = {
.name = TYPE_DIGIC_TIMER,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(DigicTimerState),
.instance_init = digic_timer_init,
.instance_finalize = digic_timer_finalize,
.class_init = digic_timer_class_init,
};
static void digic_timer_register_type(void)
{
type_register_static(&digic_timer_info);
}
type_init(digic_timer_register_type)
File diff suppressed because it is too large Load Diff
+445
View File
@@ -0,0 +1,445 @@
/*
* Samsung exynos4210 Pulse Width Modulation Timer
*
* Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
* All rights reserved.
*
* Evgeny Voevodin <[email protected]>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "hw/core/sysbus.h"
#include "migration/vmstate.h"
#include "qemu/timer.h"
#include "qemu/module.h"
#include "hw/core/ptimer.h"
#include "hw/arm/exynos4210.h"
#include "hw/core/irq.h"
#include "qom/object.h"
//#define DEBUG_PWM
#ifdef DEBUG_PWM
#define DPRINTF(fmt, ...) \
do { fprintf(stdout, "PWM: [%24s:%5d] " fmt, __func__, __LINE__, \
## __VA_ARGS__); } while (0)
#else
#define DPRINTF(fmt, ...) do {} while (0)
#endif
#define EXYNOS4210_PWM_TIMERS_NUM 5
#define EXYNOS4210_PWM_REG_MEM_SIZE 0x50
#define TCFG0 0x0000
#define TCFG1 0x0004
#define TCON 0x0008
#define TCNTB0 0x000C
#define TCMPB0 0x0010
#define TCNTO0 0x0014
#define TCNTB1 0x0018
#define TCMPB1 0x001C
#define TCNTO1 0x0020
#define TCNTB2 0x0024
#define TCMPB2 0x0028
#define TCNTO2 0x002C
#define TCNTB3 0x0030
#define TCMPB3 0x0034
#define TCNTO3 0x0038
#define TCNTB4 0x003C
#define TCNTO4 0x0040
#define TINT_CSTAT 0x0044
#define TCNTB(x) (0xC * (x))
#define TCMPB(x) (0xC * (x) + 1)
#define TCNTO(x) (0xC * (x) + 2)
#define GET_PRESCALER(reg, x) (((reg) & (0xFF << (8 * (x)))) >> 8 * (x))
#define GET_DIVIDER(reg, x) (1 << (((reg) & (0xF << (4 * (x)))) >> (4 * (x))))
/*
* Attention! Timer4 doesn't have OUTPUT_INVERTER,
* so Auto Reload bit is not accessible by macros!
*/
#define TCON_TIMER_BASE(x) (((x) ? 1 : 0) * 4 + 4 * (x))
#define TCON_TIMER_START(x) (1 << (TCON_TIMER_BASE(x) + 0))
#define TCON_TIMER_MANUAL_UPD(x) (1 << (TCON_TIMER_BASE(x) + 1))
#define TCON_TIMER_OUTPUT_INV(x) (1 << (TCON_TIMER_BASE(x) + 2))
#define TCON_TIMER_AUTO_RELOAD(x) (1 << (TCON_TIMER_BASE(x) + 3))
#define TCON_TIMER4_AUTO_RELOAD (1 << 22)
#define TINT_CSTAT_STATUS(x) (1 << (5 + (x)))
#define TINT_CSTAT_ENABLE(x) (1 << (x))
/* timer struct */
typedef struct {
uint32_t id; /* timer id */
qemu_irq irq; /* local timer irq */
uint32_t freq; /* timer frequency */
/* use ptimer.c to represent count down timer */
ptimer_state *ptimer; /* timer */
/* registers */
uint32_t reg_tcntb; /* counter register buffer */
uint32_t reg_tcmpb; /* compare register buffer */
struct Exynos4210PWMState *parent;
} Exynos4210PWM;
#define TYPE_EXYNOS4210_PWM "exynos4210.pwm"
OBJECT_DECLARE_SIMPLE_TYPE(Exynos4210PWMState, EXYNOS4210_PWM)
struct Exynos4210PWMState {
SysBusDevice parent_obj;
MemoryRegion iomem;
uint32_t reg_tcfg[2];
uint32_t reg_tcon;
uint32_t reg_tint_cstat;
Exynos4210PWM timer[EXYNOS4210_PWM_TIMERS_NUM];
};
/*** VMState ***/
static const VMStateDescription vmstate_exynos4210_pwm = {
.name = "exynos4210.pwm.pwm",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(id, Exynos4210PWM),
VMSTATE_UINT32(freq, Exynos4210PWM),
VMSTATE_PTIMER(ptimer, Exynos4210PWM),
VMSTATE_UINT32(reg_tcntb, Exynos4210PWM),
VMSTATE_UINT32(reg_tcmpb, Exynos4210PWM),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription vmstate_exynos4210_pwm_state = {
.name = "exynos4210.pwm",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_UINT32_ARRAY(reg_tcfg, Exynos4210PWMState, 2),
VMSTATE_UINT32(reg_tcon, Exynos4210PWMState),
VMSTATE_UINT32(reg_tint_cstat, Exynos4210PWMState),
VMSTATE_STRUCT_ARRAY(timer, Exynos4210PWMState,
EXYNOS4210_PWM_TIMERS_NUM, 0,
vmstate_exynos4210_pwm, Exynos4210PWM),
VMSTATE_END_OF_LIST()
}
};
/*
* PWM update frequency.
* Must be called within a ptimer_transaction_begin/commit block
* for s->timer[id].ptimer.
*/
static void exynos4210_pwm_update_freq(Exynos4210PWMState *s, uint32_t id)
{
uint32_t freq;
freq = s->timer[id].freq;
if (id > 1) {
s->timer[id].freq = 24000000 /
((GET_PRESCALER(s->reg_tcfg[0], 1) + 1) *
(GET_DIVIDER(s->reg_tcfg[1], id)));
} else {
s->timer[id].freq = 24000000 /
((GET_PRESCALER(s->reg_tcfg[0], 0) + 1) *
(GET_DIVIDER(s->reg_tcfg[1], id)));
}
if (freq != s->timer[id].freq) {
ptimer_set_freq(s->timer[id].ptimer, s->timer[id].freq);
DPRINTF("freq=%uHz\n", s->timer[id].freq);
}
}
/*
* Counter tick handler
*/
static void exynos4210_pwm_tick(void *opaque)
{
Exynos4210PWM *s = (Exynos4210PWM *)opaque;
Exynos4210PWMState *p = (Exynos4210PWMState *)s->parent;
uint32_t id = s->id;
bool cmp;
DPRINTF("timer %u tick\n", id);
/* set irq status */
p->reg_tint_cstat |= TINT_CSTAT_STATUS(id);
/* raise IRQ */
if (p->reg_tint_cstat & TINT_CSTAT_ENABLE(id)) {
DPRINTF("timer %u IRQ\n", id);
qemu_irq_raise(p->timer[id].irq);
}
/* reload timer */
if (id != 4) {
cmp = p->reg_tcon & TCON_TIMER_AUTO_RELOAD(id);
} else {
cmp = p->reg_tcon & TCON_TIMER4_AUTO_RELOAD;
}
if (cmp) {
DPRINTF("auto reload timer %u count to %x\n", id,
p->timer[id].reg_tcntb);
ptimer_set_count(p->timer[id].ptimer, p->timer[id].reg_tcntb);
ptimer_run(p->timer[id].ptimer, 1);
} else {
/* stop timer, set status to STOP, see Basic Timer Operation */
p->reg_tcon &= ~TCON_TIMER_START(id);
ptimer_stop(p->timer[id].ptimer);
}
}
/*
* PWM Read
*/
static uint64_t exynos4210_pwm_read(void *opaque, hwaddr offset,
unsigned size)
{
Exynos4210PWMState *s = (Exynos4210PWMState *)opaque;
uint32_t value = 0;
int index;
switch (offset) {
case TCFG0: case TCFG1:
index = (offset - TCFG0) >> 2;
value = s->reg_tcfg[index];
break;
case TCON:
value = s->reg_tcon;
break;
case TCNTB0: case TCNTB1:
case TCNTB2: case TCNTB3: case TCNTB4:
index = (offset - TCNTB0) / 0xC;
value = s->timer[index].reg_tcntb;
break;
case TCMPB0: case TCMPB1:
case TCMPB2: case TCMPB3:
index = (offset - TCMPB0) / 0xC;
value = s->timer[index].reg_tcmpb;
break;
case TCNTO0: case TCNTO1:
case TCNTO2: case TCNTO3: case TCNTO4:
index = (offset == TCNTO4) ? 4 : (offset - TCNTO0) / 0xC;
value = ptimer_get_count(s->timer[index].ptimer);
break;
case TINT_CSTAT:
value = s->reg_tint_cstat;
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"exynos4210.pwm: bad read offset " HWADDR_FMT_plx,
offset);
break;
}
return value;
}
/*
* PWM Write
*/
static void exynos4210_pwm_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
Exynos4210PWMState *s = (Exynos4210PWMState *)opaque;
int index;
uint32_t new_val;
int i;
switch (offset) {
case TCFG0: case TCFG1:
index = (offset - TCFG0) >> 2;
s->reg_tcfg[index] = value;
/* update timers frequencies */
for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
ptimer_transaction_begin(s->timer[i].ptimer);
exynos4210_pwm_update_freq(s, s->timer[i].id);
ptimer_transaction_commit(s->timer[i].ptimer);
}
break;
case TCON:
for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
ptimer_transaction_begin(s->timer[i].ptimer);
if ((value & TCON_TIMER_MANUAL_UPD(i)) >
(s->reg_tcon & TCON_TIMER_MANUAL_UPD(i))) {
/*
* TCNTB and TCMPB are loaded into TCNT and TCMP.
* Update timers.
*/
/* this will start timer to run, this ok, because
* during processing start bit timer will be stopped
* if needed */
ptimer_set_count(s->timer[i].ptimer, s->timer[i].reg_tcntb);
DPRINTF("set timer %d count to %x\n", i,
s->timer[i].reg_tcntb);
}
if ((value & TCON_TIMER_START(i)) >
(s->reg_tcon & TCON_TIMER_START(i))) {
/* changed to start */
ptimer_run(s->timer[i].ptimer, 1);
DPRINTF("run timer %d\n", i);
}
if ((value & TCON_TIMER_START(i)) <
(s->reg_tcon & TCON_TIMER_START(i))) {
/* changed to stop */
ptimer_stop(s->timer[i].ptimer);
DPRINTF("stop timer %d\n", i);
}
ptimer_transaction_commit(s->timer[i].ptimer);
}
s->reg_tcon = value;
break;
case TCNTB0: case TCNTB1:
case TCNTB2: case TCNTB3: case TCNTB4:
index = (offset - TCNTB0) / 0xC;
s->timer[index].reg_tcntb = value;
break;
case TCMPB0: case TCMPB1:
case TCMPB2: case TCMPB3:
index = (offset - TCMPB0) / 0xC;
s->timer[index].reg_tcmpb = value;
break;
case TINT_CSTAT:
new_val = (s->reg_tint_cstat & 0x3E0) + (0x1F & value);
new_val &= ~(0x3E0 & value);
for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
if ((new_val & TINT_CSTAT_STATUS(i)) <
(s->reg_tint_cstat & TINT_CSTAT_STATUS(i))) {
qemu_irq_lower(s->timer[i].irq);
}
}
s->reg_tint_cstat = new_val;
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"exynos4210.pwm: bad write offset " HWADDR_FMT_plx,
offset);
break;
}
}
/*
* Set default values to timer fields and registers
*/
static void exynos4210_pwm_reset(DeviceState *d)
{
Exynos4210PWMState *s = EXYNOS4210_PWM(d);
int i;
s->reg_tcfg[0] = 0x0101;
s->reg_tcfg[1] = 0x0;
s->reg_tcon = 0;
s->reg_tint_cstat = 0;
for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
s->timer[i].reg_tcmpb = 0;
s->timer[i].reg_tcntb = 0;
ptimer_transaction_begin(s->timer[i].ptimer);
exynos4210_pwm_update_freq(s, s->timer[i].id);
ptimer_stop(s->timer[i].ptimer);
ptimer_transaction_commit(s->timer[i].ptimer);
}
}
static const MemoryRegionOps exynos4210_pwm_ops = {
.read = exynos4210_pwm_read,
.write = exynos4210_pwm_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
/*
* PWM timer initialization
*/
static void exynos4210_pwm_init(Object *obj)
{
Exynos4210PWMState *s = EXYNOS4210_PWM(obj);
SysBusDevice *dev = SYS_BUS_DEVICE(obj);
int i;
for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
sysbus_init_irq(dev, &s->timer[i].irq);
s->timer[i].ptimer = ptimer_init(exynos4210_pwm_tick,
&s->timer[i],
PTIMER_POLICY_LEGACY);
s->timer[i].id = i;
s->timer[i].parent = s;
}
memory_region_init_io(&s->iomem, obj, &exynos4210_pwm_ops, s,
"exynos4210-pwm", EXYNOS4210_PWM_REG_MEM_SIZE);
sysbus_init_mmio(dev, &s->iomem);
}
static void exynos4210_pwm_finalize(Object *obj)
{
Exynos4210PWMState *s = EXYNOS4210_PWM(obj);
int i;
for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
ptimer_free(s->timer[i].ptimer);
}
}
static void exynos4210_pwm_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, exynos4210_pwm_reset);
dc->vmsd = &vmstate_exynos4210_pwm_state;
}
static const TypeInfo exynos4210_pwm_info = {
.name = TYPE_EXYNOS4210_PWM,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(Exynos4210PWMState),
.instance_init = exynos4210_pwm_init,
.instance_finalize = exynos4210_pwm_finalize,
.class_init = exynos4210_pwm_class_init,
};
static void exynos4210_pwm_register_types(void)
{
type_register_static(&exynos4210_pwm_info);
}
type_init(exynos4210_pwm_register_types)
+433
View File
@@ -0,0 +1,433 @@
/*
* QEMU GRLIB GPTimer Emulator
*
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2010-2024 AdaCore
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "qemu/osdep.h"
#include "hw/timer/grlib_gptimer.h"
#include "hw/core/sysbus.h"
#include "qemu/timer.h"
#include "hw/core/irq.h"
#include "hw/core/ptimer.h"
#include "hw/core/qdev-properties.h"
#include "qemu/module.h"
#include "trace.h"
#include "qom/object.h"
#define UNIT_REG_SIZE 16 /* Size of memory mapped regs for the unit */
#define GPTIMER_REG_SIZE 16 /* Size of memory mapped regs for a GPTimer */
#define GPTIMER_MAX_TIMERS 8
/* GPTimer Config register fields */
#define GPTIMER_ENABLE (1 << 0)
#define GPTIMER_RESTART (1 << 1)
#define GPTIMER_LOAD (1 << 2)
#define GPTIMER_INT_ENABLE (1 << 3)
#define GPTIMER_INT_PENDING (1 << 4)
#define GPTIMER_CHAIN (1 << 5) /* Not supported */
#define GPTIMER_DEBUG_HALT (1 << 6) /* Not supported */
/* Memory mapped register offsets */
#define SCALER_OFFSET 0x00
#define SCALER_RELOAD_OFFSET 0x04
#define CONFIG_OFFSET 0x08
#define COUNTER_OFFSET 0x00
#define COUNTER_RELOAD_OFFSET 0x04
#define TIMER_BASE 0x10
OBJECT_DECLARE_SIMPLE_TYPE(GPTimerUnit, GRLIB_GPTIMER)
typedef struct GPTimer GPTimer;
struct GPTimer {
struct ptimer_state *ptimer;
qemu_irq irq;
int id;
GPTimerUnit *unit;
/* registers */
uint32_t counter;
uint32_t reload;
uint32_t config;
};
struct GPTimerUnit {
SysBusDevice parent_obj;
MemoryRegion iomem;
uint32_t nr_timers; /* Number of timers available */
uint32_t freq_hz; /* System frequency */
uint32_t irq_line; /* Base irq line */
GPTimer *timers;
/* registers */
uint32_t scaler;
uint32_t reload;
uint32_t config;
};
static void grlib_gptimer_tx_begin(GPTimer *timer)
{
ptimer_transaction_begin(timer->ptimer);
}
static void grlib_gptimer_tx_commit(GPTimer *timer)
{
ptimer_transaction_commit(timer->ptimer);
}
/* Must be called within grlib_gptimer_tx_begin/commit block */
static void grlib_gptimer_enable(GPTimer *timer)
{
assert(timer != NULL);
ptimer_stop(timer->ptimer);
if (!(timer->config & GPTIMER_ENABLE)) {
/* Timer disabled */
trace_grlib_gptimer_disabled(timer->id, timer->config);
return;
}
/* ptimer is triggered when the counter reach 0 but GPTimer is triggered at
underflow. Set count + 1 to simulate the GPTimer behavior. */
trace_grlib_gptimer_enable(timer->id, timer->counter);
ptimer_set_count(timer->ptimer, (uint64_t)timer->counter + 1);
ptimer_run(timer->ptimer, 1);
}
/* Must be called within grlib_gptimer_tx_begin/commit block */
static void grlib_gptimer_restart(GPTimer *timer)
{
assert(timer != NULL);
trace_grlib_gptimer_restart(timer->id, timer->reload);
timer->counter = timer->reload;
grlib_gptimer_enable(timer);
}
static void grlib_gptimer_set_scaler(GPTimerUnit *unit, uint32_t scaler)
{
int i = 0;
uint32_t value = 0;
assert(unit != NULL);
if (scaler > 0) {
value = unit->freq_hz / (scaler + 1);
} else {
value = unit->freq_hz;
}
trace_grlib_gptimer_set_scaler(scaler, value);
for (i = 0; i < unit->nr_timers; i++) {
ptimer_transaction_begin(unit->timers[i].ptimer);
ptimer_set_freq(unit->timers[i].ptimer, value);
ptimer_transaction_commit(unit->timers[i].ptimer);
}
}
static void grlib_gptimer_hit(void *opaque)
{
GPTimer *timer = opaque;
assert(timer != NULL);
trace_grlib_gptimer_hit(timer->id);
/* Timer expired */
if (timer->config & GPTIMER_INT_ENABLE) {
/* Set the pending bit (only unset by write in the config register) */
timer->config |= GPTIMER_INT_PENDING;
qemu_irq_pulse(timer->irq);
}
if (timer->config & GPTIMER_RESTART) {
grlib_gptimer_restart(timer);
}
}
static uint64_t grlib_gptimer_read(void *opaque, hwaddr addr,
unsigned size)
{
GPTimerUnit *unit = opaque;
hwaddr timer_addr;
int id;
uint32_t value = 0;
addr &= 0xff;
/* Unit registers */
switch (addr) {
case SCALER_OFFSET:
trace_grlib_gptimer_readl(-1, addr, unit->scaler);
return unit->scaler;
case SCALER_RELOAD_OFFSET:
trace_grlib_gptimer_readl(-1, addr, unit->reload);
return unit->reload;
case CONFIG_OFFSET:
trace_grlib_gptimer_readl(-1, addr, unit->config);
return unit->config;
default:
break;
}
timer_addr = (addr % TIMER_BASE);
id = (addr - TIMER_BASE) / TIMER_BASE;
if (id >= 0 && id < unit->nr_timers) {
/* GPTimer registers */
switch (timer_addr) {
case COUNTER_OFFSET:
value = ptimer_get_count(unit->timers[id].ptimer);
trace_grlib_gptimer_readl(id, addr, value);
return value;
case COUNTER_RELOAD_OFFSET:
value = unit->timers[id].reload;
trace_grlib_gptimer_readl(id, addr, value);
return value;
case CONFIG_OFFSET:
trace_grlib_gptimer_readl(id, addr, unit->timers[id].config);
return unit->timers[id].config;
default:
break;
}
}
trace_grlib_gptimer_readl(-1, addr, 0);
return 0;
}
static void grlib_gptimer_write(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
GPTimerUnit *unit = opaque;
hwaddr timer_addr;
int id;
addr &= 0xff;
/* Unit registers */
switch (addr) {
case SCALER_OFFSET:
value &= 0xFFFF; /* clean up the value */
unit->scaler = value;
trace_grlib_gptimer_writel(-1, addr, unit->scaler);
return;
case SCALER_RELOAD_OFFSET:
value &= 0xFFFF; /* clean up the value */
unit->reload = value;
trace_grlib_gptimer_writel(-1, addr, unit->reload);
grlib_gptimer_set_scaler(unit, value);
return;
case CONFIG_OFFSET:
/* Read Only (disable timer freeze not supported) */
trace_grlib_gptimer_writel(-1, addr, 0);
return;
default:
break;
}
timer_addr = (addr % TIMER_BASE);
id = (addr - TIMER_BASE) / TIMER_BASE;
if (id >= 0 && id < unit->nr_timers) {
/* GPTimer registers */
switch (timer_addr) {
case COUNTER_OFFSET:
trace_grlib_gptimer_writel(id, addr, value);
grlib_gptimer_tx_begin(&unit->timers[id]);
unit->timers[id].counter = value;
grlib_gptimer_enable(&unit->timers[id]);
grlib_gptimer_tx_commit(&unit->timers[id]);
return;
case COUNTER_RELOAD_OFFSET:
trace_grlib_gptimer_writel(id, addr, value);
unit->timers[id].reload = value;
return;
case CONFIG_OFFSET:
trace_grlib_gptimer_writel(id, addr, value);
if (value & GPTIMER_INT_PENDING) {
/* clear pending bit */
value &= ~GPTIMER_INT_PENDING;
} else {
/* keep pending bit */
value |= unit->timers[id].config & GPTIMER_INT_PENDING;
}
unit->timers[id].config = value;
/* gptimer_restart calls gptimer_enable, so if "enable" and "load"
bits are present, we just have to call restart. */
grlib_gptimer_tx_begin(&unit->timers[id]);
if (value & GPTIMER_LOAD) {
grlib_gptimer_restart(&unit->timers[id]);
} else if (value & GPTIMER_ENABLE) {
grlib_gptimer_enable(&unit->timers[id]);
}
/* These fields must always be read as 0 */
value &= ~(GPTIMER_LOAD & GPTIMER_DEBUG_HALT);
unit->timers[id].config = value;
grlib_gptimer_tx_commit(&unit->timers[id]);
return;
default:
break;
}
}
trace_grlib_gptimer_writel(-1, addr, value);
}
static const MemoryRegionOps grlib_gptimer_ops = {
.read = grlib_gptimer_read,
.write = grlib_gptimer_write,
.endianness = DEVICE_BIG_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
};
static void grlib_gptimer_reset(DeviceState *d)
{
GPTimerUnit *unit = GRLIB_GPTIMER(d);
int i = 0;
assert(unit != NULL);
unit->scaler = 0;
unit->reload = 0;
unit->config = unit->nr_timers;
unit->config |= unit->irq_line << 3;
unit->config |= 1 << 8; /* separate interrupt */
unit->config |= 1 << 9; /* Disable timer freeze */
for (i = 0; i < unit->nr_timers; i++) {
GPTimer *timer = &unit->timers[i];
timer->counter = 0;
timer->reload = 0;
timer->config = 0;
ptimer_transaction_begin(timer->ptimer);
ptimer_stop(timer->ptimer);
ptimer_set_count(timer->ptimer, 0);
ptimer_set_freq(timer->ptimer, unit->freq_hz);
ptimer_transaction_commit(timer->ptimer);
}
}
static void grlib_gptimer_realize(DeviceState *dev, Error **errp)
{
GPTimerUnit *unit = GRLIB_GPTIMER(dev);
unsigned int i;
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
assert(unit->nr_timers > 0);
assert(unit->nr_timers <= GPTIMER_MAX_TIMERS);
unit->timers = g_malloc0(sizeof unit->timers[0] * unit->nr_timers);
for (i = 0; i < unit->nr_timers; i++) {
GPTimer *timer = &unit->timers[i];
timer->unit = unit;
timer->ptimer = ptimer_init(grlib_gptimer_hit, timer,
PTIMER_POLICY_LEGACY);
timer->id = i;
/* One IRQ line for each timer */
sysbus_init_irq(sbd, &timer->irq);
ptimer_transaction_begin(timer->ptimer);
ptimer_set_freq(timer->ptimer, unit->freq_hz);
ptimer_transaction_commit(timer->ptimer);
}
memory_region_init_io(&unit->iomem, OBJECT(unit), &grlib_gptimer_ops,
unit, "gptimer",
UNIT_REG_SIZE + GPTIMER_REG_SIZE * unit->nr_timers);
sysbus_init_mmio(sbd, &unit->iomem);
}
static const Property grlib_gptimer_properties[] = {
DEFINE_PROP_UINT32("frequency", GPTimerUnit, freq_hz, 40000000),
DEFINE_PROP_UINT32("irq-line", GPTimerUnit, irq_line, 8),
DEFINE_PROP_UINT32("nr-timers", GPTimerUnit, nr_timers, 2),
};
static void grlib_gptimer_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = grlib_gptimer_realize;
device_class_set_legacy_reset(dc, grlib_gptimer_reset);
device_class_set_props(dc, grlib_gptimer_properties);
}
static const TypeInfo grlib_gptimer_info = {
.name = TYPE_GRLIB_GPTIMER,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(GPTimerUnit),
.class_init = grlib_gptimer_class_init,
};
static void grlib_gptimer_register_types(void)
{
type_register_static(&grlib_gptimer_info);
}
type_init(grlib_gptimer_register_types)
+791
View File
@@ -0,0 +1,791 @@
/*
* High Precision Event Timer emulation
*
* Copyright (c) 2007 Alexander Graf
* Copyright (c) 2008 IBM Corporation
*
* Authors: Beth Kon <[email protected]>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
* *****************************************************************
*
* This driver attempts to emulate an HPET device in software.
*/
#include "qemu/osdep.h"
#include "hw/core/irq.h"
#include "qapi/error.h"
#include "qemu/error-report.h"
#include "qemu/timer.h"
#include "hw/core/qdev-properties.h"
#include "hw/timer/hpet.h"
#include "hw/core/sysbus.h"
#include "hw/rtc/mc146818rtc.h"
#include "hw/rtc/mc146818rtc_regs.h"
#include "migration/vmstate.h"
#include "hw/timer/i8254.h"
#include "system/address-spaces.h"
#include "qom/object.h"
#include "qemu/lockable.h"
#include "qemu/seqlock.h"
#include "qemu/main-loop.h"
#include "trace.h"
struct hpet_fw_config hpet_fw_cfg = {.count = UINT8_MAX};
#define HPET_MSI_SUPPORT 0
OBJECT_DECLARE_SIMPLE_TYPE(HPETState, HPET)
struct HPETState;
typedef struct HPETTimer { /* timers */
uint8_t tn; /*timer number*/
QEMUTimer *qemu_timer;
struct HPETState *state;
/* Memory-mapped, software visible timer registers */
uint64_t config; /* configuration/cap */
uint64_t cmp; /* comparator */
uint64_t fsb; /* FSB route */
/* Hidden register state */
uint64_t cmp64; /* comparator (extended to counter width) */
uint64_t period; /* Last value written to comparator */
uint8_t wrap_flag; /* timer pop will indicate wrap for one-shot 32-bit
* mode. Next pop will be actual timer expiration.
*/
uint64_t last; /* last value armed, to avoid timer storms */
} HPETTimer;
struct HPETState {
/*< private >*/
SysBusDevice parent_obj;
/*< public >*/
QemuMutex lock;
MemoryRegion iomem;
uint64_t hpet_offset;
QemuSeqLock state_version;
qemu_irq irqs[HPET_NUM_IRQ_ROUTES];
uint32_t flags;
uint8_t rtc_irq_level;
qemu_irq pit_enabled;
uint8_t num_timers;
uint8_t num_timers_save;
uint32_t intcap;
HPETTimer timer[HPET_MAX_TIMERS];
/* Memory-mapped, software visible registers */
uint64_t capability; /* capabilities */
uint64_t config; /* configuration */
uint64_t isr; /* interrupt status reg */
uint64_t hpet_counter; /* main counter */
uint8_t hpet_id; /* instance id */
};
static uint32_t hpet_in_legacy_mode(HPETState *s)
{
return s->config & HPET_CFG_LEGACY;
}
static uint32_t timer_int_route(struct HPETTimer *timer)
{
return (timer->config & HPET_TN_INT_ROUTE_MASK) >> HPET_TN_INT_ROUTE_SHIFT;
}
static uint32_t timer_fsb_route(HPETTimer *t)
{
return t->config & HPET_TN_FSB_ENABLE;
}
static uint32_t hpet_enabled(HPETState *s)
{
return s->config & HPET_CFG_ENABLE;
}
static uint32_t timer_is_periodic(HPETTimer *t)
{
return t->config & HPET_TN_PERIODIC;
}
static uint32_t timer_enabled(HPETTimer *t)
{
return t->config & HPET_TN_ENABLE;
}
static uint32_t hpet_time_after(uint64_t a, uint64_t b)
{
return ((int64_t)(b - a) < 0);
}
static uint64_t ticks_to_ns(uint64_t value)
{
return value * HPET_CLK_PERIOD;
}
static uint64_t ns_to_ticks(uint64_t value)
{
return value / HPET_CLK_PERIOD;
}
static uint64_t hpet_fixup_reg(uint64_t new, uint64_t old, uint64_t mask)
{
new &= mask;
new |= old & ~mask;
return new;
}
static int activating_bit(uint64_t old, uint64_t new, uint64_t mask)
{
return (!(old & mask) && (new & mask));
}
static int deactivating_bit(uint64_t old, uint64_t new, uint64_t mask)
{
return ((old & mask) && !(new & mask));
}
static uint64_t hpet_get_ticks(HPETState *s)
{
return ns_to_ticks(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->hpet_offset);
}
static uint64_t hpet_get_ns(HPETState *s, uint64_t tick)
{
return ticks_to_ns(tick) - s->hpet_offset;
}
/*
* calculate next value of the general counter that matches the
* target (either entirely, or the low 32-bit only depending on
* the timer mode).
*/
static uint64_t hpet_calculate_cmp64(HPETTimer *t, uint64_t cur_tick, uint64_t target)
{
if (t->config & HPET_TN_32BIT) {
uint64_t result = deposit64(cur_tick, 0, 32, target);
if (result < cur_tick) {
result += 0x100000000ULL;
}
return result;
} else {
return target;
}
}
static uint64_t hpet_next_wrap(uint64_t cur_tick)
{
return (cur_tick | 0xffffffffU) + 1;
}
static void update_irq(struct HPETTimer *timer, int set)
{
uint64_t mask;
HPETState *s;
int route;
if (timer->tn <= 1 && hpet_in_legacy_mode(timer->state)) {
/* if LegacyReplacementRoute bit is set, HPET specification requires
* timer0 be routed to IRQ0 in NON-APIC or IRQ2 in the I/O APIC,
* timer1 be routed to IRQ8 in NON-APIC or IRQ8 in the I/O APIC.
*/
route = (timer->tn == 0) ? 0 : RTC_ISA_IRQ;
} else {
route = timer_int_route(timer);
}
s = timer->state;
mask = 1 << timer->tn;
if (set && (timer->config & HPET_TN_TYPE_LEVEL)) {
/*
* If HPET_TN_ENABLE bit is 0, "the timer will still operate and
* generate appropriate status bits, but will not cause an interrupt"
*/
s->isr |= mask;
} else {
s->isr &= ~mask;
}
if (set && timer_enabled(timer) && hpet_enabled(s)) {
if (timer_fsb_route(timer)) {
address_space_stl_le(&address_space_memory, timer->fsb >> 32,
timer->fsb & 0xffffffff, MEMTXATTRS_UNSPECIFIED,
NULL);
} else if (timer->config & HPET_TN_TYPE_LEVEL) {
BQL_LOCK_GUARD();
qemu_irq_raise(s->irqs[route]);
} else {
BQL_LOCK_GUARD();
qemu_irq_pulse(s->irqs[route]);
}
} else {
if (!timer_fsb_route(timer)) {
BQL_LOCK_GUARD();
qemu_irq_lower(s->irqs[route]);
}
}
}
static int hpet_pre_save(void *opaque)
{
HPETState *s = opaque;
/* save current counter value */
if (hpet_enabled(s)) {
s->hpet_counter = hpet_get_ticks(s);
}
/*
* The number of timers must match on source and destination, but it was
* also added to the migration stream. Check that it matches the value
* that was configured.
*/
s->num_timers_save = s->num_timers;
return 0;
}
static bool hpet_validate_num_timers(void *opaque, int version_id)
{
HPETState *s = opaque;
return s->num_timers == s->num_timers_save;
}
static int hpet_post_load(void *opaque, int version_id)
{
HPETState *s = opaque;
int i;
for (i = 0; i < s->num_timers; i++) {
HPETTimer *t = &s->timer[i];
t->cmp64 = hpet_calculate_cmp64(t, s->hpet_counter, t->cmp);
t->last = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - NANOSECONDS_PER_SECOND;
}
return 0;
}
static bool hpet_offset_needed(void *opaque)
{
HPETState *s = opaque;
return hpet_enabled(s);
}
static bool hpet_rtc_irq_level_needed(void *opaque)
{
HPETState *s = opaque;
return s->rtc_irq_level != 0;
}
static const VMStateDescription vmstate_hpet_rtc_irq_level = {
.name = "hpet/rtc_irq_level",
.version_id = 1,
.minimum_version_id = 1,
.needed = hpet_rtc_irq_level_needed,
.fields = (const VMStateField[]) {
VMSTATE_UINT8(rtc_irq_level, HPETState),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription vmstate_hpet_offset = {
.name = "hpet/offset",
.version_id = 1,
.minimum_version_id = 1,
.needed = hpet_offset_needed,
.fields = (const VMStateField[]) {
VMSTATE_UINT64(hpet_offset, HPETState),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription vmstate_hpet_timer = {
.name = "hpet_timer",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_UINT8(tn, HPETTimer),
VMSTATE_UINT64(config, HPETTimer),
VMSTATE_UINT64(cmp, HPETTimer),
VMSTATE_UINT64(fsb, HPETTimer),
VMSTATE_UINT64(period, HPETTimer),
VMSTATE_UINT8(wrap_flag, HPETTimer),
VMSTATE_TIMER_PTR(qemu_timer, HPETTimer),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription vmstate_hpet = {
.name = "hpet",
.version_id = 2,
.minimum_version_id = 2,
.pre_save = hpet_pre_save,
.post_load = hpet_post_load,
.fields = (const VMStateField[]) {
VMSTATE_UINT64(config, HPETState),
VMSTATE_UINT64(isr, HPETState),
VMSTATE_UINT64(hpet_counter, HPETState),
VMSTATE_UINT8(num_timers_save, HPETState),
VMSTATE_VALIDATE("num_timers must match", hpet_validate_num_timers),
VMSTATE_STRUCT_VARRAY_UINT8(timer, HPETState, num_timers_save, 0,
vmstate_hpet_timer, HPETTimer),
VMSTATE_END_OF_LIST()
},
.subsections = (const VMStateDescription * const []) {
&vmstate_hpet_rtc_irq_level,
&vmstate_hpet_offset,
NULL
}
};
static void hpet_arm(HPETTimer *t, uint64_t tick)
{
uint64_t ns = hpet_get_ns(t->state, tick);
/* Clamp period to reasonable min value (1 us) */
if (timer_is_periodic(t) && ns - t->last < 1000) {
ns = t->last + 1000;
}
t->last = ns;
timer_mod(t->qemu_timer, ns);
}
/*
* timer expiration callback
*/
static void hpet_timer(void *opaque)
{
HPETTimer *t = opaque;
uint64_t period = t->period;
uint64_t cur_tick = hpet_get_ticks(t->state);
if (timer_is_periodic(t) && period != 0) {
while (hpet_time_after(cur_tick, t->cmp64)) {
t->cmp64 += period;
}
if (t->config & HPET_TN_32BIT) {
t->cmp = (uint32_t)t->cmp64;
} else {
t->cmp = t->cmp64;
}
hpet_arm(t, t->cmp64);
} else if (t->wrap_flag) {
t->wrap_flag = 0;
hpet_arm(t, t->cmp64);
}
update_irq(t, 1);
}
static void hpet_set_timer(HPETTimer *t)
{
uint64_t cur_tick = hpet_get_ticks(t->state);
t->wrap_flag = 0;
t->cmp64 = hpet_calculate_cmp64(t, cur_tick, t->cmp);
if (t->config & HPET_TN_32BIT) {
/* hpet spec says in one-shot 32-bit mode, generate an interrupt when
* counter wraps in addition to an interrupt with comparator match.
*/
if (!timer_is_periodic(t) && t->cmp64 > hpet_next_wrap(cur_tick)) {
t->wrap_flag = 1;
hpet_arm(t, hpet_next_wrap(cur_tick));
return;
}
}
hpet_arm(t, t->cmp64);
}
static void hpet_del_timer(HPETTimer *t)
{
HPETState *s = t->state;
timer_del(t->qemu_timer);
if (s->isr & (1 << t->tn)) {
/* For level-triggered interrupt, this leaves ISR set but lowers irq. */
update_irq(t, 1);
}
}
static uint64_t hpet_ram_read(void *opaque, hwaddr addr,
unsigned size)
{
HPETState *s = opaque;
int shift = (addr & 4) * 8;
uint64_t cur_tick;
trace_hpet_ram_read(addr);
addr &= ~4;
if (addr == HPET_COUNTER) {
unsigned version;
/*
* Write update is rare, so busywait here is unlikely to happen
*/
do {
version = seqlock_read_begin(&s->state_version);
if (unlikely(!hpet_enabled(s))) {
cur_tick = s->hpet_counter;
} else {
cur_tick = hpet_get_ticks(s);
}
} while (seqlock_read_retry(&s->state_version, version));
trace_hpet_ram_read_reading_counter(addr & 4, cur_tick);
return cur_tick >> shift;
}
QEMU_LOCK_GUARD(&s->lock);
/*address range of all global regs*/
if (addr <= 0xff) {
switch (addr) {
case HPET_ID: // including HPET_PERIOD
return s->capability >> shift;
case HPET_CFG:
return s->config >> shift;
case HPET_STATUS:
return s->isr >> shift;
default:
trace_hpet_ram_read_invalid();
break;
}
} else {
uint8_t timer_id = (addr - 0x100) / 0x20;
HPETTimer *timer;
if (timer_id >= s->num_timers) {
trace_hpet_timer_id_out_of_range(timer_id);
return 0;
}
timer = &s->timer[timer_id];
switch (addr & 0x1f) {
case HPET_TN_CFG: // including interrupt capabilities
return timer->config >> shift;
case HPET_TN_CMP: // comparator register
return timer->cmp >> shift;
case HPET_TN_ROUTE:
return timer->fsb >> shift;
default:
trace_hpet_ram_read_invalid();
break;
}
}
return 0;
}
static void hpet_ram_write(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
int i;
HPETState *s = opaque;
int shift = (addr & 4) * 8;
int len = MIN(size * 8, 64 - shift);
uint64_t old_val, new_val, cleared;
QEMU_LOCK_GUARD(&s->lock);
trace_hpet_ram_write(addr, value);
addr &= ~4;
/*address range of all global regs*/
if (addr <= 0xff) {
switch (addr) {
case HPET_ID:
return;
case HPET_CFG:
old_val = s->config;
new_val = deposit64(old_val, shift, len, value);
new_val = hpet_fixup_reg(new_val, old_val, HPET_CFG_WRITE_MASK);
seqlock_write_begin(&s->state_version);
s->config = new_val;
if (activating_bit(old_val, new_val, HPET_CFG_ENABLE)) {
/* Enable main counter and interrupt generation. */
s->hpet_offset =
ticks_to_ns(s->hpet_counter) - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
for (i = 0; i < s->num_timers; i++) {
if (timer_enabled(&s->timer[i]) && (s->isr & (1 << i))) {
update_irq(&s->timer[i], 1);
}
hpet_set_timer(&s->timer[i]);
}
} else if (deactivating_bit(old_val, new_val, HPET_CFG_ENABLE)) {
/* Halt main counter and disable interrupt generation. */
s->hpet_counter = hpet_get_ticks(s);
for (i = 0; i < s->num_timers; i++) {
hpet_del_timer(&s->timer[i]);
}
}
seqlock_write_end(&s->state_version);
/* i8254 and RTC output pins are disabled
* when HPET is in legacy mode */
if (activating_bit(old_val, new_val, HPET_CFG_LEGACY)) {
BQL_LOCK_GUARD();
qemu_set_irq(s->pit_enabled, 0);
qemu_irq_lower(s->irqs[0]);
qemu_irq_lower(s->irqs[RTC_ISA_IRQ]);
} else if (deactivating_bit(old_val, new_val, HPET_CFG_LEGACY)) {
BQL_LOCK_GUARD();
qemu_irq_lower(s->irqs[0]);
qemu_set_irq(s->pit_enabled, 1);
qemu_set_irq(s->irqs[RTC_ISA_IRQ], s->rtc_irq_level);
}
break;
case HPET_STATUS:
new_val = value << shift;
cleared = new_val & s->isr;
for (i = 0; i < s->num_timers; i++) {
if (cleared & (1 << i)) {
update_irq(&s->timer[i], 0);
}
}
break;
case HPET_COUNTER:
if (hpet_enabled(s)) {
trace_hpet_ram_write_counter_write_while_enabled();
}
s->hpet_counter = deposit64(s->hpet_counter, shift, len, value);
break;
default:
trace_hpet_ram_write_invalid();
break;
}
} else {
uint8_t timer_id = (addr - 0x100) / 0x20;
HPETTimer *timer;
trace_hpet_ram_write_timer_id(timer_id);
if (timer_id >= s->num_timers) {
trace_hpet_timer_id_out_of_range(timer_id);
return;
}
timer = &s->timer[timer_id];
switch (addr & 0x18) {
case HPET_TN_CFG:
trace_hpet_ram_write_tn_cfg(addr & 4);
old_val = timer->config;
new_val = deposit64(old_val, shift, len, value);
new_val = hpet_fixup_reg(new_val, old_val, HPET_TN_CFG_WRITE_MASK);
if (deactivating_bit(old_val, new_val, HPET_TN_TYPE_LEVEL)) {
/*
* Do this before changing timer->config; otherwise, if
* HPET_TN_FSB is set, update_irq will not lower the qemu_irq.
*/
update_irq(timer, 0);
}
timer->config = new_val;
if (activating_bit(old_val, new_val, HPET_TN_ENABLE)
&& (s->isr & (1 << timer_id))) {
update_irq(timer, 1);
}
if (new_val & HPET_TN_32BIT) {
timer->cmp = (uint32_t)timer->cmp;
timer->period = (uint32_t)timer->period;
}
if (hpet_enabled(s)) {
hpet_set_timer(timer);
}
break;
case HPET_TN_CMP: // comparator register
if (timer->config & HPET_TN_32BIT) {
/* High 32-bits are zero, leave them untouched. */
if (shift) {
trace_hpet_ram_write_invalid_tn_cmp();
break;
}
len = 64;
value = (uint32_t) value;
}
trace_hpet_ram_write_tn_cmp(addr & 4);
if (!timer_is_periodic(timer)
|| (timer->config & HPET_TN_SETVAL)) {
timer->cmp = deposit64(timer->cmp, shift, len, value);
}
if (timer_is_periodic(timer)) {
timer->period = deposit64(timer->period, shift, len, value);
}
timer->config &= ~HPET_TN_SETVAL;
if (hpet_enabled(s)) {
hpet_set_timer(timer);
}
break;
case HPET_TN_ROUTE:
timer->fsb = deposit64(timer->fsb, shift, len, value);
break;
default:
trace_hpet_ram_write_invalid();
break;
}
return;
}
}
static const MemoryRegionOps hpet_ram_ops = {
.read = hpet_ram_read,
.write = hpet_ram_write,
.valid = {
.min_access_size = 4,
.max_access_size = 8,
},
.impl = {
.min_access_size = 4,
.max_access_size = 8,
},
.endianness = DEVICE_LITTLE_ENDIAN,
};
static void hpet_reset(DeviceState *d)
{
HPETState *s = HPET(d);
SysBusDevice *sbd = SYS_BUS_DEVICE(d);
int i;
for (i = 0; i < s->num_timers; i++) {
HPETTimer *timer = &s->timer[i];
hpet_del_timer(timer);
timer->cmp = ~0ULL;
timer->config = HPET_TN_PERIODIC_CAP | HPET_TN_SIZE_CAP;
if (s->flags & (1 << HPET_MSI_SUPPORT)) {
timer->config |= HPET_TN_FSB_CAP;
}
/* advertise availability of ioapic int */
timer->config |= (uint64_t)s->intcap << 32;
timer->period = 0ULL;
timer->wrap_flag = 0;
}
qemu_set_irq(s->pit_enabled, 1);
s->hpet_counter = 0ULL;
s->hpet_offset = 0ULL;
s->config = 0ULL;
hpet_fw_cfg.hpet[s->hpet_id].event_timer_block_id = (uint32_t)s->capability;
hpet_fw_cfg.hpet[s->hpet_id].address = sbd->mmio[0].addr;
/* to document that the RTC lowers its output on reset as well */
s->rtc_irq_level = 0;
}
static void hpet_handle_legacy_irq(void *opaque, int n, int level)
{
HPETState *s = HPET(opaque);
if (n == HPET_LEGACY_PIT_INT) {
if (!hpet_in_legacy_mode(s)) {
BQL_LOCK_GUARD();
qemu_set_irq(s->irqs[0], level);
}
} else {
s->rtc_irq_level = level;
if (!hpet_in_legacy_mode(s)) {
BQL_LOCK_GUARD();
qemu_set_irq(s->irqs[RTC_ISA_IRQ], level);
}
}
}
static void hpet_init(Object *obj)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
HPETState *s = HPET(obj);
qemu_mutex_init(&s->lock);
seqlock_init(&s->state_version);
/* HPET Area */
memory_region_init_io(&s->iomem, obj, &hpet_ram_ops, s, "hpet", HPET_LEN);
memory_region_enable_lockless_io(&s->iomem);
sysbus_init_mmio(sbd, &s->iomem);
}
static void hpet_realize(DeviceState *dev, Error **errp)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
HPETState *s = HPET(dev);
int i;
HPETTimer *timer;
if (s->num_timers < HPET_MIN_TIMERS || s->num_timers > HPET_MAX_TIMERS) {
error_setg(errp, "hpet.num_timers must be between %d and %d",
HPET_MIN_TIMERS, HPET_MAX_TIMERS);
return;
}
if (!s->intcap) {
error_setg(errp, "hpet.hpet-intcap not initialized");
return;
}
if (hpet_fw_cfg.count == UINT8_MAX) {
/* first instance */
hpet_fw_cfg.count = 0;
}
if (hpet_fw_cfg.count == 8) {
error_setg(errp, "Only 8 instances of HPET are allowed");
return;
}
s->hpet_id = hpet_fw_cfg.count++;
for (i = 0; i < HPET_NUM_IRQ_ROUTES; i++) {
sysbus_init_irq(sbd, &s->irqs[i]);
}
for (i = 0; i < HPET_MAX_TIMERS; i++) {
timer = &s->timer[i];
timer->qemu_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, hpet_timer, timer);
timer->tn = i;
timer->state = s;
}
/* 64-bit General Capabilities and ID Register; LegacyReplacementRoute. */
s->capability = 0x8086a001ULL;
s->capability |= (s->num_timers - 1) << HPET_ID_NUM_TIM_SHIFT;
s->capability |= ((uint64_t)(HPET_CLK_PERIOD * FS_PER_NS) << 32);
qdev_init_gpio_in(dev, hpet_handle_legacy_irq, 2);
qdev_init_gpio_out(dev, &s->pit_enabled, 1);
}
static const Property hpet_device_properties[] = {
DEFINE_PROP_UINT8("timers", HPETState, num_timers, HPET_MIN_TIMERS),
DEFINE_PROP_BIT("msi", HPETState, flags, HPET_MSI_SUPPORT, false),
DEFINE_PROP_UINT32(HPET_INTCAP, HPETState, intcap, 0),
};
static void hpet_device_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = hpet_realize;
device_class_set_legacy_reset(dc, hpet_reset);
dc->vmsd = &vmstate_hpet;
device_class_set_props(dc, hpet_device_properties);
}
static const TypeInfo hpet_device_info = {
.name = TYPE_HPET,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(HPETState),
.instance_init = hpet_init,
.class_init = hpet_device_class_init,
};
static void hpet_register_types(void)
{
type_register_static(&hpet_device_info);
}
type_init(hpet_register_types)
+385
View File
@@ -0,0 +1,385 @@
/*
* QEMU 8253/8254 interval timer emulation
*
* Copyright (c) 2003-2004 Fabrice Bellard
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "qemu/osdep.h"
#include "hw/core/irq.h"
#include "qemu/module.h"
#include "qemu/timer.h"
#include "hw/timer/i8254.h"
#include "hw/timer/i8254_internal.h"
#include "qom/object.h"
#include "trace.h"
//#define DEBUG_PIT
#define RW_STATE_LSB 1
#define RW_STATE_MSB 2
#define RW_STATE_WORD0 3
#define RW_STATE_WORD1 4
typedef struct PITClass PITClass;
DECLARE_CLASS_CHECKERS(PITClass, PIT,
TYPE_I8254)
struct PITClass {
PITCommonClass parent_class;
DeviceRealize parent_realize;
};
static void pit_irq_timer_update(PITChannelState *s, int64_t current_time);
static int pit_get_count(PITChannelState *s)
{
uint64_t d;
int counter;
d = muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - s->count_load_time, PIT_FREQ,
NANOSECONDS_PER_SECOND);
switch(s->mode) {
case 0:
case 1:
case 4:
case 5:
counter = (s->count - d) & 0xffff;
break;
case 3:
/* XXX: may be incorrect for odd counts */
counter = s->count - ((2 * d) % s->count);
break;
default:
counter = s->count - (d % s->count);
break;
}
return counter;
}
/* val must be 0 or 1 */
static void pit_set_channel_gate(PITCommonState *s, PITChannelState *sc,
int val)
{
switch (sc->mode) {
default:
case 0:
case 4:
/* XXX: just disable/enable counting */
break;
case 1:
case 5:
if (sc->gate < val) {
/* restart counting on rising edge */
sc->count_load_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
pit_irq_timer_update(sc, sc->count_load_time);
}
break;
case 2:
case 3:
if (sc->gate < val) {
/* restart counting on rising edge */
sc->count_load_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
pit_irq_timer_update(sc, sc->count_load_time);
}
/* XXX: disable/enable counting */
break;
}
sc->gate = val;
}
static inline void pit_load_count(PITChannelState *s, int val)
{
if (val == 0)
val = 0x10000;
s->count_load_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
s->count = val;
pit_irq_timer_update(s, s->count_load_time);
}
/* if already latched, do not latch again */
static void pit_latch_count(PITChannelState *s)
{
if (!s->count_latched) {
s->latched_count = pit_get_count(s);
s->count_latched = s->rw_mode;
}
}
static void pit_ioport_write(void *opaque, hwaddr addr,
uint64_t val, unsigned size)
{
PITCommonState *pit = opaque;
int channel, access;
PITChannelState *s;
trace_pit_ioport_write(addr, val);
addr &= 3;
if (addr == 3) {
channel = val >> 6;
if (channel == 3) {
/* read back command */
for(channel = 0; channel < 3; channel++) {
s = &pit->channels[channel];
if (val & (2 << channel)) {
if (!(val & 0x20)) {
pit_latch_count(s);
}
if (!(val & 0x10) && !s->status_latched) {
/* status latch */
/* XXX: add BCD and null count */
s->status =
(pit_get_out(s,
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)) << 7) |
(s->rw_mode << 4) |
(s->mode << 1) |
s->bcd;
s->status_latched = 1;
}
}
}
} else {
s = &pit->channels[channel];
access = (val >> 4) & 3;
if (access == 0) {
pit_latch_count(s);
} else {
s->rw_mode = access;
s->read_state = access;
s->write_state = access;
s->mode = (val >> 1) & 7;
s->bcd = val & 1;
/* XXX: update irq timer ? */
}
}
} else {
s = &pit->channels[addr];
switch(s->write_state) {
default:
case RW_STATE_LSB:
pit_load_count(s, val);
break;
case RW_STATE_MSB:
pit_load_count(s, val << 8);
break;
case RW_STATE_WORD0:
s->write_latch = val;
s->write_state = RW_STATE_WORD1;
break;
case RW_STATE_WORD1:
pit_load_count(s, s->write_latch | (val << 8));
s->write_state = RW_STATE_WORD0;
break;
}
}
}
static uint64_t pit_ioport_read(void *opaque, hwaddr addr,
unsigned size)
{
PITCommonState *pit = opaque;
int ret, count;
PITChannelState *s;
addr &= 3;
if (addr == 3) {
/* Mode/Command register is write only, read is ignored */
return 0;
}
s = &pit->channels[addr];
if (s->status_latched) {
s->status_latched = 0;
ret = s->status;
} else if (s->count_latched) {
switch(s->count_latched) {
default:
case RW_STATE_LSB:
ret = s->latched_count & 0xff;
s->count_latched = 0;
break;
case RW_STATE_MSB:
ret = s->latched_count >> 8;
s->count_latched = 0;
break;
case RW_STATE_WORD0:
ret = s->latched_count & 0xff;
s->count_latched = RW_STATE_MSB;
break;
}
} else {
switch(s->read_state) {
default:
case RW_STATE_LSB:
count = pit_get_count(s);
ret = count & 0xff;
break;
case RW_STATE_MSB:
count = pit_get_count(s);
ret = (count >> 8) & 0xff;
break;
case RW_STATE_WORD0:
count = pit_get_count(s);
ret = count & 0xff;
s->read_state = RW_STATE_WORD1;
break;
case RW_STATE_WORD1:
count = pit_get_count(s);
ret = (count >> 8) & 0xff;
s->read_state = RW_STATE_WORD0;
break;
}
}
trace_pit_ioport_read(addr, ret);
return ret;
}
static void pit_irq_timer_update(PITChannelState *s, int64_t current_time)
{
int64_t expire_time;
int irq_level;
if (!s->irq_timer || s->irq_disabled) {
return;
}
expire_time = pit_get_next_transition_time(s, current_time);
irq_level = pit_get_out(s, current_time);
qemu_set_irq(s->irq, irq_level);
#ifdef DEBUG_PIT
printf("irq_level=%d next_delay=%f\n",
irq_level,
(double)(expire_time - current_time) / NANOSECONDS_PER_SECOND);
#endif
s->next_transition_time = expire_time;
if (expire_time != -1)
timer_mod(s->irq_timer, expire_time);
else
timer_del(s->irq_timer);
}
static void pit_irq_timer(void *opaque)
{
PITChannelState *s = opaque;
pit_irq_timer_update(s, s->next_transition_time);
}
static void pit_reset(DeviceState *dev)
{
PITCommonState *pit = PIT_COMMON(dev);
PITChannelState *s;
pit_reset_common(pit);
s = &pit->channels[0];
if (!s->irq_disabled) {
timer_mod(s->irq_timer, s->next_transition_time);
}
}
/* When HPET is operating in legacy mode, suppress the ignored timer IRQ,
* reenable it when legacy mode is left again. */
static void pit_irq_control(void *opaque, int n, int enable)
{
PITCommonState *pit = opaque;
PITChannelState *s = &pit->channels[0];
if (enable) {
s->irq_disabled = 0;
pit_irq_timer_update(s, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
} else {
s->irq_disabled = 1;
timer_del(s->irq_timer);
}
}
static const MemoryRegionOps pit_ioport_ops = {
.read = pit_ioport_read,
.write = pit_ioport_write,
.impl = {
.min_access_size = 1,
.max_access_size = 1,
},
.endianness = DEVICE_LITTLE_ENDIAN,
};
static void pit_post_load(PITCommonState *s)
{
PITChannelState *sc = &s->channels[0];
if (sc->next_transition_time != -1 && !sc->irq_disabled) {
timer_mod(sc->irq_timer, sc->next_transition_time);
} else {
timer_del(sc->irq_timer);
}
}
static void pit_realizefn(DeviceState *dev, Error **errp)
{
PITCommonState *pit = PIT_COMMON(dev);
PITClass *pc = PIT_GET_CLASS(dev);
PITChannelState *s;
s = &pit->channels[0];
/* the timer 0 is connected to an IRQ */
s->irq_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, pit_irq_timer, s);
qdev_init_gpio_out(dev, &s->irq, 1);
memory_region_init_io(&pit->ioports, OBJECT(pit), &pit_ioport_ops,
pit, "pit", 4);
qdev_init_gpio_in(dev, pit_irq_control, 1);
pc->parent_realize(dev, errp);
}
static void pit_class_initfn(ObjectClass *klass, const void *data)
{
PITClass *pc = PIT_CLASS(klass);
PITCommonClass *k = PIT_COMMON_CLASS(klass);
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_parent_realize(dc, pit_realizefn, &pc->parent_realize);
k->set_channel_gate = pit_set_channel_gate;
k->get_channel_info = pit_get_channel_info_common;
k->post_load = pit_post_load;
device_class_set_legacy_reset(dc, pit_reset);
}
static const TypeInfo pit_info = {
.name = TYPE_I8254,
.parent = TYPE_PIT_COMMON,
.instance_size = sizeof(PITCommonState),
.class_init = pit_class_initfn,
.class_size = sizeof(PITClass),
};
static void pit_register_types(void)
{
type_register_static(&pit_info);
}
type_init(pit_register_types)
+272
View File
@@ -0,0 +1,272 @@
/*
* QEMU 8253/8254 - common bits of emulated and KVM kernel model
*
* Copyright (c) 2003-2004 Fabrice Bellard
* Copyright (c) 2012 Jan Kiszka, Siemens AG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "qemu/osdep.h"
#include "hw/isa/isa.h"
#include "qemu/module.h"
#include "qemu/timer.h"
#include "hw/timer/i8254.h"
#include "hw/timer/i8254_internal.h"
#include "migration/vmstate.h"
/* val must be 0 or 1 */
void pit_set_gate(PITCommonState *pit, int channel, int val)
{
PITChannelState *s = &pit->channels[channel];
PITCommonClass *c = PIT_COMMON_GET_CLASS(pit);
c->set_channel_gate(pit, s, val);
}
/* get pit output bit */
int pit_get_out(PITChannelState *s, int64_t current_time)
{
uint64_t d;
int out;
d = muldiv64(current_time - s->count_load_time, PIT_FREQ,
NANOSECONDS_PER_SECOND);
switch (s->mode) {
default:
case 0:
case 1:
out = (d >= s->count);
break;
case 2:
if ((d % s->count) == 0 && d != 0) {
out = 1;
} else {
out = 0;
}
break;
case 3:
out = (d % s->count) < ((s->count + 1) >> 1);
break;
case 4:
case 5:
out = (d == s->count);
break;
}
return out;
}
/* return -1 if no transition will occur. */
int64_t pit_get_next_transition_time(PITChannelState *s, int64_t current_time)
{
uint64_t d, next_time, base;
int period2;
d = muldiv64(current_time - s->count_load_time, PIT_FREQ,
NANOSECONDS_PER_SECOND);
switch (s->mode) {
default:
case 0:
case 1:
if (d < s->count) {
next_time = s->count;
} else {
return -1;
}
break;
case 2:
base = QEMU_ALIGN_DOWN(d, s->count);
if ((d - base) == 0 && d != 0) {
next_time = base + s->count;
} else {
next_time = base + s->count + 1;
}
break;
case 3:
base = QEMU_ALIGN_DOWN(d, s->count);
period2 = ((s->count + 1) >> 1);
if ((d - base) < period2) {
next_time = base + period2;
} else {
next_time = base + s->count;
}
break;
case 4:
case 5:
if (d < s->count) {
next_time = s->count;
} else if (d == s->count) {
next_time = s->count + 1;
} else {
return -1;
}
break;
}
/* convert to timer units */
next_time = s->count_load_time + muldiv64(next_time, NANOSECONDS_PER_SECOND,
PIT_FREQ);
/* fix potential rounding problems */
/* XXX: better solution: use a clock at PIT_FREQ Hz */
if (next_time <= current_time) {
next_time = current_time + 1;
}
return next_time;
}
void pit_get_channel_info_common(PITCommonState *s, PITChannelState *sc,
PITChannelInfo *info)
{
info->gate = sc->gate;
info->mode = sc->mode;
info->initial_count = sc->count;
info->out = pit_get_out(sc, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
}
void pit_get_channel_info(PITCommonState *pit, int channel, PITChannelInfo *info)
{
PITChannelState *s = &pit->channels[channel];
PITCommonClass *c = PIT_COMMON_GET_CLASS(pit);
c->get_channel_info(pit, s, info);
}
void pit_reset_common(PITCommonState *pit)
{
PITChannelState *s;
int i;
for (i = 0; i < 3; i++) {
s = &pit->channels[i];
s->mode = 3;
s->gate = (i != 2);
s->count_load_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
s->count = 0x10000;
if (i == 0 && !s->irq_disabled) {
s->next_transition_time =
pit_get_next_transition_time(s, s->count_load_time);
}
}
}
static void pit_common_realize(DeviceState *dev, Error **errp)
{
ISADevice *isadev = ISA_DEVICE(dev);
PITCommonState *pit = PIT_COMMON(dev);
isa_register_ioport(isadev, &pit->ioports, pit->iobase);
qdev_set_legacy_instance_id(dev, pit->iobase, 2);
}
static const VMStateDescription vmstate_pit_channel = {
.name = "pit channel",
.version_id = 2,
.minimum_version_id = 2,
.fields = (const VMStateField[]) {
VMSTATE_INT32(count, PITChannelState),
VMSTATE_UINT16(latched_count, PITChannelState),
VMSTATE_UINT8(count_latched, PITChannelState),
VMSTATE_UINT8(status_latched, PITChannelState),
VMSTATE_UINT8(status, PITChannelState),
VMSTATE_UINT8(read_state, PITChannelState),
VMSTATE_UINT8(write_state, PITChannelState),
VMSTATE_UINT8(write_latch, PITChannelState),
VMSTATE_UINT8(rw_mode, PITChannelState),
VMSTATE_UINT8(mode, PITChannelState),
VMSTATE_UINT8(bcd, PITChannelState),
VMSTATE_UINT8(gate, PITChannelState),
VMSTATE_INT64(count_load_time, PITChannelState),
VMSTATE_INT64(next_transition_time, PITChannelState),
VMSTATE_END_OF_LIST()
}
};
static int pit_dispatch_pre_save(void *opaque)
{
PITCommonState *s = opaque;
PITCommonClass *c = PIT_COMMON_GET_CLASS(s);
if (c->pre_save) {
c->pre_save(s);
}
return 0;
}
static int pit_dispatch_post_load(void *opaque, int version_id)
{
PITCommonState *s = opaque;
PITCommonClass *c = PIT_COMMON_GET_CLASS(s);
if (c->post_load) {
c->post_load(s);
}
return 0;
}
static const VMStateDescription vmstate_pit_common = {
.name = "i8254",
.version_id = 3,
.minimum_version_id = 2,
.pre_save = pit_dispatch_pre_save,
.post_load = pit_dispatch_post_load,
.fields = (const VMStateField[]) {
VMSTATE_UINT32_V(channels[0].irq_disabled, PITCommonState, 3),
VMSTATE_STRUCT_ARRAY(channels, PITCommonState, 3, 2,
vmstate_pit_channel, PITChannelState),
VMSTATE_INT64(channels[0].next_transition_time,
PITCommonState), /* formerly irq_timer */
VMSTATE_END_OF_LIST()
}
};
static const Property pit_common_properties[] = {
DEFINE_PROP_UINT32("iobase", PITCommonState, iobase, -1),
};
static void pit_common_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = pit_common_realize;
dc->vmsd = &vmstate_pit_common;
/*
* Reason: unlike ordinary ISA devices, the PIT may need to be
* wired to the HPET, and because of that, some wiring is always
* done by board code.
*/
dc->user_creatable = false;
device_class_set_props(dc, pit_common_properties);
}
static const TypeInfo pit_common_type = {
.name = TYPE_PIT_COMMON,
.parent = TYPE_ISA_DEVICE,
.instance_size = sizeof(PITCommonState),
.class_size = sizeof(PITCommonClass),
.class_init = pit_common_class_init,
.abstract = true,
};
static void register_devices(void)
{
type_register_static(&pit_common_type);
}
type_init(register_devices);
+313
View File
@@ -0,0 +1,313 @@
/*
* QEMU lowRISC Ibex Timer device
*
* Copyright (c) 2021 Western Digital
*
* For details check the documentation here:
* https://docs.opentitan.org/hw/ip/rv_timer/doc/
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "qemu/timer.h"
#include "hw/timer/ibex_timer.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-properties.h"
#include "target/riscv/cpu.h"
#include "migration/vmstate.h"
REG32(ALERT_TEST, 0x00)
FIELD(ALERT_TEST, FATAL_FAULT, 0, 1)
REG32(CTRL, 0x04)
FIELD(CTRL, ACTIVE, 0, 1)
REG32(CFG0, 0x100)
FIELD(CFG0, PRESCALE, 0, 12)
FIELD(CFG0, STEP, 16, 8)
REG32(LOWER0, 0x104)
REG32(UPPER0, 0x108)
REG32(COMPARE_LOWER0, 0x10C)
REG32(COMPARE_UPPER0, 0x110)
REG32(INTR_ENABLE, 0x114)
FIELD(INTR_ENABLE, IE_0, 0, 1)
REG32(INTR_STATE, 0x118)
FIELD(INTR_STATE, IS_0, 0, 1)
REG32(INTR_TEST, 0x11C)
FIELD(INTR_TEST, T_0, 0, 1)
static uint64_t cpu_riscv_read_rtc(uint32_t timebase_freq)
{
return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL),
timebase_freq, NANOSECONDS_PER_SECOND);
}
static void ibex_timer_update_irqs(IbexTimerState *s)
{
uint64_t value = s->timer_compare_lower0 |
((uint64_t)s->timer_compare_upper0 << 32);
uint64_t next, diff;
uint64_t now = cpu_riscv_read_rtc(s->timebase_freq);
if (!(s->timer_ctrl & R_CTRL_ACTIVE_MASK)) {
/* Timer isn't active */
return;
}
/* Update the CPUs mtimecmp */
s->mtimecmp = value;
if (s->mtimecmp <= now) {
/*
* If the mtimecmp was in the past raise the interrupt now.
*/
qemu_irq_raise(s->m_timer_irq);
if (s->timer_intr_enable & R_INTR_ENABLE_IE_0_MASK) {
s->timer_intr_state |= R_INTR_STATE_IS_0_MASK;
qemu_set_irq(s->irq, true);
}
return;
}
/* Setup a timer to trigger the interrupt in the future */
qemu_irq_lower(s->m_timer_irq);
qemu_set_irq(s->irq, false);
diff = s->mtimecmp - now;
next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
muldiv64(diff,
NANOSECONDS_PER_SECOND,
s->timebase_freq);
if (next < qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)) {
/* We overflowed the timer, just set it as large as we can */
timer_mod(s->mtimer, 0x7FFFFFFFFFFFFFFF);
} else {
timer_mod(s->mtimer, next);
}
}
static void ibex_timer_cb(void *opaque)
{
IbexTimerState *s = opaque;
qemu_irq_raise(s->m_timer_irq);
if (s->timer_intr_enable & R_INTR_ENABLE_IE_0_MASK) {
s->timer_intr_state |= R_INTR_STATE_IS_0_MASK;
qemu_set_irq(s->irq, true);
}
}
static void ibex_timer_reset(DeviceState *dev)
{
IbexTimerState *s = IBEX_TIMER(dev);
s->mtimer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
&ibex_timer_cb, s);
s->mtimecmp = 0;
s->timer_ctrl = 0x00000000;
s->timer_cfg0 = 0x00010000;
s->timer_compare_lower0 = 0xFFFFFFFF;
s->timer_compare_upper0 = 0xFFFFFFFF;
s->timer_intr_enable = 0x00000000;
s->timer_intr_state = 0x00000000;
ibex_timer_update_irqs(s);
}
static uint64_t ibex_timer_read(void *opaque, hwaddr addr,
unsigned int size)
{
IbexTimerState *s = opaque;
uint64_t now = cpu_riscv_read_rtc(s->timebase_freq);
uint64_t retvalue = 0;
switch (addr >> 2) {
case R_ALERT_TEST:
qemu_log_mask(LOG_GUEST_ERROR,
"Attempted to read ALERT_TEST, a write only register");
break;
case R_CTRL:
retvalue = s->timer_ctrl;
break;
case R_CFG0:
retvalue = s->timer_cfg0;
break;
case R_LOWER0:
retvalue = now;
break;
case R_UPPER0:
retvalue = now >> 32;
break;
case R_COMPARE_LOWER0:
retvalue = s->timer_compare_lower0;
break;
case R_COMPARE_UPPER0:
retvalue = s->timer_compare_upper0;
break;
case R_INTR_ENABLE:
retvalue = s->timer_intr_enable;
break;
case R_INTR_STATE:
retvalue = s->timer_intr_state;
break;
case R_INTR_TEST:
qemu_log_mask(LOG_GUEST_ERROR,
"Attempted to read INTR_TEST, a write only register");
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
return 0;
}
return retvalue;
}
static void ibex_timer_write(void *opaque, hwaddr addr,
uint64_t val64, unsigned int size)
{
IbexTimerState *s = opaque;
uint32_t val = val64;
switch (addr >> 2) {
case R_ALERT_TEST:
qemu_log_mask(LOG_UNIMP, "Alert triggering not supported");
break;
case R_CTRL:
s->timer_ctrl = val;
ibex_timer_update_irqs(s);
break;
case R_CFG0:
qemu_log_mask(LOG_UNIMP, "Changing prescale or step not supported");
s->timer_cfg0 = val;
break;
case R_LOWER0:
qemu_log_mask(LOG_UNIMP, "Changing timer value is not supported");
break;
case R_UPPER0:
qemu_log_mask(LOG_UNIMP, "Changing timer value is not supported");
break;
case R_COMPARE_LOWER0:
s->timer_compare_lower0 = val;
ibex_timer_update_irqs(s);
break;
case R_COMPARE_UPPER0:
s->timer_compare_upper0 = val;
ibex_timer_update_irqs(s);
break;
case R_INTR_ENABLE:
s->timer_intr_enable = val;
break;
case R_INTR_STATE:
/* Write 1 to clear */
s->timer_intr_state &= ~val;
break;
case R_INTR_TEST:
if (s->timer_intr_enable & val & R_INTR_ENABLE_IE_0_MASK) {
s->timer_intr_state |= R_INTR_STATE_IS_0_MASK;
qemu_set_irq(s->irq, true);
}
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
}
}
static const MemoryRegionOps ibex_timer_ops = {
.read = ibex_timer_read,
.write = ibex_timer_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.impl.min_access_size = 4,
.impl.max_access_size = 4,
};
static int ibex_timer_post_load(void *opaque, int version_id)
{
IbexTimerState *s = opaque;
ibex_timer_update_irqs(s);
return 0;
}
static const VMStateDescription vmstate_ibex_timer = {
.name = TYPE_IBEX_TIMER,
.version_id = 2,
.minimum_version_id = 2,
.post_load = ibex_timer_post_load,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(timer_ctrl, IbexTimerState),
VMSTATE_UINT32(timer_cfg0, IbexTimerState),
VMSTATE_UINT32(timer_compare_lower0, IbexTimerState),
VMSTATE_UINT32(timer_compare_upper0, IbexTimerState),
VMSTATE_UINT32(timer_intr_enable, IbexTimerState),
VMSTATE_UINT32(timer_intr_state, IbexTimerState),
VMSTATE_END_OF_LIST()
}
};
static const Property ibex_timer_properties[] = {
DEFINE_PROP_UINT32("timebase-freq", IbexTimerState, timebase_freq, 10000),
};
static void ibex_timer_init(Object *obj)
{
IbexTimerState *s = IBEX_TIMER(obj);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
memory_region_init_io(&s->mmio, obj, &ibex_timer_ops, s,
TYPE_IBEX_TIMER, 0x400);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
}
static void ibex_timer_realize(DeviceState *dev, Error **errp)
{
IbexTimerState *s = IBEX_TIMER(dev);
qdev_init_gpio_out(dev, &s->m_timer_irq, 1);
}
static void ibex_timer_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, ibex_timer_reset);
dc->vmsd = &vmstate_ibex_timer;
dc->realize = ibex_timer_realize;
device_class_set_props(dc, ibex_timer_properties);
}
static const TypeInfo ibex_timer_info = {
.name = TYPE_IBEX_TIMER,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(IbexTimerState),
.instance_init = ibex_timer_init,
.class_init = ibex_timer_class_init,
};
static void ibex_timer_register_types(void)
{
type_register_static(&ibex_timer_info);
}
type_init(ibex_timer_register_types)
+437
View File
@@ -0,0 +1,437 @@
/*
* IMX EPIT Timer
*
* Copyright (c) 2008 OK Labs
* Copyright (c) 2011 NICTA Pty Ltd
* Originally written by Hans Jiang
* Updated by Peter Chubb
* Updated by Jean-Christophe Dubois <[email protected]>
* Updated by Axel Heider
*
* This code is licensed under GPL version 2 or later. See
* the COPYING file in the top-level directory.
*
*/
#include "qemu/osdep.h"
#include "hw/timer/imx_epit.h"
#include "migration/vmstate.h"
#include "hw/core/irq.h"
#include "hw/misc/imx_ccm.h"
#include "qemu/module.h"
#include "qemu/log.h"
#include "trace.h"
static const char *imx_epit_reg_name(uint32_t reg)
{
switch (reg) {
case 0:
return "CR";
case 1:
return "SR";
case 2:
return "LR";
case 3:
return "CMP";
case 4:
return "CNT";
default:
return "[?]";
}
}
/*
* Exact clock frequencies vary from board to board.
* These are typical.
*/
static const IMXClk imx_epit_clocks[] = {
CLK_NONE, /* 00 disabled */
CLK_IPG, /* 01 ipg_clk, ~532MHz */
CLK_IPG_HIGH, /* 10 ipg_clk_highfreq */
CLK_32k, /* 11 ipg_clk_32k -- ~32kHz */
};
/*
* Update interrupt status
*/
static void imx_epit_update_int(IMXEPITState *s)
{
if ((s->sr & SR_OCIF) && (s->cr & CR_OCIEN) && (s->cr & CR_EN)) {
qemu_irq_raise(s->irq);
} else {
qemu_irq_lower(s->irq);
}
}
static uint32_t imx_epit_get_freq(IMXEPITState *s)
{
uint32_t clksrc = extract32(s->cr, CR_CLKSRC_SHIFT, CR_CLKSRC_BITS);
uint32_t prescaler = 1 + extract32(s->cr, CR_PRESCALE_SHIFT, CR_PRESCALE_BITS);
uint32_t f_in = imx_ccm_get_clock_frequency(s->ccm, imx_epit_clocks[clksrc]);
uint32_t freq = f_in / prescaler;
trace_imx_epit_get_freq(freq);
return freq;
}
/*
* This is called both on hardware (device) reset and software reset.
*/
static void imx_epit_reset(IMXEPITState *s, bool is_hard_reset)
{
/* Soft reset doesn't touch some bits; hard reset clears them */
if (is_hard_reset) {
s->cr = 0;
} else {
s->cr &= (CR_EN|CR_ENMOD|CR_STOPEN|CR_DOZEN|CR_WAITEN|CR_DBGEN);
}
s->sr = 0;
s->lr = EPIT_TIMER_MAX;
s->cmp = 0;
ptimer_transaction_begin(s->timer_cmp);
ptimer_transaction_begin(s->timer_reload);
/*
* The reset switches off the input clock, so even if the CR.EN is still
* set, the timers are no longer running.
*/
assert(imx_epit_get_freq(s) == 0);
ptimer_stop(s->timer_cmp);
ptimer_stop(s->timer_reload);
/* init both timers to EPIT_TIMER_MAX */
ptimer_set_limit(s->timer_cmp, EPIT_TIMER_MAX, 1);
ptimer_set_limit(s->timer_reload, EPIT_TIMER_MAX, 1);
ptimer_transaction_commit(s->timer_cmp);
ptimer_transaction_commit(s->timer_reload);
}
static uint64_t imx_epit_read(void *opaque, hwaddr offset, unsigned size)
{
IMXEPITState *s = IMX_EPIT(opaque);
uint32_t reg_value = 0;
switch (offset >> 2) {
case 0: /* Control Register */
reg_value = s->cr;
break;
case 1: /* Status Register */
reg_value = s->sr;
break;
case 2: /* LR - ticks*/
reg_value = s->lr;
break;
case 3: /* CMP */
reg_value = s->cmp;
break;
case 4: /* CNT */
reg_value = ptimer_get_count(s->timer_reload);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
HWADDR_PRIx "\n", TYPE_IMX_EPIT, __func__, offset);
break;
}
trace_imx_epit_read(imx_epit_reg_name(offset >> 2), reg_value);
return reg_value;
}
/*
* Must be called from a ptimer_transaction_begin/commit block for
* s->timer_cmp, but outside of a transaction block of s->timer_reload,
* so the proper counter value is read.
*/
static void imx_epit_update_compare_timer(IMXEPITState *s)
{
uint64_t counter = 0;
bool is_oneshot = false;
/*
* The compare timer only has to run if the timer peripheral is active
* and there is an input clock, Otherwise it can be switched off.
*/
bool is_active = (s->cr & CR_EN) && imx_epit_get_freq(s);
if (is_active) {
/*
* Calculate next timeout for compare timer. Reading the reload
* counter returns proper results only if pending transactions
* on it are committed here. Otherwise stale values are be read.
*/
counter = ptimer_get_count(s->timer_reload);
uint64_t limit = ptimer_get_limit(s->timer_cmp);
/*
* The compare timer is a periodic timer if the limit is at least
* the compare value. Otherwise it may fire at most once in the
* current round.
*/
is_oneshot = (limit < s->cmp);
if (counter >= s->cmp) {
/* The compare timer fires in the current round. */
counter -= s->cmp;
} else if (!is_oneshot) {
/*
* The compare timer fires after a reload, as it is below the
* compare value already in this round. Note that the counter
* value calculated below can be above the 32-bit limit, which
* is legal here because the compare timer is an internal
* helper ptimer only.
*/
counter += limit - s->cmp;
} else {
/*
* The compare timer won't fire in this round, and the limit is
* set to a value below the compare value. This practically means
* it will never fire, so it can be switched off.
*/
is_active = false;
}
}
/*
* Set the compare timer and let it run, or stop it. This is agnostic
* of CR.OCIEN bit, as this bit affects interrupt generation only. The
* compare timer needs to run even if no interrupts are to be generated,
* because the SR.OCIF bit must be updated also.
* Note that the timer might already be stopped or be running with
* counter values. However, finding out when an update is needed and
* when not is not trivial. It's much easier applying the setting again,
* as this does not harm either and the overhead is negligible.
*/
if (is_active) {
ptimer_set_count(s->timer_cmp, counter);
ptimer_run(s->timer_cmp, is_oneshot ? 1 : 0);
} else {
ptimer_stop(s->timer_cmp);
}
}
static void imx_epit_write_cr(IMXEPITState *s, uint32_t value)
{
uint32_t oldcr = s->cr;
s->cr = value & 0x03ffffff;
if (s->cr & CR_SWR) {
/*
* Reset clears CR.SWR again. It does not touch CR.EN, but the timers
* are still stopped because the input clock is disabled.
*/
imx_epit_reset(s, false);
} else {
uint32_t freq;
uint32_t toggled_cr_bits = oldcr ^ s->cr;
/* re-initialize the limits if CR.RLD has changed */
bool set_limit = toggled_cr_bits & CR_RLD;
/* set the counter if the timer got just enabled and CR.ENMOD is set */
bool is_switched_on = (toggled_cr_bits & s->cr) & CR_EN;
bool set_counter = is_switched_on && (s->cr & CR_ENMOD);
ptimer_transaction_begin(s->timer_cmp);
ptimer_transaction_begin(s->timer_reload);
freq = imx_epit_get_freq(s);
if (freq) {
ptimer_set_freq(s->timer_reload, freq);
ptimer_set_freq(s->timer_cmp, freq);
}
if (set_limit || set_counter) {
uint64_t limit = (s->cr & CR_RLD) ? s->lr : EPIT_TIMER_MAX;
ptimer_set_limit(s->timer_reload, limit, set_counter ? 1 : 0);
if (set_limit) {
ptimer_set_limit(s->timer_cmp, limit, 0);
}
}
/*
* If there is an input clock and the peripheral is enabled, then
* ensure the wall clock timer is ticking. Otherwise stop the timers.
* The compare timer will be updated later.
*/
if (freq && (s->cr & CR_EN)) {
ptimer_run(s->timer_reload, 0);
} else {
ptimer_stop(s->timer_reload);
}
/* Commit changes to reload timer, so they can propagate. */
ptimer_transaction_commit(s->timer_reload);
/* Update compare timer based on the committed reload timer value. */
imx_epit_update_compare_timer(s);
ptimer_transaction_commit(s->timer_cmp);
}
/*
* The interrupt state can change due to:
* - reset clears both SR.OCIF and CR.OCIE
* - write to CR.EN or CR.OCIE
*/
imx_epit_update_int(s);
}
static void imx_epit_write_sr(IMXEPITState *s, uint32_t value)
{
/* writing 1 to SR.OCIF clears this bit and turns the interrupt off */
if (value & SR_OCIF) {
s->sr = 0; /* SR.OCIF is the only bit in this register anyway */
imx_epit_update_int(s);
}
}
static void imx_epit_write_lr(IMXEPITState *s, uint32_t value)
{
s->lr = value;
ptimer_transaction_begin(s->timer_cmp);
ptimer_transaction_begin(s->timer_reload);
if (s->cr & CR_RLD) {
/* Also set the limit if the LRD bit is set */
/* If IOVW bit is set then set the timer value */
ptimer_set_limit(s->timer_reload, s->lr, s->cr & CR_IOVW);
ptimer_set_limit(s->timer_cmp, s->lr, 0);
} else if (s->cr & CR_IOVW) {
/* If IOVW bit is set then set the timer value */
ptimer_set_count(s->timer_reload, s->lr);
}
/* Commit the changes to s->timer_reload, so they can propagate. */
ptimer_transaction_commit(s->timer_reload);
/* Update the compare timer based on the committed reload timer value. */
imx_epit_update_compare_timer(s);
ptimer_transaction_commit(s->timer_cmp);
}
static void imx_epit_write_cmp(IMXEPITState *s, uint32_t value)
{
s->cmp = value;
/* Update the compare timer based on the committed reload timer value. */
ptimer_transaction_begin(s->timer_cmp);
imx_epit_update_compare_timer(s);
ptimer_transaction_commit(s->timer_cmp);
}
static void imx_epit_write(void *opaque, hwaddr offset, uint64_t value,
unsigned size)
{
IMXEPITState *s = IMX_EPIT(opaque);
trace_imx_epit_write(imx_epit_reg_name(offset >> 2), value);
switch (offset >> 2) {
case 0: /* CR */
imx_epit_write_cr(s, (uint32_t)value);
break;
case 1: /* SR */
imx_epit_write_sr(s, (uint32_t)value);
break;
case 2: /* LR */
imx_epit_write_lr(s, (uint32_t)value);
break;
case 3: /* CMP */
imx_epit_write_cmp(s, (uint32_t)value);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
HWADDR_PRIx "\n", TYPE_IMX_EPIT, __func__, offset);
break;
}
}
static void imx_epit_cmp(void *opaque)
{
IMXEPITState *s = IMX_EPIT(opaque);
/* The cmp ptimer can't be running when the peripheral is disabled */
assert(s->cr & CR_EN);
trace_imx_epit_cmp(s->sr);
/* Set interrupt status bit SR.OCIF and update the interrupt state */
s->sr |= SR_OCIF;
imx_epit_update_int(s);
}
static void imx_epit_reload(void *opaque)
{
/* No action required on rollover of timer_reload */
}
static const MemoryRegionOps imx_epit_ops = {
.read = imx_epit_read,
.write = imx_epit_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
static const VMStateDescription vmstate_imx_timer_epit = {
.name = TYPE_IMX_EPIT,
.version_id = 3,
.minimum_version_id = 3,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(cr, IMXEPITState),
VMSTATE_UINT32(sr, IMXEPITState),
VMSTATE_UINT32(lr, IMXEPITState),
VMSTATE_UINT32(cmp, IMXEPITState),
VMSTATE_PTIMER(timer_reload, IMXEPITState),
VMSTATE_PTIMER(timer_cmp, IMXEPITState),
VMSTATE_END_OF_LIST()
}
};
static void imx_epit_realize(DeviceState *dev, Error **errp)
{
IMXEPITState *s = IMX_EPIT(dev);
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
sysbus_init_irq(sbd, &s->irq);
memory_region_init_io(&s->iomem, OBJECT(s), &imx_epit_ops, s, TYPE_IMX_EPIT,
0x00001000);
sysbus_init_mmio(sbd, &s->iomem);
/*
* The reload timer keeps running when the peripheral is enabled. It is a
* kind of wall clock that does not generate any interrupts. The callback
* needs to be provided, but it does nothing as the ptimer already supports
* all necessary reloading functionality.
*/
s->timer_reload = ptimer_init(imx_epit_reload, s, PTIMER_POLICY_LEGACY);
/*
* The compare timer is running only when the peripheral configuration is
* in a state that will generate compare interrupts.
*/
s->timer_cmp = ptimer_init(imx_epit_cmp, s, PTIMER_POLICY_LEGACY);
}
static void imx_epit_dev_reset(DeviceState *dev)
{
IMXEPITState *s = IMX_EPIT(dev);
imx_epit_reset(s, true);
}
static void imx_epit_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = imx_epit_realize;
device_class_set_legacy_reset(dc, imx_epit_dev_reset);
dc->vmsd = &vmstate_imx_timer_epit;
dc->desc = "i.MX periodic timer";
}
static const TypeInfo imx_epit_info = {
.name = TYPE_IMX_EPIT,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(IMXEPITState),
.class_init = imx_epit_class_init,
};
static void imx_epit_register_types(void)
{
type_register_static(&imx_epit_info);
}
type_init(imx_epit_register_types)
+647
View File
@@ -0,0 +1,647 @@
/*
* IMX GPT Timer
*
* Copyright (c) 2008 OK Labs
* Copyright (c) 2011 NICTA Pty Ltd
* Originally written by Hans Jiang
* Updated by Peter Chubb
* Updated by Jean-Christophe Dubois <[email protected]>
* Updated by Gaurav Sharma <[email protected]>
*
* This code is licensed under GPL version 2 or later. See
* the COPYING file in the top-level directory.
*
*/
#include "qemu/osdep.h"
#include "hw/core/irq.h"
#include "hw/timer/imx_gpt.h"
#include "migration/vmstate.h"
#include "qemu/module.h"
#include "qemu/log.h"
#include "trace.h"
static const char *imx_gpt_reg_name(uint32_t reg)
{
switch (reg) {
case 0:
return "CR";
case 1:
return "PR";
case 2:
return "SR";
case 3:
return "IR";
case 4:
return "OCR1";
case 5:
return "OCR2";
case 6:
return "OCR3";
case 7:
return "ICR1";
case 8:
return "ICR2";
case 9:
return "CNT";
default:
return "[?]";
}
}
static const VMStateDescription vmstate_imx_timer_gpt = {
.name = TYPE_IMX_GPT,
.version_id = 3,
.minimum_version_id = 3,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(cr, IMXGPTState),
VMSTATE_UINT32(pr, IMXGPTState),
VMSTATE_UINT32(sr, IMXGPTState),
VMSTATE_UINT32(ir, IMXGPTState),
VMSTATE_UINT32(ocr1, IMXGPTState),
VMSTATE_UINT32(ocr2, IMXGPTState),
VMSTATE_UINT32(ocr3, IMXGPTState),
VMSTATE_UINT32(icr1, IMXGPTState),
VMSTATE_UINT32(icr2, IMXGPTState),
VMSTATE_UINT32(cnt, IMXGPTState),
VMSTATE_UINT32(next_timeout, IMXGPTState),
VMSTATE_UINT32(next_int, IMXGPTState),
VMSTATE_UINT32(freq, IMXGPTState),
VMSTATE_PTIMER(timer, IMXGPTState),
VMSTATE_END_OF_LIST()
}
};
static const IMXClk imx25_gpt_clocks[] = {
CLK_NONE, /* 000 No clock source */
CLK_IPG, /* 001 ipg_clk, 532MHz*/
CLK_IPG_HIGH, /* 010 ipg_clk_highfreq */
CLK_NONE, /* 011 not defined */
CLK_32k, /* 100 ipg_clk_32k */
CLK_32k, /* 101 ipg_clk_32k */
CLK_32k, /* 110 ipg_clk_32k */
CLK_32k, /* 111 ipg_clk_32k */
};
static const IMXClk imx31_gpt_clocks[] = {
CLK_NONE, /* 000 No clock source */
CLK_IPG, /* 001 ipg_clk, 532MHz*/
CLK_IPG_HIGH, /* 010 ipg_clk_highfreq */
CLK_NONE, /* 011 not defined */
CLK_32k, /* 100 ipg_clk_32k */
CLK_NONE, /* 101 not defined */
CLK_NONE, /* 110 not defined */
CLK_NONE, /* 111 not defined */
};
static const IMXClk imx6_gpt_clocks[] = {
CLK_NONE, /* 000 No clock source */
CLK_IPG, /* 001 ipg_clk, 532MHz*/
CLK_IPG_HIGH, /* 010 ipg_clk_highfreq */
CLK_EXT, /* 011 External clock */
CLK_32k, /* 100 ipg_clk_32k */
CLK_HIGH_DIV, /* 101 reference clock / 8 */
CLK_NONE, /* 110 not defined */
CLK_HIGH, /* 111 reference clock */
};
static const IMXClk imx6ul_gpt_clocks[] = {
CLK_NONE, /* 000 No clock source */
CLK_IPG, /* 001 ipg_clk, 532MHz*/
CLK_IPG_HIGH, /* 010 ipg_clk_highfreq */
CLK_EXT, /* 011 External clock */
CLK_32k, /* 100 ipg_clk_32k */
CLK_NONE, /* 101 not defined */
CLK_NONE, /* 110 not defined */
CLK_NONE, /* 111 not defined */
};
static const IMXClk imx7_gpt_clocks[] = {
CLK_NONE, /* 000 No clock source */
CLK_IPG, /* 001 ipg_clk, 532MHz*/
CLK_IPG_HIGH, /* 010 ipg_clk_highfreq */
CLK_EXT, /* 011 External clock */
CLK_32k, /* 100 ipg_clk_32k */
CLK_HIGH, /* 101 reference clock */
CLK_NONE, /* 110 not defined */
CLK_NONE, /* 111 not defined */
};
static const IMXClk imx8mp_gpt_clocks[] = {
CLK_NONE, /* 000 No clock source */
CLK_IPG, /* 001 ipg_clk, 532MHz */
CLK_IPG_HIGH, /* 010 ipg_clk_highfreq */
CLK_EXT, /* 011 External clock */
CLK_32k, /* 100 ipg_clk_32k */
CLK_HIGH, /* 101 ipg_clk_16M */
CLK_NONE, /* 110 not defined */
CLK_NONE, /* 111 not defined */
};
static const IMXClk imx8mm_gpt_clocks[] = {
CLK_NONE, /* 000 No clock source */
CLK_IPG, /* 001 ipg_clk, 532MHz */
CLK_IPG_HIGH, /* 010 ipg_clk_highfreq */
CLK_EXT, /* 011 External clock */
CLK_32k, /* 100 ipg_clk_32k */
CLK_HIGH, /* 101 ipg_clk_16M */
CLK_NONE, /* 110 not defined */
CLK_NONE, /* 111 not defined */
};
/* Must be called from within ptimer_transaction_begin/commit block */
static void imx_gpt_set_freq(IMXGPTState *s)
{
uint32_t clksrc = extract32(s->cr, GPT_CR_CLKSRC_SHIFT, 3);
s->freq = imx_ccm_get_clock_frequency(s->ccm,
s->clocks[clksrc]) / (1 + s->pr);
trace_imx_gpt_set_freq(clksrc, s->freq);
if (s->freq) {
ptimer_set_freq(s->timer, s->freq);
}
}
static void imx_gpt_update_int(IMXGPTState *s)
{
if ((s->sr & s->ir) && (s->cr & GPT_CR_EN)) {
qemu_irq_raise(s->irq);
} else {
qemu_irq_lower(s->irq);
}
}
static uint32_t imx_gpt_update_count(IMXGPTState *s)
{
s->cnt = s->next_timeout - (uint32_t)ptimer_get_count(s->timer);
return s->cnt;
}
static inline uint32_t imx_gpt_find_limit(uint32_t count, uint32_t reg,
uint32_t timeout)
{
if ((count < reg) && (timeout > reg)) {
timeout = reg;
}
return timeout;
}
/* Must be called from within ptimer_transaction_begin/commit block */
static void imx_gpt_compute_next_timeout(IMXGPTState *s, bool event)
{
uint32_t timeout = GPT_TIMER_MAX;
uint32_t count;
long long limit;
if (!(s->cr & GPT_CR_EN)) {
/* if not enabled just return */
return;
}
/* update the count */
count = imx_gpt_update_count(s);
if (event) {
/*
* This is an event (the ptimer reached 0 and stopped), and the
* timer counter is now equal to s->next_timeout.
*/
if (!(s->cr & GPT_CR_FRR) && (count == s->ocr1)) {
/* We are in restart mode and we crossed the compare channel 1
* value. We need to reset the counter to 0.
*/
count = s->cnt = s->next_timeout = 0;
} else if (count == GPT_TIMER_MAX) {
/* We reached GPT_TIMER_MAX so we need to rollover */
count = s->cnt = s->next_timeout = 0;
}
}
/* now, find the next timeout related to count */
if (s->ir & GPT_IR_OF1IE) {
timeout = imx_gpt_find_limit(count, s->ocr1, timeout);
}
if (s->ir & GPT_IR_OF2IE) {
timeout = imx_gpt_find_limit(count, s->ocr2, timeout);
}
if (s->ir & GPT_IR_OF3IE) {
timeout = imx_gpt_find_limit(count, s->ocr3, timeout);
}
/* find the next set of interrupts to raise for next timer event */
s->next_int = 0;
if ((s->ir & GPT_IR_OF1IE) && (timeout == s->ocr1)) {
s->next_int |= GPT_SR_OF1;
}
if ((s->ir & GPT_IR_OF2IE) && (timeout == s->ocr2)) {
s->next_int |= GPT_SR_OF2;
}
if ((s->ir & GPT_IR_OF3IE) && (timeout == s->ocr3)) {
s->next_int |= GPT_SR_OF3;
}
if ((s->ir & GPT_IR_ROVIE) && (timeout == GPT_TIMER_MAX)) {
s->next_int |= GPT_SR_ROV;
}
/* the new range to count down from */
limit = timeout - imx_gpt_update_count(s);
if (limit < 0) {
/*
* if we reach here, then QEMU is running too slow and we pass the
* timeout limit while computing it. Let's deliver the interrupt
* and compute a new limit.
*/
s->sr |= s->next_int;
imx_gpt_compute_next_timeout(s, event);
imx_gpt_update_int(s);
} else {
/* New timeout value */
s->next_timeout = timeout;
/* reset the limit to the computed range */
ptimer_set_limit(s->timer, limit, 1);
}
}
static uint64_t imx_gpt_read(void *opaque, hwaddr offset, unsigned size)
{
IMXGPTState *s = IMX_GPT(opaque);
uint32_t reg_value = 0;
switch (offset >> 2) {
case 0: /* Control Register */
reg_value = s->cr;
break;
case 1: /* prescaler */
reg_value = s->pr;
break;
case 2: /* Status Register */
reg_value = s->sr;
break;
case 3: /* Interrupt Register */
reg_value = s->ir;
break;
case 4: /* Output Compare Register 1 */
reg_value = s->ocr1;
break;
case 5: /* Output Compare Register 2 */
reg_value = s->ocr2;
break;
case 6: /* Output Compare Register 3 */
reg_value = s->ocr3;
break;
case 7: /* input Capture Register 1 */
qemu_log_mask(LOG_UNIMP, "[%s]%s: icr1 feature is not implemented\n",
TYPE_IMX_GPT, __func__);
reg_value = s->icr1;
break;
case 8: /* input Capture Register 2 */
qemu_log_mask(LOG_UNIMP, "[%s]%s: icr2 feature is not implemented\n",
TYPE_IMX_GPT, __func__);
reg_value = s->icr2;
break;
case 9: /* cnt */
imx_gpt_update_count(s);
reg_value = s->cnt;
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
HWADDR_PRIx "\n", TYPE_IMX_GPT, __func__, offset);
break;
}
trace_imx_gpt_read(imx_gpt_reg_name(offset >> 2), reg_value);
return reg_value;
}
static void imx_gpt_reset_common(IMXGPTState *s, bool is_soft_reset)
{
ptimer_transaction_begin(s->timer);
/* stop timer */
ptimer_stop(s->timer);
/* Soft reset and hard reset differ only in their handling of the CR
* register -- soft reset preserves the values of some bits there.
*/
if (is_soft_reset) {
/* Clear all CR bits except those that are preserved by soft reset. */
s->cr &= GPT_CR_EN | GPT_CR_ENMOD | GPT_CR_STOPEN | GPT_CR_DOZEN |
GPT_CR_WAITEN | GPT_CR_DBGEN |
(GPT_CR_CLKSRC_MASK << GPT_CR_CLKSRC_SHIFT);
} else {
s->cr = 0;
}
s->sr = 0;
s->pr = 0;
s->ir = 0;
s->cnt = 0;
s->ocr1 = GPT_TIMER_MAX;
s->ocr2 = GPT_TIMER_MAX;
s->ocr3 = GPT_TIMER_MAX;
s->icr1 = 0;
s->icr2 = 0;
s->next_timeout = GPT_TIMER_MAX;
s->next_int = 0;
/* compute new freq */
imx_gpt_set_freq(s);
/* reset the limit to GPT_TIMER_MAX */
ptimer_set_limit(s->timer, GPT_TIMER_MAX, 1);
/* if the timer is still enabled, restart it */
if (s->freq && (s->cr & GPT_CR_EN)) {
ptimer_run(s->timer, 1);
}
ptimer_transaction_commit(s->timer);
}
static void imx_gpt_soft_reset(DeviceState *dev)
{
IMXGPTState *s = IMX_GPT(dev);
imx_gpt_reset_common(s, true);
}
static void imx_gpt_reset(DeviceState *dev)
{
IMXGPTState *s = IMX_GPT(dev);
imx_gpt_reset_common(s, false);
}
static void imx_gpt_write(void *opaque, hwaddr offset, uint64_t value,
unsigned size)
{
IMXGPTState *s = IMX_GPT(opaque);
uint32_t oldreg;
trace_imx_gpt_write(imx_gpt_reg_name(offset >> 2), (uint32_t)value);
switch (offset >> 2) {
case 0:
oldreg = s->cr;
s->cr = value & ~0x7c14;
if (s->cr & GPT_CR_SWR) { /* force reset */
/* handle the reset */
imx_gpt_soft_reset(DEVICE(s));
} else {
/* set our freq, as the source might have changed */
ptimer_transaction_begin(s->timer);
imx_gpt_set_freq(s);
if ((oldreg ^ s->cr) & GPT_CR_EN) {
if (s->cr & GPT_CR_EN) {
if (s->cr & GPT_CR_ENMOD) {
s->next_timeout = GPT_TIMER_MAX;
ptimer_set_count(s->timer, GPT_TIMER_MAX);
imx_gpt_compute_next_timeout(s, false);
}
ptimer_run(s->timer, 1);
} else {
/* stop timer */
ptimer_stop(s->timer);
}
}
ptimer_transaction_commit(s->timer);
}
break;
case 1: /* Prescaler */
s->pr = value & 0xfff;
ptimer_transaction_begin(s->timer);
imx_gpt_set_freq(s);
ptimer_transaction_commit(s->timer);
break;
case 2: /* SR */
s->sr &= ~(value & 0x3f);
imx_gpt_update_int(s);
break;
case 3: /* IR -- interrupt register */
s->ir = value & 0x3f;
imx_gpt_update_int(s);
ptimer_transaction_begin(s->timer);
imx_gpt_compute_next_timeout(s, false);
ptimer_transaction_commit(s->timer);
break;
case 4: /* OCR1 -- output compare register */
s->ocr1 = value;
ptimer_transaction_begin(s->timer);
/* In non-freerun mode, reset count when this register is written */
if (!(s->cr & GPT_CR_FRR)) {
s->next_timeout = GPT_TIMER_MAX;
ptimer_set_limit(s->timer, GPT_TIMER_MAX, 1);
}
/* compute the new timeout */
imx_gpt_compute_next_timeout(s, false);
ptimer_transaction_commit(s->timer);
break;
case 5: /* OCR2 -- output compare register */
s->ocr2 = value;
/* compute the new timeout */
ptimer_transaction_begin(s->timer);
imx_gpt_compute_next_timeout(s, false);
ptimer_transaction_commit(s->timer);
break;
case 6: /* OCR3 -- output compare register */
s->ocr3 = value;
/* compute the new timeout */
ptimer_transaction_begin(s->timer);
imx_gpt_compute_next_timeout(s, false);
ptimer_transaction_commit(s->timer);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
HWADDR_PRIx "\n", TYPE_IMX_GPT, __func__, offset);
break;
}
}
static void imx_gpt_timeout(void *opaque)
{
IMXGPTState *s = IMX_GPT(opaque);
trace_imx_gpt_timeout();
s->sr |= s->next_int;
s->next_int = 0;
imx_gpt_compute_next_timeout(s, true);
imx_gpt_update_int(s);
if (s->freq && (s->cr & GPT_CR_EN)) {
ptimer_run(s->timer, 1);
}
}
static const MemoryRegionOps imx_gpt_ops = {
.read = imx_gpt_read,
.write = imx_gpt_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
static void imx_gpt_realize(DeviceState *dev, Error **errp)
{
IMXGPTState *s = IMX_GPT(dev);
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
sysbus_init_irq(sbd, &s->irq);
memory_region_init_io(&s->iomem, OBJECT(s), &imx_gpt_ops, s, TYPE_IMX_GPT,
0x00001000);
sysbus_init_mmio(sbd, &s->iomem);
s->timer = ptimer_init(imx_gpt_timeout, s, PTIMER_POLICY_LEGACY);
}
static void imx_gpt_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = imx_gpt_realize;
device_class_set_legacy_reset(dc, imx_gpt_reset);
dc->vmsd = &vmstate_imx_timer_gpt;
dc->desc = "i.MX general timer";
}
static void imx25_gpt_init(Object *obj)
{
IMXGPTState *s = IMX_GPT(obj);
s->clocks = imx25_gpt_clocks;
}
static void imx31_gpt_init(Object *obj)
{
IMXGPTState *s = IMX_GPT(obj);
s->clocks = imx31_gpt_clocks;
}
static void imx6_gpt_init(Object *obj)
{
IMXGPTState *s = IMX_GPT(obj);
s->clocks = imx6_gpt_clocks;
}
static void imx6ul_gpt_init(Object *obj)
{
IMXGPTState *s = IMX_GPT(obj);
s->clocks = imx6ul_gpt_clocks;
}
static void imx7_gpt_init(Object *obj)
{
IMXGPTState *s = IMX_GPT(obj);
s->clocks = imx7_gpt_clocks;
}
static void imx8mp_gpt_init(Object *obj)
{
IMXGPTState *s = IMX_GPT(obj);
s->clocks = imx8mp_gpt_clocks;
}
static void imx8mm_gpt_init(Object *obj)
{
IMXGPTState *s = IMX_GPT(obj);
s->clocks = imx8mm_gpt_clocks;
}
static const TypeInfo imx25_gpt_info = {
.name = TYPE_IMX25_GPT,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(IMXGPTState),
.instance_init = imx25_gpt_init,
.class_init = imx_gpt_class_init,
};
static const TypeInfo imx31_gpt_info = {
.name = TYPE_IMX31_GPT,
.parent = TYPE_IMX25_GPT,
.instance_init = imx31_gpt_init,
};
static const TypeInfo imx6_gpt_info = {
.name = TYPE_IMX6_GPT,
.parent = TYPE_IMX25_GPT,
.instance_init = imx6_gpt_init,
};
static const TypeInfo imx6ul_gpt_info = {
.name = TYPE_IMX6UL_GPT,
.parent = TYPE_IMX25_GPT,
.instance_init = imx6ul_gpt_init,
};
static const TypeInfo imx7_gpt_info = {
.name = TYPE_IMX7_GPT,
.parent = TYPE_IMX25_GPT,
.instance_init = imx7_gpt_init,
};
static const TypeInfo imx8mp_gpt_info = {
.name = TYPE_IMX8MP_GPT,
.parent = TYPE_IMX25_GPT,
.instance_init = imx8mp_gpt_init,
};
static const TypeInfo imx8mm_gpt_info = {
.name = TYPE_IMX8MM_GPT,
.parent = TYPE_IMX25_GPT,
.instance_init = imx8mm_gpt_init,
};
static void imx_gpt_register_types(void)
{
type_register_static(&imx25_gpt_info);
type_register_static(&imx31_gpt_info);
type_register_static(&imx6_gpt_info);
type_register_static(&imx6ul_gpt_info);
type_register_static(&imx7_gpt_info);
type_register_static(&imx8mp_gpt_info);
type_register_static(&imx8mm_gpt_info);
}
type_init(imx_gpt_register_types)
+38
View File
@@ -0,0 +1,38 @@
system_ss.add(when: 'CONFIG_A9_GTIMER', if_true: files('a9gtimer.c'))
system_ss.add(when: 'CONFIG_ALLWINNER_A10_PIT', if_true: files('allwinner-a10-pit.c'))
system_ss.add(when: 'CONFIG_ARM_MPTIMER', if_true: files('arm_mptimer.c'))
system_ss.add(when: 'CONFIG_ARM_TIMER', if_true: files('arm_timer.c'))
system_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('armv7m_systick.c'))
system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_timer.c'))
system_ss.add(when: 'CONFIG_CADENCE', if_true: files('cadence_ttc.c'))
system_ss.add(when: 'CONFIG_CMSDK_APB_DUALTIMER', if_true: files('cmsdk-apb-dualtimer.c'))
system_ss.add(when: 'CONFIG_CMSDK_APB_TIMER', if_true: files('cmsdk-apb-timer.c'))
system_ss.add(when: 'CONFIG_RENESAS_TMR', if_true: files('renesas_tmr.c'))
system_ss.add(when: 'CONFIG_RENESAS_CMT', if_true: files('renesas_cmt.c'))
system_ss.add(when: 'CONFIG_DIGIC', if_true: files('digic-timer.c'))
system_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_mct.c'))
system_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_pwm.c'))
system_ss.add(when: 'CONFIG_GRLIB', if_true: files('grlib_gptimer.c'))
system_ss.add(when: 'CONFIG_HPET_C', if_true: files('hpet.c'))
system_ss.add(when: 'CONFIG_I8254', if_true: files('i8254_common.c', 'i8254.c'))
system_ss.add(when: 'CONFIG_IMX', if_true: files('imx_epit.c'))
system_ss.add(when: 'CONFIG_IMX', if_true: files('imx_gpt.c'))
system_ss.add(when: 'CONFIG_MIPS_CPS', if_true: files('mips_gictimer.c'))
system_ss.add(when: 'CONFIG_MSF2', if_true: files('mss-timer.c'))
system_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_timer.c'))
system_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('nrf51_timer.c'))
system_ss.add(when: 'CONFIG_PXA2XX_TIMER', if_true: files('pxa2xx_timer.c'))
system_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_systmr.c'))
system_ss.add(when: 'CONFIG_SH_TIMER', if_true: files('sh_timer.c'))
system_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_timer.c'))
system_ss.add(when: 'CONFIG_SSE_COUNTER', if_true: files('sse-counter.c'))
system_ss.add(when: 'CONFIG_SSE_TIMER', if_true: files('sse-timer.c'))
system_ss.add(when: 'CONFIG_STELLARIS_GPTM', if_true: files('stellaris-gptm.c'))
system_ss.add(when: 'CONFIG_STM32F2XX_TIMER', if_true: files('stm32f2xx_timer.c'))
system_ss.add(when: 'CONFIG_XILINX', if_true: files('xilinx_timer.c'))
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'))
system_ss.add(when: 'CONFIG_HEX_QTIMER', if_true: files('qct-qtimer.c'))
+145
View File
@@ -0,0 +1,145 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) 2016 Imagination Technologies
*/
#include "qemu/osdep.h"
#include "qemu/timer.h"
#include "hw/timer/mips_gictimer.h"
#define TIMER_PERIOD 10 /* 10 ns period for 100 Mhz frequency */
uint32_t mips_gictimer_get_freq(MIPSGICTimerState *gic)
{
return NANOSECONDS_PER_SECOND / TIMER_PERIOD;
}
static void gic_vptimer_update(MIPSGICTimerState *gictimer,
uint32_t vp_index, uint64_t now)
{
uint64_t next;
uint32_t wait;
wait = gictimer->vptimers[vp_index].comparelo - gictimer->sh_counterlo -
(uint32_t)(now / TIMER_PERIOD);
next = now + (uint64_t)wait * TIMER_PERIOD;
timer_mod(gictimer->vptimers[vp_index].qtimer, next);
}
static void gic_vptimer_expire(MIPSGICTimerState *gictimer, uint32_t vp_index,
uint64_t now)
{
if (gictimer->countstop) {
/* timer stopped */
return;
}
gictimer->cb(gictimer->opaque, vp_index);
gic_vptimer_update(gictimer, vp_index, now);
}
static void gic_vptimer_cb(void *opaque)
{
MIPSGICTimerVPState *vptimer = opaque;
MIPSGICTimerState *gictimer = vptimer->gictimer;
gic_vptimer_expire(gictimer, vptimer->vp_index,
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
}
uint32_t mips_gictimer_get_sh_count(MIPSGICTimerState *gictimer)
{
int i;
if (gictimer->countstop) {
return gictimer->sh_counterlo;
} else {
uint64_t now;
now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
for (i = 0; i < gictimer->num_vps; i++) {
if (timer_pending(gictimer->vptimers[i].qtimer)
&& timer_expired(gictimer->vptimers[i].qtimer, now)) {
/* The timer has already expired. */
gic_vptimer_expire(gictimer, i, now);
}
}
return gictimer->sh_counterlo + (uint32_t)(now / TIMER_PERIOD);
}
}
void mips_gictimer_store_sh_count(MIPSGICTimerState *gictimer, uint64_t count)
{
int i;
uint64_t now;
if (gictimer->countstop || !gictimer->vptimers[0].qtimer) {
gictimer->sh_counterlo = count;
} else {
/* Store new count register */
now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
gictimer->sh_counterlo = count - (uint32_t)(now / TIMER_PERIOD);
/* Update timer timer */
for (i = 0; i < gictimer->num_vps; i++) {
gic_vptimer_update(gictimer, i, now);
}
}
}
uint32_t mips_gictimer_get_vp_compare(MIPSGICTimerState *gictimer,
uint32_t vp_index)
{
return gictimer->vptimers[vp_index].comparelo;
}
void mips_gictimer_store_vp_compare(MIPSGICTimerState *gictimer,
uint32_t vp_index, uint64_t compare)
{
gictimer->vptimers[vp_index].comparelo = (uint32_t) compare;
gic_vptimer_update(gictimer, vp_index,
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
}
uint8_t mips_gictimer_get_countstop(MIPSGICTimerState *gictimer)
{
return gictimer->countstop;
}
void mips_gictimer_start_count(MIPSGICTimerState *gictimer)
{
gictimer->countstop = 0;
mips_gictimer_store_sh_count(gictimer, gictimer->sh_counterlo);
}
void mips_gictimer_stop_count(MIPSGICTimerState *gictimer)
{
int i;
gictimer->countstop = 1;
/* Store the current value */
gictimer->sh_counterlo +=
(uint32_t)(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / TIMER_PERIOD);
for (i = 0; i < gictimer->num_vps; i++) {
timer_del(gictimer->vptimers[i].qtimer);
}
}
MIPSGICTimerState *mips_gictimer_init(void *opaque, uint32_t nvps,
MIPSGICTimerCB *cb)
{
int i;
MIPSGICTimerState *gictimer = g_new(MIPSGICTimerState, 1);
gictimer->vptimers = g_new(MIPSGICTimerVPState, nvps);
gictimer->countstop = 1;
gictimer->num_vps = nvps;
gictimer->opaque = opaque;
gictimer->cb = cb;
for (i = 0; i < nvps; i++) {
gictimer->vptimers[i].gictimer = gictimer;
gictimer->vptimers[i].vp_index = i;
gictimer->vptimers[i].qtimer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
&gic_vptimer_cb,
&gictimer->vptimers[i]);
}
return gictimer;
}
+307
View File
@@ -0,0 +1,307 @@
/*
* Block model of System timer present in
* Microsemi's SmartFusion2 and SmartFusion SoCs.
*
* Copyright (c) 2017 Subbaraya Sundeep <[email protected]>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "qemu/osdep.h"
#include "qemu/module.h"
#include "qemu/log.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-properties.h"
#include "hw/timer/mss-timer.h"
#include "migration/vmstate.h"
#ifndef MSS_TIMER_ERR_DEBUG
#define MSS_TIMER_ERR_DEBUG 0
#endif
#define DB_PRINT_L(lvl, fmt, args...) do { \
if (MSS_TIMER_ERR_DEBUG >= lvl) { \
qemu_log("%s: " fmt "\n", __func__, ## args); \
} \
} while (0)
#define DB_PRINT(fmt, args...) DB_PRINT_L(1, fmt, ## args)
#define R_TIM_VAL 0
#define R_TIM_LOADVAL 1
#define R_TIM_BGLOADVAL 2
#define R_TIM_CTRL 3
#define R_TIM_RIS 4
#define R_TIM_MIS 5
#define TIMER_CTRL_ENBL (1 << 0)
#define TIMER_CTRL_ONESHOT (1 << 1)
#define TIMER_CTRL_INTR (1 << 2)
#define TIMER_RIS_ACK (1 << 0)
#define TIMER_RST_CLR (1 << 6)
#define TIMER_MODE (1 << 0)
static void timer_update_irq(struct Msf2Timer *st)
{
bool isr, ier;
isr = !!(st->regs[R_TIM_RIS] & TIMER_RIS_ACK);
ier = !!(st->regs[R_TIM_CTRL] & TIMER_CTRL_INTR);
qemu_set_irq(st->irq, (ier && isr));
}
/* Must be called from within a ptimer_transaction_begin/commit block */
static void timer_update(struct Msf2Timer *st)
{
uint64_t count;
if (!(st->regs[R_TIM_CTRL] & TIMER_CTRL_ENBL)) {
ptimer_stop(st->ptimer);
return;
}
count = st->regs[R_TIM_LOADVAL];
ptimer_set_limit(st->ptimer, count, 1);
ptimer_run(st->ptimer, 1);
}
static uint64_t
timer_read(void *opaque, hwaddr offset, unsigned int size)
{
MSSTimerState *t = opaque;
hwaddr addr;
struct Msf2Timer *st;
uint32_t ret = 0;
int timer = 0;
int isr;
int ier;
addr = offset >> 2;
/*
* Two independent timers has same base address.
* Based on address passed figure out which timer is being used.
*/
if ((addr >= R_TIM1_MAX) && (addr < NUM_TIMERS * R_TIM1_MAX)) {
timer = 1;
addr -= R_TIM1_MAX;
}
st = &t->timers[timer];
switch (addr) {
case R_TIM_VAL:
ret = ptimer_get_count(st->ptimer);
break;
case R_TIM_MIS:
isr = !!(st->regs[R_TIM_RIS] & TIMER_RIS_ACK);
ier = !!(st->regs[R_TIM_CTRL] & TIMER_CTRL_INTR);
ret = ier & isr;
break;
default:
if (addr < R_TIM1_MAX) {
ret = st->regs[addr];
} else {
qemu_log_mask(LOG_GUEST_ERROR,
TYPE_MSS_TIMER": 64-bit mode not supported\n");
return ret;
}
break;
}
DB_PRINT("timer=%d 0x%" HWADDR_PRIx "=0x%" PRIx32, timer, offset,
ret);
return ret;
}
static void
timer_write(void *opaque, hwaddr offset,
uint64_t val64, unsigned int size)
{
MSSTimerState *t = opaque;
hwaddr addr;
struct Msf2Timer *st;
int timer = 0;
uint32_t value = val64;
addr = offset >> 2;
/*
* Two independent timers has same base address.
* Based on addr passed figure out which timer is being used.
*/
if ((addr >= R_TIM1_MAX) && (addr < NUM_TIMERS * R_TIM1_MAX)) {
timer = 1;
addr -= R_TIM1_MAX;
}
st = &t->timers[timer];
DB_PRINT("addr=0x%" HWADDR_PRIx " val=0x%" PRIx32 " (timer=%d)", offset,
value, timer);
switch (addr) {
case R_TIM_CTRL:
st->regs[R_TIM_CTRL] = value;
ptimer_transaction_begin(st->ptimer);
timer_update(st);
ptimer_transaction_commit(st->ptimer);
break;
case R_TIM_RIS:
if (value & TIMER_RIS_ACK) {
st->regs[R_TIM_RIS] &= ~TIMER_RIS_ACK;
}
break;
case R_TIM_LOADVAL:
st->regs[R_TIM_LOADVAL] = value;
if (st->regs[R_TIM_CTRL] & TIMER_CTRL_ENBL) {
ptimer_transaction_begin(st->ptimer);
timer_update(st);
ptimer_transaction_commit(st->ptimer);
}
break;
case R_TIM_BGLOADVAL:
st->regs[R_TIM_BGLOADVAL] = value;
st->regs[R_TIM_LOADVAL] = value;
break;
case R_TIM_VAL:
case R_TIM_MIS:
break;
default:
/* All non-64-bit regs covered by the switch cases */
assert(addr >= R_TIM1_MAX);
qemu_log_mask(LOG_GUEST_ERROR,
TYPE_MSS_TIMER": 64-bit mode not supported\n");
return;
}
timer_update_irq(st);
}
static const MemoryRegionOps timer_ops = {
.read = timer_read,
.write = timer_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.valid = {
.min_access_size = 1,
.max_access_size = 4
}
};
static void timer_hit(void *opaque)
{
struct Msf2Timer *st = opaque;
st->regs[R_TIM_RIS] |= TIMER_RIS_ACK;
if (!(st->regs[R_TIM_CTRL] & TIMER_CTRL_ONESHOT)) {
timer_update(st);
}
timer_update_irq(st);
}
static void mss_timer_init(Object *obj)
{
MSSTimerState *t = MSS_TIMER(obj);
int i;
/* Init all the ptimers. */
for (i = 0; i < NUM_TIMERS; i++) {
struct Msf2Timer *st = &t->timers[i];
st->ptimer = ptimer_init(timer_hit, st, PTIMER_POLICY_LEGACY);
ptimer_transaction_begin(st->ptimer);
ptimer_set_freq(st->ptimer, t->freq_hz);
ptimer_transaction_commit(st->ptimer);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &st->irq);
}
memory_region_init_io(&t->mmio, OBJECT(t), &timer_ops, t, TYPE_MSS_TIMER,
NUM_TIMERS * R_TIM1_MAX * 4);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &t->mmio);
}
static void mss_timer_finalize(Object *obj)
{
MSSTimerState *t = MSS_TIMER(obj);
int i;
for (i = 0; i < NUM_TIMERS; i++) {
struct Msf2Timer *st = &t->timers[i];
ptimer_free(st->ptimer);
}
}
static const VMStateDescription vmstate_timers = {
.name = "mss-timer-block",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_PTIMER(ptimer, struct Msf2Timer),
VMSTATE_UINT32_ARRAY(regs, struct Msf2Timer, R_TIM1_MAX),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription vmstate_mss_timer = {
.name = TYPE_MSS_TIMER,
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(freq_hz, MSSTimerState),
VMSTATE_STRUCT_ARRAY(timers, MSSTimerState, NUM_TIMERS, 0,
vmstate_timers, struct Msf2Timer),
VMSTATE_END_OF_LIST()
}
};
static const Property mss_timer_properties[] = {
/* Libero GUI shows 100Mhz as default for clocks */
DEFINE_PROP_UINT32("clock-frequency", MSSTimerState, freq_hz,
100 * 1000000),
};
static void mss_timer_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_props(dc, mss_timer_properties);
dc->vmsd = &vmstate_mss_timer;
}
static const TypeInfo mss_timer_info = {
.name = TYPE_MSS_TIMER,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(MSSTimerState),
.instance_init = mss_timer_init,
.instance_finalize = mss_timer_finalize,
.class_init = mss_timer_class_init,
};
static void mss_timer_register_types(void)
{
type_register_static(&mss_timer_info);
}
type_init(mss_timer_register_types)
+717
View File
@@ -0,0 +1,717 @@
/*
* Nuvoton NPCM7xx Timer Controller
*
* Copyright 2020 Google LLC
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "qemu/osdep.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-clock.h"
#include "hw/core/qdev-properties.h"
#include "hw/timer/npcm7xx_timer.h"
#include "migration/vmstate.h"
#include "qemu/bitops.h"
#include "qemu/error-report.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "qemu/timer.h"
#include "qemu/units.h"
#include "trace.h"
/* 32-bit register indices. */
enum NPCM7xxTimerRegisters {
NPCM7XX_TIMER_TCSR0,
NPCM7XX_TIMER_TCSR1,
NPCM7XX_TIMER_TICR0,
NPCM7XX_TIMER_TICR1,
NPCM7XX_TIMER_TDR0,
NPCM7XX_TIMER_TDR1,
NPCM7XX_TIMER_TISR,
NPCM7XX_TIMER_WTCR,
NPCM7XX_TIMER_TCSR2,
NPCM7XX_TIMER_TCSR3,
NPCM7XX_TIMER_TICR2,
NPCM7XX_TIMER_TICR3,
NPCM7XX_TIMER_TDR2,
NPCM7XX_TIMER_TDR3,
NPCM7XX_TIMER_TCSR4 = 0x0040 / sizeof(uint32_t),
NPCM7XX_TIMER_TICR4 = 0x0048 / sizeof(uint32_t),
NPCM7XX_TIMER_TDR4 = 0x0050 / sizeof(uint32_t),
NPCM7XX_TIMER_REGS_END,
};
/* Register field definitions. */
#define NPCM7XX_TCSR_CEN BIT(30)
#define NPCM7XX_TCSR_IE BIT(29)
#define NPCM7XX_TCSR_PERIODIC BIT(27)
#define NPCM7XX_TCSR_CRST BIT(26)
#define NPCM7XX_TCSR_CACT BIT(25)
#define NPCM7XX_TCSR_RSVD 0x01ffff00
#define NPCM7XX_TCSR_PRESCALE_START 0
#define NPCM7XX_TCSR_PRESCALE_LEN 8
#define NPCM7XX_WTCR_WTCLK(rv) extract32(rv, 10, 2)
#define NPCM7XX_WTCR_FREEZE_EN BIT(9)
#define NPCM7XX_WTCR_WTE BIT(7)
#define NPCM7XX_WTCR_WTIE BIT(6)
#define NPCM7XX_WTCR_WTIS(rv) extract32(rv, 4, 2)
#define NPCM7XX_WTCR_WTIF BIT(3)
#define NPCM7XX_WTCR_WTRF BIT(2)
#define NPCM7XX_WTCR_WTRE BIT(1)
#define NPCM7XX_WTCR_WTR BIT(0)
/*
* The number of clock cycles between interrupt and reset in watchdog, used
* by the software to handle the interrupt before system is reset.
*/
#define NPCM7XX_WATCHDOG_INTERRUPT_TO_RESET_CYCLES 1024
/* Start or resume the timer. */
static void npcm7xx_timer_start(NPCM7xxBaseTimer *t)
{
int64_t now;
now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
t->expires_ns = now + t->remaining_ns;
timer_mod(&t->qtimer, t->expires_ns);
}
/* Stop counting. Record the time remaining so we can continue later. */
static void npcm7xx_timer_pause(NPCM7xxBaseTimer *t)
{
int64_t now;
timer_del(&t->qtimer);
now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
t->remaining_ns = t->expires_ns - now;
}
/* Delete the timer and reset it to default state. */
static void npcm7xx_timer_clear(NPCM7xxBaseTimer *t)
{
timer_del(&t->qtimer);
t->expires_ns = 0;
t->remaining_ns = 0;
}
/*
* Returns the index of timer in the tc->timer array. This can be used to
* locate the registers that belong to this timer.
*/
static int npcm7xx_timer_index(NPCM7xxTimerCtrlState *tc, NPCM7xxTimer *timer)
{
int index = timer - tc->timer;
g_assert(index >= 0 && index < NPCM7XX_TIMERS_PER_CTRL);
return index;
}
/* Return the value by which to divide the reference clock rate. */
static uint32_t npcm7xx_tcsr_prescaler(uint32_t tcsr)
{
return extract32(tcsr, NPCM7XX_TCSR_PRESCALE_START,
NPCM7XX_TCSR_PRESCALE_LEN) + 1;
}
/* Convert a timer cycle count to a time interval in nanoseconds. */
static int64_t npcm7xx_timer_count_to_ns(NPCM7xxTimer *t, uint32_t count)
{
int64_t ticks = count;
ticks *= npcm7xx_tcsr_prescaler(t->tcsr);
return clock_ticks_to_ns(t->ctrl->clock, ticks);
}
/* Convert a time interval in nanoseconds to a timer cycle count. */
static uint32_t npcm7xx_timer_ns_to_count(NPCM7xxTimer *t, int64_t ns)
{
if (ns < 0) {
return 0;
}
return clock_ns_to_ticks(t->ctrl->clock, ns) /
npcm7xx_tcsr_prescaler(t->tcsr);
}
static uint32_t npcm7xx_watchdog_timer_prescaler(const NPCM7xxWatchdogTimer *t)
{
switch (NPCM7XX_WTCR_WTCLK(t->wtcr)) {
case 0:
return 1;
case 1:
return 256;
case 2:
return 2048;
case 3:
return 65536;
default:
g_assert_not_reached();
}
}
static void npcm7xx_watchdog_timer_reset_cycles(NPCM7xxWatchdogTimer *t,
int64_t cycles)
{
int64_t ticks = cycles * npcm7xx_watchdog_timer_prescaler(t);
int64_t ns = clock_ticks_to_ns(t->ctrl->clock, ticks);
/*
* The reset function always clears the current timer. The caller of the
* this needs to decide whether to start the watchdog timer based on
* specific flag in WTCR.
*/
npcm7xx_timer_clear(&t->base_timer);
t->base_timer.remaining_ns = ns;
}
static void npcm7xx_watchdog_timer_reset(NPCM7xxWatchdogTimer *t)
{
int64_t cycles = 1;
uint32_t s = NPCM7XX_WTCR_WTIS(t->wtcr);
g_assert(s <= 3);
cycles <<= NPCM7XX_WATCHDOG_BASETIME_SHIFT;
cycles <<= 2 * s;
npcm7xx_watchdog_timer_reset_cycles(t, cycles);
}
/*
* Raise the interrupt line if there's a pending interrupt and interrupts are
* enabled for this timer. If not, lower it.
*/
static void npcm7xx_timer_check_interrupt(NPCM7xxTimer *t)
{
NPCM7xxTimerCtrlState *tc = t->ctrl;
int index = npcm7xx_timer_index(tc, t);
bool pending = (t->tcsr & NPCM7XX_TCSR_IE) && (tc->tisr & BIT(index));
qemu_set_irq(t->irq, pending);
trace_npcm7xx_timer_irq(DEVICE(tc)->canonical_path, index, pending);
}
/*
* Called when the counter reaches zero. Sets the interrupt flag, and either
* restarts or disables the timer.
*/
static void npcm7xx_timer_reached_zero(NPCM7xxTimer *t)
{
NPCM7xxTimerCtrlState *tc = t->ctrl;
int index = npcm7xx_timer_index(tc, t);
tc->tisr |= BIT(index);
if (t->tcsr & NPCM7XX_TCSR_PERIODIC) {
t->base_timer.remaining_ns = npcm7xx_timer_count_to_ns(t, t->ticr);
if (t->tcsr & NPCM7XX_TCSR_CEN) {
npcm7xx_timer_start(&t->base_timer);
}
} else {
t->tcsr &= ~(NPCM7XX_TCSR_CEN | NPCM7XX_TCSR_CACT);
}
npcm7xx_timer_check_interrupt(t);
}
/*
* Restart the timer from its initial value. If the timer was enabled and stays
* enabled, adjust the QEMU timer according to the new count. If the timer is
* transitioning from disabled to enabled, the caller is expected to start the
* timer later.
*/
static void npcm7xx_timer_restart(NPCM7xxTimer *t, uint32_t old_tcsr)
{
t->base_timer.remaining_ns = npcm7xx_timer_count_to_ns(t, t->ticr);
if (old_tcsr & t->tcsr & NPCM7XX_TCSR_CEN) {
npcm7xx_timer_start(&t->base_timer);
}
}
/* Register read and write handlers */
static uint32_t npcm7xx_timer_read_tdr(NPCM7xxTimer *t)
{
if (t->tcsr & NPCM7XX_TCSR_CEN) {
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
return npcm7xx_timer_ns_to_count(t, t->base_timer.expires_ns - now);
}
return npcm7xx_timer_ns_to_count(t, t->base_timer.remaining_ns);
}
static void npcm7xx_timer_write_tcsr(NPCM7xxTimer *t, uint32_t new_tcsr)
{
uint32_t old_tcsr = t->tcsr;
uint32_t tdr;
if (new_tcsr & NPCM7XX_TCSR_RSVD) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: reserved bits in 0x%08x ignored\n",
__func__, new_tcsr);
new_tcsr &= ~NPCM7XX_TCSR_RSVD;
}
if (new_tcsr & NPCM7XX_TCSR_CACT) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: read-only bits in 0x%08x ignored\n",
__func__, new_tcsr);
new_tcsr &= ~NPCM7XX_TCSR_CACT;
}
if ((new_tcsr & NPCM7XX_TCSR_CRST) && (new_tcsr & NPCM7XX_TCSR_CEN)) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: both CRST and CEN set; ignoring CEN.\n",
__func__);
new_tcsr &= ~NPCM7XX_TCSR_CEN;
}
/* Calculate the value of TDR before potentially changing the prescaler. */
tdr = npcm7xx_timer_read_tdr(t);
t->tcsr = (t->tcsr & NPCM7XX_TCSR_CACT) | new_tcsr;
if (npcm7xx_tcsr_prescaler(old_tcsr) != npcm7xx_tcsr_prescaler(new_tcsr)) {
/* Recalculate time remaining based on the current TDR value. */
t->base_timer.remaining_ns = npcm7xx_timer_count_to_ns(t, tdr);
if (old_tcsr & t->tcsr & NPCM7XX_TCSR_CEN) {
npcm7xx_timer_start(&t->base_timer);
}
}
if ((old_tcsr ^ new_tcsr) & NPCM7XX_TCSR_IE) {
npcm7xx_timer_check_interrupt(t);
}
if (new_tcsr & NPCM7XX_TCSR_CRST) {
npcm7xx_timer_restart(t, old_tcsr);
t->tcsr &= ~NPCM7XX_TCSR_CRST;
}
if ((old_tcsr ^ new_tcsr) & NPCM7XX_TCSR_CEN) {
if (new_tcsr & NPCM7XX_TCSR_CEN) {
t->tcsr |= NPCM7XX_TCSR_CACT;
npcm7xx_timer_start(&t->base_timer);
} else {
t->tcsr &= ~NPCM7XX_TCSR_CACT;
npcm7xx_timer_pause(&t->base_timer);
if (t->base_timer.remaining_ns <= 0) {
npcm7xx_timer_reached_zero(t);
}
}
}
}
static void npcm7xx_timer_write_ticr(NPCM7xxTimer *t, uint32_t new_ticr)
{
t->ticr = new_ticr;
npcm7xx_timer_restart(t, t->tcsr);
}
static void npcm7xx_timer_write_tisr(NPCM7xxTimerCtrlState *s, uint32_t value)
{
int i;
s->tisr &= ~value;
for (i = 0; i < ARRAY_SIZE(s->timer); i++) {
if (value & (1U << i)) {
npcm7xx_timer_check_interrupt(&s->timer[i]);
}
}
}
static void npcm7xx_timer_write_wtcr(NPCM7xxWatchdogTimer *t, uint32_t new_wtcr)
{
uint32_t old_wtcr = t->wtcr;
/*
* WTIF and WTRF are cleared by writing 1. Writing 0 makes these bits
* unchanged.
*/
if (new_wtcr & NPCM7XX_WTCR_WTIF) {
new_wtcr &= ~NPCM7XX_WTCR_WTIF;
} else if (old_wtcr & NPCM7XX_WTCR_WTIF) {
new_wtcr |= NPCM7XX_WTCR_WTIF;
}
if (new_wtcr & NPCM7XX_WTCR_WTRF) {
new_wtcr &= ~NPCM7XX_WTCR_WTRF;
} else if (old_wtcr & NPCM7XX_WTCR_WTRF) {
new_wtcr |= NPCM7XX_WTCR_WTRF;
}
t->wtcr = new_wtcr;
if (new_wtcr & NPCM7XX_WTCR_WTR) {
t->wtcr &= ~NPCM7XX_WTCR_WTR;
npcm7xx_watchdog_timer_reset(t);
if (new_wtcr & NPCM7XX_WTCR_WTE) {
npcm7xx_timer_start(&t->base_timer);
}
} else if ((old_wtcr ^ new_wtcr) & NPCM7XX_WTCR_WTE) {
if (new_wtcr & NPCM7XX_WTCR_WTE) {
npcm7xx_timer_start(&t->base_timer);
} else {
npcm7xx_timer_pause(&t->base_timer);
}
}
}
static hwaddr npcm7xx_tcsr_index(hwaddr reg)
{
switch (reg) {
case NPCM7XX_TIMER_TCSR0:
return 0;
case NPCM7XX_TIMER_TCSR1:
return 1;
case NPCM7XX_TIMER_TCSR2:
return 2;
case NPCM7XX_TIMER_TCSR3:
return 3;
case NPCM7XX_TIMER_TCSR4:
return 4;
default:
g_assert_not_reached();
}
}
static hwaddr npcm7xx_ticr_index(hwaddr reg)
{
switch (reg) {
case NPCM7XX_TIMER_TICR0:
return 0;
case NPCM7XX_TIMER_TICR1:
return 1;
case NPCM7XX_TIMER_TICR2:
return 2;
case NPCM7XX_TIMER_TICR3:
return 3;
case NPCM7XX_TIMER_TICR4:
return 4;
default:
g_assert_not_reached();
}
}
static hwaddr npcm7xx_tdr_index(hwaddr reg)
{
switch (reg) {
case NPCM7XX_TIMER_TDR0:
return 0;
case NPCM7XX_TIMER_TDR1:
return 1;
case NPCM7XX_TIMER_TDR2:
return 2;
case NPCM7XX_TIMER_TDR3:
return 3;
case NPCM7XX_TIMER_TDR4:
return 4;
default:
g_assert_not_reached();
}
}
static uint64_t npcm7xx_timer_read(void *opaque, hwaddr offset, unsigned size)
{
NPCM7xxTimerCtrlState *s = opaque;
uint64_t value = 0;
hwaddr reg;
reg = offset / sizeof(uint32_t);
switch (reg) {
case NPCM7XX_TIMER_TCSR0:
case NPCM7XX_TIMER_TCSR1:
case NPCM7XX_TIMER_TCSR2:
case NPCM7XX_TIMER_TCSR3:
case NPCM7XX_TIMER_TCSR4:
value = s->timer[npcm7xx_tcsr_index(reg)].tcsr;
break;
case NPCM7XX_TIMER_TICR0:
case NPCM7XX_TIMER_TICR1:
case NPCM7XX_TIMER_TICR2:
case NPCM7XX_TIMER_TICR3:
case NPCM7XX_TIMER_TICR4:
value = s->timer[npcm7xx_ticr_index(reg)].ticr;
break;
case NPCM7XX_TIMER_TDR0:
case NPCM7XX_TIMER_TDR1:
case NPCM7XX_TIMER_TDR2:
case NPCM7XX_TIMER_TDR3:
case NPCM7XX_TIMER_TDR4:
value = npcm7xx_timer_read_tdr(&s->timer[npcm7xx_tdr_index(reg)]);
break;
case NPCM7XX_TIMER_TISR:
value = s->tisr;
break;
case NPCM7XX_TIMER_WTCR:
value = s->watchdog_timer.wtcr;
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: invalid offset 0x%04" HWADDR_PRIx "\n",
__func__, offset);
break;
}
trace_npcm7xx_timer_read(DEVICE(s)->canonical_path, offset, value);
return value;
}
static void npcm7xx_timer_write(void *opaque, hwaddr offset,
uint64_t v, unsigned size)
{
uint32_t reg = offset / sizeof(uint32_t);
NPCM7xxTimerCtrlState *s = opaque;
uint32_t value = v;
trace_npcm7xx_timer_write(DEVICE(s)->canonical_path, offset, value);
switch (reg) {
case NPCM7XX_TIMER_TCSR0:
case NPCM7XX_TIMER_TCSR1:
case NPCM7XX_TIMER_TCSR2:
case NPCM7XX_TIMER_TCSR3:
case NPCM7XX_TIMER_TCSR4:
npcm7xx_timer_write_tcsr(&s->timer[npcm7xx_tcsr_index(reg)], value);
return;
case NPCM7XX_TIMER_TICR0:
case NPCM7XX_TIMER_TICR1:
case NPCM7XX_TIMER_TICR2:
case NPCM7XX_TIMER_TICR3:
case NPCM7XX_TIMER_TICR4:
npcm7xx_timer_write_ticr(&s->timer[npcm7xx_ticr_index(reg)], value);
return;
case NPCM7XX_TIMER_TDR0:
case NPCM7XX_TIMER_TDR1:
case NPCM7XX_TIMER_TDR2:
case NPCM7XX_TIMER_TDR3:
case NPCM7XX_TIMER_TDR4:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: register @ 0x%04" HWADDR_PRIx " is read-only\n",
__func__, offset);
return;
case NPCM7XX_TIMER_TISR:
npcm7xx_timer_write_tisr(s, value);
return;
case NPCM7XX_TIMER_WTCR:
npcm7xx_timer_write_wtcr(&s->watchdog_timer, value);
return;
}
qemu_log_mask(LOG_GUEST_ERROR,
"%s: invalid offset 0x%04" HWADDR_PRIx "\n",
__func__, offset);
}
static const struct MemoryRegionOps npcm7xx_timer_ops = {
.read = npcm7xx_timer_read,
.write = npcm7xx_timer_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
.unaligned = false,
},
};
/* Called when the QEMU timer expires. */
static void npcm7xx_timer_expired(void *opaque)
{
NPCM7xxTimer *t = opaque;
if (t->tcsr & NPCM7XX_TCSR_CEN) {
npcm7xx_timer_reached_zero(t);
}
}
static void npcm7xx_timer_enter_reset(Object *obj, ResetType type)
{
NPCM7xxTimerCtrlState *s = NPCM7XX_TIMER(obj);
int i;
for (i = 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) {
NPCM7xxTimer *t = &s->timer[i];
npcm7xx_timer_clear(&t->base_timer);
t->tcsr = 0x00000005;
t->ticr = 0x00000000;
}
s->tisr = 0x00000000;
/*
* Set WTCLK to 1(default) and reset all flags except WTRF.
* WTRF is not reset during a core domain reset.
*/
s->watchdog_timer.wtcr = 0x00000400 | (s->watchdog_timer.wtcr &
NPCM7XX_WTCR_WTRF);
}
static void npcm7xx_watchdog_timer_expired(void *opaque)
{
NPCM7xxWatchdogTimer *t = opaque;
if (t->wtcr & NPCM7XX_WTCR_WTE) {
if (t->wtcr & NPCM7XX_WTCR_WTIF) {
if (t->wtcr & NPCM7XX_WTCR_WTRE) {
t->wtcr |= NPCM7XX_WTCR_WTRF;
/* send reset signal to CLK module*/
qemu_irq_raise(t->reset_signal);
}
} else {
t->wtcr |= NPCM7XX_WTCR_WTIF;
if (t->wtcr & NPCM7XX_WTCR_WTIE) {
/* send interrupt */
qemu_irq_raise(t->irq);
}
npcm7xx_watchdog_timer_reset_cycles(t,
NPCM7XX_WATCHDOG_INTERRUPT_TO_RESET_CYCLES);
npcm7xx_timer_start(&t->base_timer);
}
}
}
static void npcm7xx_timer_hold_reset(Object *obj, ResetType type)
{
NPCM7xxTimerCtrlState *s = NPCM7XX_TIMER(obj);
int i;
for (i = 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) {
qemu_irq_lower(s->timer[i].irq);
}
qemu_irq_lower(s->watchdog_timer.irq);
}
static void npcm7xx_timer_init(Object *obj)
{
NPCM7xxTimerCtrlState *s = NPCM7XX_TIMER(obj);
DeviceState *dev = DEVICE(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
int i;
NPCM7xxWatchdogTimer *w;
for (i = 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) {
NPCM7xxTimer *t = &s->timer[i];
t->ctrl = s;
timer_init_ns(&t->base_timer.qtimer, QEMU_CLOCK_VIRTUAL,
npcm7xx_timer_expired, t);
sysbus_init_irq(sbd, &t->irq);
}
w = &s->watchdog_timer;
w->ctrl = s;
timer_init_ns(&w->base_timer.qtimer, QEMU_CLOCK_VIRTUAL,
npcm7xx_watchdog_timer_expired, w);
sysbus_init_irq(sbd, &w->irq);
memory_region_init_io(&s->iomem, obj, &npcm7xx_timer_ops, s,
TYPE_NPCM7XX_TIMER, 4 * KiB);
sysbus_init_mmio(sbd, &s->iomem);
qdev_init_gpio_out_named(dev, &w->reset_signal,
NPCM7XX_WATCHDOG_RESET_GPIO_OUT, 1);
s->clock = qdev_init_clock_in(dev, "clock", NULL, NULL, 0);
}
static const VMStateDescription vmstate_npcm7xx_base_timer = {
.name = "npcm7xx-base-timer",
.version_id = 0,
.minimum_version_id = 0,
.fields = (const VMStateField[]) {
VMSTATE_TIMER(qtimer, NPCM7xxBaseTimer),
VMSTATE_INT64(expires_ns, NPCM7xxBaseTimer),
VMSTATE_INT64(remaining_ns, NPCM7xxBaseTimer),
VMSTATE_END_OF_LIST(),
},
};
static const VMStateDescription vmstate_npcm7xx_timer = {
.name = "npcm7xx-timer",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_STRUCT(base_timer, NPCM7xxTimer,
0, vmstate_npcm7xx_base_timer,
NPCM7xxBaseTimer),
VMSTATE_UINT32(tcsr, NPCM7xxTimer),
VMSTATE_UINT32(ticr, NPCM7xxTimer),
VMSTATE_END_OF_LIST(),
},
};
static const VMStateDescription vmstate_npcm7xx_watchdog_timer = {
.name = "npcm7xx-watchdog-timer",
.version_id = 0,
.minimum_version_id = 0,
.fields = (const VMStateField[]) {
VMSTATE_STRUCT(base_timer, NPCM7xxWatchdogTimer,
0, vmstate_npcm7xx_base_timer,
NPCM7xxBaseTimer),
VMSTATE_UINT32(wtcr, NPCM7xxWatchdogTimer),
VMSTATE_END_OF_LIST(),
},
};
static const VMStateDescription vmstate_npcm7xx_timer_ctrl = {
.name = "npcm7xx-timer-ctrl",
.version_id = 2,
.minimum_version_id = 2,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(tisr, NPCM7xxTimerCtrlState),
VMSTATE_CLOCK(clock, NPCM7xxTimerCtrlState),
VMSTATE_STRUCT_ARRAY(timer, NPCM7xxTimerCtrlState,
NPCM7XX_TIMERS_PER_CTRL, 0, vmstate_npcm7xx_timer,
NPCM7xxTimer),
VMSTATE_STRUCT(watchdog_timer, NPCM7xxTimerCtrlState,
0, vmstate_npcm7xx_watchdog_timer,
NPCM7xxWatchdogTimer),
VMSTATE_END_OF_LIST(),
},
};
static void npcm7xx_timer_class_init(ObjectClass *klass, const void *data)
{
ResettableClass *rc = RESETTABLE_CLASS(klass);
DeviceClass *dc = DEVICE_CLASS(klass);
QEMU_BUILD_BUG_ON(NPCM7XX_TIMER_REGS_END > NPCM7XX_TIMER_NR_REGS);
dc->desc = "NPCM7xx Timer Controller";
dc->vmsd = &vmstate_npcm7xx_timer_ctrl;
rc->phases.enter = npcm7xx_timer_enter_reset;
rc->phases.hold = npcm7xx_timer_hold_reset;
}
static const TypeInfo npcm7xx_timer_info = {
.name = TYPE_NPCM7XX_TIMER,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(NPCM7xxTimerCtrlState),
.class_init = npcm7xx_timer_class_init,
.instance_init = npcm7xx_timer_init,
};
static void npcm7xx_timer_register_type(void)
{
type_register_static(&npcm7xx_timer_info);
}
type_init(npcm7xx_timer_register_type);
+408
View File
@@ -0,0 +1,408 @@
/*
* nRF51 System-on-Chip Timer peripheral
*
* Reference Manual: http://infocenter.nordicsemi.com/pdf/nRF51_RM_v3.0.pdf
* Product Spec: http://infocenter.nordicsemi.com/pdf/nRF51822_PS_v3.1.pdf
*
* Copyright 2018 Steffen Görtz <[email protected]>
* Copyright (c) 2019 Red Hat, Inc.
*
* This code is licensed under the GPL version 2 or later. See
* the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "hw/arm/nrf51.h"
#include "hw/core/irq.h"
#include "hw/timer/nrf51_timer.h"
#include "hw/core/qdev-properties.h"
#include "migration/vmstate.h"
#include "trace.h"
#define TIMER_CLK_FREQ 16000000UL
static uint32_t const bitwidths[] = {16, 8, 24, 32};
static uint32_t ns_to_ticks(NRF51TimerState *s, int64_t ns)
{
uint32_t freq = TIMER_CLK_FREQ >> s->prescaler;
return muldiv64(ns, freq, NANOSECONDS_PER_SECOND);
}
static int64_t ticks_to_ns(NRF51TimerState *s, uint32_t ticks)
{
uint32_t freq = TIMER_CLK_FREQ >> s->prescaler;
return muldiv64(ticks, NANOSECONDS_PER_SECOND, freq);
}
/* Returns number of ticks since last call */
static uint32_t update_counter(NRF51TimerState *s, int64_t now)
{
uint32_t ticks = ns_to_ticks(s, now - s->update_counter_ns);
s->counter = (s->counter + ticks) % BIT(bitwidths[s->bitmode]);
/*
* Only advance the sync time to the timestamp of the last tick,
* not all the way to 'now', so we don't lose time if we do
* multiple resyncs in a single tick.
*/
s->update_counter_ns += ticks_to_ns(s, ticks);
return ticks;
}
/* Assumes s->counter is up-to-date */
static void rearm_timer(NRF51TimerState *s, int64_t now)
{
int64_t min_ns = INT64_MAX;
size_t i;
for (i = 0; i < NRF51_TIMER_REG_COUNT; i++) {
int64_t delta_ns;
if (s->events_compare[i]) {
continue; /* already expired, ignore it for now */
}
if (s->cc[i] <= s->counter) {
delta_ns = ticks_to_ns(s, BIT(bitwidths[s->bitmode]) -
s->counter + s->cc[i]);
} else {
delta_ns = ticks_to_ns(s, s->cc[i] - s->counter);
}
if (delta_ns < min_ns) {
min_ns = delta_ns;
}
}
if (min_ns != INT64_MAX) {
timer_mod_ns(&s->timer, now + min_ns);
}
}
static void update_irq(NRF51TimerState *s)
{
bool flag = false;
size_t i;
for (i = 0; i < NRF51_TIMER_REG_COUNT; i++) {
flag |= s->events_compare[i] && extract32(s->inten, 16 + i, 1);
}
qemu_set_irq(s->irq, flag);
}
static void timer_expire(void *opaque)
{
NRF51TimerState *s = NRF51_TIMER(opaque);
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
uint32_t cc_remaining[NRF51_TIMER_REG_COUNT];
bool should_stop = false;
uint32_t ticks;
size_t i;
for (i = 0; i < NRF51_TIMER_REG_COUNT; i++) {
if (s->cc[i] > s->counter) {
cc_remaining[i] = s->cc[i] - s->counter;
} else {
cc_remaining[i] = BIT(bitwidths[s->bitmode]) -
s->counter + s->cc[i];
}
}
ticks = update_counter(s, now);
for (i = 0; i < NRF51_TIMER_REG_COUNT; i++) {
if (cc_remaining[i] <= ticks) {
s->events_compare[i] = 1;
if (s->shorts & BIT(i)) {
s->timer_start_ns = now;
s->update_counter_ns = s->timer_start_ns;
s->counter = 0;
}
should_stop |= s->shorts & BIT(i + 8);
}
}
update_irq(s);
if (should_stop) {
s->running = false;
timer_del(&s->timer);
} else {
rearm_timer(s, now);
}
}
static void counter_compare(NRF51TimerState *s)
{
uint32_t counter = s->counter;
size_t i;
for (i = 0; i < NRF51_TIMER_REG_COUNT; i++) {
if (counter == s->cc[i]) {
s->events_compare[i] = 1;
if (s->shorts & BIT(i)) {
s->counter = 0;
}
}
}
}
static uint64_t nrf51_timer_read(void *opaque, hwaddr offset, unsigned int size)
{
NRF51TimerState *s = NRF51_TIMER(opaque);
uint64_t r = 0;
switch (offset) {
case NRF51_TIMER_EVENT_COMPARE_0 ... NRF51_TIMER_EVENT_COMPARE_3:
r = s->events_compare[(offset - NRF51_TIMER_EVENT_COMPARE_0) / 4];
break;
case NRF51_TIMER_REG_SHORTS:
r = s->shorts;
break;
case NRF51_TIMER_REG_INTENSET:
r = s->inten;
break;
case NRF51_TIMER_REG_INTENCLR:
r = s->inten;
break;
case NRF51_TIMER_REG_MODE:
r = s->mode;
break;
case NRF51_TIMER_REG_BITMODE:
r = s->bitmode;
break;
case NRF51_TIMER_REG_PRESCALER:
r = s->prescaler;
break;
case NRF51_TIMER_REG_CC0 ... NRF51_TIMER_REG_CC3:
r = s->cc[(offset - NRF51_TIMER_REG_CC0) / 4];
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: bad read offset 0x%" HWADDR_PRIx "\n",
__func__, offset);
}
trace_nrf51_timer_read(s->id, offset, r, size);
return r;
}
static void nrf51_timer_write(void *opaque, hwaddr offset,
uint64_t value, unsigned int size)
{
NRF51TimerState *s = NRF51_TIMER(opaque);
uint64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
size_t idx;
trace_nrf51_timer_write(s->id, offset, value, size);
switch (offset) {
case NRF51_TIMER_TASK_START:
if (value == NRF51_TRIGGER_TASK && s->mode == NRF51_TIMER_TIMER) {
s->running = true;
s->timer_start_ns = now - ticks_to_ns(s, s->counter);
s->update_counter_ns = s->timer_start_ns;
rearm_timer(s, now);
}
break;
case NRF51_TIMER_TASK_STOP:
case NRF51_TIMER_TASK_SHUTDOWN:
if (value == NRF51_TRIGGER_TASK) {
s->running = false;
timer_del(&s->timer);
}
break;
case NRF51_TIMER_TASK_COUNT:
if (value == NRF51_TRIGGER_TASK && s->mode == NRF51_TIMER_COUNTER) {
s->counter = (s->counter + 1) % BIT(bitwidths[s->bitmode]);
counter_compare(s);
}
break;
case NRF51_TIMER_TASK_CLEAR:
if (value == NRF51_TRIGGER_TASK) {
s->timer_start_ns = now;
s->update_counter_ns = s->timer_start_ns;
s->counter = 0;
if (s->running) {
rearm_timer(s, now);
}
}
break;
case NRF51_TIMER_TASK_CAPTURE_0 ... NRF51_TIMER_TASK_CAPTURE_3:
if (value == NRF51_TRIGGER_TASK) {
if (s->running) {
timer_expire(s); /* update counter and all state */
}
idx = (offset - NRF51_TIMER_TASK_CAPTURE_0) / 4;
s->cc[idx] = s->counter;
trace_nrf51_timer_set_count(s->id, idx, s->counter);
}
break;
case NRF51_TIMER_EVENT_COMPARE_0 ... NRF51_TIMER_EVENT_COMPARE_3:
if (value == NRF51_EVENT_CLEAR) {
s->events_compare[(offset - NRF51_TIMER_EVENT_COMPARE_0) / 4] = 0;
if (s->running) {
timer_expire(s); /* update counter and all state */
}
}
break;
case NRF51_TIMER_REG_SHORTS:
s->shorts = value & NRF51_TIMER_REG_SHORTS_MASK;
break;
case NRF51_TIMER_REG_INTENSET:
s->inten |= value & NRF51_TIMER_REG_INTEN_MASK;
break;
case NRF51_TIMER_REG_INTENCLR:
s->inten &= ~(value & NRF51_TIMER_REG_INTEN_MASK);
break;
case NRF51_TIMER_REG_MODE:
s->mode = value;
break;
case NRF51_TIMER_REG_BITMODE:
if (s->mode == NRF51_TIMER_TIMER && s->running) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: erroneous change of BITMODE while timer is running\n",
__func__);
}
s->bitmode = value & NRF51_TIMER_REG_BITMODE_MASK;
break;
case NRF51_TIMER_REG_PRESCALER:
if (s->mode == NRF51_TIMER_TIMER && s->running) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: erroneous change of PRESCALER while timer is running\n",
__func__);
}
s->prescaler = value & NRF51_TIMER_REG_PRESCALER_MASK;
break;
case NRF51_TIMER_REG_CC0 ... NRF51_TIMER_REG_CC3:
if (s->running) {
timer_expire(s); /* update counter */
}
idx = (offset - NRF51_TIMER_REG_CC0) / 4;
s->cc[idx] = value % BIT(bitwidths[s->bitmode]);
if (s->running) {
rearm_timer(s, now);
}
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: bad write offset 0x%" HWADDR_PRIx "\n",
__func__, offset);
}
update_irq(s);
}
static const MemoryRegionOps rng_ops = {
.read = nrf51_timer_read,
.write = nrf51_timer_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.impl.min_access_size = 4,
.impl.max_access_size = 4,
};
static void nrf51_timer_init(Object *obj)
{
NRF51TimerState *s = NRF51_TIMER(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
memory_region_init_io(&s->iomem, obj, &rng_ops, s,
TYPE_NRF51_TIMER, NRF51_PERIPHERAL_SIZE);
sysbus_init_mmio(sbd, &s->iomem);
sysbus_init_irq(sbd, &s->irq);
timer_init_ns(&s->timer, QEMU_CLOCK_VIRTUAL, timer_expire, s);
}
static void nrf51_timer_reset(DeviceState *dev)
{
NRF51TimerState *s = NRF51_TIMER(dev);
timer_del(&s->timer);
s->timer_start_ns = 0x00;
s->update_counter_ns = 0x00;
s->counter = 0x00;
s->running = false;
memset(s->events_compare, 0x00, sizeof(s->events_compare));
memset(s->cc, 0x00, sizeof(s->cc));
s->shorts = 0x00;
s->inten = 0x00;
s->mode = 0x00;
s->bitmode = 0x00;
s->prescaler = 0x00;
}
static int nrf51_timer_post_load(void *opaque, int version_id)
{
NRF51TimerState *s = NRF51_TIMER(opaque);
if (s->running && s->mode == NRF51_TIMER_TIMER) {
timer_expire(s);
}
return 0;
}
static const VMStateDescription vmstate_nrf51_timer = {
.name = TYPE_NRF51_TIMER,
.version_id = 1,
.post_load = nrf51_timer_post_load,
.fields = (const VMStateField[]) {
VMSTATE_TIMER(timer, NRF51TimerState),
VMSTATE_INT64(timer_start_ns, NRF51TimerState),
VMSTATE_INT64(update_counter_ns, NRF51TimerState),
VMSTATE_UINT32(counter, NRF51TimerState),
VMSTATE_BOOL(running, NRF51TimerState),
VMSTATE_UINT8_ARRAY(events_compare, NRF51TimerState,
NRF51_TIMER_REG_COUNT),
VMSTATE_UINT32_ARRAY(cc, NRF51TimerState, NRF51_TIMER_REG_COUNT),
VMSTATE_UINT32(shorts, NRF51TimerState),
VMSTATE_UINT32(inten, NRF51TimerState),
VMSTATE_UINT32(mode, NRF51TimerState),
VMSTATE_UINT32(bitmode, NRF51TimerState),
VMSTATE_UINT32(prescaler, NRF51TimerState),
VMSTATE_END_OF_LIST()
}
};
static const Property nrf51_timer_properties[] = {
DEFINE_PROP_UINT8("id", NRF51TimerState, id, 0),
};
static void nrf51_timer_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, nrf51_timer_reset);
dc->vmsd = &vmstate_nrf51_timer;
device_class_set_props(dc, nrf51_timer_properties);
}
static const TypeInfo nrf51_timer_info = {
.name = TYPE_NRF51_TIMER,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(NRF51TimerState),
.instance_init = nrf51_timer_init,
.class_init = nrf51_timer_class_init
};
static void nrf51_timer_register_types(void)
{
type_register_static(&nrf51_timer_info);
}
type_init(nrf51_timer_register_types)
+596
View File
@@ -0,0 +1,596 @@
/*
* Intel XScale PXA255/270 OS Timers.
*
* Copyright (c) 2006 Openedhand Ltd.
* Copyright (c) 2006 Thorsten Zitterell
*
* This code is licensed under the GPL.
*/
#include "qemu/osdep.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-properties.h"
#include "qemu/timer.h"
#include "system/runstate.h"
#include "hw/core/sysbus.h"
#include "migration/vmstate.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "qom/object.h"
#include "system/watchdog.h"
#define OSMR0 0x00
#define OSMR1 0x04
#define OSMR2 0x08
#define OSMR3 0x0c
#define OSMR4 0x80
#define OSMR5 0x84
#define OSMR6 0x88
#define OSMR7 0x8c
#define OSMR8 0x90
#define OSMR9 0x94
#define OSMR10 0x98
#define OSMR11 0x9c
#define OSCR 0x10 /* OS Timer Count */
#define OSCR4 0x40
#define OSCR5 0x44
#define OSCR6 0x48
#define OSCR7 0x4c
#define OSCR8 0x50
#define OSCR9 0x54
#define OSCR10 0x58
#define OSCR11 0x5c
#define OSSR 0x14 /* Timer status register */
#define OWER 0x18
#define OIER 0x1c /* Interrupt enable register 3-0 to E3-E0 */
#define OMCR4 0xc0 /* OS Match Control registers */
#define OMCR5 0xc4
#define OMCR6 0xc8
#define OMCR7 0xcc
#define OMCR8 0xd0
#define OMCR9 0xd4
#define OMCR10 0xd8
#define OMCR11 0xdc
#define OSNR 0x20
#define PXA25X_FREQ 3686400 /* 3.6864 MHz */
static int pxa2xx_timer4_freq[8] = {
[0] = 0,
[1] = 32768,
[2] = 1000,
[3] = 1,
[4] = 1000000,
/* [5] is the "Externally supplied clock". Assign if necessary. */
[5 ... 7] = 0,
};
#define TYPE_PXA2XX_TIMER "pxa2xx-timer"
OBJECT_DECLARE_SIMPLE_TYPE(PXA2xxTimerInfo, PXA2XX_TIMER)
typedef struct {
uint32_t value;
qemu_irq irq;
QEMUTimer *qtimer;
int num;
PXA2xxTimerInfo *info;
} PXA2xxTimer0;
typedef struct {
PXA2xxTimer0 tm;
int32_t oldclock;
int32_t clock;
uint64_t lastload;
uint32_t freq;
uint32_t control;
} PXA2xxTimer4;
struct PXA2xxTimerInfo {
SysBusDevice parent_obj;
MemoryRegion iomem;
uint32_t flags;
int32_t clock;
int32_t oldclock;
uint64_t lastload;
uint32_t freq;
PXA2xxTimer0 timer[4];
uint32_t events;
uint32_t irq_enabled;
uint32_t reset3;
uint32_t snapshot;
qemu_irq irq4;
PXA2xxTimer4 tm4[8];
};
#define PXA2XX_TIMER_HAVE_TM4 0
static inline int pxa2xx_timer_has_tm4(PXA2xxTimerInfo *s)
{
return s->flags & (1 << PXA2XX_TIMER_HAVE_TM4);
}
static void pxa2xx_timer_update(void *opaque, uint64_t now_qemu)
{
PXA2xxTimerInfo *s = (PXA2xxTimerInfo *) opaque;
int i;
uint32_t now_vm;
uint64_t new_qemu;
now_vm = s->clock +
muldiv64(now_qemu - s->lastload, s->freq, NANOSECONDS_PER_SECOND);
for (i = 0; i < 4; i ++) {
new_qemu = now_qemu + muldiv64((uint32_t) (s->timer[i].value - now_vm),
NANOSECONDS_PER_SECOND, s->freq);
timer_mod(s->timer[i].qtimer, new_qemu);
}
}
static void pxa2xx_timer_update4(void *opaque, uint64_t now_qemu, int n)
{
PXA2xxTimerInfo *s = (PXA2xxTimerInfo *) opaque;
uint32_t now_vm;
uint64_t new_qemu;
static const int counters[8] = { 0, 0, 0, 0, 4, 4, 6, 6 };
int counter;
assert(n < ARRAY_SIZE(counters));
if (s->tm4[n].control & (1 << 7))
counter = n;
else
counter = counters[n];
if (!s->tm4[counter].freq) {
timer_del(s->tm4[n].tm.qtimer);
return;
}
now_vm = s->tm4[counter].clock + muldiv64(now_qemu -
s->tm4[counter].lastload,
s->tm4[counter].freq, NANOSECONDS_PER_SECOND);
new_qemu = now_qemu + muldiv64((uint32_t) (s->tm4[n].tm.value - now_vm),
NANOSECONDS_PER_SECOND, s->tm4[counter].freq);
timer_mod(s->tm4[n].tm.qtimer, new_qemu);
}
static uint64_t pxa2xx_timer_read(void *opaque, hwaddr offset,
unsigned size)
{
PXA2xxTimerInfo *s = (PXA2xxTimerInfo *) opaque;
int tm = 0;
switch (offset) {
case OSMR3: tm ++;
/* fall through */
case OSMR2: tm ++;
/* fall through */
case OSMR1: tm ++;
/* fall through */
case OSMR0:
return s->timer[tm].value;
case OSMR11: tm ++;
/* fall through */
case OSMR10: tm ++;
/* fall through */
case OSMR9: tm ++;
/* fall through */
case OSMR8: tm ++;
/* fall through */
case OSMR7: tm ++;
/* fall through */
case OSMR6: tm ++;
/* fall through */
case OSMR5: tm ++;
/* fall through */
case OSMR4:
if (!pxa2xx_timer_has_tm4(s))
goto badreg;
return s->tm4[tm].tm.value;
case OSCR:
return s->clock + muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) -
s->lastload, s->freq, NANOSECONDS_PER_SECOND);
case OSCR11: tm ++;
/* fall through */
case OSCR10: tm ++;
/* fall through */
case OSCR9: tm ++;
/* fall through */
case OSCR8: tm ++;
/* fall through */
case OSCR7: tm ++;
/* fall through */
case OSCR6: tm ++;
/* fall through */
case OSCR5: tm ++;
/* fall through */
case OSCR4:
if (!pxa2xx_timer_has_tm4(s))
goto badreg;
if ((tm == 9 - 4 || tm == 11 - 4) && (s->tm4[tm].control & (1 << 9))) {
if (s->tm4[tm - 1].freq)
s->snapshot = s->tm4[tm - 1].clock + muldiv64(
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) -
s->tm4[tm - 1].lastload,
s->tm4[tm - 1].freq, NANOSECONDS_PER_SECOND);
else
s->snapshot = s->tm4[tm - 1].clock;
}
if (!s->tm4[tm].freq)
return s->tm4[tm].clock;
return s->tm4[tm].clock +
muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) -
s->tm4[tm].lastload, s->tm4[tm].freq,
NANOSECONDS_PER_SECOND);
case OIER:
return s->irq_enabled;
case OSSR: /* Status register */
return s->events;
case OWER:
return s->reset3;
case OMCR11: tm ++;
/* fall through */
case OMCR10: tm ++;
/* fall through */
case OMCR9: tm ++;
/* fall through */
case OMCR8: tm ++;
/* fall through */
case OMCR7: tm ++;
/* fall through */
case OMCR6: tm ++;
/* fall through */
case OMCR5: tm ++;
/* fall through */
case OMCR4:
if (!pxa2xx_timer_has_tm4(s))
goto badreg;
return s->tm4[tm].control;
case OSNR:
return s->snapshot;
default:
qemu_log_mask(LOG_UNIMP,
"%s: unknown register 0x%02" HWADDR_PRIx "\n",
__func__, offset);
break;
badreg:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: incorrect register 0x%02" HWADDR_PRIx "\n",
__func__, offset);
}
return 0;
}
static void pxa2xx_timer_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
int i, tm = 0;
PXA2xxTimerInfo *s = (PXA2xxTimerInfo *) opaque;
switch (offset) {
case OSMR3: tm ++;
/* fall through */
case OSMR2: tm ++;
/* fall through */
case OSMR1: tm ++;
/* fall through */
case OSMR0:
s->timer[tm].value = value;
pxa2xx_timer_update(s, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
break;
case OSMR11: tm ++;
/* fall through */
case OSMR10: tm ++;
/* fall through */
case OSMR9: tm ++;
/* fall through */
case OSMR8: tm ++;
/* fall through */
case OSMR7: tm ++;
/* fall through */
case OSMR6: tm ++;
/* fall through */
case OSMR5: tm ++;
/* fall through */
case OSMR4:
if (!pxa2xx_timer_has_tm4(s))
goto badreg;
s->tm4[tm].tm.value = value;
pxa2xx_timer_update4(s, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), tm);
break;
case OSCR:
s->oldclock = s->clock;
s->lastload = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
s->clock = value;
pxa2xx_timer_update(s, s->lastload);
break;
case OSCR11: tm ++;
/* fall through */
case OSCR10: tm ++;
/* fall through */
case OSCR9: tm ++;
/* fall through */
case OSCR8: tm ++;
/* fall through */
case OSCR7: tm ++;
/* fall through */
case OSCR6: tm ++;
/* fall through */
case OSCR5: tm ++;
/* fall through */
case OSCR4:
if (!pxa2xx_timer_has_tm4(s))
goto badreg;
s->tm4[tm].oldclock = s->tm4[tm].clock;
s->tm4[tm].lastload = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
s->tm4[tm].clock = value;
pxa2xx_timer_update4(s, s->tm4[tm].lastload, tm);
break;
case OIER:
s->irq_enabled = value & 0xfff;
break;
case OSSR: /* Status register */
value &= s->events;
s->events &= ~value;
for (i = 0; i < 4; i ++, value >>= 1)
if (value & 1)
qemu_irq_lower(s->timer[i].irq);
if (pxa2xx_timer_has_tm4(s) && !(s->events & 0xff0) && value)
qemu_irq_lower(s->irq4);
break;
case OWER: /* XXX: Reset on OSMR3 match? */
s->reset3 = value;
break;
case OMCR7: tm ++;
/* fall through */
case OMCR6: tm ++;
/* fall through */
case OMCR5: tm ++;
/* fall through */
case OMCR4:
if (!pxa2xx_timer_has_tm4(s))
goto badreg;
s->tm4[tm].control = value & 0x0ff;
/* XXX Stop if running (shouldn't happen) */
if ((value & (1 << 7)) || tm == 0)
s->tm4[tm].freq = pxa2xx_timer4_freq[value & 7];
else {
s->tm4[tm].freq = 0;
pxa2xx_timer_update4(s, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), tm);
}
break;
case OMCR11: tm ++;
/* fall through */
case OMCR10: tm ++;
/* fall through */
case OMCR9: tm ++;
/* fall through */
case OMCR8: tm += 4;
if (!pxa2xx_timer_has_tm4(s))
goto badreg;
s->tm4[tm].control = value & 0x3ff;
/* XXX Stop if running (shouldn't happen) */
if ((value & (1 << 7)) || !(tm & 1))
s->tm4[tm].freq =
pxa2xx_timer4_freq[(value & (1 << 8)) ? 0 : (value & 7)];
else {
s->tm4[tm].freq = 0;
pxa2xx_timer_update4(s, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), tm);
}
break;
default:
qemu_log_mask(LOG_UNIMP,
"%s: unknown register 0x%02" HWADDR_PRIx " "
"(value 0x%08" PRIx64 ")\n", __func__, offset, value);
break;
badreg:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: incorrect register 0x%02" HWADDR_PRIx " "
"(value 0x%08" PRIx64 ")\n", __func__, offset, value);
}
}
static const MemoryRegionOps pxa2xx_timer_ops = {
.read = pxa2xx_timer_read,
.write = pxa2xx_timer_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
static void pxa2xx_timer_tick(void *opaque)
{
PXA2xxTimer0 *t = (PXA2xxTimer0 *) opaque;
PXA2xxTimerInfo *i = t->info;
if (i->irq_enabled & (1 << t->num)) {
i->events |= 1 << t->num;
qemu_irq_raise(t->irq);
}
if (t->num == 3)
if (i->reset3 & 1) {
i->reset3 = 0;
watchdog_perform_action();
}
}
static void pxa2xx_timer_tick4(void *opaque)
{
PXA2xxTimer4 *t = (PXA2xxTimer4 *) opaque;
PXA2xxTimerInfo *i = (PXA2xxTimerInfo *) t->tm.info;
pxa2xx_timer_tick(&t->tm);
if (t->control & (1 << 3))
t->clock = 0;
if (t->control & (1 << 6))
pxa2xx_timer_update4(i, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), t->tm.num - 4);
if (i->events & 0xff0)
qemu_irq_raise(i->irq4);
}
static int pxa25x_timer_post_load(void *opaque, int version_id)
{
PXA2xxTimerInfo *s = (PXA2xxTimerInfo *) opaque;
int64_t now;
int i;
now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
pxa2xx_timer_update(s, now);
if (pxa2xx_timer_has_tm4(s))
for (i = 0; i < 8; i ++)
pxa2xx_timer_update4(s, now, i);
return 0;
}
static void pxa2xx_timer_init(Object *obj)
{
PXA2xxTimerInfo *s = PXA2XX_TIMER(obj);
SysBusDevice *dev = SYS_BUS_DEVICE(obj);
s->irq_enabled = 0;
s->oldclock = 0;
s->clock = 0;
s->lastload = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
s->reset3 = 0;
memory_region_init_io(&s->iomem, obj, &pxa2xx_timer_ops, s,
"pxa2xx-timer", 0x00001000);
sysbus_init_mmio(dev, &s->iomem);
}
static void pxa2xx_timer_realize(DeviceState *dev, Error **errp)
{
PXA2xxTimerInfo *s = PXA2XX_TIMER(dev);
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
int i;
for (i = 0; i < 4; i ++) {
s->timer[i].value = 0;
sysbus_init_irq(sbd, &s->timer[i].irq);
s->timer[i].info = s;
s->timer[i].num = i;
s->timer[i].qtimer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
pxa2xx_timer_tick, &s->timer[i]);
}
if (s->flags & (1 << PXA2XX_TIMER_HAVE_TM4)) {
sysbus_init_irq(sbd, &s->irq4);
for (i = 0; i < 8; i ++) {
s->tm4[i].tm.value = 0;
s->tm4[i].tm.info = s;
s->tm4[i].tm.num = i + 4;
s->tm4[i].freq = 0;
s->tm4[i].control = 0x0;
s->tm4[i].tm.qtimer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
pxa2xx_timer_tick4, &s->tm4[i]);
}
}
}
static const VMStateDescription vmstate_pxa2xx_timer0_regs = {
.name = "pxa2xx_timer0",
.version_id = 2,
.minimum_version_id = 2,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(value, PXA2xxTimer0),
VMSTATE_END_OF_LIST(),
},
};
static const VMStateDescription vmstate_pxa2xx_timer4_regs = {
.name = "pxa2xx_timer4",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_STRUCT(tm, PXA2xxTimer4, 1,
vmstate_pxa2xx_timer0_regs, PXA2xxTimer0),
VMSTATE_INT32(oldclock, PXA2xxTimer4),
VMSTATE_INT32(clock, PXA2xxTimer4),
VMSTATE_UINT64(lastload, PXA2xxTimer4),
VMSTATE_UINT32(freq, PXA2xxTimer4),
VMSTATE_UINT32(control, PXA2xxTimer4),
VMSTATE_END_OF_LIST(),
},
};
static bool pxa2xx_timer_has_tm4_test(void *opaque, int version_id)
{
return pxa2xx_timer_has_tm4(opaque);
}
static const VMStateDescription vmstate_pxa2xx_timer_regs = {
.name = "pxa2xx_timer",
.version_id = 1,
.minimum_version_id = 1,
.post_load = pxa25x_timer_post_load,
.fields = (const VMStateField[]) {
VMSTATE_INT32(clock, PXA2xxTimerInfo),
VMSTATE_INT32(oldclock, PXA2xxTimerInfo),
VMSTATE_UINT64(lastload, PXA2xxTimerInfo),
VMSTATE_STRUCT_ARRAY(timer, PXA2xxTimerInfo, 4, 1,
vmstate_pxa2xx_timer0_regs, PXA2xxTimer0),
VMSTATE_UINT32(events, PXA2xxTimerInfo),
VMSTATE_UINT32(irq_enabled, PXA2xxTimerInfo),
VMSTATE_UINT32(reset3, PXA2xxTimerInfo),
VMSTATE_UINT32(snapshot, PXA2xxTimerInfo),
VMSTATE_STRUCT_ARRAY_TEST(tm4, PXA2xxTimerInfo, 8,
pxa2xx_timer_has_tm4_test, 0,
vmstate_pxa2xx_timer4_regs, PXA2xxTimer4),
VMSTATE_END_OF_LIST(),
}
};
static const Property pxa25x_timer_dev_properties[] = {
DEFINE_PROP_UINT32("freq", PXA2xxTimerInfo, freq, PXA25X_FREQ),
DEFINE_PROP_BIT("tm4", PXA2xxTimerInfo, flags,
PXA2XX_TIMER_HAVE_TM4, false),
};
static void pxa25x_timer_dev_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->desc = "PXA25x timer";
device_class_set_props(dc, pxa25x_timer_dev_properties);
}
static const TypeInfo pxa25x_timer_dev_info = {
.name = "pxa25x-timer",
.parent = TYPE_PXA2XX_TIMER,
.instance_size = sizeof(PXA2xxTimerInfo),
.class_init = pxa25x_timer_dev_class_init,
};
static void pxa2xx_timer_class_init(ObjectClass *oc, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
dc->realize = pxa2xx_timer_realize;
dc->vmsd = &vmstate_pxa2xx_timer_regs;
}
static const TypeInfo pxa2xx_timer_type_info = {
.name = TYPE_PXA2XX_TIMER,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(PXA2xxTimerInfo),
.instance_init = pxa2xx_timer_init,
.abstract = true,
.class_init = pxa2xx_timer_class_init,
};
static void pxa2xx_timer_register_types(void)
{
type_register_static(&pxa2xx_timer_type_info);
type_register_static(&pxa25x_timer_dev_info);
}
type_init(pxa2xx_timer_register_types)
+667
View File
@@ -0,0 +1,667 @@
/*
* 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, &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;
}
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, &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;
}
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)
+282
View File
@@ -0,0 +1,282 @@
/*
* Renesas 16bit Compare-match timer
*
* Datasheet: RX62N Group, RX621 Group User's Manual: Hardware
* (Rev.1.40 R01UH0033EJ0140)
*
* Copyright (c) 2019 Yoshinori Sato
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2 or later, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "hw/core/irq.h"
#include "hw/core/registerfields.h"
#include "hw/core/qdev-properties.h"
#include "hw/timer/renesas_cmt.h"
#include "migration/vmstate.h"
/*
* +0 CMSTR - common control
* +2 CMCR - ch0
* +4 CMCNT - ch0
* +6 CMCOR - ch0
* +8 CMCR - ch1
* +10 CMCNT - ch1
* +12 CMCOR - ch1
* If we think that the address of CH 0 has an offset of +2,
* we can treat it with the same address as CH 1, so define it like that.
*/
REG16(CMSTR, 0)
FIELD(CMSTR, STR0, 0, 1)
FIELD(CMSTR, STR1, 1, 1)
FIELD(CMSTR, STR, 0, 2)
/* This addeess is channel offset */
REG16(CMCR, 0)
FIELD(CMCR, CKS, 0, 2)
FIELD(CMCR, CMIE, 6, 1)
REG16(CMCNT, 2)
REG16(CMCOR, 4)
static void update_events(RCMTState *cmt, int ch)
{
int64_t next_time;
if ((cmt->cmstr & (1 << ch)) == 0) {
/* count disable, so not happened next event. */
return;
}
next_time = cmt->cmcor[ch] - cmt->cmcnt[ch];
next_time *= NANOSECONDS_PER_SECOND;
next_time /= cmt->input_freq;
/*
* CKS -> div rate
* 0 -> 8 (1 << 3)
* 1 -> 32 (1 << 5)
* 2 -> 128 (1 << 7)
* 3 -> 512 (1 << 9)
*/
next_time *= 1 << (3 + FIELD_EX16(cmt->cmcr[ch], CMCR, CKS) * 2);
next_time += qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
timer_mod(&cmt->timer[ch], next_time);
}
static int64_t read_cmcnt(RCMTState *cmt, int ch)
{
int64_t delta, now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
if (cmt->cmstr & (1 << ch)) {
delta = (now - cmt->tick[ch]);
delta /= NANOSECONDS_PER_SECOND;
delta /= cmt->input_freq;
delta /= 1 << (3 + FIELD_EX16(cmt->cmcr[ch], CMCR, CKS) * 2);
cmt->tick[ch] = now;
return cmt->cmcnt[ch] + delta;
} else {
return cmt->cmcnt[ch];
}
}
static uint64_t cmt_read(void *opaque, hwaddr offset, unsigned size)
{
RCMTState *cmt = opaque;
int ch = offset / 0x08;
uint64_t ret;
if (offset == A_CMSTR) {
ret = 0;
ret = FIELD_DP16(ret, CMSTR, STR,
FIELD_EX16(cmt->cmstr, CMSTR, STR));
return ret;
} else {
offset &= 0x07;
if (ch == 0) {
offset -= 0x02;
}
switch (offset) {
case A_CMCR:
ret = 0;
ret = FIELD_DP16(ret, CMCR, CKS,
FIELD_EX16(cmt->cmstr, CMCR, CKS));
ret = FIELD_DP16(ret, CMCR, CMIE,
FIELD_EX16(cmt->cmstr, CMCR, CMIE));
return ret;
case A_CMCNT:
return read_cmcnt(cmt, ch);
case A_CMCOR:
return cmt->cmcor[ch];
}
}
qemu_log_mask(LOG_UNIMP, "renesas_cmt: Register 0x%" HWADDR_PRIX " "
"not implemented\n",
offset);
return UINT64_MAX;
}
static void start_stop(RCMTState *cmt, int ch, int st)
{
if (st) {
update_events(cmt, ch);
} else {
timer_del(&cmt->timer[ch]);
}
}
static void cmt_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
{
RCMTState *cmt = opaque;
int ch = offset / 0x08;
if (offset == A_CMSTR) {
cmt->cmstr = FIELD_EX16(val, CMSTR, STR);
start_stop(cmt, 0, FIELD_EX16(cmt->cmstr, CMSTR, STR0));
start_stop(cmt, 1, FIELD_EX16(cmt->cmstr, CMSTR, STR1));
} else {
offset &= 0x07;
if (ch == 0) {
offset -= 0x02;
}
switch (offset) {
case A_CMCR:
cmt->cmcr[ch] = FIELD_DP16(cmt->cmcr[ch], CMCR, CKS,
FIELD_EX16(val, CMCR, CKS));
cmt->cmcr[ch] = FIELD_DP16(cmt->cmcr[ch], CMCR, CMIE,
FIELD_EX16(val, CMCR, CMIE));
break;
case 2:
cmt->cmcnt[ch] = val;
break;
case 4:
cmt->cmcor[ch] = val;
break;
default:
qemu_log_mask(LOG_UNIMP, "renesas_cmt: Register 0x%" HWADDR_PRIX " "
"not implemented\n",
offset);
return;
}
if (FIELD_EX16(cmt->cmstr, CMSTR, STR) & (1 << ch)) {
update_events(cmt, ch);
}
}
}
static const MemoryRegionOps cmt_ops = {
.write = cmt_write,
.read = cmt_read,
.endianness = DEVICE_NATIVE_ENDIAN,
.impl = {
.min_access_size = 2,
.max_access_size = 2,
},
.valid = {
.min_access_size = 2,
.max_access_size = 2,
},
};
static void timer_events(RCMTState *cmt, int ch)
{
cmt->cmcnt[ch] = 0;
cmt->tick[ch] = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
update_events(cmt, ch);
if (FIELD_EX16(cmt->cmcr[ch], CMCR, CMIE)) {
qemu_irq_pulse(cmt->cmi[ch]);
}
}
static void timer_event0(void *opaque)
{
RCMTState *cmt = opaque;
timer_events(cmt, 0);
}
static void timer_event1(void *opaque)
{
RCMTState *cmt = opaque;
timer_events(cmt, 1);
}
static void rcmt_reset(DeviceState *dev)
{
RCMTState *cmt = RCMT(dev);
cmt->cmstr = 0;
cmt->cmcr[0] = cmt->cmcr[1] = 0;
cmt->cmcnt[0] = cmt->cmcnt[1] = 0;
cmt->cmcor[0] = cmt->cmcor[1] = 0xffff;
}
static void rcmt_init(Object *obj)
{
SysBusDevice *d = SYS_BUS_DEVICE(obj);
RCMTState *cmt = RCMT(obj);
int i;
memory_region_init_io(&cmt->memory, OBJECT(cmt), &cmt_ops,
cmt, "renesas-cmt", 0x10);
sysbus_init_mmio(d, &cmt->memory);
for (i = 0; i < ARRAY_SIZE(cmt->cmi); i++) {
sysbus_init_irq(d, &cmt->cmi[i]);
}
timer_init_ns(&cmt->timer[0], QEMU_CLOCK_VIRTUAL, timer_event0, cmt);
timer_init_ns(&cmt->timer[1], QEMU_CLOCK_VIRTUAL, timer_event1, cmt);
}
static const VMStateDescription vmstate_rcmt = {
.name = "rx-cmt",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_UINT16(cmstr, RCMTState),
VMSTATE_UINT16_ARRAY(cmcr, RCMTState, CMT_CH),
VMSTATE_UINT16_ARRAY(cmcnt, RCMTState, CMT_CH),
VMSTATE_UINT16_ARRAY(cmcor, RCMTState, CMT_CH),
VMSTATE_INT64_ARRAY(tick, RCMTState, CMT_CH),
VMSTATE_TIMER_ARRAY(timer, RCMTState, CMT_CH),
VMSTATE_END_OF_LIST()
}
};
static const Property rcmt_properties[] = {
DEFINE_PROP_UINT64("input-freq", RCMTState, input_freq, 0),
};
static void rcmt_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->vmsd = &vmstate_rcmt;
device_class_set_legacy_reset(dc, rcmt_reset);
device_class_set_props(dc, rcmt_properties);
}
static const TypeInfo rcmt_info = {
.name = TYPE_RENESAS_CMT,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(RCMTState),
.instance_init = rcmt_init,
.class_init = rcmt_class_init,
};
static void rcmt_register_types(void)
{
type_register_static(&rcmt_info);
}
type_init(rcmt_register_types)
+492
View File
@@ -0,0 +1,492 @@
/*
* Renesas 8bit timer
*
* Datasheet: RX62N Group, RX621 Group User's Manual: Hardware
* (Rev.1.40 R01UH0033EJ0140)
*
* Copyright (c) 2019 Yoshinori Sato
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2 or later, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "hw/core/irq.h"
#include "hw/core/registerfields.h"
#include "hw/core/qdev-properties.h"
#include "hw/timer/renesas_tmr.h"
#include "migration/vmstate.h"
REG8(TCR, 0)
FIELD(TCR, CCLR, 3, 2)
FIELD(TCR, OVIE, 5, 1)
FIELD(TCR, CMIEA, 6, 1)
FIELD(TCR, CMIEB, 7, 1)
REG8(TCSR, 2)
FIELD(TCSR, OSA, 0, 2)
FIELD(TCSR, OSB, 2, 2)
FIELD(TCSR, ADTE, 4, 2)
REG8(TCORA, 4)
REG8(TCORB, 6)
REG8(TCNT, 8)
REG8(TCCR, 10)
FIELD(TCCR, CKS, 0, 3)
FIELD(TCCR, CSS, 3, 2)
FIELD(TCCR, TMRIS, 7, 1)
#define CSS_EXTERNAL 0x00
#define CSS_INTERNAL 0x01
#define CSS_INVALID 0x02
#define CSS_CASCADING 0x03
#define CCLR_A 0x01
#define CCLR_B 0x02
static const int clkdiv[] = {0, 1, 2, 8, 32, 64, 1024, 8192};
static uint8_t concat_reg(uint8_t *reg)
{
return (reg[0] << 8) | reg[1];
}
static void update_events(RTMRState *tmr, int ch)
{
uint16_t diff[TMR_NR_EVENTS], min;
int64_t next_time;
int i, event;
if (tmr->tccr[ch] == 0) {
return;
}
if (FIELD_EX8(tmr->tccr[ch], TCCR, CSS) == 0) {
/* external clock mode */
/* event not happened */
return;
}
if (FIELD_EX8(tmr->tccr[0], TCCR, CSS) == CSS_CASCADING) {
/* cascading mode */
if (ch == 1) {
tmr->next[ch] = none;
return;
}
diff[cmia] = concat_reg(tmr->tcora) - concat_reg(tmr->tcnt);
diff[cmib] = concat_reg(tmr->tcorb) - concat_reg(tmr->tcnt);
diff[ovi] = 0x10000 - concat_reg(tmr->tcnt);
} else {
/* separate mode */
diff[cmia] = tmr->tcora[ch] - tmr->tcnt[ch];
diff[cmib] = tmr->tcorb[ch] - tmr->tcnt[ch];
diff[ovi] = 0x100 - tmr->tcnt[ch];
}
/* Search for the most recently occurring event. */
for (event = 0, min = diff[0], i = 1; i < none; i++) {
if (min > diff[i]) {
event = i;
min = diff[i];
}
}
tmr->next[ch] = event;
next_time = diff[event];
next_time *= clkdiv[FIELD_EX8(tmr->tccr[ch], TCCR, CKS)];
next_time *= NANOSECONDS_PER_SECOND;
next_time /= tmr->input_freq;
next_time += qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
timer_mod(&tmr->timer[ch], next_time);
}
static int elapsed_time(RTMRState *tmr, int ch, int64_t delta)
{
int divrate = clkdiv[FIELD_EX8(tmr->tccr[ch], TCCR, CKS)];
int et;
tmr->div_round[ch] += delta;
if (divrate > 0) {
et = tmr->div_round[ch] / divrate;
tmr->div_round[ch] %= divrate;
} else {
/* disable clock. so no update */
et = 0;
}
return et;
}
static uint16_t read_tcnt(RTMRState *tmr, unsigned size, int ch)
{
int64_t delta, now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
int elapsed, ovf = 0;
uint16_t tcnt[2];
uint32_t ret;
delta = (now - tmr->tick) * NANOSECONDS_PER_SECOND / tmr->input_freq;
if (delta > 0) {
tmr->tick = now;
switch (FIELD_EX8(tmr->tccr[1], TCCR, CSS)) {
case CSS_INTERNAL:
/* timer1 count update */
elapsed = elapsed_time(tmr, 1, delta);
if (elapsed >= 0x100) {
ovf = elapsed >> 8;
}
tcnt[1] = tmr->tcnt[1] + (elapsed & 0xff);
break;
case CSS_INVALID: /* guest error to have set this */
case CSS_EXTERNAL: /* QEMU doesn't implement these */
case CSS_CASCADING:
tcnt[1] = tmr->tcnt[1];
break;
default:
g_assert_not_reached();
}
switch (FIELD_EX8(tmr->tccr[0], TCCR, CSS)) {
case CSS_INTERNAL:
elapsed = elapsed_time(tmr, 0, delta);
tcnt[0] = tmr->tcnt[0] + elapsed;
break;
case CSS_CASCADING:
tcnt[0] = tmr->tcnt[0] + ovf;
break;
case CSS_INVALID: /* guest error to have set this */
case CSS_EXTERNAL: /* QEMU doesn't implement this */
tcnt[0] = tmr->tcnt[0];
break;
default:
g_assert_not_reached();
}
} else {
tcnt[0] = tmr->tcnt[0];
tcnt[1] = tmr->tcnt[1];
}
if (size == 1) {
return tcnt[ch];
} else {
ret = 0;
ret = deposit32(ret, 0, 8, tcnt[1]);
ret = deposit32(ret, 8, 8, tcnt[0]);
return ret;
}
}
static uint8_t read_tccr(uint8_t r)
{
uint8_t tccr = 0;
tccr = FIELD_DP8(tccr, TCCR, TMRIS,
FIELD_EX8(r, TCCR, TMRIS));
tccr = FIELD_DP8(tccr, TCCR, CSS,
FIELD_EX8(r, TCCR, CSS));
tccr = FIELD_DP8(tccr, TCCR, CKS,
FIELD_EX8(r, TCCR, CKS));
return tccr;
}
static uint64_t tmr_read(void *opaque, hwaddr addr, unsigned size)
{
RTMRState *tmr = opaque;
int ch = addr & 1;
uint64_t ret;
if (size == 2 && (ch != 0 || addr == A_TCR || addr == A_TCSR)) {
qemu_log_mask(LOG_GUEST_ERROR, "renesas_tmr: Invalid read size 0x%"
HWADDR_PRIX "\n",
addr);
return UINT64_MAX;
}
switch (addr & 0x0e) {
case A_TCR:
ret = 0;
ret = FIELD_DP8(ret, TCR, CCLR,
FIELD_EX8(tmr->tcr[ch], TCR, CCLR));
ret = FIELD_DP8(ret, TCR, OVIE,
FIELD_EX8(tmr->tcr[ch], TCR, OVIE));
ret = FIELD_DP8(ret, TCR, CMIEA,
FIELD_EX8(tmr->tcr[ch], TCR, CMIEA));
ret = FIELD_DP8(ret, TCR, CMIEB,
FIELD_EX8(tmr->tcr[ch], TCR, CMIEB));
return ret;
case A_TCSR:
ret = 0;
ret = FIELD_DP8(ret, TCSR, OSA,
FIELD_EX8(tmr->tcsr[ch], TCSR, OSA));
ret = FIELD_DP8(ret, TCSR, OSB,
FIELD_EX8(tmr->tcsr[ch], TCSR, OSB));
switch (ch) {
case 0:
ret = FIELD_DP8(ret, TCSR, ADTE,
FIELD_EX8(tmr->tcsr[ch], TCSR, ADTE));
break;
case 1: /* CH1 ADTE unimplement always 1 */
ret = FIELD_DP8(ret, TCSR, ADTE, 1);
break;
}
return ret;
case A_TCORA:
if (size == 1) {
return tmr->tcora[ch];
} else if (ch == 0) {
return concat_reg(tmr->tcora);
}
/* fall through */
case A_TCORB:
if (size == 1) {
return tmr->tcorb[ch];
} else {
return concat_reg(tmr->tcorb);
}
case A_TCNT:
return read_tcnt(tmr, size, ch);
case A_TCCR:
if (size == 1) {
return read_tccr(tmr->tccr[ch]);
} else {
return read_tccr(tmr->tccr[0]) << 8 | read_tccr(tmr->tccr[1]);
}
default:
qemu_log_mask(LOG_UNIMP, "renesas_tmr: Register 0x%" HWADDR_PRIX
" not implemented\n",
addr);
break;
}
return UINT64_MAX;
}
static void tmr_write_count(RTMRState *tmr, int ch, unsigned size,
uint8_t *reg, uint64_t val)
{
if (size == 1) {
reg[ch] = val;
update_events(tmr, ch);
} else {
reg[0] = extract32(val, 8, 8);
reg[1] = extract32(val, 0, 8);
update_events(tmr, 0);
update_events(tmr, 1);
}
}
static void tmr_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
{
RTMRState *tmr = opaque;
int ch = addr & 1;
if (size == 2 && (ch != 0 || addr == A_TCR || addr == A_TCSR)) {
qemu_log_mask(LOG_GUEST_ERROR,
"renesas_tmr: Invalid write size 0x%" HWADDR_PRIX "\n",
addr);
return;
}
switch (addr & 0x0e) {
case A_TCR:
tmr->tcr[ch] = val;
break;
case A_TCSR:
tmr->tcsr[ch] = val;
break;
case A_TCORA:
tmr_write_count(tmr, ch, size, tmr->tcora, val);
break;
case A_TCORB:
tmr_write_count(tmr, ch, size, tmr->tcorb, val);
break;
case A_TCNT:
tmr_write_count(tmr, ch, size, tmr->tcnt, val);
break;
case A_TCCR:
tmr_write_count(tmr, ch, size, tmr->tccr, val);
break;
default:
qemu_log_mask(LOG_UNIMP, "renesas_tmr: Register 0x%" HWADDR_PRIX
" not implemented\n",
addr);
break;
}
}
static const MemoryRegionOps tmr_ops = {
.write = tmr_write,
.read = tmr_read,
.endianness = DEVICE_LITTLE_ENDIAN,
.impl = {
.min_access_size = 1,
.max_access_size = 2,
},
.valid = {
.min_access_size = 1,
.max_access_size = 2,
},
};
static void timer_events(RTMRState *tmr, int ch);
static uint16_t issue_event(RTMRState *tmr, int ch, int sz,
uint16_t tcnt, uint16_t tcora, uint16_t tcorb)
{
uint16_t ret = tcnt;
switch (tmr->next[ch]) {
case none:
break;
case cmia:
if (tcnt >= tcora) {
if (FIELD_EX8(tmr->tcr[ch], TCR, CCLR) == CCLR_A) {
ret = tcnt - tcora;
}
if (FIELD_EX8(tmr->tcr[ch], TCR, CMIEA)) {
qemu_irq_pulse(tmr->cmia[ch]);
}
if (sz == 8 && ch == 0 &&
FIELD_EX8(tmr->tccr[1], TCCR, CSS) == CSS_CASCADING) {
tmr->tcnt[1]++;
timer_events(tmr, 1);
}
}
break;
case cmib:
if (tcnt >= tcorb) {
if (FIELD_EX8(tmr->tcr[ch], TCR, CCLR) == CCLR_B) {
ret = tcnt - tcorb;
}
if (FIELD_EX8(tmr->tcr[ch], TCR, CMIEB)) {
qemu_irq_pulse(tmr->cmib[ch]);
}
}
break;
case ovi:
if ((tcnt >= (1 << sz)) && FIELD_EX8(tmr->tcr[ch], TCR, OVIE)) {
qemu_irq_pulse(tmr->ovi[ch]);
}
break;
default:
g_assert_not_reached();
}
return ret;
}
static void timer_events(RTMRState *tmr, int ch)
{
uint16_t tcnt;
tmr->tcnt[ch] = read_tcnt(tmr, 1, ch);
if (FIELD_EX8(tmr->tccr[0], TCCR, CSS) != CSS_CASCADING) {
tmr->tcnt[ch] = issue_event(tmr, ch, 8,
tmr->tcnt[ch],
tmr->tcora[ch],
tmr->tcorb[ch]) & 0xff;
} else {
if (ch == 1) {
return;
}
tcnt = issue_event(tmr, ch, 16,
concat_reg(tmr->tcnt),
concat_reg(tmr->tcora),
concat_reg(tmr->tcorb));
tmr->tcnt[0] = (tcnt >> 8) & 0xff;
tmr->tcnt[1] = tcnt & 0xff;
}
update_events(tmr, ch);
}
static void timer_event0(void *opaque)
{
RTMRState *tmr = opaque;
timer_events(tmr, 0);
}
static void timer_event1(void *opaque)
{
RTMRState *tmr = opaque;
timer_events(tmr, 1);
}
static void rtmr_reset(DeviceState *dev)
{
RTMRState *tmr = RTMR(dev);
tmr->tcr[0] = tmr->tcr[1] = 0x00;
tmr->tcsr[0] = 0x00;
tmr->tcsr[1] = 0x10;
tmr->tcnt[0] = tmr->tcnt[1] = 0x00;
tmr->tcora[0] = tmr->tcora[1] = 0xff;
tmr->tcorb[0] = tmr->tcorb[1] = 0xff;
tmr->tccr[0] = tmr->tccr[1] = 0x00;
tmr->next[0] = tmr->next[1] = none;
tmr->tick = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
}
static void rtmr_init(Object *obj)
{
SysBusDevice *d = SYS_BUS_DEVICE(obj);
RTMRState *tmr = RTMR(obj);
int i;
memory_region_init_io(&tmr->memory, OBJECT(tmr), &tmr_ops,
tmr, "renesas-tmr", 0x10);
sysbus_init_mmio(d, &tmr->memory);
for (i = 0; i < ARRAY_SIZE(tmr->ovi); i++) {
sysbus_init_irq(d, &tmr->cmia[i]);
sysbus_init_irq(d, &tmr->cmib[i]);
sysbus_init_irq(d, &tmr->ovi[i]);
}
timer_init_ns(&tmr->timer[0], QEMU_CLOCK_VIRTUAL, timer_event0, tmr);
timer_init_ns(&tmr->timer[1], QEMU_CLOCK_VIRTUAL, timer_event1, tmr);
}
static const VMStateDescription vmstate_rtmr = {
.name = "rx-tmr",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_INT64(tick, RTMRState),
VMSTATE_UINT8_ARRAY(tcnt, RTMRState, TMR_CH),
VMSTATE_UINT8_ARRAY(tcora, RTMRState, TMR_CH),
VMSTATE_UINT8_ARRAY(tcorb, RTMRState, TMR_CH),
VMSTATE_UINT8_ARRAY(tcr, RTMRState, TMR_CH),
VMSTATE_UINT8_ARRAY(tccr, RTMRState, TMR_CH),
VMSTATE_UINT8_ARRAY(tcor, RTMRState, TMR_CH),
VMSTATE_UINT8_ARRAY(tcsr, RTMRState, TMR_CH),
VMSTATE_INT64_ARRAY(div_round, RTMRState, TMR_CH),
VMSTATE_UINT8_ARRAY(next, RTMRState, TMR_CH),
VMSTATE_TIMER_ARRAY(timer, RTMRState, TMR_CH),
VMSTATE_END_OF_LIST()
}
};
static const Property rtmr_properties[] = {
DEFINE_PROP_UINT64("input-freq", RTMRState, input_freq, 0),
};
static void rtmr_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->vmsd = &vmstate_rtmr;
device_class_set_legacy_reset(dc, rtmr_reset);
device_class_set_props(dc, rtmr_properties);
}
static const TypeInfo rtmr_info = {
.name = TYPE_RENESAS_TMR,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(RTMRState),
.instance_init = rtmr_init,
.class_init = rtmr_class_init,
};
static void rtmr_register_types(void)
{
type_register_static(&rtmr_info);
}
type_init(rtmr_register_types)
+373
View File
@@ -0,0 +1,373 @@
/*
* SuperH Timer modules.
*
* Copyright (c) 2007 Magnus Damm
* Based on arm_timer.c by Paul Brook
* Copyright (c) 2005-2006 CodeSourcery.
*
* This code is licensed under the GPL.
*/
#include "qemu/osdep.h"
#include "system/memory.h"
#include "qemu/log.h"
#include "hw/core/irq.h"
#include "hw/sh4/sh.h"
#include "hw/timer/tmu012.h"
#include "hw/core/ptimer.h"
#include "trace.h"
#define TIMER_TCR_TPSC (7 << 0)
#define TIMER_TCR_CKEG (3 << 3)
#define TIMER_TCR_UNIE (1 << 5)
#define TIMER_TCR_ICPE (3 << 6)
#define TIMER_TCR_UNF (1 << 8)
#define TIMER_TCR_ICPF (1 << 9)
#define TIMER_TCR_RESERVED (0x3f << 10)
#define TIMER_FEAT_CAPT (1 << 0)
#define TIMER_FEAT_EXTCLK (1 << 1)
#define OFFSET_TCOR 0
#define OFFSET_TCNT 1
#define OFFSET_TCR 2
#define OFFSET_TCPR 3
typedef struct {
ptimer_state *timer;
uint32_t tcnt;
uint32_t tcor;
uint32_t tcr;
uint32_t tcpr;
int freq;
int int_level;
int old_level;
int feat;
int enabled;
qemu_irq irq;
} SHTimerState;
/* Check all active timers, and schedule the next timer interrupt. */
static void sh_timer_update(SHTimerState *s)
{
int new_level = s->int_level && (s->tcr & TIMER_TCR_UNIE);
if (new_level != s->old_level) {
qemu_set_irq(s->irq, new_level);
}
s->old_level = s->int_level;
s->int_level = new_level;
}
static uint32_t sh_timer_read(void *opaque, hwaddr offset)
{
SHTimerState *s = opaque;
switch (offset >> 2) {
case OFFSET_TCOR:
return s->tcor;
case OFFSET_TCNT:
return ptimer_get_count(s->timer);
case OFFSET_TCR:
return s->tcr | (s->int_level ? TIMER_TCR_UNF : 0);
case OFFSET_TCPR:
if (s->feat & TIMER_FEAT_CAPT) {
return s->tcpr;
}
}
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
__func__, offset);
return 0;
}
static void sh_timer_write(void *opaque, hwaddr offset, uint32_t value)
{
SHTimerState *s = opaque;
int freq;
switch (offset >> 2) {
case OFFSET_TCOR:
s->tcor = value;
ptimer_transaction_begin(s->timer);
ptimer_set_limit(s->timer, s->tcor, 0);
ptimer_transaction_commit(s->timer);
break;
case OFFSET_TCNT:
s->tcnt = value;
ptimer_transaction_begin(s->timer);
ptimer_set_count(s->timer, s->tcnt);
ptimer_transaction_commit(s->timer);
break;
case OFFSET_TCR:
ptimer_transaction_begin(s->timer);
if (s->enabled) {
/*
* Pause the timer if it is running. This may cause some inaccuracy
* due to rounding, but avoids a whole lot of other messiness
*/
ptimer_stop(s->timer);
}
freq = s->freq;
/* ??? Need to recalculate expiry time after changing divisor. */
switch (value & TIMER_TCR_TPSC) {
case 0:
freq >>= 2;
break;
case 1:
freq >>= 4;
break;
case 2:
freq >>= 6;
break;
case 3:
freq >>= 8;
break;
case 4:
freq >>= 10;
break;
case 6:
case 7:
if (s->feat & TIMER_FEAT_EXTCLK) {
break;
}
/* fallthrough */
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Reserved TPSC value\n", __func__);
}
switch ((value & TIMER_TCR_CKEG) >> 3) {
case 0:
break;
case 1:
case 2:
case 3:
if (s->feat & TIMER_FEAT_EXTCLK) {
break;
}
/* fallthrough */
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Reserved CKEG value\n", __func__);
}
switch ((value & TIMER_TCR_ICPE) >> 6) {
case 0:
break;
case 2:
case 3:
if (s->feat & TIMER_FEAT_CAPT) {
break;
}
/* fallthrough */
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Reserved ICPE value\n", __func__);
}
if ((value & TIMER_TCR_UNF) == 0) {
s->int_level = 0;
}
value &= ~TIMER_TCR_UNF;
if ((value & TIMER_TCR_ICPF) && (!(s->feat & TIMER_FEAT_CAPT))) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Reserved ICPF value\n", __func__);
}
value &= ~TIMER_TCR_ICPF; /* capture not supported */
if (value & TIMER_TCR_RESERVED) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Reserved TCR bits set\n", __func__);
}
s->tcr = value;
ptimer_set_limit(s->timer, s->tcor, 0);
ptimer_set_freq(s->timer, freq);
if (s->enabled) {
/* Restart the timer if still enabled. */
ptimer_run(s->timer, 0);
}
ptimer_transaction_commit(s->timer);
break;
case OFFSET_TCPR:
if (s->feat & TIMER_FEAT_CAPT) {
s->tcpr = value;
break;
}
/* fallthrough */
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, offset);
}
sh_timer_update(s);
}
static void sh_timer_start_stop(void *opaque, int enable)
{
SHTimerState *s = opaque;
trace_sh_timer_start_stop(enable, s->enabled);
ptimer_transaction_begin(s->timer);
if (s->enabled && !enable) {
ptimer_stop(s->timer);
}
if (!s->enabled && enable) {
ptimer_run(s->timer, 0);
}
ptimer_transaction_commit(s->timer);
s->enabled = !!enable;
}
static void sh_timer_tick(void *opaque)
{
SHTimerState *s = opaque;
s->int_level = s->enabled;
sh_timer_update(s);
}
static void *sh_timer_init(uint32_t freq, int feat, qemu_irq irq)
{
SHTimerState *s;
s = g_malloc0(sizeof(*s));
s->freq = freq;
s->feat = feat;
s->tcor = 0xffffffff;
s->tcnt = 0xffffffff;
s->tcpr = 0xdeadbeef;
s->tcr = 0;
s->enabled = 0;
s->irq = irq;
s->timer = ptimer_init(sh_timer_tick, s, PTIMER_POLICY_LEGACY);
sh_timer_write(s, OFFSET_TCOR >> 2, s->tcor);
sh_timer_write(s, OFFSET_TCNT >> 2, s->tcnt);
sh_timer_write(s, OFFSET_TCPR >> 2, s->tcpr);
sh_timer_write(s, OFFSET_TCR >> 2, s->tcpr);
/* ??? Save/restore. */
return s;
}
typedef struct {
MemoryRegion iomem;
MemoryRegion iomem_p4;
MemoryRegion iomem_a7;
void *timer[3];
int level[3];
uint32_t tocr;
uint32_t tstr;
int feat;
} tmu012_state;
static uint64_t tmu012_read(void *opaque, hwaddr offset, unsigned size)
{
tmu012_state *s = opaque;
trace_sh_timer_read(offset);
if (offset >= 0x20) {
if (!(s->feat & TMU012_FEAT_3CHAN)) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad channel offset 0x%" HWADDR_PRIx "\n",
__func__, offset);
}
return sh_timer_read(s->timer[2], offset - 0x20);
}
if (offset >= 0x14) {
return sh_timer_read(s->timer[1], offset - 0x14);
}
if (offset >= 0x08) {
return sh_timer_read(s->timer[0], offset - 0x08);
}
if (offset == 4) {
return s->tstr;
}
if ((s->feat & TMU012_FEAT_TOCR) && offset == 0) {
return s->tocr;
}
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, offset);
return 0;
}
static void tmu012_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
tmu012_state *s = opaque;
trace_sh_timer_write(offset, value);
if (offset >= 0x20) {
if (!(s->feat & TMU012_FEAT_3CHAN)) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad channel offset 0x%" HWADDR_PRIx "\n",
__func__, offset);
}
sh_timer_write(s->timer[2], offset - 0x20, value);
return;
}
if (offset >= 0x14) {
sh_timer_write(s->timer[1], offset - 0x14, value);
return;
}
if (offset >= 0x08) {
sh_timer_write(s->timer[0], offset - 0x08, value);
return;
}
if (offset == 4) {
sh_timer_start_stop(s->timer[0], value & (1 << 0));
sh_timer_start_stop(s->timer[1], value & (1 << 1));
if (s->feat & TMU012_FEAT_3CHAN) {
sh_timer_start_stop(s->timer[2], value & (1 << 2));
} else {
if (value & (1 << 2)) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad channel\n", __func__);
}
}
s->tstr = value;
return;
}
if ((s->feat & TMU012_FEAT_TOCR) && offset == 0) {
s->tocr = value & (1 << 0);
}
}
static const MemoryRegionOps tmu012_ops = {
.read = tmu012_read,
.write = tmu012_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
void tmu012_init(MemoryRegion *sysmem, hwaddr base, int feat, uint32_t freq,
qemu_irq ch0_irq, qemu_irq ch1_irq,
qemu_irq ch2_irq0, qemu_irq ch2_irq1)
{
tmu012_state *s;
int timer_feat = (feat & TMU012_FEAT_EXTCLK) ? TIMER_FEAT_EXTCLK : 0;
s = g_malloc0(sizeof(*s));
s->feat = feat;
s->timer[0] = sh_timer_init(freq, timer_feat, ch0_irq);
s->timer[1] = sh_timer_init(freq, timer_feat, ch1_irq);
if (feat & TMU012_FEAT_3CHAN) {
s->timer[2] = sh_timer_init(freq, timer_feat | TIMER_FEAT_CAPT,
ch2_irq0); /* ch2_irq1 not supported */
}
memory_region_init_io(&s->iomem, NULL, &tmu012_ops, s, "timer", 0x30);
memory_region_init_alias(&s->iomem_p4, NULL, "timer-p4",
&s->iomem, 0, memory_region_size(&s->iomem));
memory_region_add_subregion(sysmem, P4ADDR(base), &s->iomem_p4);
memory_region_init_alias(&s->iomem_a7, NULL, "timer-a7",
&s->iomem, 0, memory_region_size(&s->iomem));
memory_region_add_subregion(sysmem, A7ADDR(base), &s->iomem_a7);
/* ??? Save/restore. */
}
+467
View File
@@ -0,0 +1,467 @@
/*
* SiFive PWM
*
* Copyright (c) 2020 Western Digital
*
* Author: Alistair Francis <[email protected]>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "qemu/osdep.h"
#include "trace.h"
#include "hw/core/irq.h"
#include "hw/timer/sifive_pwm.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/registerfields.h"
#include "migration/vmstate.h"
#include "qemu/log.h"
#include "qemu/module.h"
#define HAS_PWM_EN_BITS(cfg) ((cfg & R_CONFIG_ENONESHOT_MASK) || \
(cfg & R_CONFIG_ENALWAYS_MASK))
#define PWMCMP_MASK 0xFFFF
#define PWMCOUNT_MASK 0x7FFFFFFF
REG32(CONFIG, 0x00)
FIELD(CONFIG, SCALE, 0, 4)
FIELD(CONFIG, STICKY, 8, 1)
FIELD(CONFIG, ZEROCMP, 9, 1)
FIELD(CONFIG, DEGLITCH, 10, 1)
FIELD(CONFIG, ENALWAYS, 12, 1)
FIELD(CONFIG, ENONESHOT, 13, 1)
FIELD(CONFIG, CMP0CENTER, 16, 1)
FIELD(CONFIG, CMP1CENTER, 17, 1)
FIELD(CONFIG, CMP2CENTER, 18, 1)
FIELD(CONFIG, CMP3CENTER, 19, 1)
FIELD(CONFIG, CMP0GANG, 24, 1)
FIELD(CONFIG, CMP1GANG, 25, 1)
FIELD(CONFIG, CMP2GANG, 26, 1)
FIELD(CONFIG, CMP3GANG, 27, 1)
FIELD(CONFIG, CMP0IP, 28, 1)
FIELD(CONFIG, CMP1IP, 29, 1)
FIELD(CONFIG, CMP2IP, 30, 1)
FIELD(CONFIG, CMP3IP, 31, 1)
REG32(COUNT, 0x08)
REG32(PWMS, 0x10)
REG32(PWMCMP0, 0x20)
REG32(PWMCMP1, 0x24)
REG32(PWMCMP2, 0x28)
REG32(PWMCMP3, 0x2C)
static inline uint64_t sifive_pwm_ns_to_ticks(SiFivePwmState *s,
uint64_t time)
{
return muldiv64(time, s->freq_hz, NANOSECONDS_PER_SECOND);
}
static inline uint64_t sifive_pwm_ticks_to_ns(SiFivePwmState *s,
uint64_t ticks)
{
return muldiv64(ticks, NANOSECONDS_PER_SECOND, s->freq_hz);
}
static inline uint64_t sifive_pwm_compute_scale(SiFivePwmState *s)
{
return s->pwmcfg & R_CONFIG_SCALE_MASK;
}
static void sifive_pwm_set_alarms(SiFivePwmState *s)
{
uint64_t now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
if (HAS_PWM_EN_BITS(s->pwmcfg)) {
/*
* Subtract ticks from number of ticks when the timer was zero
* and mask to the register width.
*/
uint64_t pwmcount = (sifive_pwm_ns_to_ticks(s, now_ns) -
s->tick_offset) & PWMCOUNT_MASK;
uint64_t scale = sifive_pwm_compute_scale(s);
/* PWMs only contains PWMCMP_MASK bits starting at scale */
uint64_t pwms = (pwmcount & (PWMCMP_MASK << scale)) >> scale;
for (int i = 0; i < SIFIVE_PWM_CHANS; i++) {
uint64_t pwmcmp = s->pwmcmp[i] & PWMCMP_MASK;
uint64_t pwmcmp_ticks = pwmcmp << scale;
/*
* Per circuit diagram and spec, both cases raises corresponding
* IP bit one clock cycle after time expires.
*/
if (pwmcmp > pwms) {
uint64_t offset = pwmcmp_ticks - pwmcount + 1;
uint64_t when_to_fire = now_ns +
sifive_pwm_ticks_to_ns(s, offset);
trace_sifive_pwm_set_alarm(when_to_fire, now_ns);
timer_mod(&s->timer[i], when_to_fire);
} else {
/* Schedule interrupt for next cycle */
trace_sifive_pwm_set_alarm(now_ns + 1, now_ns);
timer_mod(&s->timer[i], now_ns + 1);
}
}
} else {
/*
* If timer incrementing disabled, just do pwms > pwmcmp check since
* a write may have happened to PWMs.
*/
uint64_t pwmcount = (s->tick_offset) & PWMCOUNT_MASK;
uint64_t scale = sifive_pwm_compute_scale(s);
uint64_t pwms = (pwmcount & (PWMCMP_MASK << scale)) >> scale;
for (int i = 0; i < SIFIVE_PWM_CHANS; i++) {
uint64_t pwmcmp = s->pwmcmp[i] & PWMCMP_MASK;
if (pwms >= pwmcmp) {
trace_sifive_pwm_set_alarm(now_ns + 1, now_ns);
timer_mod(&s->timer[i], now_ns + 1);
} else {
/* Effectively disable timer by scheduling far in future. */
trace_sifive_pwm_set_alarm(0xFFFFFFFFFFFFFF, now_ns);
timer_mod(&s->timer[i], 0xFFFFFFFFFFFFFF);
}
}
}
}
static void sifive_pwm_interrupt(SiFivePwmState *s, int num)
{
uint64_t now = sifive_pwm_ns_to_ticks(s,
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
bool was_incrementing = HAS_PWM_EN_BITS(s->pwmcfg);
trace_sifive_pwm_interrupt(num);
s->pwmcfg |= R_CONFIG_CMP0IP_MASK << num;
qemu_irq_raise(s->irqs[num]);
/*
* If the zerocmp is set and pwmcmp0 raised the interrupt
* reset the zero ticks.
*/
if ((s->pwmcfg & R_CONFIG_ZEROCMP_MASK) && (num == 0)) {
/* If reset signal conditions, disable ENONESHOT. */
s->pwmcfg &= ~R_CONFIG_ENONESHOT_MASK;
if (was_incrementing) {
/* If incrementing, time in ticks is when pwmcount is zero */
s->tick_offset = now;
} else {
/* If not incrementing, pwmcount = 0 */
s->tick_offset = 0;
}
}
/*
* If carryout bit set, which we discern via looking for overflow,
* also reset ENONESHOT.
*/
if (was_incrementing &&
((now & PWMCOUNT_MASK) < (s->tick_offset & PWMCOUNT_MASK))) {
s->pwmcfg &= ~R_CONFIG_ENONESHOT_MASK;
}
/* Schedule or disable interrupts */
sifive_pwm_set_alarms(s);
/* If was enabled, and now not enabled, switch tick rep */
if (was_incrementing && !HAS_PWM_EN_BITS(s->pwmcfg)) {
s->tick_offset = (now - s->tick_offset) & PWMCOUNT_MASK;
}
}
static void sifive_pwm_interrupt_0(void *opaque)
{
SiFivePwmState *s = opaque;
sifive_pwm_interrupt(s, 0);
}
static void sifive_pwm_interrupt_1(void *opaque)
{
SiFivePwmState *s = opaque;
sifive_pwm_interrupt(s, 1);
}
static void sifive_pwm_interrupt_2(void *opaque)
{
SiFivePwmState *s = opaque;
sifive_pwm_interrupt(s, 2);
}
static void sifive_pwm_interrupt_3(void *opaque)
{
SiFivePwmState *s = opaque;
sifive_pwm_interrupt(s, 3);
}
static uint64_t sifive_pwm_read(void *opaque, hwaddr addr,
unsigned int size)
{
SiFivePwmState *s = opaque;
uint64_t cur_time, scale;
uint64_t now = sifive_pwm_ns_to_ticks(s,
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
trace_sifive_pwm_read(addr);
switch (addr) {
case A_CONFIG:
return s->pwmcfg;
case A_COUNT:
cur_time = s->tick_offset;
if (HAS_PWM_EN_BITS(s->pwmcfg)) {
cur_time = now - cur_time;
}
/*
* Return the value in the counter with bit 31 always 0
* This is allowed to wrap around so we don't need to check that.
*/
return cur_time & PWMCOUNT_MASK;
case A_PWMS:
cur_time = s->tick_offset;
scale = sifive_pwm_compute_scale(s);
if (HAS_PWM_EN_BITS(s->pwmcfg)) {
cur_time = now - cur_time;
}
return ((cur_time & PWMCOUNT_MASK) >> scale) & PWMCMP_MASK;
case A_PWMCMP0:
return s->pwmcmp[0] & PWMCMP_MASK;
case A_PWMCMP1:
return s->pwmcmp[1] & PWMCMP_MASK;
case A_PWMCMP2:
return s->pwmcmp[2] & PWMCMP_MASK;
case A_PWMCMP3:
return s->pwmcmp[3] & PWMCMP_MASK;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
return 0;
}
return 0;
}
static void sifive_pwm_write(void *opaque, hwaddr addr,
uint64_t val64, unsigned int size)
{
SiFivePwmState *s = opaque;
uint32_t value = val64;
uint64_t new_offset, scale;
uint64_t now = sifive_pwm_ns_to_ticks(s,
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
trace_sifive_pwm_write(value, addr);
switch (addr) {
case A_CONFIG:
if (value & (R_CONFIG_CMP0CENTER_MASK | R_CONFIG_CMP1CENTER_MASK |
R_CONFIG_CMP2CENTER_MASK | R_CONFIG_CMP3CENTER_MASK)) {
qemu_log_mask(LOG_UNIMP, "%s: CMPxCENTER is not supported\n",
__func__);
}
if (value & (R_CONFIG_CMP0GANG_MASK | R_CONFIG_CMP1GANG_MASK |
R_CONFIG_CMP2GANG_MASK | R_CONFIG_CMP3GANG_MASK)) {
qemu_log_mask(LOG_UNIMP, "%s: CMPxGANG is not supported\n",
__func__);
}
if (value & (R_CONFIG_CMP0IP_MASK | R_CONFIG_CMP1IP_MASK |
R_CONFIG_CMP2IP_MASK | R_CONFIG_CMP3IP_MASK)) {
qemu_log_mask(LOG_UNIMP, "%s: CMPxIP is not supported\n",
__func__);
}
if (!(value & R_CONFIG_CMP0IP_MASK)) {
qemu_irq_lower(s->irqs[0]);
}
if (!(value & R_CONFIG_CMP1IP_MASK)) {
qemu_irq_lower(s->irqs[1]);
}
if (!(value & R_CONFIG_CMP2IP_MASK)) {
qemu_irq_lower(s->irqs[2]);
}
if (!(value & R_CONFIG_CMP3IP_MASK)) {
qemu_irq_lower(s->irqs[3]);
}
/*
* If this write enables the timer increment
* set the time when pwmcount was zero to be cur_time - pwmcount.
* If this write disables the timer increment
* convert back from pwmcount to the time in ticks
* when pwmcount was zero.
*/
if ((!HAS_PWM_EN_BITS(s->pwmcfg) && HAS_PWM_EN_BITS(value)) ||
(HAS_PWM_EN_BITS(s->pwmcfg) && !HAS_PWM_EN_BITS(value))) {
s->tick_offset = (now - s->tick_offset) & PWMCOUNT_MASK;
}
s->pwmcfg = value;
break;
case A_COUNT:
/* The guest changed the counter, updated the offset value. */
new_offset = value;
if (HAS_PWM_EN_BITS(s->pwmcfg)) {
new_offset = now - new_offset;
}
s->tick_offset = new_offset;
break;
case A_PWMS:
scale = sifive_pwm_compute_scale(s);
new_offset = (((value & PWMCMP_MASK) << scale) & PWMCOUNT_MASK);
if (HAS_PWM_EN_BITS(s->pwmcfg)) {
new_offset = now - new_offset;
}
s->tick_offset = new_offset;
break;
case A_PWMCMP0:
s->pwmcmp[0] = value & PWMCMP_MASK;
break;
case A_PWMCMP1:
s->pwmcmp[1] = value & PWMCMP_MASK;
break;
case A_PWMCMP2:
s->pwmcmp[2] = value & PWMCMP_MASK;
break;
case A_PWMCMP3:
s->pwmcmp[3] = value & PWMCMP_MASK;
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
}
/* Update the alarms to reflect possible updated values */
sifive_pwm_set_alarms(s);
}
static void sifive_pwm_reset(DeviceState *dev)
{
SiFivePwmState *s = SIFIVE_PWM(dev);
uint64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
s->pwmcfg = 0x00000000;
s->pwmcmp[0] = 0x00000000;
s->pwmcmp[1] = 0x00000000;
s->pwmcmp[2] = 0x00000000;
s->pwmcmp[3] = 0x00000000;
s->tick_offset = sifive_pwm_ns_to_ticks(s, now);
}
static const MemoryRegionOps sifive_pwm_ops = {
.read = sifive_pwm_read,
.write = sifive_pwm_write,
.endianness = DEVICE_LITTLE_ENDIAN,
};
static const VMStateDescription vmstate_sifive_pwm = {
.name = TYPE_SIFIVE_PWM,
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_TIMER_ARRAY(timer, SiFivePwmState, 4),
VMSTATE_UINT64(tick_offset, SiFivePwmState),
VMSTATE_UINT32(pwmcfg, SiFivePwmState),
VMSTATE_UINT32_ARRAY(pwmcmp, SiFivePwmState, 4),
VMSTATE_END_OF_LIST()
}
};
static const Property sifive_pwm_properties[] = {
/* 0.5Ghz per spec after FSBL */
DEFINE_PROP_UINT64("clock-frequency", struct SiFivePwmState,
freq_hz, 500000000ULL),
};
static void sifive_pwm_init(Object *obj)
{
SiFivePwmState *s = SIFIVE_PWM(obj);
int i;
for (i = 0; i < SIFIVE_PWM_IRQS; i++) {
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irqs[i]);
}
memory_region_init_io(&s->mmio, obj, &sifive_pwm_ops, s,
TYPE_SIFIVE_PWM, 0x100);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
}
static void sifive_pwm_realize(DeviceState *dev, Error **errp)
{
SiFivePwmState *s = SIFIVE_PWM(dev);
timer_init_ns(&s->timer[0], QEMU_CLOCK_VIRTUAL,
sifive_pwm_interrupt_0, s);
timer_init_ns(&s->timer[1], QEMU_CLOCK_VIRTUAL,
sifive_pwm_interrupt_1, s);
timer_init_ns(&s->timer[2], QEMU_CLOCK_VIRTUAL,
sifive_pwm_interrupt_2, s);
timer_init_ns(&s->timer[3], QEMU_CLOCK_VIRTUAL,
sifive_pwm_interrupt_3, s);
}
static void sifive_pwm_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, sifive_pwm_reset);
device_class_set_props(dc, sifive_pwm_properties);
dc->vmsd = &vmstate_sifive_pwm;
dc->realize = sifive_pwm_realize;
}
static const TypeInfo sifive_pwm_info = {
.name = TYPE_SIFIVE_PWM,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(SiFivePwmState),
.instance_init = sifive_pwm_init,
.class_init = sifive_pwm_class_init,
};
static void sifive_pwm_register_types(void)
{
type_register_static(&sifive_pwm_info);
}
type_init(sifive_pwm_register_types)
+460
View File
@@ -0,0 +1,460 @@
/*
* QEMU Sparc SLAVIO timer controller emulation
*
* Copyright (c) 2003-2005 Fabrice Bellard
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "qemu/osdep.h"
#include "qemu/timer.h"
#include "hw/core/irq.h"
#include "hw/core/ptimer.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/sysbus.h"
#include "migration/vmstate.h"
#include "trace.h"
#include "qemu/module.h"
#include "qom/object.h"
/*
* Registers of hardware timer in sun4m.
*
* This is the timer/counter part of chip STP2001 (Slave I/O), also
* produced as NCR89C105. See
* http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR89C105.txt
*
* The 31-bit counter is incremented every 500ns by bit 9. Bits 8..0
* are zero. Bit 31 is 1 when count has been reached.
*
* Per-CPU timers interrupt local CPU, system timer uses normal
* interrupt routing.
*
*/
#define MAX_CPUS 16
typedef struct CPUTimerState {
qemu_irq irq;
ptimer_state *timer;
uint32_t count, counthigh, reached;
/* processor only */
uint32_t run;
uint64_t limit;
} CPUTimerState;
#define TYPE_SLAVIO_TIMER "slavio_timer"
OBJECT_DECLARE_SIMPLE_TYPE(SLAVIO_TIMERState, SLAVIO_TIMER)
typedef struct TimerContext {
MemoryRegion iomem;
SLAVIO_TIMERState *s;
unsigned int timer_index; /* 0 for system, 1 ... MAX_CPUS for CPU timers */
} TimerContext;
struct SLAVIO_TIMERState {
SysBusDevice parent_obj;
uint32_t num_cpus;
uint32_t cputimer_mode;
CPUTimerState cputimer[MAX_CPUS + 1];
TimerContext timer_context[MAX_CPUS + 1];
};
#define SYS_TIMER_SIZE 0x14
#define CPU_TIMER_SIZE 0x10
#define TIMER_LIMIT 0
#define TIMER_COUNTER 1
#define TIMER_COUNTER_NORST 2
#define TIMER_STATUS 3
#define TIMER_MODE 4
#define TIMER_COUNT_MASK32 0xfffffe00
#define TIMER_LIMIT_MASK32 0x7fffffff
#define TIMER_MAX_COUNT64 0x7ffffffffffffe00ULL
#define TIMER_MAX_COUNT32 0x7ffffe00ULL
#define TIMER_REACHED 0x80000000
#define TIMER_PERIOD 500ULL // 500ns
#define LIMIT_TO_PERIODS(l) (((l) >> 9) - 1)
#define PERIODS_TO_LIMIT(l) (((l) + 1) << 9)
static int slavio_timer_is_user(TimerContext *tc)
{
SLAVIO_TIMERState *s = tc->s;
unsigned int timer_index = tc->timer_index;
return timer_index != 0 && (s->cputimer_mode & (1 << (timer_index - 1)));
}
// Update count, set irq, update expire_time
// Convert from ptimer countdown units
static void slavio_timer_get_out(CPUTimerState *t)
{
uint64_t count, limit;
if (t->limit == 0) { /* free-run system or processor counter */
limit = TIMER_MAX_COUNT32;
} else {
limit = t->limit;
}
count = limit - PERIODS_TO_LIMIT(ptimer_get_count(t->timer));
trace_slavio_timer_get_out(t->limit, t->counthigh, t->count);
t->count = count & TIMER_COUNT_MASK32;
t->counthigh = count >> 32;
}
// timer callback
static void slavio_timer_irq(void *opaque)
{
TimerContext *tc = opaque;
SLAVIO_TIMERState *s = tc->s;
CPUTimerState *t = &s->cputimer[tc->timer_index];
slavio_timer_get_out(t);
trace_slavio_timer_irq(t->counthigh, t->count);
/* if limit is 0 (free-run), there will be no match */
if (t->limit != 0) {
t->reached = TIMER_REACHED;
}
/* there is no interrupt if user timer or free-run */
if (!slavio_timer_is_user(tc) && t->limit != 0) {
qemu_irq_raise(t->irq);
}
}
static uint64_t slavio_timer_mem_readl(void *opaque, hwaddr addr,
unsigned size)
{
TimerContext *tc = opaque;
SLAVIO_TIMERState *s = tc->s;
uint32_t saddr, ret;
unsigned int timer_index = tc->timer_index;
CPUTimerState *t = &s->cputimer[timer_index];
saddr = addr >> 2;
switch (saddr) {
case TIMER_LIMIT:
// read limit (system counter mode) or read most signifying
// part of counter (user mode)
if (slavio_timer_is_user(tc)) {
// read user timer MSW
slavio_timer_get_out(t);
ret = t->counthigh | t->reached;
} else {
// read limit
// clear irq
qemu_irq_lower(t->irq);
t->reached = 0;
ret = t->limit & TIMER_LIMIT_MASK32;
}
break;
case TIMER_COUNTER:
// read counter and reached bit (system mode) or read lsbits
// of counter (user mode)
slavio_timer_get_out(t);
if (slavio_timer_is_user(tc)) { // read user timer LSW
ret = t->count & TIMER_MAX_COUNT64;
} else { // read limit
ret = (t->count & TIMER_MAX_COUNT32) |
t->reached;
}
break;
case TIMER_STATUS:
// only available in processor counter/timer
// read start/stop status
if (timer_index > 0) {
ret = t->run;
} else {
ret = 0;
}
break;
case TIMER_MODE:
// only available in system counter
// read user/system mode
ret = s->cputimer_mode;
break;
default:
trace_slavio_timer_mem_readl_invalid(addr);
ret = 0;
break;
}
trace_slavio_timer_mem_readl(addr, ret);
return ret;
}
static void slavio_timer_mem_writel(void *opaque, hwaddr addr,
uint64_t val, unsigned size)
{
TimerContext *tc = opaque;
SLAVIO_TIMERState *s = tc->s;
uint32_t saddr;
unsigned int timer_index = tc->timer_index;
CPUTimerState *t = &s->cputimer[timer_index];
trace_slavio_timer_mem_writel(addr, val);
saddr = addr >> 2;
switch (saddr) {
case TIMER_LIMIT:
ptimer_transaction_begin(t->timer);
if (slavio_timer_is_user(tc)) {
uint64_t count;
// set user counter MSW, reset counter
t->limit = TIMER_MAX_COUNT64;
t->counthigh = val & (TIMER_MAX_COUNT64 >> 32);
t->reached = 0;
count = ((uint64_t)t->counthigh << 32) | t->count;
trace_slavio_timer_mem_writel_limit(timer_index, count);
ptimer_set_count(t->timer, LIMIT_TO_PERIODS(t->limit - count));
} else {
// set limit, reset counter
qemu_irq_lower(t->irq);
t->limit = val & TIMER_MAX_COUNT32;
if (t->limit == 0) { /* free-run */
ptimer_set_limit(t->timer,
LIMIT_TO_PERIODS(TIMER_MAX_COUNT32), 1);
} else {
ptimer_set_limit(t->timer, LIMIT_TO_PERIODS(t->limit), 1);
}
}
ptimer_transaction_commit(t->timer);
break;
case TIMER_COUNTER:
if (slavio_timer_is_user(tc)) {
uint64_t count;
// set user counter LSW, reset counter
t->limit = TIMER_MAX_COUNT64;
t->count = val & TIMER_MAX_COUNT64;
t->reached = 0;
count = ((uint64_t)t->counthigh) << 32 | t->count;
trace_slavio_timer_mem_writel_limit(timer_index, count);
ptimer_transaction_begin(t->timer);
ptimer_set_count(t->timer, LIMIT_TO_PERIODS(t->limit - count));
ptimer_transaction_commit(t->timer);
} else {
trace_slavio_timer_mem_writel_counter_invalid();
}
break;
case TIMER_COUNTER_NORST:
// set limit without resetting counter
t->limit = val & TIMER_MAX_COUNT32;
ptimer_transaction_begin(t->timer);
if (t->limit == 0) { /* free-run */
ptimer_set_limit(t->timer, LIMIT_TO_PERIODS(TIMER_MAX_COUNT32), 0);
} else {
ptimer_set_limit(t->timer, LIMIT_TO_PERIODS(t->limit), 0);
}
ptimer_transaction_commit(t->timer);
break;
case TIMER_STATUS:
ptimer_transaction_begin(t->timer);
if (slavio_timer_is_user(tc)) {
// start/stop user counter
if (val & 1) {
trace_slavio_timer_mem_writel_status_start(timer_index);
ptimer_run(t->timer, 0);
} else {
trace_slavio_timer_mem_writel_status_stop(timer_index);
ptimer_stop(t->timer);
}
}
t->run = val & 1;
ptimer_transaction_commit(t->timer);
break;
case TIMER_MODE:
if (timer_index == 0) {
unsigned int i;
for (i = 0; i < s->num_cpus; i++) {
unsigned int processor = 1 << i;
CPUTimerState *curr_timer = &s->cputimer[i + 1];
ptimer_transaction_begin(curr_timer->timer);
// check for a change in timer mode for this processor
if ((val & processor) != (s->cputimer_mode & processor)) {
if (val & processor) { // counter -> user timer
qemu_irq_lower(curr_timer->irq);
// counters are always running
if (!curr_timer->run) {
ptimer_stop(curr_timer->timer);
}
// user timer limit is always the same
curr_timer->limit = TIMER_MAX_COUNT64;
ptimer_set_limit(curr_timer->timer,
LIMIT_TO_PERIODS(curr_timer->limit),
1);
// set this processors user timer bit in config
// register
s->cputimer_mode |= processor;
trace_slavio_timer_mem_writel_mode_user(timer_index);
} else { // user timer -> counter
// start the counter
ptimer_run(curr_timer->timer, 0);
// clear this processors user timer bit in config
// register
s->cputimer_mode &= ~processor;
trace_slavio_timer_mem_writel_mode_counter(timer_index);
}
}
ptimer_transaction_commit(curr_timer->timer);
}
} else {
trace_slavio_timer_mem_writel_mode_invalid();
}
break;
default:
trace_slavio_timer_mem_writel_invalid(addr);
break;
}
}
static const MemoryRegionOps slavio_timer_mem_ops = {
.read = slavio_timer_mem_readl,
.write = slavio_timer_mem_writel,
.endianness = DEVICE_BIG_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 8,
},
.impl = {
.min_access_size = 4,
.max_access_size = 4,
},
};
static const VMStateDescription vmstate_timer = {
.name ="timer",
.version_id = 3,
.minimum_version_id = 3,
.fields = (const VMStateField[]) {
VMSTATE_UINT64(limit, CPUTimerState),
VMSTATE_UINT32(count, CPUTimerState),
VMSTATE_UINT32(counthigh, CPUTimerState),
VMSTATE_UINT32(reached, CPUTimerState),
VMSTATE_UINT32(run , CPUTimerState),
VMSTATE_PTIMER(timer, CPUTimerState),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription vmstate_slavio_timer = {
.name ="slavio_timer",
.version_id = 3,
.minimum_version_id = 3,
.fields = (const VMStateField[]) {
VMSTATE_STRUCT_ARRAY(cputimer, SLAVIO_TIMERState, MAX_CPUS + 1, 3,
vmstate_timer, CPUTimerState),
VMSTATE_END_OF_LIST()
}
};
static void slavio_timer_reset(DeviceState *d)
{
SLAVIO_TIMERState *s = SLAVIO_TIMER(d);
unsigned int i;
CPUTimerState *curr_timer;
for (i = 0; i <= MAX_CPUS; i++) {
curr_timer = &s->cputimer[i];
curr_timer->limit = 0;
curr_timer->count = 0;
curr_timer->reached = 0;
if (i <= s->num_cpus) {
ptimer_transaction_begin(curr_timer->timer);
ptimer_set_limit(curr_timer->timer,
LIMIT_TO_PERIODS(TIMER_MAX_COUNT32), 1);
ptimer_run(curr_timer->timer, 0);
curr_timer->run = 1;
ptimer_transaction_commit(curr_timer->timer);
}
}
s->cputimer_mode = 0;
}
static void slavio_timer_init(Object *obj)
{
SLAVIO_TIMERState *s = SLAVIO_TIMER(obj);
SysBusDevice *dev = SYS_BUS_DEVICE(obj);
unsigned int i;
TimerContext *tc;
for (i = 0; i <= MAX_CPUS; i++) {
uint64_t size;
char timer_name[20];
tc = &s->timer_context[i];
tc->s = s;
tc->timer_index = i;
s->cputimer[i].timer = ptimer_init(slavio_timer_irq, tc,
PTIMER_POLICY_LEGACY);
ptimer_transaction_begin(s->cputimer[i].timer);
ptimer_set_period(s->cputimer[i].timer, TIMER_PERIOD);
ptimer_transaction_commit(s->cputimer[i].timer);
size = i == 0 ? SYS_TIMER_SIZE : CPU_TIMER_SIZE;
snprintf(timer_name, sizeof(timer_name), "timer-%i", i);
memory_region_init_io(&tc->iomem, obj, &slavio_timer_mem_ops, tc,
timer_name, size);
sysbus_init_mmio(dev, &tc->iomem);
sysbus_init_irq(dev, &s->cputimer[i].irq);
}
}
static void slavio_timer_finalize(Object *obj)
{
SLAVIO_TIMERState *s = SLAVIO_TIMER(obj);
for (int i = 0; i <= MAX_CPUS; i++) {
ptimer_free(s->cputimer[i].timer);
}
}
static const Property slavio_timer_properties[] = {
DEFINE_PROP_UINT32("num_cpus", SLAVIO_TIMERState, num_cpus, 0),
};
static void slavio_timer_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, slavio_timer_reset);
dc->vmsd = &vmstate_slavio_timer;
device_class_set_props(dc, slavio_timer_properties);
}
static const TypeInfo slavio_timer_info = {
.name = TYPE_SLAVIO_TIMER,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(SLAVIO_TIMERState),
.instance_init = slavio_timer_init,
.instance_finalize = slavio_timer_finalize,
.class_init = slavio_timer_class_init,
};
static void slavio_timer_register_types(void)
{
type_register_static(&slavio_timer_info);
}
type_init(slavio_timer_register_types)
+473
View File
@@ -0,0 +1,473 @@
/*
* Arm SSE Subsystem System Counter
*
* Copyright (c) 2020 Linaro Limited
* Written by Peter Maydell
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 or
* (at your option) any later version.
*/
/*
* This is a model of the "System counter" which is documented in
* the Arm SSE-123 Example Subsystem Technical Reference Manual:
* https://developer.arm.com/documentation/101370/latest/
*
* The system counter is a non-stop 64-bit up-counter. It provides
* this count value to other devices like the SSE system timer,
* which are driven by this system timestamp rather than directly
* from a clock. Internally to the counter the count is actually
* 88-bit precision (64.24 fixed point), with a programmable scale factor.
*
* The hardware has the optional feature that it supports dynamic
* clock switching, where two clock inputs are connected, and which
* one is used is selected via a CLKSEL input signal. Since the
* users of this device in QEMU don't use this feature, we only model
* the HWCLKSW=0 configuration.
*/
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "qemu/timer.h"
#include "qapi/error.h"
#include "trace.h"
#include "hw/timer/sse-counter.h"
#include "hw/core/sysbus.h"
#include "hw/core/registerfields.h"
#include "hw/core/clock.h"
#include "hw/core/qdev-clock.h"
#include "migration/vmstate.h"
/* Registers in the control frame */
REG32(CNTCR, 0x0)
FIELD(CNTCR, EN, 0, 1)
FIELD(CNTCR, HDBG, 1, 1)
FIELD(CNTCR, SCEN, 2, 1)
FIELD(CNTCR, INTRMASK, 3, 1)
FIELD(CNTCR, PSLVERRDIS, 4, 1)
FIELD(CNTCR, INTRCLR, 5, 1)
/*
* Although CNTCR defines interrupt-related bits, the counter doesn't
* appear to actually have an interrupt output. So INTRCLR is
* effectively a RAZ/WI bit, as are the reserved bits [31:6].
*/
#define CNTCR_VALID_MASK (R_CNTCR_EN_MASK | R_CNTCR_HDBG_MASK | \
R_CNTCR_SCEN_MASK | R_CNTCR_INTRMASK_MASK | \
R_CNTCR_PSLVERRDIS_MASK)
REG32(CNTSR, 0x4)
REG32(CNTCV_LO, 0x8)
REG32(CNTCV_HI, 0xc)
REG32(CNTSCR, 0x10) /* Aliased with CNTSCR0 */
REG32(CNTID, 0x1c)
FIELD(CNTID, CNTSC, 0, 4)
FIELD(CNTID, CNTCS, 16, 1)
FIELD(CNTID, CNTSELCLK, 17, 2)
FIELD(CNTID, CNTSCR_OVR, 19, 1)
REG32(CNTSCR0, 0xd0)
REG32(CNTSCR1, 0xd4)
/* Registers in the status frame */
REG32(STATUS_CNTCV_LO, 0x0)
REG32(STATUS_CNTCV_HI, 0x4)
/* Standard ID registers, present in both frames */
REG32(PID4, 0xFD0)
REG32(PID5, 0xFD4)
REG32(PID6, 0xFD8)
REG32(PID7, 0xFDC)
REG32(PID0, 0xFE0)
REG32(PID1, 0xFE4)
REG32(PID2, 0xFE8)
REG32(PID3, 0xFEC)
REG32(CID0, 0xFF0)
REG32(CID1, 0xFF4)
REG32(CID2, 0xFF8)
REG32(CID3, 0xFFC)
/* PID/CID values */
static const int control_id[] = {
0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
0xba, 0xb0, 0x0b, 0x00, /* PID0..PID3 */
0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
};
static const int status_id[] = {
0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
0xbb, 0xb0, 0x0b, 0x00, /* PID0..PID3 */
0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
};
static void sse_counter_notify_users(SSECounter *s)
{
/*
* Notify users of the count timestamp that they may
* need to recalculate.
*/
notifier_list_notify(&s->notifier_list, NULL);
}
static bool sse_counter_enabled(SSECounter *s)
{
return (s->cntcr & R_CNTCR_EN_MASK) != 0;
}
uint64_t sse_counter_tick_to_time(SSECounter *s, uint64_t tick)
{
if (!sse_counter_enabled(s)) {
return UINT64_MAX;
}
tick -= s->ticks_then;
if (s->cntcr & R_CNTCR_SCEN_MASK) {
/* Adjust the tick count to account for the scale factor */
tick = muldiv64(tick, 0x01000000, s->cntscr0);
}
return s->ns_then + clock_ticks_to_ns(s->clk, tick);
}
void sse_counter_register_consumer(SSECounter *s, Notifier *notifier)
{
/*
* For the moment we assume that both we and the devices
* which consume us last for the life of the simulation,
* and so there is no mechanism for removing a notifier.
*/
notifier_list_add(&s->notifier_list, notifier);
}
uint64_t sse_counter_for_timestamp(SSECounter *s, uint64_t now)
{
/* Return the CNTCV value for a particular timestamp (clock ns value). */
uint64_t ticks;
if (!sse_counter_enabled(s)) {
/* Counter is disabled and does not increment */
return s->ticks_then;
}
ticks = clock_ns_to_ticks(s->clk, now - s->ns_then);
if (s->cntcr & R_CNTCR_SCEN_MASK) {
/*
* Scaling is enabled. The CNTSCR value is the amount added to
* the underlying 88-bit counter for every tick of the
* underlying clock; CNTCV is the top 64 bits of that full
* 88-bit value. Multiplying the tick count by CNTSCR tells us
* how much the full 88-bit counter has moved on; we then
* divide that by 0x01000000 to find out how much the 64-bit
* visible portion has advanced. muldiv64() gives us the
* necessary at-least-88-bit precision for the intermediate
* result.
*/
ticks = muldiv64(ticks, s->cntscr0, 0x01000000);
}
return s->ticks_then + ticks;
}
static uint64_t sse_cntcv(SSECounter *s)
{
/* Return the CNTCV value for the current time */
return sse_counter_for_timestamp(s, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
}
static void sse_write_cntcv(SSECounter *s, uint32_t value, unsigned startbit)
{
/*
* Write one 32-bit half of the counter value; startbit is the
* bit position of this half in the 64-bit word, either 0 or 32.
*/
uint64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
uint64_t cntcv = sse_counter_for_timestamp(s, now);
cntcv = deposit64(cntcv, startbit, 32, value);
s->ticks_then = cntcv;
s->ns_then = now;
sse_counter_notify_users(s);
}
static uint64_t sse_counter_control_read(void *opaque, hwaddr offset,
unsigned size)
{
SSECounter *s = SSE_COUNTER(opaque);
uint64_t r;
switch (offset) {
case A_CNTCR:
r = s->cntcr;
break;
case A_CNTSR:
/*
* The only bit here is DBGH, indicating that the counter has been
* halted via the Halt-on-Debug signal. We don't implement halting
* debug, so the whole register always reads as zero.
*/
r = 0;
break;
case A_CNTCV_LO:
r = extract64(sse_cntcv(s), 0, 32);
break;
case A_CNTCV_HI:
r = extract64(sse_cntcv(s), 32, 32);
break;
case A_CNTID:
/*
* For our implementation:
* - CNTSCR can only be written when CNTCR.EN == 0
* - HWCLKSW=0, so selected clock is always CLK0
* - counter scaling is implemented
*/
r = (1 << R_CNTID_CNTSELCLK_SHIFT) | (1 << R_CNTID_CNTSC_SHIFT);
break;
case A_CNTSCR:
case A_CNTSCR0:
r = s->cntscr0;
break;
case A_CNTSCR1:
/* If HWCLKSW == 0, CNTSCR1 is RAZ/WI */
r = 0;
break;
case A_PID4 ... A_CID3:
r = control_id[(offset - A_PID4) / 4];
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"SSE System Counter control frame read: bad offset 0x%x",
(unsigned)offset);
r = 0;
break;
}
trace_sse_counter_control_read(offset, r, size);
return r;
}
static void sse_counter_control_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
SSECounter *s = SSE_COUNTER(opaque);
trace_sse_counter_control_write(offset, value, size);
switch (offset) {
case A_CNTCR:
/*
* Although CNTCR defines interrupt-related bits, the counter doesn't
* appear to actually have an interrupt output. So INTRCLR is
* effectively a RAZ/WI bit, as are the reserved bits [31:6].
* The documentation does not explicitly say so, but we assume
* that changing the scale factor while the counter is enabled
* by toggling CNTCR.SCEN has the same behaviour (making the counter
* value UNKNOWN) as changing it by writing to CNTSCR, and so we
* don't need to try to recalculate for that case.
*/
value &= CNTCR_VALID_MASK;
if ((value ^ s->cntcr) & R_CNTCR_EN_MASK) {
/*
* Whether the counter is being enabled or disabled, the
* required action is the same: sync the (ns_then, ticks_then)
* tuple.
*/
uint64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
s->ticks_then = sse_counter_for_timestamp(s, now);
s->ns_then = now;
sse_counter_notify_users(s);
}
s->cntcr = value;
break;
case A_CNTCV_LO:
sse_write_cntcv(s, value, 0);
break;
case A_CNTCV_HI:
sse_write_cntcv(s, value, 32);
break;
case A_CNTSCR:
case A_CNTSCR0:
/*
* If the scale registers are changed when the counter is enabled,
* the count value becomes UNKNOWN. So we don't try to recalculate
* anything here but only do it on a write to CNTCR.EN.
*/
s->cntscr0 = value;
break;
case A_CNTSCR1:
/* If HWCLKSW == 0, CNTSCR1 is RAZ/WI */
break;
case A_CNTSR:
case A_CNTID:
case A_PID4 ... A_CID3:
qemu_log_mask(LOG_GUEST_ERROR,
"SSE System Counter control frame: write to RO offset 0x%x\n",
(unsigned)offset);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"SSE System Counter control frame: write to bad offset 0x%x\n",
(unsigned)offset);
break;
}
}
static uint64_t sse_counter_status_read(void *opaque, hwaddr offset,
unsigned size)
{
SSECounter *s = SSE_COUNTER(opaque);
uint64_t r;
switch (offset) {
case A_STATUS_CNTCV_LO:
r = extract64(sse_cntcv(s), 0, 32);
break;
case A_STATUS_CNTCV_HI:
r = extract64(sse_cntcv(s), 32, 32);
break;
case A_PID4 ... A_CID3:
r = status_id[(offset - A_PID4) / 4];
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"SSE System Counter status frame read: bad offset 0x%x",
(unsigned)offset);
r = 0;
break;
}
trace_sse_counter_status_read(offset, r, size);
return r;
}
static void sse_counter_status_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
trace_sse_counter_status_write(offset, value, size);
switch (offset) {
case A_STATUS_CNTCV_LO:
case A_STATUS_CNTCV_HI:
case A_PID4 ... A_CID3:
qemu_log_mask(LOG_GUEST_ERROR,
"SSE System Counter status frame: write to RO offset 0x%x\n",
(unsigned)offset);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"SSE System Counter status frame: write to bad offset 0x%x\n",
(unsigned)offset);
break;
}
}
static const MemoryRegionOps sse_counter_control_ops = {
.read = sse_counter_control_read,
.write = sse_counter_control_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid.min_access_size = 4,
.valid.max_access_size = 4,
};
static const MemoryRegionOps sse_counter_status_ops = {
.read = sse_counter_status_read,
.write = sse_counter_status_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid.min_access_size = 4,
.valid.max_access_size = 4,
};
static void sse_counter_reset(DeviceState *dev)
{
SSECounter *s = SSE_COUNTER(dev);
trace_sse_counter_reset();
s->cntcr = 0;
s->cntscr0 = 0x01000000;
s->ns_then = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
s->ticks_then = 0;
}
static void sse_clk_callback(void *opaque, ClockEvent event)
{
SSECounter *s = SSE_COUNTER(opaque);
uint64_t now;
switch (event) {
case ClockPreUpdate:
/*
* Before the clock period updates, set (ticks_then, ns_then)
* to the current time and tick count (as calculated with
* the old clock period).
*/
if (sse_counter_enabled(s)) {
now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
s->ticks_then = sse_counter_for_timestamp(s, now);
s->ns_then = now;
}
break;
case ClockUpdate:
sse_counter_notify_users(s);
break;
default:
break;
}
}
static void sse_counter_init(Object *obj)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
SSECounter *s = SSE_COUNTER(obj);
notifier_list_init(&s->notifier_list);
s->clk = qdev_init_clock_in(DEVICE(obj), "CLK", sse_clk_callback, s,
ClockPreUpdate | ClockUpdate);
memory_region_init_io(&s->control_mr, obj, &sse_counter_control_ops,
s, "sse-counter-control", 0x1000);
memory_region_init_io(&s->status_mr, obj, &sse_counter_status_ops,
s, "sse-counter-status", 0x1000);
sysbus_init_mmio(sbd, &s->control_mr);
sysbus_init_mmio(sbd, &s->status_mr);
}
static void sse_counter_realize(DeviceState *dev, Error **errp)
{
SSECounter *s = SSE_COUNTER(dev);
if (!clock_has_source(s->clk)) {
error_setg(errp, "SSE system counter: CLK must be connected");
return;
}
}
static const VMStateDescription sse_counter_vmstate = {
.name = "sse-counter",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_CLOCK(clk, SSECounter),
VMSTATE_END_OF_LIST()
}
};
static void sse_counter_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = sse_counter_realize;
dc->vmsd = &sse_counter_vmstate;
device_class_set_legacy_reset(dc, sse_counter_reset);
}
static const TypeInfo sse_counter_info = {
.name = TYPE_SSE_COUNTER,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(SSECounter),
.instance_init = sse_counter_init,
.class_init = sse_counter_class_init,
};
static void sse_counter_register_types(void)
{
type_register_static(&sse_counter_info);
}
type_init(sse_counter_register_types);
+470
View File
@@ -0,0 +1,470 @@
/*
* Arm SSE Subsystem System Timer
*
* Copyright (c) 2020 Linaro Limited
* Written by Peter Maydell
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 or
* (at your option) any later version.
*/
/*
* This is a model of the "System timer" which is documented in
* the Arm SSE-123 Example Subsystem Technical Reference Manual:
* https://developer.arm.com/documentation/101370/latest/
*
* The timer is based around a simple 64-bit incrementing counter
* (readable from CNTPCT_HI/LO). The timer fires when
* Counter - CompareValue >= 0.
* The CompareValue is guest-writable, via CNTP_CVAL_HI/LO.
* CNTP_TVAL is an alternative view of the CompareValue defined by
* TimerValue = CompareValue[31:0] - Counter[31:0]
* which can be both read and written.
* This part is similar to the generic timer in an Arm A-class CPU.
*
* The timer also has a separate auto-increment timer. When this
* timer is enabled, then the AutoIncrValue is set to:
* AutoIncrValue = Reload + Counter
* and this timer fires when
* Counter - AutoIncrValue >= 0
* at which point, an interrupt is generated and the new AutoIncrValue
* is calculated.
* When the auto-increment timer is enabled, interrupt generation
* via the compare/timervalue registers is disabled.
*/
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "qemu/timer.h"
#include "qapi/error.h"
#include "trace.h"
#include "hw/timer/sse-timer.h"
#include "hw/timer/sse-counter.h"
#include "hw/core/sysbus.h"
#include "hw/core/irq.h"
#include "hw/core/registerfields.h"
#include "hw/core/clock.h"
#include "hw/core/qdev-clock.h"
#include "hw/core/qdev-properties.h"
#include "migration/vmstate.h"
REG32(CNTPCT_LO, 0x0)
REG32(CNTPCT_HI, 0x4)
REG32(CNTFRQ, 0x10)
REG32(CNTP_CVAL_LO, 0x20)
REG32(CNTP_CVAL_HI, 0x24)
REG32(CNTP_TVAL, 0x28)
REG32(CNTP_CTL, 0x2c)
FIELD(CNTP_CTL, ENABLE, 0, 1)
FIELD(CNTP_CTL, IMASK, 1, 1)
FIELD(CNTP_CTL, ISTATUS, 2, 1)
REG32(CNTP_AIVAL_LO, 0x40)
REG32(CNTP_AIVAL_HI, 0x44)
REG32(CNTP_AIVAL_RELOAD, 0x48)
REG32(CNTP_AIVAL_CTL, 0x4c)
FIELD(CNTP_AIVAL_CTL, EN, 0, 1)
FIELD(CNTP_AIVAL_CTL, CLR, 1, 1)
REG32(CNTP_CFG, 0x50)
FIELD(CNTP_CFG, AIVAL, 0, 4)
#define R_CNTP_CFG_AIVAL_IMPLEMENTED 1
REG32(PID4, 0xFD0)
REG32(PID5, 0xFD4)
REG32(PID6, 0xFD8)
REG32(PID7, 0xFDC)
REG32(PID0, 0xFE0)
REG32(PID1, 0xFE4)
REG32(PID2, 0xFE8)
REG32(PID3, 0xFEC)
REG32(CID0, 0xFF0)
REG32(CID1, 0xFF4)
REG32(CID2, 0xFF8)
REG32(CID3, 0xFFC)
/* PID/CID values */
static const int timer_id[] = {
0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
0xb7, 0xb0, 0x0b, 0x00, /* PID0..PID3 */
0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
};
static bool sse_is_autoinc(SSETimer *s)
{
return (s->cntp_aival_ctl & R_CNTP_AIVAL_CTL_EN_MASK) != 0;
}
static bool sse_enabled(SSETimer *s)
{
return (s->cntp_ctl & R_CNTP_CTL_ENABLE_MASK) != 0;
}
static uint64_t sse_cntpct(SSETimer *s)
{
/* Return the CNTPCT value for the current time */
return sse_counter_for_timestamp(s->counter,
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
}
static bool sse_timer_status(SSETimer *s)
{
/*
* Return true if timer condition is met. This is used for both
* the CNTP_CTL.ISTATUS bit and for whether (unless masked) we
* assert our IRQ.
* The documentation is unclear about the behaviour of ISTATUS when
* in autoincrement mode; we assume that it follows CNTP_AIVAL_CTL.CLR
* (ie whether the autoincrement timer is asserting the interrupt).
*/
if (!sse_enabled(s)) {
return false;
}
if (sse_is_autoinc(s)) {
return s->cntp_aival_ctl & R_CNTP_AIVAL_CTL_CLR_MASK;
} else {
return sse_cntpct(s) >= s->cntp_cval;
}
}
static void sse_update_irq(SSETimer *s)
{
bool irqstate = (!(s->cntp_ctl & R_CNTP_CTL_IMASK_MASK) &&
sse_timer_status(s));
qemu_set_irq(s->irq, irqstate);
}
static void sse_set_timer(SSETimer *s, uint64_t nexttick)
{
/* Set the timer to expire at nexttick */
uint64_t expiry = sse_counter_tick_to_time(s->counter, nexttick);
if (expiry <= INT64_MAX) {
timer_mod_ns(&s->timer, expiry);
} else {
/*
* nexttick is so far in the future that it would overflow the
* signed 64-bit range of a QEMUTimer. Since timer_mod_ns()
* expiry times are absolute, not relative, we are never going
* to be able to set the timer to this value, so we must just
* assume that guest execution can never run so long that it
* reaches the theoretical point when the timer fires.
* This is also the code path for "counter is not running",
* which is signalled by expiry == UINT64_MAX.
*/
timer_del(&s->timer);
}
}
static void sse_recalc_timer(SSETimer *s)
{
/* Recalculate the normal timer */
uint64_t count, nexttick;
if (sse_is_autoinc(s)) {
return;
}
if (!sse_enabled(s)) {
timer_del(&s->timer);
return;
}
count = sse_cntpct(s);
if (count >= s->cntp_cval) {
/*
* Timer condition already met. In theory we have a transition when
* the count rolls back over to 0, but that is so far in the future
* that it is not representable as a timer_mod() expiry, so in
* fact sse_set_timer() will always just delete the timer.
*/
nexttick = UINT64_MAX;
} else {
/* Next transition is when count hits cval */
nexttick = s->cntp_cval;
}
sse_set_timer(s, nexttick);
sse_update_irq(s);
}
static void sse_autoinc(SSETimer *s)
{
/* Auto-increment the AIVAL, and set the timer accordingly */
s->cntp_aival = sse_cntpct(s) + s->cntp_aival_reload;
sse_set_timer(s, s->cntp_aival);
}
static void sse_timer_cb(void *opaque)
{
SSETimer *s = SSE_TIMER(opaque);
if (sse_is_autoinc(s)) {
uint64_t count = sse_cntpct(s);
if (count >= s->cntp_aival) {
/* Timer condition met, set CLR and do another autoinc */
s->cntp_aival_ctl |= R_CNTP_AIVAL_CTL_CLR_MASK;
s->cntp_aival = count + s->cntp_aival_reload;
}
sse_set_timer(s, s->cntp_aival);
sse_update_irq(s);
} else {
sse_recalc_timer(s);
}
}
static uint64_t sse_timer_read(void *opaque, hwaddr offset, unsigned size)
{
SSETimer *s = SSE_TIMER(opaque);
uint64_t r;
switch (offset) {
case A_CNTPCT_LO:
r = extract64(sse_cntpct(s), 0, 32);
break;
case A_CNTPCT_HI:
r = extract64(sse_cntpct(s), 32, 32);
break;
case A_CNTFRQ:
r = s->cntfrq;
break;
case A_CNTP_CVAL_LO:
r = extract64(s->cntp_cval, 0, 32);
break;
case A_CNTP_CVAL_HI:
r = extract64(s->cntp_cval, 32, 32);
break;
case A_CNTP_TVAL:
r = extract64(s->cntp_cval - sse_cntpct(s), 0, 32);
break;
case A_CNTP_CTL:
r = s->cntp_ctl;
if (sse_timer_status(s)) {
r |= R_CNTP_CTL_ISTATUS_MASK;
}
break;
case A_CNTP_AIVAL_LO:
r = extract64(s->cntp_aival, 0, 32);
break;
case A_CNTP_AIVAL_HI:
r = extract64(s->cntp_aival, 32, 32);
break;
case A_CNTP_AIVAL_RELOAD:
r = s->cntp_aival_reload;
break;
case A_CNTP_AIVAL_CTL:
/*
* All the bits of AIVAL_CTL are documented as WO, but this is probably
* a documentation error. We implement them as readable.
*/
r = s->cntp_aival_ctl;
break;
case A_CNTP_CFG:
r = R_CNTP_CFG_AIVAL_IMPLEMENTED << R_CNTP_CFG_AIVAL_SHIFT;
break;
case A_PID4 ... A_CID3:
r = timer_id[(offset - A_PID4) / 4];
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"SSE System Timer read: bad offset 0x%x",
(unsigned) offset);
r = 0;
break;
}
trace_sse_timer_read(offset, r, size);
return r;
}
static void sse_timer_write(void *opaque, hwaddr offset, uint64_t value,
unsigned size)
{
SSETimer *s = SSE_TIMER(opaque);
trace_sse_timer_write(offset, value, size);
switch (offset) {
case A_CNTFRQ:
s->cntfrq = value;
break;
case A_CNTP_CVAL_LO:
s->cntp_cval = deposit64(s->cntp_cval, 0, 32, value);
sse_recalc_timer(s);
break;
case A_CNTP_CVAL_HI:
s->cntp_cval = deposit64(s->cntp_cval, 32, 32, value);
sse_recalc_timer(s);
break;
case A_CNTP_TVAL:
s->cntp_cval = sse_cntpct(s) + sextract64(value, 0, 32);
sse_recalc_timer(s);
break;
case A_CNTP_CTL:
{
uint32_t old_ctl = s->cntp_ctl;
value &= R_CNTP_CTL_ENABLE_MASK | R_CNTP_CTL_IMASK_MASK;
s->cntp_ctl = value;
if ((old_ctl ^ s->cntp_ctl) & R_CNTP_CTL_ENABLE_MASK) {
if (sse_enabled(s)) {
if (sse_is_autoinc(s)) {
sse_autoinc(s);
} else {
sse_recalc_timer(s);
}
}
}
sse_update_irq(s);
break;
}
case A_CNTP_AIVAL_RELOAD:
s->cntp_aival_reload = value;
break;
case A_CNTP_AIVAL_CTL:
{
uint32_t old_ctl = s->cntp_aival_ctl;
/* EN bit is writable; CLR bit is write-0-to-clear, write-1-ignored */
s->cntp_aival_ctl &= ~R_CNTP_AIVAL_CTL_EN_MASK;
s->cntp_aival_ctl |= value & R_CNTP_AIVAL_CTL_EN_MASK;
if (!(value & R_CNTP_AIVAL_CTL_CLR_MASK)) {
s->cntp_aival_ctl &= ~R_CNTP_AIVAL_CTL_CLR_MASK;
}
if ((old_ctl ^ s->cntp_aival_ctl) & R_CNTP_AIVAL_CTL_EN_MASK) {
/* Auto-increment toggled on/off */
if (sse_enabled(s)) {
if (sse_is_autoinc(s)) {
sse_autoinc(s);
} else {
sse_recalc_timer(s);
}
}
}
sse_update_irq(s);
break;
}
case A_CNTPCT_LO:
case A_CNTPCT_HI:
case A_CNTP_CFG:
case A_CNTP_AIVAL_LO:
case A_CNTP_AIVAL_HI:
case A_PID4 ... A_CID3:
qemu_log_mask(LOG_GUEST_ERROR,
"SSE System Timer write: write to RO offset 0x%x\n",
(unsigned)offset);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"SSE System Timer write: bad offset 0x%x\n",
(unsigned)offset);
break;
}
}
static const MemoryRegionOps sse_timer_ops = {
.read = sse_timer_read,
.write = sse_timer_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid.min_access_size = 4,
.valid.max_access_size = 4,
};
static void sse_timer_reset(DeviceState *dev)
{
SSETimer *s = SSE_TIMER(dev);
trace_sse_timer_reset();
timer_del(&s->timer);
s->cntfrq = 0;
s->cntp_ctl = 0;
s->cntp_cval = 0;
s->cntp_aival = 0;
s->cntp_aival_ctl = 0;
s->cntp_aival_reload = 0;
}
static void sse_timer_counter_callback(Notifier *notifier, void *data)
{
SSETimer *s = container_of(notifier, SSETimer, counter_notifier);
/* System counter told us we need to recalculate */
if (sse_enabled(s)) {
if (sse_is_autoinc(s)) {
sse_set_timer(s, s->cntp_aival);
} else {
sse_recalc_timer(s);
}
}
}
static void sse_timer_init(Object *obj)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
SSETimer *s = SSE_TIMER(obj);
memory_region_init_io(&s->iomem, obj, &sse_timer_ops,
s, "sse-timer", 0x1000);
sysbus_init_mmio(sbd, &s->iomem);
sysbus_init_irq(sbd, &s->irq);
}
static void sse_timer_realize(DeviceState *dev, Error **errp)
{
SSETimer *s = SSE_TIMER(dev);
if (!s->counter) {
error_setg(errp, "counter property was not set");
return;
}
s->counter_notifier.notify = sse_timer_counter_callback;
sse_counter_register_consumer(s->counter, &s->counter_notifier);
timer_init_ns(&s->timer, QEMU_CLOCK_VIRTUAL, sse_timer_cb, s);
}
static const VMStateDescription sse_timer_vmstate = {
.name = "sse-timer",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_TIMER(timer, SSETimer),
VMSTATE_UINT32(cntfrq, SSETimer),
VMSTATE_UINT32(cntp_ctl, SSETimer),
VMSTATE_UINT64(cntp_cval, SSETimer),
VMSTATE_UINT64(cntp_aival, SSETimer),
VMSTATE_UINT32(cntp_aival_ctl, SSETimer),
VMSTATE_UINT32(cntp_aival_reload, SSETimer),
VMSTATE_END_OF_LIST()
}
};
static const Property sse_timer_properties[] = {
DEFINE_PROP_LINK("counter", SSETimer, counter, TYPE_SSE_COUNTER, SSECounter *),
};
static void sse_timer_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = sse_timer_realize;
dc->vmsd = &sse_timer_vmstate;
device_class_set_legacy_reset(dc, sse_timer_reset);
device_class_set_props(dc, sse_timer_properties);
}
static const TypeInfo sse_timer_info = {
.name = TYPE_SSE_TIMER,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(SSETimer),
.instance_init = sse_timer_init,
.class_init = sse_timer_class_init,
};
static void sse_timer_register_types(void)
{
type_register_static(&sse_timer_info);
}
type_init(sse_timer_register_types);
+332
View File
@@ -0,0 +1,332 @@
/*
* 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/core/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 = (const 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, const 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)
+346
View File
@@ -0,0 +1,346 @@
/*
* STM32F2XX Timer
*
* Copyright (c) 2014 Alistair Francis <[email protected]>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "qemu/osdep.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-properties.h"
#include "hw/timer/stm32f2xx_timer.h"
#include "migration/vmstate.h"
#include "qemu/log.h"
#include "qemu/module.h"
#ifndef STM_TIMER_ERR_DEBUG
#define STM_TIMER_ERR_DEBUG 0
#endif
#define DB_PRINT_L(lvl, fmt, args...) do { \
if (STM_TIMER_ERR_DEBUG >= lvl) { \
qemu_log("%s: " fmt, __func__, ## args); \
} \
} while (0)
#define DB_PRINT(fmt, args...) DB_PRINT_L(1, fmt, ## args)
static void stm32f2xx_timer_set_alarm(STM32F2XXTimerState *s, int64_t now);
static void stm32f2xx_timer_interrupt(void *opaque)
{
STM32F2XXTimerState *s = opaque;
DB_PRINT("Interrupt\n");
if (s->tim_dier & TIM_DIER_UIE && s->tim_cr1 & TIM_CR1_CEN) {
s->tim_sr |= 1;
qemu_irq_pulse(s->irq);
stm32f2xx_timer_set_alarm(s, s->hit_time);
}
if (s->tim_ccmr1 & (TIM_CCMR1_OC2M2 | TIM_CCMR1_OC2M1) &&
!(s->tim_ccmr1 & TIM_CCMR1_OC2M0) &&
s->tim_ccmr1 & TIM_CCMR1_OC2PE &&
s->tim_ccer & TIM_CCER_CC2E) {
/* PWM 2 - Mode 1 */
DB_PRINT("PWM2 Duty Cycle: %d%%\n",
s->tim_ccr2 / (100 * (s->tim_psc + 1)));
}
}
static inline int64_t stm32f2xx_ns_to_ticks(STM32F2XXTimerState *s, int64_t t)
{
return muldiv64(t, s->freq_hz, 1000000000ULL) / (s->tim_psc + 1);
}
static void stm32f2xx_timer_set_alarm(STM32F2XXTimerState *s, int64_t now)
{
uint64_t ticks;
int64_t now_ticks;
if (s->tim_arr == 0) {
return;
}
DB_PRINT("Alarm set at: 0x%x\n", s->tim_cr1);
now_ticks = stm32f2xx_ns_to_ticks(s, now);
ticks = s->tim_arr - (now_ticks - s->tick_offset);
DB_PRINT("Alarm set in %d ticks\n", (int) ticks);
s->hit_time = muldiv64((ticks + (uint64_t) now_ticks) * (s->tim_psc + 1),
1000000000ULL, s->freq_hz);
timer_mod(s->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->hit_time);
DB_PRINT("Wait Time: %" PRId64 " ticks\n", s->hit_time);
}
static void stm32f2xx_timer_reset(DeviceState *dev)
{
STM32F2XXTimerState *s = STM32F2XXTIMER(dev);
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
s->tim_cr1 = 0;
s->tim_cr2 = 0;
s->tim_smcr = 0;
s->tim_dier = 0;
s->tim_sr = 0;
s->tim_egr = 0;
s->tim_ccmr1 = 0;
s->tim_ccmr2 = 0;
s->tim_ccer = 0;
s->tim_psc = 0;
s->tim_arr = 0;
s->tim_ccr1 = 0;
s->tim_ccr2 = 0;
s->tim_ccr3 = 0;
s->tim_ccr4 = 0;
s->tim_dcr = 0;
s->tim_dmar = 0;
s->tim_or = 0;
s->tick_offset = stm32f2xx_ns_to_ticks(s, now);
}
static uint64_t stm32f2xx_timer_read(void *opaque, hwaddr offset,
unsigned size)
{
STM32F2XXTimerState *s = opaque;
DB_PRINT("Read 0x%"HWADDR_PRIx"\n", offset);
switch (offset) {
case TIM_CR1:
return s->tim_cr1;
case TIM_CR2:
return s->tim_cr2;
case TIM_SMCR:
return s->tim_smcr;
case TIM_DIER:
return s->tim_dier;
case TIM_SR:
return s->tim_sr;
case TIM_EGR:
return s->tim_egr;
case TIM_CCMR1:
return s->tim_ccmr1;
case TIM_CCMR2:
return s->tim_ccmr2;
case TIM_CCER:
return s->tim_ccer;
case TIM_CNT:
return stm32f2xx_ns_to_ticks(s, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)) -
s->tick_offset;
case TIM_PSC:
return s->tim_psc;
case TIM_ARR:
return s->tim_arr;
case TIM_CCR1:
return s->tim_ccr1;
case TIM_CCR2:
return s->tim_ccr2;
case TIM_CCR3:
return s->tim_ccr3;
case TIM_CCR4:
return s->tim_ccr4;
case TIM_DCR:
return s->tim_dcr;
case TIM_DMAR:
return s->tim_dmar;
case TIM_OR:
return s->tim_or;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, offset);
}
return 0;
}
static void stm32f2xx_timer_write(void *opaque, hwaddr offset,
uint64_t val64, unsigned size)
{
STM32F2XXTimerState *s = opaque;
uint32_t value = val64;
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
uint32_t timer_val = 0;
DB_PRINT("Write 0x%x, 0x%"HWADDR_PRIx"\n", value, offset);
switch (offset) {
case TIM_CR1:
s->tim_cr1 = value;
return;
case TIM_CR2:
s->tim_cr2 = value;
return;
case TIM_SMCR:
s->tim_smcr = value;
return;
case TIM_DIER:
s->tim_dier = value;
return;
case TIM_SR:
/* This is set by hardware and cleared by software */
s->tim_sr &= value;
return;
case TIM_EGR:
s->tim_egr = value;
if (s->tim_egr & TIM_EGR_UG) {
timer_val = 0;
break;
}
return;
case TIM_CCMR1:
s->tim_ccmr1 = value;
return;
case TIM_CCMR2:
s->tim_ccmr2 = value;
return;
case TIM_CCER:
s->tim_ccer = value;
return;
case TIM_PSC:
timer_val = stm32f2xx_ns_to_ticks(s, now) - s->tick_offset;
s->tim_psc = value & 0xFFFF;
break;
case TIM_CNT:
timer_val = value;
break;
case TIM_ARR:
s->tim_arr = value;
stm32f2xx_timer_set_alarm(s, now);
return;
case TIM_CCR1:
s->tim_ccr1 = value;
return;
case TIM_CCR2:
s->tim_ccr2 = value;
return;
case TIM_CCR3:
s->tim_ccr3 = value;
return;
case TIM_CCR4:
s->tim_ccr4 = value;
return;
case TIM_DCR:
s->tim_dcr = value;
return;
case TIM_DMAR:
s->tim_dmar = value;
return;
case TIM_OR:
s->tim_or = value;
return;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, offset);
return;
}
/* This means that a register write has affected the timer in a way that
* requires a refresh of both tick_offset and the alarm.
*/
s->tick_offset = stm32f2xx_ns_to_ticks(s, now) - timer_val;
stm32f2xx_timer_set_alarm(s, now);
}
static const MemoryRegionOps stm32f2xx_timer_ops = {
.read = stm32f2xx_timer_read,
.write = stm32f2xx_timer_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
static const VMStateDescription vmstate_stm32f2xx_timer = {
.name = TYPE_STM32F2XX_TIMER,
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_INT64(tick_offset, STM32F2XXTimerState),
VMSTATE_UINT32(tim_cr1, STM32F2XXTimerState),
VMSTATE_UINT32(tim_cr2, STM32F2XXTimerState),
VMSTATE_UINT32(tim_smcr, STM32F2XXTimerState),
VMSTATE_UINT32(tim_dier, STM32F2XXTimerState),
VMSTATE_UINT32(tim_sr, STM32F2XXTimerState),
VMSTATE_UINT32(tim_egr, STM32F2XXTimerState),
VMSTATE_UINT32(tim_ccmr1, STM32F2XXTimerState),
VMSTATE_UINT32(tim_ccmr2, STM32F2XXTimerState),
VMSTATE_UINT32(tim_ccer, STM32F2XXTimerState),
VMSTATE_UINT32(tim_psc, STM32F2XXTimerState),
VMSTATE_UINT32(tim_arr, STM32F2XXTimerState),
VMSTATE_UINT32(tim_ccr1, STM32F2XXTimerState),
VMSTATE_UINT32(tim_ccr2, STM32F2XXTimerState),
VMSTATE_UINT32(tim_ccr3, STM32F2XXTimerState),
VMSTATE_UINT32(tim_ccr4, STM32F2XXTimerState),
VMSTATE_UINT32(tim_dcr, STM32F2XXTimerState),
VMSTATE_UINT32(tim_dmar, STM32F2XXTimerState),
VMSTATE_UINT32(tim_or, STM32F2XXTimerState),
VMSTATE_END_OF_LIST()
}
};
static const Property stm32f2xx_timer_properties[] = {
DEFINE_PROP_UINT64("clock-frequency", struct STM32F2XXTimerState,
freq_hz, 1000000000),
};
static void stm32f2xx_timer_init(Object *obj)
{
STM32F2XXTimerState *s = STM32F2XXTIMER(obj);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
memory_region_init_io(&s->iomem, obj, &stm32f2xx_timer_ops, s,
"stm32f2xx_timer", 0x400);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
}
static void stm32f2xx_timer_realize(DeviceState *dev, Error **errp)
{
STM32F2XXTimerState *s = STM32F2XXTIMER(dev);
s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, stm32f2xx_timer_interrupt, s);
}
static void stm32f2xx_timer_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, stm32f2xx_timer_reset);
device_class_set_props(dc, stm32f2xx_timer_properties);
dc->vmsd = &vmstate_stm32f2xx_timer;
dc->realize = stm32f2xx_timer_realize;
}
static const TypeInfo stm32f2xx_timer_info = {
.name = TYPE_STM32F2XX_TIMER,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(STM32F2XXTimerState),
.instance_init = stm32f2xx_timer_init,
.class_init = stm32f2xx_timer_class_init,
};
static void stm32f2xx_timer_register_types(void)
{
type_register_static(&stm32f2xx_timer_info);
}
type_init(stm32f2xx_timer_register_types)
+135
View File
@@ -0,0 +1,135 @@
# See docs/devel/tracing.rst for syntax documentation.
# slavio_timer.c
slavio_timer_get_out(uint64_t limit, uint32_t counthigh, uint32_t count) "limit 0x%"PRIx64" count 0x%x0x%08x"
slavio_timer_irq(uint32_t counthigh, uint32_t count) "callback: count 0x%x0x%08x"
slavio_timer_mem_readl_invalid(uint64_t addr) "invalid read address 0x%"PRIx64
slavio_timer_mem_readl(uint64_t addr, uint32_t ret) "read 0x%"PRIx64" = 0x%08x"
slavio_timer_mem_writel(uint64_t addr, uint32_t val) "write 0x%"PRIx64" = 0x%08x"
slavio_timer_mem_writel_limit(unsigned int timer_index, uint64_t count) "processor %d user timer set to 0x%016"PRIx64
slavio_timer_mem_writel_counter_invalid(void) "not user timer"
slavio_timer_mem_writel_status_start(unsigned int timer_index) "processor %d user timer started"
slavio_timer_mem_writel_status_stop(unsigned int timer_index) "processor %d user timer stopped"
slavio_timer_mem_writel_mode_user(unsigned int timer_index) "processor %d changed from counter to user timer"
slavio_timer_mem_writel_mode_counter(unsigned int timer_index) "processor %d changed from user timer to counter"
slavio_timer_mem_writel_mode_invalid(void) "not system timer"
slavio_timer_mem_writel_invalid(uint64_t addr) "invalid write address 0x%"PRIx64
# grlib_gptimer.c
grlib_gptimer_enable(int id, uint32_t count) "timer:%d set count 0x%x and run"
grlib_gptimer_disabled(int id, uint32_t config) "timer:%d Timer disable config 0x%x"
grlib_gptimer_restart(int id, uint32_t reload) "timer:%d reload val: 0x%x"
grlib_gptimer_set_scaler(uint32_t scaler, uint32_t freq) "scaler:0x%x freq:%uHz"
grlib_gptimer_hit(int id) "timer:%d HIT"
grlib_gptimer_readl(int id, uint64_t addr, uint32_t val) "timer:%d addr 0x%"PRIx64" 0x%x"
grlib_gptimer_writel(int id, uint64_t addr, uint32_t val) "timer:%d addr 0x%"PRIx64" 0x%x"
# aspeed_timer.c
aspeed_timer_ctrl_enable(uint8_t i, bool enable) "Timer %" PRIu8 ": %d"
aspeed_timer_ctrl_external_clock(uint8_t i, bool enable) "Timer %" PRIu8 ": %d"
aspeed_timer_ctrl_overflow_interrupt(uint8_t i, bool enable) "Timer %" PRIu8 ": %d"
aspeed_timer_ctrl_pulse_enable(uint8_t i, bool enable) "Timer %" PRIu8 ": %d"
aspeed_timer_set_ctrl2(uint32_t value) "Value: 0x%" PRIx32
aspeed_timer_set_value(int timer, int reg, uint32_t value) "Timer %d register %d: 0x%" PRIx32
aspeed_timer_read(uint64_t offset, uint64_t value) "From 0x%" PRIx64 ": 0x%" PRIx64
# armv7m_systick.c
systick_reload(void) "systick reload"
systick_timer_tick(void) "systick tick"
systick_read(uint64_t addr, uint32_t value, unsigned size) "systick read addr 0x%" PRIx64 " data 0x%" PRIx32 " size %u"
systick_write(uint64_t addr, uint32_t value, unsigned size) "systick write addr 0x%" PRIx64 " data 0x%" PRIx32 " size %u"
# cmsdk-apb-timer.c
cmsdk_apb_timer_read(uint64_t offset, uint64_t data, unsigned size) "CMSDK APB timer read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
cmsdk_apb_timer_write(uint64_t offset, uint64_t data, unsigned size) "CMSDK APB timer write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
cmsdk_apb_timer_reset(void) "CMSDK APB timer: reset"
# cmsdk-apb-dualtimer.c
cmsdk_apb_dualtimer_read(uint64_t offset, uint64_t data, unsigned size) "CMSDK APB dualtimer read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
cmsdk_apb_dualtimer_write(uint64_t offset, uint64_t data, unsigned size) "CMSDK APB dualtimer write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
cmsdk_apb_dualtimer_reset(void) "CMSDK APB dualtimer: reset"
# i8254.c
pit_ioport_read(uint8_t addr, uint32_t value) "[0x%" PRIx8 "] -> 0x%" PRIx32
pit_ioport_write(uint8_t addr, uint32_t value) "[0x%" PRIx8 "] <- 0x%" PRIx32
# imx_gpt.c
imx_gpt_set_freq(uint32_t clksrc, uint32_t freq) "Setting clksrc %u to %u Hz"
imx_gpt_read(const char *name, uint64_t value) "%s -> 0x%08" PRIx64
imx_gpt_write(const char *name, uint64_t value) "%s <- 0x%08" PRIx64
imx_gpt_timeout(void) ""
# npcm7xx_timer.c
npcm7xx_timer_read(const char *id, uint64_t offset, uint64_t value) " %s offset: 0x%04" PRIx64 " value 0x%08" PRIx64
npcm7xx_timer_write(const char *id, uint64_t offset, uint64_t value) "%s offset: 0x%04" PRIx64 " value 0x%08" PRIx64
npcm7xx_timer_irq(const char *id, int timer, int state) "%s timer %d state %d"
# nrf51_timer.c
nrf51_timer_read(uint8_t timer_id, uint64_t addr, uint32_t value, unsigned size) "timer %u read addr 0x%" PRIx64 " data 0x%" PRIx32 " size %u"
nrf51_timer_write(uint8_t timer_id, uint64_t addr, uint32_t value, unsigned size) "timer %u write addr 0x%" PRIx64 " data 0x%" PRIx32 " size %u"
nrf51_timer_set_count(uint8_t timer_id, uint8_t counter_id, uint32_t value) "timer %u counter %u count 0x%" PRIx32
# bcm2835_systmr.c
bcm2835_systmr_timer_expired(unsigned id) "timer #%u expired"
bcm2835_systmr_irq_ack(unsigned id) "timer #%u acked"
bcm2835_systmr_read(uint64_t offset, uint64_t data) "timer read: offset 0x%" PRIx64 " data 0x%" PRIx64
bcm2835_systmr_write(uint64_t offset, uint32_t data) "timer write: offset 0x%" PRIx64 " data 0x%" PRIx32
bcm2835_systmr_run(unsigned id, uint64_t delay_us) "timer #%u expiring in %"PRIu64" us"
# avr_timer16.c
avr_timer16_read(uint8_t addr, uint8_t value) "timer16 read addr:%u value:%u"
avr_timer16_read_ifr(uint8_t value) "timer16 read addr:ifr value:%u"
avr_timer16_read_imsk(uint8_t value) "timer16 read addr:imsk value:%u"
avr_timer16_write(uint8_t addr, uint8_t value) "timer16 write addr:%u value:%u"
avr_timer16_write_imsk(uint8_t value) "timer16 write addr:imsk value:%u"
avr_timer16_interrupt_count(uint8_t cnt) "count: %u"
avr_timer16_interrupt_overflow(const char *reason) "overflow: %s"
avr_timer16_next_alarm(uint64_t delay_ns) "next alarm: %" PRIu64 " ns from now"
avr_timer16_clksrc_update(uint64_t freq_hz, uint64_t period_ns, uint64_t delay_s) "timer frequency: %" PRIu64 " Hz, period: %" PRIu64 " ns (%" PRId64 " us)"
# sse_counter.c
sse_counter_control_read(uint64_t offset, uint64_t data, unsigned size) "SSE system counter control frame read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
sse_counter_control_write(uint64_t offset, uint64_t data, unsigned size) "SSE system counter control framen write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
sse_counter_status_read(uint64_t offset, uint64_t data, unsigned size) "SSE system counter status frame read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
sse_counter_status_write(uint64_t offset, uint64_t data, unsigned size) "SSE system counter status frame write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
sse_counter_reset(void) "SSE system counter: reset"
# sse_timer.c
sse_timer_read(uint64_t offset, uint64_t data, unsigned size) "SSE system timer read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
sse_timer_write(uint64_t offset, uint64_t data, unsigned size) "SSE system timer write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
sse_timer_reset(void) "SSE system timer: reset"
# sifive_pwm.c
sifive_pwm_set_alarm(uint64_t alarm, uint64_t now) "Setting alarm to: 0x%" PRIx64 ", now: 0x%" PRIx64
sifive_pwm_interrupt(int num) "Interrupt %d"
sifive_pwm_read(uint64_t offset) "Read at address: 0x%" PRIx64
sifive_pwm_write(uint64_t data, uint64_t offset) "Write 0x%" PRIx64 " at address: 0x%" PRIx64
# sh_timer.c
sh_timer_start_stop(int enable, int current) "%d (%d)"
sh_timer_read(uint64_t offset) "tmu012_read 0x%" PRIx64
sh_timer_write(uint64_t offset, uint64_t value) "tmu012_write 0x%" PRIx64 " 0x%08" PRIx64
# hpet.c
hpet_timer_id_out_of_range(uint8_t timer_id) "timer id out of range: 0x%" PRIx8
hpet_ram_read(uint64_t addr) "enter hpet_ram_readl at 0x%" PRIx64
hpet_ram_read_reading_counter(uint8_t reg_off, uint64_t cur_tick) "reading counter + %" PRIu8 " = 0x%" PRIx64
hpet_ram_read_invalid(void) "invalid hpet_ram_readl"
hpet_ram_write(uint64_t addr, uint64_t value) "enter hpet_ram_writel at 0x%" PRIx64 " = 0x%" PRIx64
hpet_ram_write_timer_id(uint8_t timer_id) "hpet_ram_writel timer_id = %" PRIu8
hpet_ram_write_tn_cfg(uint8_t reg_off) "hpet_ram_writel HPET_TN_CFG + %" PRIu8
hpet_ram_write_tn_cmp(uint8_t reg_off) "hpet_ram_writel HPET_TN_CMP + %" PRIu8
hpet_ram_write_invalid_tn_cmp(void) "invalid HPET_TN_CMP + 4 write"
hpet_ram_write_invalid(void) "invalid hpet_ram_writel"
hpet_ram_write_counter_write_while_enabled(void) "Writing counter while HPET enabled!"
# imx_epit.c
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
+1
View File
@@ -0,0 +1 @@
#include "trace/trace-hw_timer.h"
+292
View File
@@ -0,0 +1,292 @@
/*
* QEMU model of the Xilinx timer block.
*
* Copyright (c) 2009 Edgar E. Iglesias.
*
* DS573: https://docs.amd.com/v/u/en-US/xps_timer
* LogiCORE IP XPS Timer/Counter (v1.02a)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "hw/core/sysbus.h"
#include "hw/core/irq.h"
#include "hw/core/ptimer.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "qom/object.h"
#define D(x)
#define R_TCSR 0
#define R_TLR 1
#define R_TCR 2
#define R_MAX 4
#define TCSR_MDT (1<<0)
#define TCSR_UDT (1<<1)
#define TCSR_GENT (1<<2)
#define TCSR_CAPT (1<<3)
#define TCSR_ARHT (1<<4)
#define TCSR_LOAD (1<<5)
#define TCSR_ENIT (1<<6)
#define TCSR_ENT (1<<7)
#define TCSR_TINT (1<<8)
#define TCSR_PWMA (1<<9)
#define TCSR_ENALL (1<<10)
struct xlx_timer
{
ptimer_state *ptimer;
void *parent;
int nr; /* for debug. */
unsigned long timer_div;
uint32_t regs[R_MAX];
};
#define TYPE_XILINX_TIMER "xlnx.xps-timer"
typedef struct XpsTimerState XpsTimerState;
DECLARE_INSTANCE_CHECKER(XpsTimerState, XILINX_TIMER, TYPE_XILINX_TIMER)
struct XpsTimerState
{
SysBusDevice parent_obj;
EndianMode model_endianness;
MemoryRegion mmio;
qemu_irq irq;
uint8_t one_timer_only;
uint32_t freq_hz;
struct xlx_timer *timers;
};
static inline unsigned int num_timers(XpsTimerState *t)
{
return 2 - t->one_timer_only;
}
static inline unsigned int timer_from_addr(hwaddr addr)
{
/* Timers get a 4x32bit control reg area each. */
return addr >> 2;
}
static void timer_update_irq(XpsTimerState *t)
{
unsigned int i, irq = 0;
uint32_t csr;
for (i = 0; i < num_timers(t); i++) {
csr = t->timers[i].regs[R_TCSR];
irq |= (csr & TCSR_TINT) && (csr & TCSR_ENIT);
}
/* All timers within the same slave share a single IRQ line. */
qemu_set_irq(t->irq, !!irq);
}
static uint64_t
timer_read(void *opaque, hwaddr addr, unsigned int size)
{
XpsTimerState *t = opaque;
struct xlx_timer *xt;
uint32_t r = 0;
unsigned int timer;
addr >>= 2;
timer = timer_from_addr(addr);
xt = &t->timers[timer];
/* Further decoding to address a specific timers reg. */
addr &= 0x3;
switch (addr)
{
case R_TCR:
r = ptimer_get_count(xt->ptimer);
if (!(xt->regs[R_TCSR] & TCSR_UDT))
r = ~r;
D(qemu_log("xlx_timer t=%d read counter=%x udt=%d\n",
timer, r, xt->regs[R_TCSR] & TCSR_UDT));
break;
default:
if (addr < ARRAY_SIZE(xt->regs))
r = xt->regs[addr];
break;
}
D(fprintf(stderr, "%s timer=%d %x=%x\n", __func__, timer, addr * 4, r));
return r;
}
/* Must be called inside ptimer transaction block */
static void timer_enable(struct xlx_timer *xt)
{
uint64_t count;
D(fprintf(stderr, "%s timer=%d down=%d\n", __func__,
xt->nr, xt->regs[R_TCSR] & TCSR_UDT));
ptimer_stop(xt->ptimer);
if (xt->regs[R_TCSR] & TCSR_UDT)
count = xt->regs[R_TLR];
else
count = ~0 - xt->regs[R_TLR];
ptimer_set_limit(xt->ptimer, count, 1);
ptimer_run(xt->ptimer, 1);
}
static void
timer_write(void *opaque, hwaddr addr,
uint64_t val64, unsigned int size)
{
XpsTimerState *t = opaque;
struct xlx_timer *xt;
unsigned int timer;
uint32_t value = val64;
addr >>= 2;
timer = timer_from_addr(addr);
xt = &t->timers[timer];
D(fprintf(stderr, "%s addr=%x val=%x (timer=%d off=%d)\n",
__func__, addr * 4, value, timer, addr & 3));
/* Further decoding to address a specific timers reg. */
addr &= 3;
switch (addr)
{
case R_TCSR:
if (value & TCSR_TINT)
value &= ~TCSR_TINT;
xt->regs[addr] = value & 0x7ff;
if (value & TCSR_ENT) {
ptimer_transaction_begin(xt->ptimer);
timer_enable(xt);
ptimer_transaction_commit(xt->ptimer);
}
break;
default:
if (addr < ARRAY_SIZE(xt->regs))
xt->regs[addr] = value;
break;
}
timer_update_irq(t);
}
static const MemoryRegionOps timer_ops[2] = {
[0 ... 1] = {
.read = timer_read,
.write = timer_write,
.impl = {
.min_access_size = 4,
.max_access_size = 4,
},
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
},
[0].endianness = DEVICE_LITTLE_ENDIAN,
[1].endianness = DEVICE_BIG_ENDIAN,
};
static void timer_hit(void *opaque)
{
struct xlx_timer *xt = opaque;
XpsTimerState *t = xt->parent;
D(fprintf(stderr, "%s %d\n", __func__, xt->nr));
xt->regs[R_TCSR] |= TCSR_TINT;
if (xt->regs[R_TCSR] & TCSR_ARHT)
timer_enable(xt);
timer_update_irq(t);
}
static void xilinx_timer_realize(DeviceState *dev, Error **errp)
{
XpsTimerState *t = XILINX_TIMER(dev);
unsigned int i;
if (t->model_endianness == ENDIAN_MODE_UNSPECIFIED) {
error_setg(errp, TYPE_XILINX_TIMER " property 'endianness'"
" must be set to 'big' or 'little'");
return;
}
/* Init all the ptimers. */
t->timers = g_malloc0(sizeof t->timers[0] * num_timers(t));
for (i = 0; i < num_timers(t); i++) {
struct xlx_timer *xt = &t->timers[i];
xt->parent = t;
xt->nr = i;
xt->ptimer = ptimer_init(timer_hit, xt, PTIMER_POLICY_LEGACY);
ptimer_transaction_begin(xt->ptimer);
ptimer_set_freq(xt->ptimer, t->freq_hz);
ptimer_transaction_commit(xt->ptimer);
}
memory_region_init_io(&t->mmio, OBJECT(t),
&timer_ops[t->model_endianness == ENDIAN_MODE_BIG],
t, "xlnx.xps-timer", R_MAX * 4 * num_timers(t));
sysbus_init_mmio(SYS_BUS_DEVICE(dev), &t->mmio);
}
static void xilinx_timer_init(Object *obj)
{
XpsTimerState *t = XILINX_TIMER(obj);
/* All timers share a single irq line. */
sysbus_init_irq(SYS_BUS_DEVICE(obj), &t->irq);
}
static const Property xilinx_timer_properties[] = {
DEFINE_PROP_ENDIAN_NODEFAULT("endianness", XpsTimerState, model_endianness),
DEFINE_PROP_UINT32("clock-frequency", XpsTimerState, freq_hz, 62 * 1000000),
DEFINE_PROP_UINT8("one-timer-only", XpsTimerState, one_timer_only, 0),
};
static void xilinx_timer_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = xilinx_timer_realize;
device_class_set_props(dc, xilinx_timer_properties);
}
static const TypeInfo xilinx_timer_info = {
.name = TYPE_XILINX_TIMER,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(XpsTimerState),
.instance_init = xilinx_timer_init,
.class_init = xilinx_timer_class_init,
};
static void xilinx_timer_register_types(void)
{
type_register_static(&xilinx_timer_info);
}
type_init(xilinx_timer_register_types)