aboutsummaryrefslogtreecommitdiffstats
path: root/hw/timer
diff options
context:
space:
mode:
authorTimos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>2023-10-10 11:40:56 +0000
committerTimos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>2023-10-10 11:40:56 +0000
commite02cda008591317b1625707ff8e115a4841aa889 (patch)
treeaee302e3cf8b59ec2d32ec481be3d1afddfc8968 /hw/timer
parentcc668e6b7e0ffd8c9d130513d12053cf5eda1d3b (diff)
Introduce Virtio-loopback epsilon release:
Epsilon release introduces a new compatibility layer which make virtio-loopback design to work with QEMU and rust-vmm vhost-user backend without require any changes. Signed-off-by: Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com> Change-Id: I52e57563e08a7d0bdc002f8e928ee61ba0c53dd9
Diffstat (limited to 'hw/timer')
-rw-r--r--hw/timer/Kconfig62
-rw-r--r--hw/timer/a9gtimer.c377
-rw-r--r--hw/timer/allwinner-a10-pit.c316
-rw-r--r--hw/timer/altera_timer.c244
-rw-r--r--hw/timer/arm_mptimer.c331
-rw-r--r--hw/timer/arm_timer.c419
-rw-r--r--hw/timer/armv7m_systick.c310
-rw-r--r--hw/timer/aspeed_timer.c756
-rw-r--r--hw/timer/avr_timer16.c621
-rw-r--r--hw/timer/bcm2835_systmr.c178
-rw-r--r--hw/timer/cadence_ttc.c503
-rw-r--r--hw/timer/cmsdk-apb-dualtimer.c559
-rw-r--r--hw/timer/cmsdk-apb-timer.c286
-rw-r--r--hw/timer/digic-timer.c186
-rw-r--r--hw/timer/etraxfs_timer.c376
-rw-r--r--hw/timer/exynos4210_mct.c1568
-rw-r--r--hw/timer/exynos4210_pwm.c445
-rw-r--r--hw/timer/grlib_gptimer.c432
-rw-r--r--hw/timer/hpet.c807
-rw-r--r--hw/timer/i8254.c385
-rw-r--r--hw/timer/i8254_common.c271
-rw-r--r--hw/timer/ibex_timer.c312
-rw-r--r--hw/timer/imx_epit.c377
-rw-r--r--hw/timer/imx_gpt.c583
-rw-r--r--hw/timer/meson.build40
-rw-r--r--hw/timer/mips_gictimer.c145
-rw-r--r--hw/timer/mss-timer.c311
-rw-r--r--hw/timer/npcm7xx_timer.c714
-rw-r--r--hw/timer/nrf51_timer.c404
-rw-r--r--hw/timer/omap_gptimer.c514
-rw-r--r--hw/timer/omap_synctimer.c110
-rw-r--r--hw/timer/pxa2xx_timer.c621
-rw-r--r--hw/timer/renesas_cmt.c283
-rw-r--r--hw/timer/renesas_tmr.c493
-rw-r--r--hw/timer/sh_timer.c373
-rw-r--r--hw/timer/sifive_pwm.c468
-rw-r--r--hw/timer/slavio_timer.c450
-rw-r--r--hw/timer/sse-counter.c473
-rw-r--r--hw/timer/sse-timer.c471
-rw-r--r--hw/timer/stellaris-gptm.c332
-rw-r--r--hw/timer/stm32f2xx_timer.c347
-rw-r--r--hw/timer/trace-events101
-rw-r--r--hw/timer/trace.h1
-rw-r--r--hw/timer/xilinx_timer.c273
44 files changed, 17628 insertions, 0 deletions
diff --git a/hw/timer/Kconfig b/hw/timer/Kconfig
new file mode 100644
index 000000000..010be7ed1
--- /dev/null
+++ b/hw/timer/Kconfig
@@ -0,0 +1,62 @@
+config ARM_TIMER
+ bool
+ select PTIMER
+
+config ARM_MPTIMER
+ bool
+ select PTIMER
+
+config A9_GTIMER
+ bool
+
+config HPET
+ bool
+ default y if PC
+
+config I8254
+ bool
+ depends on ISA_BUS
+
+config ALTERA_TIMER
+ bool
+ select PTIMER
+
+config ALLWINNER_A10_PIT
+ bool
+ select PTIMER
+
+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
diff --git a/hw/timer/a9gtimer.c b/hw/timer/a9gtimer.c
new file mode 100644
index 000000000..7233068a3
--- /dev/null
+++ b/hw/timer/a9gtimer.c
@@ -0,0 +1,377 @@
+/*
+ * Global peripheral timer block for ARM A9MP
+ *
+ * (C) 2013 Xilinx Inc.
+ *
+ * Written by François LEGAL
+ * Written by Peter Crosthwaite <peter.crosthwaite@xilinx.com>
+ *
+ * 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/hw.h"
+#include "hw/irq.h"
+#include "hw/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"
+
+#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 (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 const VMStateDescription vmstate_a9_gtimer_per_cpu = {
+ .name = "arm.cortex-a9-global-timer.percpu",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (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 = {
+ .name = "arm.cortex-a9-global-timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (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()
+ }
+};
+
+static Property a9_gtimer_properties[] = {
+ DEFINE_PROP_UINT32("num-cpu", A9GTimerState, num_cpu, 0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void a9_gtimer_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = a9_gtimer_realize;
+ dc->vmsd = &vmstate_a9_gtimer;
+ dc->reset = 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)
diff --git a/hw/timer/allwinner-a10-pit.c b/hw/timer/allwinner-a10-pit.c
new file mode 100644
index 000000000..c3fc2a4da
--- /dev/null
+++ b/hw/timer/allwinner-a10-pit.c
@@ -0,0 +1,316 @@
+/*
+ * Allwinner A10 timer device emulation
+ *
+ * Copyright (C) 2013 Li Guang
+ * Written by Li Guang <lig.fnst@cn.fujitsu.com>
+ *
+ * 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/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/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_NATIVE_ENDIAN,
+};
+
+static 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),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_a10_pit = {
+ .name = "a10.pit",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (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_DEFAULT);
+ }
+}
+
+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, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = 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);
diff --git a/hw/timer/altera_timer.c b/hw/timer/altera_timer.c
new file mode 100644
index 000000000..c6e02d2b5
--- /dev/null
+++ b/hw/timer/altera_timer.c
@@ -0,0 +1,244 @@
+/*
+ * QEMU model of the Altera timer.
+ *
+ * Copyright (c) 2012 Chris Wulff <crwulff@gmail.com>
+ *
+ * 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>
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+
+#include "hw/sysbus.h"
+#include "hw/irq.h"
+#include "hw/ptimer.h"
+#include "hw/qdev-properties.h"
+#include "qom/object.h"
+
+#define R_STATUS 0
+#define R_CONTROL 1
+#define R_PERIODL 2
+#define R_PERIODH 3
+#define R_SNAPL 4
+#define R_SNAPH 5
+#define R_MAX 6
+
+#define STATUS_TO 0x0001
+#define STATUS_RUN 0x0002
+
+#define CONTROL_ITO 0x0001
+#define CONTROL_CONT 0x0002
+#define CONTROL_START 0x0004
+#define CONTROL_STOP 0x0008
+
+#define TYPE_ALTERA_TIMER "ALTR.timer"
+OBJECT_DECLARE_SIMPLE_TYPE(AlteraTimer, ALTERA_TIMER)
+
+struct AlteraTimer {
+ SysBusDevice busdev;
+ MemoryRegion mmio;
+ qemu_irq irq;
+ uint32_t freq_hz;
+ ptimer_state *ptimer;
+ uint32_t regs[R_MAX];
+};
+
+static int timer_irq_state(AlteraTimer *t)
+{
+ bool irq = (t->regs[R_STATUS] & STATUS_TO) &&
+ (t->regs[R_CONTROL] & CONTROL_ITO);
+ return irq;
+}
+
+static uint64_t timer_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ AlteraTimer *t = opaque;
+ uint64_t r = 0;
+
+ addr >>= 2;
+
+ switch (addr) {
+ case R_CONTROL:
+ r = t->regs[R_CONTROL] & (CONTROL_ITO | CONTROL_CONT);
+ break;
+
+ default:
+ if (addr < ARRAY_SIZE(t->regs)) {
+ r = t->regs[addr];
+ }
+ break;
+ }
+
+ return r;
+}
+
+static void timer_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned int size)
+{
+ AlteraTimer *t = opaque;
+ uint64_t tvalue;
+ uint32_t count = 0;
+ int irqState = timer_irq_state(t);
+
+ addr >>= 2;
+
+ switch (addr) {
+ case R_STATUS:
+ /* The timeout bit is cleared by writing the status register. */
+ t->regs[R_STATUS] &= ~STATUS_TO;
+ break;
+
+ case R_CONTROL:
+ ptimer_transaction_begin(t->ptimer);
+ t->regs[R_CONTROL] = value & (CONTROL_ITO | CONTROL_CONT);
+ if ((value & CONTROL_START) &&
+ !(t->regs[R_STATUS] & STATUS_RUN)) {
+ ptimer_run(t->ptimer, 1);
+ t->regs[R_STATUS] |= STATUS_RUN;
+ }
+ if ((value & CONTROL_STOP) && (t->regs[R_STATUS] & STATUS_RUN)) {
+ ptimer_stop(t->ptimer);
+ t->regs[R_STATUS] &= ~STATUS_RUN;
+ }
+ ptimer_transaction_commit(t->ptimer);
+ break;
+
+ case R_PERIODL:
+ case R_PERIODH:
+ ptimer_transaction_begin(t->ptimer);
+ t->regs[addr] = value & 0xFFFF;
+ if (t->regs[R_STATUS] & STATUS_RUN) {
+ ptimer_stop(t->ptimer);
+ t->regs[R_STATUS] &= ~STATUS_RUN;
+ }
+ tvalue = (t->regs[R_PERIODH] << 16) | t->regs[R_PERIODL];
+ ptimer_set_limit(t->ptimer, tvalue + 1, 1);
+ ptimer_transaction_commit(t->ptimer);
+ break;
+
+ case R_SNAPL:
+ case R_SNAPH:
+ count = ptimer_get_count(t->ptimer);
+ t->regs[R_SNAPL] = count & 0xFFFF;
+ t->regs[R_SNAPH] = count >> 16;
+ break;
+
+ default:
+ break;
+ }
+
+ if (irqState != timer_irq_state(t)) {
+ qemu_set_irq(t->irq, timer_irq_state(t));
+ }
+}
+
+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)
+{
+ AlteraTimer *t = opaque;
+ const uint64_t tvalue = (t->regs[R_PERIODH] << 16) | t->regs[R_PERIODL];
+
+ t->regs[R_STATUS] |= STATUS_TO;
+
+ ptimer_set_limit(t->ptimer, tvalue + 1, 1);
+
+ if (!(t->regs[R_CONTROL] & CONTROL_CONT)) {
+ t->regs[R_STATUS] &= ~STATUS_RUN;
+ ptimer_set_count(t->ptimer, tvalue);
+ } else {
+ ptimer_run(t->ptimer, 1);
+ }
+
+ qemu_set_irq(t->irq, timer_irq_state(t));
+}
+
+static void altera_timer_realize(DeviceState *dev, Error **errp)
+{
+ AlteraTimer *t = ALTERA_TIMER(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ if (t->freq_hz == 0) {
+ error_setg(errp, "\"clock-frequency\" property must be provided.");
+ return;
+ }
+
+ t->ptimer = ptimer_init(timer_hit, t, PTIMER_POLICY_DEFAULT);
+ ptimer_transaction_begin(t->ptimer);
+ ptimer_set_freq(t->ptimer, t->freq_hz);
+ ptimer_transaction_commit(t->ptimer);
+
+ memory_region_init_io(&t->mmio, OBJECT(t), &timer_ops, t,
+ TYPE_ALTERA_TIMER, R_MAX * sizeof(uint32_t));
+ sysbus_init_mmio(sbd, &t->mmio);
+}
+
+static void altera_timer_init(Object *obj)
+{
+ AlteraTimer *t = ALTERA_TIMER(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ sysbus_init_irq(sbd, &t->irq);
+}
+
+static void altera_timer_reset(DeviceState *dev)
+{
+ AlteraTimer *t = ALTERA_TIMER(dev);
+
+ ptimer_transaction_begin(t->ptimer);
+ ptimer_stop(t->ptimer);
+ ptimer_set_limit(t->ptimer, 0xffffffff, 1);
+ ptimer_transaction_commit(t->ptimer);
+ memset(t->regs, 0, sizeof(t->regs));
+}
+
+static Property altera_timer_properties[] = {
+ DEFINE_PROP_UINT32("clock-frequency", AlteraTimer, freq_hz, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void altera_timer_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = altera_timer_realize;
+ device_class_set_props(dc, altera_timer_properties);
+ dc->reset = altera_timer_reset;
+}
+
+static const TypeInfo altera_timer_info = {
+ .name = TYPE_ALTERA_TIMER,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(AlteraTimer),
+ .instance_init = altera_timer_init,
+ .class_init = altera_timer_class_init,
+};
+
+static void altera_timer_register(void)
+{
+ type_register_static(&altera_timer_info);
+}
+
+type_init(altera_timer_register)
diff --git a/hw/timer/arm_mptimer.c b/hw/timer/arm_mptimer.c
new file mode 100644
index 000000000..cdfca3000
--- /dev/null
+++ b/hw/timer/arm_mptimer.c
@@ -0,0 +1,331 @@
+/*
+ * 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/hw.h"
+#include "hw/irq.h"
+#include "hw/ptimer.h"
+#include "hw/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 = (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 = (VMStateField[]) {
+ VMSTATE_STRUCT_VARRAY_UINT32(timerblock, ARMMPTimerState, num_cpu,
+ 3, vmstate_timerblock, TimerBlock),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property arm_mptimer_properties[] = {
+ DEFINE_PROP_UINT32("num-cpu", ARMMPTimerState, num_cpu, 0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void arm_mptimer_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = arm_mptimer_realize;
+ dc->vmsd = &vmstate_arm_mptimer;
+ dc->reset = 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)
diff --git a/hw/timer/arm_timer.c b/hw/timer/arm_timer.c
new file mode 100644
index 000000000..15caff0e4
--- /dev/null
+++ b/hw/timer/arm_timer.c
@@ -0,0 +1,419 @@
+/*
+ * 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/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/timer.h"
+#include "hw/irq.h"
+#include "hw/ptimer.h"
+#include "hw/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 = (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 = (arm_timer_state *)g_malloc0(sizeof(arm_timer_state));
+ s->freq = freq;
+ s->control = TIMER_CTRL_IE;
+
+ s->timer = ptimer_init(arm_timer_tick, s, PTIMER_POLICY_DEFAULT);
+ vmstate_register(NULL, VMSTATE_INSTANCE_ID_ANY, &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 = (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 Property sp804_properties[] = {
+ DEFINE_PROP_UINT32("freq0", SP804State, freq0, 1000000),
+ DEFINE_PROP_UINT32("freq1", SP804State, freq1, 1000000),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void sp804_class_init(ObjectClass *klass, 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)
diff --git a/hw/timer/armv7m_systick.c b/hw/timer/armv7m_systick.c
new file mode 100644
index 000000000..3bd951dd0
--- /dev/null
+++ b/hw/timer/armv7m_systick.c
@@ -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/irq.h"
+#include "hw/sysbus.h"
+#include "hw/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_ENABLE) {
+ if (value & SYSTICK_ENABLE) {
+ ptimer_run(s->ptimer, 0);
+ } else {
+ ptimer_stop(s->ptimer);
+ }
+ }
+
+ if ((oldval ^ value) & SYSTICK_CLKSOURCE) {
+ systick_set_period_from_clock(s);
+ }
+ 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 = (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, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_systick;
+ dc->reset = 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)
diff --git a/hw/timer/aspeed_timer.c b/hw/timer/aspeed_timer.c
new file mode 100644
index 000000000..42c47d2ce
--- /dev/null
+++ b/hw/timer/aspeed_timer.c
@@ -0,0 +1,756 @@
+/*
+ * ASPEED AST2400 Timer
+ *
+ * Andrew Jeffery <andrew@aj.id.au>
+ *
+ * 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/irq.h"
+#include "hw/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/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(MAX(calculate_match(t, 0), calculate_match(t, 1)), 0);
+ 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(void *opaque, hwaddr offset, unsigned size)
+{
+ AspeedTimerCtrlState *s = opaque;
+ 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:
+ value = ASPEED_TIMER_GET_CLASS(s)->read(s, offset);
+ break;
+ }
+ trace_aspeed_timer_read(offset, size, value);
+ 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(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ const uint32_t tv = (uint32_t)(value & 0xFFFFFFFF);
+ const int reg = (offset & 0xf) / 4;
+ AspeedTimerCtrlState *s = opaque;
+
+ 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:
+ ASPEED_TIMER_GET_CLASS(s)->write(s, offset, value);
+ break;
+ }
+}
+
+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:
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ value = 0;
+ break;
+ }
+ 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:
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ 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:
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ value = 0;
+ break;
+ }
+ 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:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ 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:
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ value = 0;
+ break;
+ }
+ 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:
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\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(DeviceState *dev)
+{
+ int i;
+ AspeedTimerCtrlState *s = ASPEED_TIMER(dev);
+
+ 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 = (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 = (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 Property aspeed_timer_properties[] = {
+ DEFINE_PROP_LINK("scu", AspeedTimerCtrlState, scu, TYPE_ASPEED_SCU,
+ AspeedSCUState *),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void timer_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = aspeed_timer_realize;
+ dc->reset = aspeed_timer_reset;
+ dc->desc = "ASPEED Timer";
+ dc->vmsd = &vmstate_aspeed_timer_state;
+ device_class_set_props(dc, aspeed_timer_properties);
+}
+
+static const TypeInfo aspeed_timer_info = {
+ .name = TYPE_ASPEED_TIMER,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(AspeedTimerCtrlState),
+ .class_init = timer_class_init,
+ .class_size = sizeof(AspeedTimerClass),
+ .abstract = true,
+};
+
+static void aspeed_2400_timer_class_init(ObjectClass *klass, 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 const TypeInfo aspeed_2400_timer_info = {
+ .name = TYPE_ASPEED_2400_TIMER,
+ .parent = TYPE_ASPEED_TIMER,
+ .class_init = aspeed_2400_timer_class_init,
+};
+
+static void aspeed_2500_timer_class_init(ObjectClass *klass, 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 const TypeInfo aspeed_2500_timer_info = {
+ .name = TYPE_ASPEED_2500_TIMER,
+ .parent = TYPE_ASPEED_TIMER,
+ .class_init = aspeed_2500_timer_class_init,
+};
+
+static void aspeed_2600_timer_class_init(ObjectClass *klass, 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 const TypeInfo aspeed_2600_timer_info = {
+ .name = TYPE_ASPEED_2600_TIMER,
+ .parent = TYPE_ASPEED_TIMER,
+ .class_init = aspeed_2600_timer_class_init,
+};
+
+static void aspeed_timer_register_types(void)
+{
+ type_register_static(&aspeed_timer_info);
+ type_register_static(&aspeed_2400_timer_info);
+ type_register_static(&aspeed_2500_timer_info);
+ type_register_static(&aspeed_2600_timer_info);
+}
+
+type_init(aspeed_timer_register_types)
diff --git a/hw/timer/avr_timer16.c b/hw/timer/avr_timer16.c
new file mode 100644
index 000000000..c48555da5
--- /dev/null
+++ b/hw/timer/avr_timer16.c
@@ -0,0 +1,621 @@
+/*
+ * 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/irq.h"
+#include "hw/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 Property avr_timer16_properties[] = {
+ DEFINE_PROP_UINT8("id", struct AVRTimer16State, id, 0),
+ DEFINE_PROP_UINT64("cpu-frequency-hz", struct AVRTimer16State,
+ cpu_freq_hz, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+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, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = 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)
diff --git a/hw/timer/bcm2835_systmr.c b/hw/timer/bcm2835_systmr.c
new file mode 100644
index 000000000..67669a57f
--- /dev/null
+++ b/hw/timer/bcm2835_systmr.c
@@ -0,0 +1,178 @@
+/*
+ * BCM2835 SYS timer emulation
+ *
+ * Copyright (C) 2019 Philippe Mathieu-Daudé <f4bug@amsat.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Datasheet: BCM2835 ARM Peripherals (C6357-M-1398)
+ * https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf
+ *
+ * Only the free running 64-bit counter is implemented.
+ * The 4 COMPARE registers and the interruption are not implemented.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/timer.h"
+#include "hw/timer/bcm2835_systmr.h"
+#include "hw/registerfields.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+REG32(CTRL_STATUS, 0x00)
+REG32(COUNTER_LOW, 0x04)
+REG32(COUNTER_HIGH, 0x08)
+REG32(COMPARE0, 0x0c)
+REG32(COMPARE1, 0x10)
+REG32(COMPARE2, 0x14)
+REG32(COMPARE3, 0x18)
+
+static void bcm2835_systmr_timer_expire(void *opaque)
+{
+ BCM2835SystemTimerCompare *tmr = opaque;
+
+ trace_bcm2835_systmr_timer_expired(tmr->id);
+ tmr->state->reg.ctrl_status |= 1 << tmr->id;
+ qemu_set_irq(tmr->irq, 1);
+}
+
+static uint64_t bcm2835_systmr_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ BCM2835SystemTimerState *s = BCM2835_SYSTIMER(opaque);
+ uint64_t r = 0;
+
+ switch (offset) {
+ case A_CTRL_STATUS:
+ r = s->reg.ctrl_status;
+ break;
+ case A_COMPARE0 ... A_COMPARE3:
+ r = s->reg.compare[(offset - A_COMPARE0) >> 2];
+ break;
+ case A_COUNTER_LOW:
+ case A_COUNTER_HIGH:
+ /* Free running counter at 1MHz */
+ r = qemu_clock_get_us(QEMU_CLOCK_VIRTUAL);
+ r >>= 8 * (offset - A_COUNTER_LOW);
+ r &= UINT32_MAX;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ break;
+ }
+ trace_bcm2835_systmr_read(offset, r);
+
+ return r;
+}
+
+static void bcm2835_systmr_write(void *opaque, hwaddr offset,
+ uint64_t value64, unsigned size)
+{
+ BCM2835SystemTimerState *s = BCM2835_SYSTIMER(opaque);
+ int index;
+ uint32_t value = value64;
+ uint32_t triggers_delay_us;
+ uint64_t now;
+
+ trace_bcm2835_systmr_write(offset, value);
+ switch (offset) {
+ case A_CTRL_STATUS:
+ s->reg.ctrl_status &= ~value; /* Ack */
+ for (index = 0; index < ARRAY_SIZE(s->tmr); index++) {
+ if (extract32(value, index, 1)) {
+ trace_bcm2835_systmr_irq_ack(index);
+ qemu_set_irq(s->tmr[index].irq, 0);
+ }
+ }
+ break;
+ case A_COMPARE0 ... A_COMPARE3:
+ index = (offset - A_COMPARE0) >> 2;
+ s->reg.compare[index] = value;
+ now = qemu_clock_get_us(QEMU_CLOCK_VIRTUAL);
+ /* Compare lower 32-bits of the free-running counter. */
+ triggers_delay_us = value - now;
+ trace_bcm2835_systmr_run(index, triggers_delay_us);
+ timer_mod(&s->tmr[index].timer, now + triggers_delay_us);
+ break;
+ case A_COUNTER_LOW:
+ case A_COUNTER_HIGH:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: read-only ofs 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps bcm2835_systmr_ops = {
+ .read = bcm2835_systmr_read,
+ .write = bcm2835_systmr_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void bcm2835_systmr_reset(DeviceState *dev)
+{
+ BCM2835SystemTimerState *s = BCM2835_SYSTIMER(dev);
+
+ memset(&s->reg, 0, sizeof(s->reg));
+}
+
+static void bcm2835_systmr_realize(DeviceState *dev, Error **errp)
+{
+ BCM2835SystemTimerState *s = BCM2835_SYSTIMER(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(dev), &bcm2835_systmr_ops,
+ s, "bcm2835-sys-timer", 0x20);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
+
+ for (size_t i = 0; i < ARRAY_SIZE(s->tmr); i++) {
+ s->tmr[i].id = i;
+ s->tmr[i].state = s;
+ sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->tmr[i].irq);
+ timer_init_us(&s->tmr[i].timer, QEMU_CLOCK_VIRTUAL,
+ bcm2835_systmr_timer_expire, &s->tmr[i]);
+ }
+}
+
+static const VMStateDescription bcm2835_systmr_vmstate = {
+ .name = "bcm2835_sys_timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(reg.ctrl_status, BCM2835SystemTimerState),
+ VMSTATE_UINT32_ARRAY(reg.compare, BCM2835SystemTimerState,
+ BCM2835_SYSTIMER_COUNT),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void bcm2835_systmr_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = bcm2835_systmr_realize;
+ dc->reset = 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);
diff --git a/hw/timer/cadence_ttc.c b/hw/timer/cadence_ttc.c
new file mode 100644
index 000000000..64108241b
--- /dev/null
+++ b/hw/timer/cadence_ttc.c
@@ -0,0 +1,503 @@
+/*
+ * Xilinx Zynq cadence TTC model
+ *
+ * Copyright (c) 2011 Xilinx Inc.
+ * Copyright (c) 2012 Peter A.G. Crosthwaite (peter.crosthwaite@petalogix.com)
+ * 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/irq.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "qom/object.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
+
+typedef struct {
+ QEMUTimer *timer;
+ int freq;
+
+ uint32_t reg_clock;
+ uint32_t reg_count;
+ uint32_t reg_value;
+ uint16_t reg_interval;
+ uint16_t reg_match[3];
+ uint32_t reg_intr;
+ uint32_t reg_intr_en;
+ uint32_t reg_event_ctrl;
+ uint32_t reg_event;
+
+ uint64_t cpu_time;
+ unsigned int cpu_time_valid;
+
+ qemu_irq irq;
+} CadenceTimerState;
+
+#define TYPE_CADENCE_TTC "cadence_ttc"
+OBJECT_DECLARE_SIMPLE_TYPE(CadenceTTCState, CADENCE_TTC)
+
+struct CadenceTTCState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ CadenceTimerState timer[3];
+};
+
+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 = (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 = (VMStateField[]) {
+ VMSTATE_STRUCT_ARRAY(timer, CadenceTTCState, 3, 0,
+ vmstate_cadence_timer,
+ CadenceTimerState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void cadence_ttc_class_init(ObjectClass *klass, 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)
diff --git a/hw/timer/cmsdk-apb-dualtimer.c b/hw/timer/cmsdk-apb-dualtimer.c
new file mode 100644
index 000000000..d4a509c79
--- /dev/null
+++ b/hw/timer/cmsdk-apb-dualtimer.c
@@ -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/sysbus.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/registerfields.h"
+#include "hw/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 = (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 = (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, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = cmsdk_apb_dualtimer_realize;
+ dc->vmsd = &cmsdk_apb_dualtimer_vmstate;
+ dc->reset = 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);
diff --git a/hw/timer/cmsdk-apb-timer.c b/hw/timer/cmsdk-apb-timer.c
new file mode 100644
index 000000000..68aa1a763
--- /dev/null
+++ b/hw/timer/cmsdk-apb-timer.c
@@ -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/sysbus.h"
+#include "hw/irq.h"
+#include "hw/registerfields.h"
+#include "hw/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 = (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, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = cmsdk_apb_timer_realize;
+ dc->vmsd = &cmsdk_apb_timer_vmstate;
+ dc->reset = 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);
diff --git a/hw/timer/digic-timer.c b/hw/timer/digic-timer.c
new file mode 100644
index 000000000..e3aae4a45
--- /dev/null
+++ b/hw/timer/digic-timer.c
@@ -0,0 +1,186 @@
+/*
+ * QEMU model of the Canon DIGIC timer block.
+ *
+ * Copyright (C) 2013 Antony Pavlov <antonynpavlov@gmail.com>
+ *
+ * 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/sysbus.h"
+#include "hw/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 = (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"
+ TARGET_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"
+ TARGET_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_DEFAULT);
+
+ /*
+ * 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, void *class_data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = 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)
diff --git a/hw/timer/etraxfs_timer.c b/hw/timer/etraxfs_timer.c
new file mode 100644
index 000000000..4ba662190
--- /dev/null
+++ b/hw/timer/etraxfs_timer.c
@@ -0,0 +1,376 @@
+/*
+ * QEMU ETRAX Timers
+ *
+ * Copyright (c) 2007 Edgar E. Iglesias, Axis Communications AB.
+ *
+ * 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/sysbus.h"
+#include "sysemu/reset.h"
+#include "sysemu/runstate.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "hw/irq.h"
+#include "hw/ptimer.h"
+#include "qom/object.h"
+
+#define D(x)
+
+#define RW_TMR0_DIV 0x00
+#define R_TMR0_DATA 0x04
+#define RW_TMR0_CTRL 0x08
+#define RW_TMR1_DIV 0x10
+#define R_TMR1_DATA 0x14
+#define RW_TMR1_CTRL 0x18
+#define R_TIME 0x38
+#define RW_WD_CTRL 0x40
+#define R_WD_STAT 0x44
+#define RW_INTR_MASK 0x48
+#define RW_ACK_INTR 0x4c
+#define R_INTR 0x50
+#define R_MASKED_INTR 0x54
+
+#define TYPE_ETRAX_FS_TIMER "etraxfs-timer"
+typedef struct ETRAXTimerState ETRAXTimerState;
+DECLARE_INSTANCE_CHECKER(ETRAXTimerState, ETRAX_TIMER,
+ TYPE_ETRAX_FS_TIMER)
+
+struct ETRAXTimerState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ qemu_irq irq;
+ qemu_irq nmi;
+
+ ptimer_state *ptimer_t0;
+ ptimer_state *ptimer_t1;
+ ptimer_state *ptimer_wd;
+
+ int wd_hits;
+
+ /* Control registers. */
+ uint32_t rw_tmr0_div;
+ uint32_t r_tmr0_data;
+ uint32_t rw_tmr0_ctrl;
+
+ uint32_t rw_tmr1_div;
+ uint32_t r_tmr1_data;
+ uint32_t rw_tmr1_ctrl;
+
+ uint32_t rw_wd_ctrl;
+
+ uint32_t rw_intr_mask;
+ uint32_t rw_ack_intr;
+ uint32_t r_intr;
+ uint32_t r_masked_intr;
+};
+
+static uint64_t
+timer_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ ETRAXTimerState *t = opaque;
+ uint32_t r = 0;
+
+ switch (addr) {
+ case R_TMR0_DATA:
+ r = ptimer_get_count(t->ptimer_t0);
+ break;
+ case R_TMR1_DATA:
+ r = ptimer_get_count(t->ptimer_t1);
+ break;
+ case R_TIME:
+ r = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / 10;
+ break;
+ case RW_INTR_MASK:
+ r = t->rw_intr_mask;
+ break;
+ case R_MASKED_INTR:
+ r = t->r_intr & t->rw_intr_mask;
+ break;
+ default:
+ D(printf ("%s %x\n", __func__, addr));
+ break;
+ }
+ return r;
+}
+
+static void update_ctrl(ETRAXTimerState *t, int tnum)
+{
+ unsigned int op;
+ unsigned int freq;
+ unsigned int freq_hz;
+ unsigned int div;
+ uint32_t ctrl;
+
+ ptimer_state *timer;
+
+ if (tnum == 0) {
+ ctrl = t->rw_tmr0_ctrl;
+ div = t->rw_tmr0_div;
+ timer = t->ptimer_t0;
+ } else {
+ ctrl = t->rw_tmr1_ctrl;
+ div = t->rw_tmr1_div;
+ timer = t->ptimer_t1;
+ }
+
+
+ op = ctrl & 3;
+ freq = ctrl >> 2;
+ freq_hz = 32000000;
+
+ switch (freq)
+ {
+ case 0:
+ case 1:
+ D(printf ("extern or disabled timer clock?\n"));
+ break;
+ case 4: freq_hz = 29493000; break;
+ case 5: freq_hz = 32000000; break;
+ case 6: freq_hz = 32768000; break;
+ case 7: freq_hz = 100000000; break;
+ default:
+ abort();
+ break;
+ }
+
+ D(printf ("freq_hz=%d div=%d\n", freq_hz, div));
+ ptimer_transaction_begin(timer);
+ ptimer_set_freq(timer, freq_hz);
+ ptimer_set_limit(timer, div, 0);
+
+ switch (op)
+ {
+ case 0:
+ /* Load. */
+ ptimer_set_limit(timer, div, 1);
+ break;
+ case 1:
+ /* Hold. */
+ ptimer_stop(timer);
+ break;
+ case 2:
+ /* Run. */
+ ptimer_run(timer, 0);
+ break;
+ default:
+ abort();
+ break;
+ }
+ ptimer_transaction_commit(timer);
+}
+
+static void timer_update_irq(ETRAXTimerState *t)
+{
+ t->r_intr &= ~(t->rw_ack_intr);
+ t->r_masked_intr = t->r_intr & t->rw_intr_mask;
+
+ D(printf("%s: masked_intr=%x\n", __func__, t->r_masked_intr));
+ qemu_set_irq(t->irq, !!t->r_masked_intr);
+}
+
+static void timer0_hit(void *opaque)
+{
+ ETRAXTimerState *t = opaque;
+ t->r_intr |= 1;
+ timer_update_irq(t);
+}
+
+static void timer1_hit(void *opaque)
+{
+ ETRAXTimerState *t = opaque;
+ t->r_intr |= 2;
+ timer_update_irq(t);
+}
+
+static void watchdog_hit(void *opaque)
+{
+ ETRAXTimerState *t = opaque;
+ if (t->wd_hits == 0) {
+ /* real hw gives a single tick before reseting but we are
+ a bit friendlier to compensate for our slower execution. */
+ ptimer_set_count(t->ptimer_wd, 10);
+ ptimer_run(t->ptimer_wd, 1);
+ qemu_irq_raise(t->nmi);
+ }
+ else
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+
+ t->wd_hits++;
+}
+
+static inline void timer_watchdog_update(ETRAXTimerState *t, uint32_t value)
+{
+ unsigned int wd_en = t->rw_wd_ctrl & (1 << 8);
+ unsigned int wd_key = t->rw_wd_ctrl >> 9;
+ unsigned int wd_cnt = t->rw_wd_ctrl & 511;
+ unsigned int new_key = value >> 9 & ((1 << 7) - 1);
+ unsigned int new_cmd = (value >> 8) & 1;
+
+ /* If the watchdog is enabled, they written key must match the
+ complement of the previous. */
+ wd_key = ~wd_key & ((1 << 7) - 1);
+
+ if (wd_en && wd_key != new_key)
+ return;
+
+ D(printf("en=%d new_key=%x oldkey=%x cmd=%d cnt=%d\n",
+ wd_en, new_key, wd_key, new_cmd, wd_cnt));
+
+ if (t->wd_hits)
+ qemu_irq_lower(t->nmi);
+
+ t->wd_hits = 0;
+
+ ptimer_transaction_begin(t->ptimer_wd);
+ ptimer_set_freq(t->ptimer_wd, 760);
+ if (wd_cnt == 0)
+ wd_cnt = 256;
+ ptimer_set_count(t->ptimer_wd, wd_cnt);
+ if (new_cmd)
+ ptimer_run(t->ptimer_wd, 1);
+ else
+ ptimer_stop(t->ptimer_wd);
+
+ t->rw_wd_ctrl = value;
+ ptimer_transaction_commit(t->ptimer_wd);
+}
+
+static void
+timer_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ ETRAXTimerState *t = opaque;
+ uint32_t value = val64;
+
+ switch (addr)
+ {
+ case RW_TMR0_DIV:
+ t->rw_tmr0_div = value;
+ break;
+ case RW_TMR0_CTRL:
+ D(printf ("RW_TMR0_CTRL=%x\n", value));
+ t->rw_tmr0_ctrl = value;
+ update_ctrl(t, 0);
+ break;
+ case RW_TMR1_DIV:
+ t->rw_tmr1_div = value;
+ break;
+ case RW_TMR1_CTRL:
+ D(printf ("RW_TMR1_CTRL=%x\n", value));
+ t->rw_tmr1_ctrl = value;
+ update_ctrl(t, 1);
+ break;
+ case RW_INTR_MASK:
+ D(printf ("RW_INTR_MASK=%x\n", value));
+ t->rw_intr_mask = value;
+ timer_update_irq(t);
+ break;
+ case RW_WD_CTRL:
+ timer_watchdog_update(t, value);
+ break;
+ case RW_ACK_INTR:
+ t->rw_ack_intr = value;
+ timer_update_irq(t);
+ t->rw_ack_intr = 0;
+ break;
+ default:
+ printf ("%s " TARGET_FMT_plx " %x\n",
+ __func__, addr, value);
+ break;
+ }
+}
+
+static const MemoryRegionOps timer_ops = {
+ .read = timer_read,
+ .write = timer_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4
+ }
+};
+
+static void etraxfs_timer_reset_enter(Object *obj, ResetType type)
+{
+ ETRAXTimerState *t = ETRAX_TIMER(obj);
+
+ ptimer_transaction_begin(t->ptimer_t0);
+ ptimer_stop(t->ptimer_t0);
+ ptimer_transaction_commit(t->ptimer_t0);
+ ptimer_transaction_begin(t->ptimer_t1);
+ ptimer_stop(t->ptimer_t1);
+ ptimer_transaction_commit(t->ptimer_t1);
+ ptimer_transaction_begin(t->ptimer_wd);
+ ptimer_stop(t->ptimer_wd);
+ ptimer_transaction_commit(t->ptimer_wd);
+ t->rw_wd_ctrl = 0;
+ t->r_intr = 0;
+ t->rw_intr_mask = 0;
+}
+
+static void etraxfs_timer_reset_hold(Object *obj)
+{
+ ETRAXTimerState *t = ETRAX_TIMER(obj);
+
+ qemu_irq_lower(t->irq);
+}
+
+static void etraxfs_timer_realize(DeviceState *dev, Error **errp)
+{
+ ETRAXTimerState *t = ETRAX_TIMER(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ t->ptimer_t0 = ptimer_init(timer0_hit, t, PTIMER_POLICY_DEFAULT);
+ t->ptimer_t1 = ptimer_init(timer1_hit, t, PTIMER_POLICY_DEFAULT);
+ t->ptimer_wd = ptimer_init(watchdog_hit, t, PTIMER_POLICY_DEFAULT);
+
+ sysbus_init_irq(sbd, &t->irq);
+ sysbus_init_irq(sbd, &t->nmi);
+
+ memory_region_init_io(&t->mmio, OBJECT(t), &timer_ops, t,
+ "etraxfs-timer", 0x5c);
+ sysbus_init_mmio(sbd, &t->mmio);
+}
+
+static void etraxfs_timer_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ dc->realize = etraxfs_timer_realize;
+ rc->phases.enter = etraxfs_timer_reset_enter;
+ rc->phases.hold = etraxfs_timer_reset_hold;
+}
+
+static const TypeInfo etraxfs_timer_info = {
+ .name = TYPE_ETRAX_FS_TIMER,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ETRAXTimerState),
+ .class_init = etraxfs_timer_class_init,
+};
+
+static void etraxfs_timer_register_types(void)
+{
+ type_register_static(&etraxfs_timer_info);
+}
+
+type_init(etraxfs_timer_register_types)
diff --git a/hw/timer/exynos4210_mct.c b/hw/timer/exynos4210_mct.c
new file mode 100644
index 000000000..d0e534399
--- /dev/null
+++ b/hw/timer/exynos4210_mct.c
@@ -0,0 +1,1568 @@
+/*
+ * Samsung exynos4210 Multi Core timer
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ *
+ * Evgeny Voevodin <e.voevodin@samsung.com>
+ *
+ * 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/>.
+ */
+
+/*
+ * Global Timer:
+ *
+ * Consists of two timers. First represents Free Running Counter and second
+ * is used to measure interval from FRC to nearest comparator.
+ *
+ * 0 UINT64_MAX
+ * | timer0 |
+ * | <-------------------------------------------------------------- |
+ * | --------------------------------------------frc---------------> |
+ * |______________________________________________|__________________|
+ * CMP0 CMP1 CMP2 | CMP3
+ * __| |_
+ * | timer1 |
+ * | -------------> |
+ * frc CMPx
+ *
+ * Problem: when implementing global timer as is, overflow arises.
+ * next_time = cur_time + period * count;
+ * period and count are 64 bits width.
+ * Lets arm timer for MCT_GT_COUNTER_STEP count and update internal G_CNT
+ * register during each event.
+ *
+ * Problem: both timers need to be implemented using MCT_XT_COUNTER_STEP because
+ * local timer contains two counters: TCNT and ICNT. TCNT == 0 -> ICNT--.
+ * IRQ is generated when ICNT riches zero. Implementation where TCNT == 0
+ * generates IRQs suffers from too frequently events. Better to have one
+ * uint64_t counter equal to TCNT*ICNT and arm ptimer.c for a minimum(TCNT*ICNT,
+ * MCT_GT_COUNTER_STEP); (yes, if target tunes ICNT * TCNT to be too low values,
+ * there is no way to avoid frequently events).
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/timer.h"
+#include "qemu/module.h"
+#include "hw/ptimer.h"
+
+#include "hw/arm/exynos4210.h"
+#include "hw/irq.h"
+#include "qom/object.h"
+
+//#define DEBUG_MCT
+
+#ifdef DEBUG_MCT
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stdout, "MCT: [%24s:%5d] " fmt, __func__, __LINE__, \
+ ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+#define MCT_CFG 0x000
+#define G_CNT_L 0x100
+#define G_CNT_U 0x104
+#define G_CNT_WSTAT 0x110
+#define G_COMP0_L 0x200
+#define G_COMP0_U 0x204
+#define G_COMP0_ADD_INCR 0x208
+#define G_COMP1_L 0x210
+#define G_COMP1_U 0x214
+#define G_COMP1_ADD_INCR 0x218
+#define G_COMP2_L 0x220
+#define G_COMP2_U 0x224
+#define G_COMP2_ADD_INCR 0x228
+#define G_COMP3_L 0x230
+#define G_COMP3_U 0x234
+#define G_COMP3_ADD_INCR 0x238
+#define G_TCON 0x240
+#define G_INT_CSTAT 0x244
+#define G_INT_ENB 0x248
+#define G_WSTAT 0x24C
+#define L0_TCNTB 0x300
+#define L0_TCNTO 0x304
+#define L0_ICNTB 0x308
+#define L0_ICNTO 0x30C
+#define L0_FRCNTB 0x310
+#define L0_FRCNTO 0x314
+#define L0_TCON 0x320
+#define L0_INT_CSTAT 0x330
+#define L0_INT_ENB 0x334
+#define L0_WSTAT 0x340
+#define L1_TCNTB 0x400
+#define L1_TCNTO 0x404
+#define L1_ICNTB 0x408
+#define L1_ICNTO 0x40C
+#define L1_FRCNTB 0x410
+#define L1_FRCNTO 0x414
+#define L1_TCON 0x420
+#define L1_INT_CSTAT 0x430
+#define L1_INT_ENB 0x434
+#define L1_WSTAT 0x440
+
+#define MCT_CFG_GET_PRESCALER(x) ((x) & 0xFF)
+#define MCT_CFG_GET_DIVIDER(x) (1 << ((x) >> 8 & 7))
+
+#define GET_G_COMP_IDX(offset) (((offset) - G_COMP0_L) / 0x10)
+#define GET_G_COMP_ADD_INCR_IDX(offset) (((offset) - G_COMP0_ADD_INCR) / 0x10)
+
+#define G_COMP_L(x) (G_COMP0_L + (x) * 0x10)
+#define G_COMP_U(x) (G_COMP0_U + (x) * 0x10)
+
+#define G_COMP_ADD_INCR(x) (G_COMP0_ADD_INCR + (x) * 0x10)
+
+/* MCT bits */
+#define G_TCON_COMP_ENABLE(x) (1 << 2 * (x))
+#define G_TCON_AUTO_ICREMENT(x) (1 << (2 * (x) + 1))
+#define G_TCON_TIMER_ENABLE (1 << 8)
+
+#define G_INT_ENABLE(x) (1 << (x))
+#define G_INT_CSTAT_COMP(x) (1 << (x))
+
+#define G_CNT_WSTAT_L 1
+#define G_CNT_WSTAT_U 2
+
+#define G_WSTAT_COMP_L(x) (1 << 4 * (x))
+#define G_WSTAT_COMP_U(x) (1 << ((4 * (x)) + 1))
+#define G_WSTAT_COMP_ADDINCR(x) (1 << ((4 * (x)) + 2))
+#define G_WSTAT_TCON_WRITE (1 << 16)
+
+#define GET_L_TIMER_IDX(offset) ((((offset) & 0xF00) - L0_TCNTB) / 0x100)
+#define GET_L_TIMER_CNT_REG_IDX(offset, lt_i) \
+ (((offset) - (L0_TCNTB + 0x100 * (lt_i))) >> 2)
+
+#define L_ICNTB_MANUAL_UPDATE (1 << 31)
+
+#define L_TCON_TICK_START (1)
+#define L_TCON_INT_START (1 << 1)
+#define L_TCON_INTERVAL_MODE (1 << 2)
+#define L_TCON_FRC_START (1 << 3)
+
+#define L_INT_CSTAT_INTCNT (1 << 0)
+#define L_INT_CSTAT_FRCCNT (1 << 1)
+
+#define L_INT_INTENB_ICNTEIE (1 << 0)
+#define L_INT_INTENB_FRCEIE (1 << 1)
+
+#define L_WSTAT_TCNTB_WRITE (1 << 0)
+#define L_WSTAT_ICNTB_WRITE (1 << 1)
+#define L_WSTAT_FRCCNTB_WRITE (1 << 2)
+#define L_WSTAT_TCON_WRITE (1 << 3)
+
+enum LocalTimerRegCntIndexes {
+ L_REG_CNT_TCNTB,
+ L_REG_CNT_TCNTO,
+ L_REG_CNT_ICNTB,
+ L_REG_CNT_ICNTO,
+ L_REG_CNT_FRCCNTB,
+ L_REG_CNT_FRCCNTO,
+
+ L_REG_CNT_AMOUNT
+};
+
+#define MCT_SFR_SIZE 0x444
+
+#define MCT_GT_CMP_NUM 4
+
+#define MCT_GT_COUNTER_STEP 0x100000000ULL
+#define MCT_LT_COUNTER_STEP 0x100000000ULL
+#define MCT_LT_CNT_LOW_LIMIT 0x100
+
+/* global timer */
+typedef struct {
+ qemu_irq irq[MCT_GT_CMP_NUM];
+
+ struct gregs {
+ uint64_t cnt;
+ uint32_t cnt_wstat;
+ uint32_t tcon;
+ uint32_t int_cstat;
+ uint32_t int_enb;
+ uint32_t wstat;
+ uint64_t comp[MCT_GT_CMP_NUM];
+ uint32_t comp_add_incr[MCT_GT_CMP_NUM];
+ } reg;
+
+ uint64_t count; /* Value FRC was armed with */
+ int32_t curr_comp; /* Current comparator FRC is running to */
+
+ ptimer_state *ptimer_frc; /* FRC timer */
+
+} Exynos4210MCTGT;
+
+/* local timer */
+typedef struct {
+ int id; /* timer id */
+ qemu_irq irq; /* local timer irq */
+
+ struct tick_timer {
+ uint32_t cnt_run; /* cnt timer is running */
+ uint32_t int_run; /* int timer is running */
+
+ uint32_t last_icnto;
+ uint32_t last_tcnto;
+ uint32_t tcntb; /* initial value for TCNTB */
+ uint32_t icntb; /* initial value for ICNTB */
+
+ /* for step mode */
+ uint64_t distance; /* distance to count to the next event */
+ uint64_t progress; /* progress when counting by steps */
+ uint64_t count; /* count to arm timer with */
+
+ ptimer_state *ptimer_tick; /* timer for tick counter */
+ } tick_timer;
+
+ /* use ptimer.c to represent count down timer */
+
+ ptimer_state *ptimer_frc; /* timer for free running counter */
+
+ /* registers */
+ struct lregs {
+ uint32_t cnt[L_REG_CNT_AMOUNT];
+ uint32_t tcon;
+ uint32_t int_cstat;
+ uint32_t int_enb;
+ uint32_t wstat;
+ } reg;
+
+} Exynos4210MCTLT;
+
+#define TYPE_EXYNOS4210_MCT "exynos4210.mct"
+OBJECT_DECLARE_SIMPLE_TYPE(Exynos4210MCTState, EXYNOS4210_MCT)
+
+struct Exynos4210MCTState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ /* Registers */
+ uint32_t reg_mct_cfg;
+
+ Exynos4210MCTLT l_timer[2];
+ Exynos4210MCTGT g_timer;
+
+ uint32_t freq; /* all timers tick frequency, TCLK */
+};
+
+/*** VMState ***/
+static const VMStateDescription vmstate_tick_timer = {
+ .name = "exynos4210.mct.tick_timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(cnt_run, struct tick_timer),
+ VMSTATE_UINT32(int_run, struct tick_timer),
+ VMSTATE_UINT32(last_icnto, struct tick_timer),
+ VMSTATE_UINT32(last_tcnto, struct tick_timer),
+ VMSTATE_UINT32(tcntb, struct tick_timer),
+ VMSTATE_UINT32(icntb, struct tick_timer),
+ VMSTATE_UINT64(distance, struct tick_timer),
+ VMSTATE_UINT64(progress, struct tick_timer),
+ VMSTATE_UINT64(count, struct tick_timer),
+ VMSTATE_PTIMER(ptimer_tick, struct tick_timer),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_lregs = {
+ .name = "exynos4210.mct.lregs",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(cnt, struct lregs, L_REG_CNT_AMOUNT),
+ VMSTATE_UINT32(tcon, struct lregs),
+ VMSTATE_UINT32(int_cstat, struct lregs),
+ VMSTATE_UINT32(int_enb, struct lregs),
+ VMSTATE_UINT32(wstat, struct lregs),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_exynos4210_mct_lt = {
+ .name = "exynos4210.mct.lt",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(id, Exynos4210MCTLT),
+ VMSTATE_STRUCT(tick_timer, Exynos4210MCTLT, 0,
+ vmstate_tick_timer,
+ struct tick_timer),
+ VMSTATE_PTIMER(ptimer_frc, Exynos4210MCTLT),
+ VMSTATE_STRUCT(reg, Exynos4210MCTLT, 0,
+ vmstate_lregs,
+ struct lregs),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_gregs = {
+ .name = "exynos4210.mct.lregs",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(cnt, struct gregs),
+ VMSTATE_UINT32(cnt_wstat, struct gregs),
+ VMSTATE_UINT32(tcon, struct gregs),
+ VMSTATE_UINT32(int_cstat, struct gregs),
+ VMSTATE_UINT32(int_enb, struct gregs),
+ VMSTATE_UINT32(wstat, struct gregs),
+ VMSTATE_UINT64_ARRAY(comp, struct gregs, MCT_GT_CMP_NUM),
+ VMSTATE_UINT32_ARRAY(comp_add_incr, struct gregs,
+ MCT_GT_CMP_NUM),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_exynos4210_mct_gt = {
+ .name = "exynos4210.mct.lt",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(reg, Exynos4210MCTGT, 0, vmstate_gregs,
+ struct gregs),
+ VMSTATE_UINT64(count, Exynos4210MCTGT),
+ VMSTATE_INT32(curr_comp, Exynos4210MCTGT),
+ VMSTATE_PTIMER(ptimer_frc, Exynos4210MCTGT),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_exynos4210_mct_state = {
+ .name = "exynos4210.mct",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(reg_mct_cfg, Exynos4210MCTState),
+ VMSTATE_STRUCT_ARRAY(l_timer, Exynos4210MCTState, 2, 0,
+ vmstate_exynos4210_mct_lt, Exynos4210MCTLT),
+ VMSTATE_STRUCT(g_timer, Exynos4210MCTState, 0,
+ vmstate_exynos4210_mct_gt, Exynos4210MCTGT),
+ VMSTATE_UINT32(freq, Exynos4210MCTState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void exynos4210_mct_update_freq(Exynos4210MCTState *s);
+
+/*
+ * Set counter of FRC global timer.
+ * Must be called within exynos4210_gfrc_tx_begin/commit block.
+ */
+static void exynos4210_gfrc_set_count(Exynos4210MCTGT *s, uint64_t count)
+{
+ s->count = count;
+ DPRINTF("global timer frc set count 0x%llx\n", count);
+ ptimer_set_count(s->ptimer_frc, count);
+}
+
+/*
+ * Get counter of FRC global timer.
+ */
+static uint64_t exynos4210_gfrc_get_count(Exynos4210MCTGT *s)
+{
+ uint64_t count = 0;
+ count = ptimer_get_count(s->ptimer_frc);
+ count = s->count - count;
+ return s->reg.cnt + count;
+}
+
+/*
+ * Stop global FRC timer
+ * Must be called within exynos4210_gfrc_tx_begin/commit block.
+ */
+static void exynos4210_gfrc_stop(Exynos4210MCTGT *s)
+{
+ DPRINTF("global timer frc stop\n");
+
+ ptimer_stop(s->ptimer_frc);
+}
+
+/*
+ * Start global FRC timer
+ * Must be called within exynos4210_gfrc_tx_begin/commit block.
+ */
+static void exynos4210_gfrc_start(Exynos4210MCTGT *s)
+{
+ DPRINTF("global timer frc start\n");
+
+ ptimer_run(s->ptimer_frc, 1);
+}
+
+/*
+ * Start ptimer transaction for global FRC timer; this is just for
+ * consistency with the way we wrap operations like stop and run.
+ */
+static void exynos4210_gfrc_tx_begin(Exynos4210MCTGT *s)
+{
+ ptimer_transaction_begin(s->ptimer_frc);
+}
+
+/* Commit ptimer transaction for global FRC timer. */
+static void exynos4210_gfrc_tx_commit(Exynos4210MCTGT *s)
+{
+ ptimer_transaction_commit(s->ptimer_frc);
+}
+
+/*
+ * Find next nearest Comparator. If current Comparator value equals to other
+ * Comparator value, skip them both
+ */
+static int32_t exynos4210_gcomp_find(Exynos4210MCTState *s)
+{
+ int res;
+ int i;
+ int enabled;
+ uint64_t min;
+ int min_comp_i;
+ uint64_t gfrc;
+ uint64_t distance;
+ uint64_t distance_min;
+ int comp_i;
+
+ /* get gfrc count */
+ gfrc = exynos4210_gfrc_get_count(&s->g_timer);
+
+ min = UINT64_MAX;
+ distance_min = UINT64_MAX;
+ comp_i = MCT_GT_CMP_NUM;
+ min_comp_i = MCT_GT_CMP_NUM;
+ enabled = 0;
+
+ /* lookup for nearest comparator */
+ for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+
+ if (s->g_timer.reg.tcon & G_TCON_COMP_ENABLE(i)) {
+
+ enabled = 1;
+
+ if (s->g_timer.reg.comp[i] > gfrc) {
+ /* Comparator is upper then FRC */
+ distance = s->g_timer.reg.comp[i] - gfrc;
+
+ if (distance <= distance_min) {
+ distance_min = distance;
+ comp_i = i;
+ }
+ } else {
+ /* Comparator is below FRC, find the smallest */
+
+ if (s->g_timer.reg.comp[i] <= min) {
+ min = s->g_timer.reg.comp[i];
+ min_comp_i = i;
+ }
+ }
+ }
+ }
+
+ if (!enabled) {
+ /* All Comparators disabled */
+ res = -1;
+ } else if (comp_i < MCT_GT_CMP_NUM) {
+ /* Found upper Comparator */
+ res = comp_i;
+ } else {
+ /* All Comparators are below or equal to FRC */
+ res = min_comp_i;
+ }
+
+ DPRINTF("found comparator %d: comp 0x%llx distance 0x%llx, gfrc 0x%llx\n",
+ res,
+ s->g_timer.reg.comp[res],
+ distance_min,
+ gfrc);
+
+ return res;
+}
+
+/*
+ * Get distance to nearest Comparator
+ */
+static uint64_t exynos4210_gcomp_get_distance(Exynos4210MCTState *s, int32_t id)
+{
+ if (id == -1) {
+ /* no enabled Comparators, choose max distance */
+ return MCT_GT_COUNTER_STEP;
+ }
+ if (s->g_timer.reg.comp[id] - s->g_timer.reg.cnt < MCT_GT_COUNTER_STEP) {
+ return s->g_timer.reg.comp[id] - s->g_timer.reg.cnt;
+ } else {
+ return MCT_GT_COUNTER_STEP;
+ }
+}
+
+/*
+ * Restart global FRC timer
+ * Must be called within exynos4210_gfrc_tx_begin/commit block.
+ */
+static void exynos4210_gfrc_restart(Exynos4210MCTState *s)
+{
+ uint64_t distance;
+
+ exynos4210_gfrc_stop(&s->g_timer);
+
+ s->g_timer.curr_comp = exynos4210_gcomp_find(s);
+
+ distance = exynos4210_gcomp_get_distance(s, s->g_timer.curr_comp);
+
+ if (distance > MCT_GT_COUNTER_STEP || !distance) {
+ distance = MCT_GT_COUNTER_STEP;
+ }
+
+ exynos4210_gfrc_set_count(&s->g_timer, distance);
+ exynos4210_gfrc_start(&s->g_timer);
+}
+
+/*
+ * Raise global timer CMP IRQ
+ */
+static void exynos4210_gcomp_raise_irq(void *opaque, uint32_t id)
+{
+ Exynos4210MCTGT *s = opaque;
+
+ /* If CSTAT is pending and IRQ is enabled */
+ if ((s->reg.int_cstat & G_INT_CSTAT_COMP(id)) &&
+ (s->reg.int_enb & G_INT_ENABLE(id))) {
+ DPRINTF("gcmp timer[%u] IRQ\n", id);
+ qemu_irq_raise(s->irq[id]);
+ }
+}
+
+/*
+ * Lower global timer CMP IRQ
+ */
+static void exynos4210_gcomp_lower_irq(void *opaque, uint32_t id)
+{
+ Exynos4210MCTGT *s = opaque;
+ qemu_irq_lower(s->irq[id]);
+}
+
+/*
+ * Global timer FRC event handler.
+ * Each event occurs when internal counter reaches counter + MCT_GT_COUNTER_STEP
+ * Every time we arm global FRC timer to count for MCT_GT_COUNTER_STEP value
+ */
+static void exynos4210_gfrc_event(void *opaque)
+{
+ Exynos4210MCTState *s = (Exynos4210MCTState *)opaque;
+ int i;
+ uint64_t distance;
+
+ DPRINTF("\n");
+
+ s->g_timer.reg.cnt += s->g_timer.count;
+
+ /* Process all comparators */
+ for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+
+ if (s->g_timer.reg.cnt == s->g_timer.reg.comp[i]) {
+ /* reached nearest comparator */
+
+ s->g_timer.reg.int_cstat |= G_INT_CSTAT_COMP(i);
+
+ /* Auto increment */
+ if (s->g_timer.reg.tcon & G_TCON_AUTO_ICREMENT(i)) {
+ s->g_timer.reg.comp[i] += s->g_timer.reg.comp_add_incr[i];
+ }
+
+ /* IRQ */
+ exynos4210_gcomp_raise_irq(&s->g_timer, i);
+ }
+ }
+
+ /* Reload FRC to reach nearest comparator */
+ s->g_timer.curr_comp = exynos4210_gcomp_find(s);
+ distance = exynos4210_gcomp_get_distance(s, s->g_timer.curr_comp);
+ if (distance > MCT_GT_COUNTER_STEP || !distance) {
+ distance = MCT_GT_COUNTER_STEP;
+ }
+ exynos4210_gfrc_set_count(&s->g_timer, distance);
+
+ exynos4210_gfrc_start(&s->g_timer);
+}
+
+/*
+ * Get counter of FRC local timer.
+ */
+static uint64_t exynos4210_lfrc_get_count(Exynos4210MCTLT *s)
+{
+ return ptimer_get_count(s->ptimer_frc);
+}
+
+/*
+ * Set counter of FRC local timer.
+ * Must be called from within exynos4210_lfrc_tx_begin/commit block.
+ */
+static void exynos4210_lfrc_update_count(Exynos4210MCTLT *s)
+{
+ if (!s->reg.cnt[L_REG_CNT_FRCCNTB]) {
+ ptimer_set_count(s->ptimer_frc, MCT_LT_COUNTER_STEP);
+ } else {
+ ptimer_set_count(s->ptimer_frc, s->reg.cnt[L_REG_CNT_FRCCNTB]);
+ }
+}
+
+/*
+ * Start local FRC timer
+ * Must be called from within exynos4210_lfrc_tx_begin/commit block.
+ */
+static void exynos4210_lfrc_start(Exynos4210MCTLT *s)
+{
+ ptimer_run(s->ptimer_frc, 1);
+}
+
+/*
+ * Stop local FRC timer
+ * Must be called from within exynos4210_lfrc_tx_begin/commit block.
+ */
+static void exynos4210_lfrc_stop(Exynos4210MCTLT *s)
+{
+ ptimer_stop(s->ptimer_frc);
+}
+
+/* Start ptimer transaction for local FRC timer */
+static void exynos4210_lfrc_tx_begin(Exynos4210MCTLT *s)
+{
+ ptimer_transaction_begin(s->ptimer_frc);
+}
+
+/* Commit ptimer transaction for local FRC timer */
+static void exynos4210_lfrc_tx_commit(Exynos4210MCTLT *s)
+{
+ ptimer_transaction_commit(s->ptimer_frc);
+}
+
+/*
+ * Local timer free running counter tick handler
+ */
+static void exynos4210_lfrc_event(void *opaque)
+{
+ Exynos4210MCTLT * s = (Exynos4210MCTLT *)opaque;
+
+ /* local frc expired */
+
+ DPRINTF("\n");
+
+ s->reg.int_cstat |= L_INT_CSTAT_FRCCNT;
+
+ /* update frc counter */
+ exynos4210_lfrc_update_count(s);
+
+ /* raise irq */
+ if (s->reg.int_enb & L_INT_INTENB_FRCEIE) {
+ qemu_irq_raise(s->irq);
+ }
+
+ /* we reached here, this means that timer is enabled */
+ exynos4210_lfrc_start(s);
+}
+
+static uint32_t exynos4210_ltick_int_get_cnto(struct tick_timer *s);
+static uint32_t exynos4210_ltick_cnt_get_cnto(struct tick_timer *s);
+static void exynos4210_ltick_recalc_count(struct tick_timer *s);
+
+/*
+ * Action on enabling local tick int timer
+ */
+static void exynos4210_ltick_int_start(struct tick_timer *s)
+{
+ if (!s->int_run) {
+ s->int_run = 1;
+ }
+}
+
+/*
+ * Action on disabling local tick int timer
+ */
+static void exynos4210_ltick_int_stop(struct tick_timer *s)
+{
+ if (s->int_run) {
+ s->last_icnto = exynos4210_ltick_int_get_cnto(s);
+ s->int_run = 0;
+ }
+}
+
+/*
+ * Get count for INT timer
+ */
+static uint32_t exynos4210_ltick_int_get_cnto(struct tick_timer *s)
+{
+ uint32_t icnto;
+ uint64_t remain;
+ uint64_t count;
+ uint64_t counted;
+ uint64_t cur_progress;
+
+ count = ptimer_get_count(s->ptimer_tick);
+ if (count) {
+ /* timer is still counting, called not from event */
+ counted = s->count - ptimer_get_count(s->ptimer_tick);
+ cur_progress = s->progress + counted;
+ } else {
+ /* timer expired earlier */
+ cur_progress = s->progress;
+ }
+
+ remain = s->distance - cur_progress;
+
+ if (!s->int_run) {
+ /* INT is stopped. */
+ icnto = s->last_icnto;
+ } else {
+ /* Both are counting */
+ icnto = remain / s->tcntb;
+ }
+
+ return icnto;
+}
+
+/*
+ * Start local tick cnt timer.
+ * Must be called within exynos4210_ltick_tx_begin/commit block.
+ */
+static void exynos4210_ltick_cnt_start(struct tick_timer *s)
+{
+ if (!s->cnt_run) {
+
+ exynos4210_ltick_recalc_count(s);
+ ptimer_set_count(s->ptimer_tick, s->count);
+ ptimer_run(s->ptimer_tick, 1);
+
+ s->cnt_run = 1;
+ }
+}
+
+/*
+ * Stop local tick cnt timer.
+ * Must be called within exynos4210_ltick_tx_begin/commit block.
+ */
+static void exynos4210_ltick_cnt_stop(struct tick_timer *s)
+{
+ if (s->cnt_run) {
+
+ s->last_tcnto = exynos4210_ltick_cnt_get_cnto(s);
+
+ if (s->int_run) {
+ exynos4210_ltick_int_stop(s);
+ }
+
+ ptimer_stop(s->ptimer_tick);
+
+ s->cnt_run = 0;
+ }
+}
+
+/* Start ptimer transaction for local tick timer */
+static void exynos4210_ltick_tx_begin(struct tick_timer *s)
+{
+ ptimer_transaction_begin(s->ptimer_tick);
+}
+
+/* Commit ptimer transaction for local tick timer */
+static void exynos4210_ltick_tx_commit(struct tick_timer *s)
+{
+ ptimer_transaction_commit(s->ptimer_tick);
+}
+
+/*
+ * Get counter for CNT timer
+ */
+static uint32_t exynos4210_ltick_cnt_get_cnto(struct tick_timer *s)
+{
+ uint32_t tcnto;
+ uint32_t icnto;
+ uint64_t remain;
+ uint64_t counted;
+ uint64_t count;
+ uint64_t cur_progress;
+
+ count = ptimer_get_count(s->ptimer_tick);
+ if (count) {
+ /* timer is still counting, called not from event */
+ counted = s->count - ptimer_get_count(s->ptimer_tick);
+ cur_progress = s->progress + counted;
+ } else {
+ /* timer expired earlier */
+ cur_progress = s->progress;
+ }
+
+ remain = s->distance - cur_progress;
+
+ if (!s->cnt_run) {
+ /* Both are stopped. */
+ tcnto = s->last_tcnto;
+ } else if (!s->int_run) {
+ /* INT counter is stopped, progress is by CNT timer */
+ tcnto = remain % s->tcntb;
+ } else {
+ /* Both are counting */
+ icnto = remain / s->tcntb;
+ if (icnto) {
+ tcnto = remain % (icnto * s->tcntb);
+ } else {
+ tcnto = remain % s->tcntb;
+ }
+ }
+
+ return tcnto;
+}
+
+/*
+ * Set new values of counters for CNT and INT timers
+ * Must be called within exynos4210_ltick_tx_begin/commit block.
+ */
+static void exynos4210_ltick_set_cntb(struct tick_timer *s, uint32_t new_cnt,
+ uint32_t new_int)
+{
+ uint32_t cnt_stopped = 0;
+ uint32_t int_stopped = 0;
+
+ if (s->cnt_run) {
+ exynos4210_ltick_cnt_stop(s);
+ cnt_stopped = 1;
+ }
+
+ if (s->int_run) {
+ exynos4210_ltick_int_stop(s);
+ int_stopped = 1;
+ }
+
+ s->tcntb = new_cnt + 1;
+ s->icntb = new_int + 1;
+
+ if (cnt_stopped) {
+ exynos4210_ltick_cnt_start(s);
+ }
+ if (int_stopped) {
+ exynos4210_ltick_int_start(s);
+ }
+
+}
+
+/*
+ * Calculate new counter value for tick timer
+ */
+static void exynos4210_ltick_recalc_count(struct tick_timer *s)
+{
+ uint64_t to_count;
+
+ if ((s->cnt_run && s->last_tcnto) || (s->int_run && s->last_icnto)) {
+ /*
+ * one or both timers run and not counted to the end;
+ * distance is not passed, recalculate with last_tcnto * last_icnto
+ */
+
+ if (s->last_tcnto) {
+ to_count = (uint64_t)s->last_tcnto * s->last_icnto;
+ } else {
+ to_count = s->last_icnto;
+ }
+ } else {
+ /* distance is passed, recalculate with tcnto * icnto */
+ if (s->icntb) {
+ s->distance = (uint64_t)s->tcntb * s->icntb;
+ } else {
+ s->distance = s->tcntb;
+ }
+
+ to_count = s->distance;
+ s->progress = 0;
+ }
+
+ if (to_count > MCT_LT_COUNTER_STEP) {
+ /* count by step */
+ s->count = MCT_LT_COUNTER_STEP;
+ } else {
+ s->count = to_count;
+ }
+}
+
+/*
+ * Initialize tick_timer
+ */
+static void exynos4210_ltick_timer_init(struct tick_timer *s)
+{
+ exynos4210_ltick_int_stop(s);
+ exynos4210_ltick_tx_begin(s);
+ exynos4210_ltick_cnt_stop(s);
+ exynos4210_ltick_tx_commit(s);
+
+ s->count = 0;
+ s->distance = 0;
+ s->progress = 0;
+ s->icntb = 0;
+ s->tcntb = 0;
+}
+
+/*
+ * tick_timer event.
+ * Raises when abstract tick_timer expires.
+ */
+static void exynos4210_ltick_timer_event(struct tick_timer *s)
+{
+ s->progress += s->count;
+}
+
+/*
+ * Local timer tick counter handler.
+ * Don't use reloaded timers. If timer counter = zero
+ * then handler called but after handler finished no
+ * timer reload occurs.
+ */
+static void exynos4210_ltick_event(void *opaque)
+{
+ Exynos4210MCTLT * s = (Exynos4210MCTLT *)opaque;
+ uint32_t tcnto;
+ uint32_t icnto;
+#ifdef DEBUG_MCT
+ static uint64_t time1[2] = {0};
+ static uint64_t time2[2] = {0};
+#endif
+
+ /* Call tick_timer event handler, it will update its tcntb and icntb. */
+ exynos4210_ltick_timer_event(&s->tick_timer);
+
+ /* get tick_timer cnt */
+ tcnto = exynos4210_ltick_cnt_get_cnto(&s->tick_timer);
+
+ /* get tick_timer int */
+ icnto = exynos4210_ltick_int_get_cnto(&s->tick_timer);
+
+ /* raise IRQ if needed */
+ if (!icnto && s->reg.tcon & L_TCON_INT_START) {
+ /* INT counter enabled and expired */
+
+ s->reg.int_cstat |= L_INT_CSTAT_INTCNT;
+
+ /* raise interrupt if enabled */
+ if (s->reg.int_enb & L_INT_INTENB_ICNTEIE) {
+#ifdef DEBUG_MCT
+ time2[s->id] = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ DPRINTF("local timer[%d] IRQ: %llx\n", s->id,
+ time2[s->id] - time1[s->id]);
+ time1[s->id] = time2[s->id];
+#endif
+ qemu_irq_raise(s->irq);
+ }
+
+ /* reload ICNTB */
+ if (s->reg.tcon & L_TCON_INTERVAL_MODE) {
+ exynos4210_ltick_set_cntb(&s->tick_timer,
+ s->reg.cnt[L_REG_CNT_TCNTB],
+ s->reg.cnt[L_REG_CNT_ICNTB]);
+ }
+ } else {
+ /* reload TCNTB */
+ if (!tcnto) {
+ exynos4210_ltick_set_cntb(&s->tick_timer,
+ s->reg.cnt[L_REG_CNT_TCNTB],
+ icnto);
+ }
+ }
+
+ /* start tick_timer cnt */
+ exynos4210_ltick_cnt_start(&s->tick_timer);
+
+ /* start tick_timer int */
+ exynos4210_ltick_int_start(&s->tick_timer);
+}
+
+static void tx_ptimer_set_freq(ptimer_state *s, uint32_t freq)
+{
+ /*
+ * callers of exynos4210_mct_update_freq() never do anything
+ * else that needs to be in the same ptimer transaction, so
+ * to avoid a lot of repetition we have a convenience function
+ * for begin/set_freq/commit.
+ */
+ ptimer_transaction_begin(s);
+ ptimer_set_freq(s, freq);
+ ptimer_transaction_commit(s);
+}
+
+/* update timer frequency */
+static void exynos4210_mct_update_freq(Exynos4210MCTState *s)
+{
+ uint32_t freq = s->freq;
+ s->freq = 24000000 /
+ ((MCT_CFG_GET_PRESCALER(s->reg_mct_cfg) + 1) *
+ MCT_CFG_GET_DIVIDER(s->reg_mct_cfg));
+
+ if (freq != s->freq) {
+ DPRINTF("freq=%uHz\n", s->freq);
+
+ /* global timer */
+ tx_ptimer_set_freq(s->g_timer.ptimer_frc, s->freq);
+
+ /* local timer */
+ tx_ptimer_set_freq(s->l_timer[0].tick_timer.ptimer_tick, s->freq);
+ tx_ptimer_set_freq(s->l_timer[0].ptimer_frc, s->freq);
+ tx_ptimer_set_freq(s->l_timer[1].tick_timer.ptimer_tick, s->freq);
+ tx_ptimer_set_freq(s->l_timer[1].ptimer_frc, s->freq);
+ }
+}
+
+/* set defaul_timer values for all fields */
+static void exynos4210_mct_reset(DeviceState *d)
+{
+ Exynos4210MCTState *s = EXYNOS4210_MCT(d);
+ uint32_t i;
+
+ s->reg_mct_cfg = 0;
+
+ /* global timer */
+ memset(&s->g_timer.reg, 0, sizeof(s->g_timer.reg));
+ exynos4210_gfrc_tx_begin(&s->g_timer);
+ exynos4210_gfrc_stop(&s->g_timer);
+ exynos4210_gfrc_tx_commit(&s->g_timer);
+
+ /* local timer */
+ memset(s->l_timer[0].reg.cnt, 0, sizeof(s->l_timer[0].reg.cnt));
+ memset(s->l_timer[1].reg.cnt, 0, sizeof(s->l_timer[1].reg.cnt));
+ for (i = 0; i < 2; i++) {
+ s->l_timer[i].reg.int_cstat = 0;
+ s->l_timer[i].reg.int_enb = 0;
+ s->l_timer[i].reg.tcon = 0;
+ s->l_timer[i].reg.wstat = 0;
+ s->l_timer[i].tick_timer.count = 0;
+ s->l_timer[i].tick_timer.distance = 0;
+ s->l_timer[i].tick_timer.progress = 0;
+ exynos4210_lfrc_tx_begin(&s->l_timer[i]);
+ ptimer_stop(s->l_timer[i].ptimer_frc);
+ exynos4210_lfrc_tx_commit(&s->l_timer[i]);
+
+ exynos4210_ltick_timer_init(&s->l_timer[i].tick_timer);
+ }
+
+ exynos4210_mct_update_freq(s);
+
+}
+
+/* Multi Core Timer read */
+static uint64_t exynos4210_mct_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ Exynos4210MCTState *s = (Exynos4210MCTState *)opaque;
+ int index;
+ int shift;
+ uint64_t count;
+ uint32_t value = 0;
+ int lt_i;
+
+ switch (offset) {
+
+ case MCT_CFG:
+ value = s->reg_mct_cfg;
+ break;
+
+ case G_CNT_L: case G_CNT_U:
+ shift = 8 * (offset & 0x4);
+ count = exynos4210_gfrc_get_count(&s->g_timer);
+ value = UINT32_MAX & (count >> shift);
+ DPRINTF("read FRC=0x%llx\n", count);
+ break;
+
+ case G_CNT_WSTAT:
+ value = s->g_timer.reg.cnt_wstat;
+ break;
+
+ case G_COMP_L(0): case G_COMP_L(1): case G_COMP_L(2): case G_COMP_L(3):
+ case G_COMP_U(0): case G_COMP_U(1): case G_COMP_U(2): case G_COMP_U(3):
+ index = GET_G_COMP_IDX(offset);
+ shift = 8 * (offset & 0x4);
+ value = UINT32_MAX & (s->g_timer.reg.comp[index] >> shift);
+ break;
+
+ case G_TCON:
+ value = s->g_timer.reg.tcon;
+ break;
+
+ case G_INT_CSTAT:
+ value = s->g_timer.reg.int_cstat;
+ break;
+
+ case G_INT_ENB:
+ value = s->g_timer.reg.int_enb;
+ break;
+ case G_WSTAT:
+ value = s->g_timer.reg.wstat;
+ break;
+
+ case G_COMP0_ADD_INCR: case G_COMP1_ADD_INCR:
+ case G_COMP2_ADD_INCR: case G_COMP3_ADD_INCR:
+ value = s->g_timer.reg.comp_add_incr[GET_G_COMP_ADD_INCR_IDX(offset)];
+ break;
+
+ /* Local timers */
+ case L0_TCNTB: case L0_ICNTB: case L0_FRCNTB:
+ case L1_TCNTB: case L1_ICNTB: case L1_FRCNTB:
+ lt_i = GET_L_TIMER_IDX(offset);
+ index = GET_L_TIMER_CNT_REG_IDX(offset, lt_i);
+ value = s->l_timer[lt_i].reg.cnt[index];
+ break;
+
+ case L0_TCNTO: case L1_TCNTO:
+ lt_i = GET_L_TIMER_IDX(offset);
+
+ value = exynos4210_ltick_cnt_get_cnto(&s->l_timer[lt_i].tick_timer);
+ DPRINTF("local timer[%d] read TCNTO %x\n", lt_i, value);
+ break;
+
+ case L0_ICNTO: case L1_ICNTO:
+ lt_i = GET_L_TIMER_IDX(offset);
+
+ value = exynos4210_ltick_int_get_cnto(&s->l_timer[lt_i].tick_timer);
+ DPRINTF("local timer[%d] read ICNTO %x\n", lt_i, value);
+ break;
+
+ case L0_FRCNTO: case L1_FRCNTO:
+ lt_i = GET_L_TIMER_IDX(offset);
+
+ value = exynos4210_lfrc_get_count(&s->l_timer[lt_i]);
+ break;
+
+ case L0_TCON: case L1_TCON:
+ lt_i = ((offset & 0xF00) - L0_TCNTB) / 0x100;
+ value = s->l_timer[lt_i].reg.tcon;
+ break;
+
+ case L0_INT_CSTAT: case L1_INT_CSTAT:
+ lt_i = ((offset & 0xF00) - L0_TCNTB) / 0x100;
+ value = s->l_timer[lt_i].reg.int_cstat;
+ break;
+
+ case L0_INT_ENB: case L1_INT_ENB:
+ lt_i = ((offset & 0xF00) - L0_TCNTB) / 0x100;
+ value = s->l_timer[lt_i].reg.int_enb;
+ break;
+
+ case L0_WSTAT: case L1_WSTAT:
+ lt_i = ((offset & 0xF00) - L0_TCNTB) / 0x100;
+ value = s->l_timer[lt_i].reg.wstat;
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIX "\n",
+ __func__, offset);
+ break;
+ }
+ return value;
+}
+
+/* MCT write */
+static void exynos4210_mct_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ Exynos4210MCTState *s = (Exynos4210MCTState *)opaque;
+ int index; /* index in buffer which represents register set */
+ int shift;
+ int lt_i;
+ uint64_t new_frc;
+ uint32_t i;
+ uint32_t old_val;
+#ifdef DEBUG_MCT
+ static uint32_t icntb_max[2] = {0};
+ static uint32_t icntb_min[2] = {UINT32_MAX, UINT32_MAX};
+ static uint32_t tcntb_max[2] = {0};
+ static uint32_t tcntb_min[2] = {UINT32_MAX, UINT32_MAX};
+#endif
+
+ new_frc = s->g_timer.reg.cnt;
+
+ switch (offset) {
+
+ case MCT_CFG:
+ s->reg_mct_cfg = value;
+ exynos4210_mct_update_freq(s);
+ break;
+
+ case G_CNT_L:
+ case G_CNT_U:
+ if (offset == G_CNT_L) {
+
+ DPRINTF("global timer write to reg.cntl %llx\n", value);
+
+ new_frc = (s->g_timer.reg.cnt & (uint64_t)UINT32_MAX << 32) + value;
+ s->g_timer.reg.cnt_wstat |= G_CNT_WSTAT_L;
+ }
+ if (offset == G_CNT_U) {
+
+ DPRINTF("global timer write to reg.cntu %llx\n", value);
+
+ new_frc = (s->g_timer.reg.cnt & UINT32_MAX) +
+ ((uint64_t)value << 32);
+ s->g_timer.reg.cnt_wstat |= G_CNT_WSTAT_U;
+ }
+
+ s->g_timer.reg.cnt = new_frc;
+ exynos4210_gfrc_tx_begin(&s->g_timer);
+ exynos4210_gfrc_restart(s);
+ exynos4210_gfrc_tx_commit(&s->g_timer);
+ break;
+
+ case G_CNT_WSTAT:
+ s->g_timer.reg.cnt_wstat &= ~(value);
+ break;
+
+ case G_COMP_L(0): case G_COMP_L(1): case G_COMP_L(2): case G_COMP_L(3):
+ case G_COMP_U(0): case G_COMP_U(1): case G_COMP_U(2): case G_COMP_U(3):
+ index = GET_G_COMP_IDX(offset);
+ shift = 8 * (offset & 0x4);
+ s->g_timer.reg.comp[index] =
+ (s->g_timer.reg.comp[index] &
+ (((uint64_t)UINT32_MAX << 32) >> shift)) +
+ (value << shift);
+
+ DPRINTF("comparator %d write 0x%llx val << %d\n", index, value, shift);
+
+ if (offset & 0x4) {
+ s->g_timer.reg.wstat |= G_WSTAT_COMP_U(index);
+ } else {
+ s->g_timer.reg.wstat |= G_WSTAT_COMP_L(index);
+ }
+
+ exynos4210_gfrc_tx_begin(&s->g_timer);
+ exynos4210_gfrc_restart(s);
+ exynos4210_gfrc_tx_commit(&s->g_timer);
+ break;
+
+ case G_TCON:
+ old_val = s->g_timer.reg.tcon;
+ s->g_timer.reg.tcon = value;
+ s->g_timer.reg.wstat |= G_WSTAT_TCON_WRITE;
+
+ DPRINTF("global timer write to reg.g_tcon %llx\n", value);
+
+ exynos4210_gfrc_tx_begin(&s->g_timer);
+
+ /* Start FRC if transition from disabled to enabled */
+ if ((value & G_TCON_TIMER_ENABLE) > (old_val &
+ G_TCON_TIMER_ENABLE)) {
+ exynos4210_gfrc_restart(s);
+ }
+ if ((value & G_TCON_TIMER_ENABLE) < (old_val &
+ G_TCON_TIMER_ENABLE)) {
+ exynos4210_gfrc_stop(&s->g_timer);
+ }
+
+ /* Start CMP if transition from disabled to enabled */
+ for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+ if ((value & G_TCON_COMP_ENABLE(i)) != (old_val &
+ G_TCON_COMP_ENABLE(i))) {
+ exynos4210_gfrc_restart(s);
+ }
+ }
+
+ exynos4210_gfrc_tx_commit(&s->g_timer);
+ break;
+
+ case G_INT_CSTAT:
+ s->g_timer.reg.int_cstat &= ~(value);
+ for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+ if (value & G_INT_CSTAT_COMP(i)) {
+ exynos4210_gcomp_lower_irq(&s->g_timer, i);
+ }
+ }
+ break;
+
+ case G_INT_ENB:
+ /* Raise IRQ if transition from disabled to enabled and CSTAT pending */
+ for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+ if ((value & G_INT_ENABLE(i)) > (s->g_timer.reg.tcon &
+ G_INT_ENABLE(i))) {
+ if (s->g_timer.reg.int_cstat & G_INT_CSTAT_COMP(i)) {
+ exynos4210_gcomp_raise_irq(&s->g_timer, i);
+ }
+ }
+
+ if ((value & G_INT_ENABLE(i)) < (s->g_timer.reg.tcon &
+ G_INT_ENABLE(i))) {
+ exynos4210_gcomp_lower_irq(&s->g_timer, i);
+ }
+ }
+
+ DPRINTF("global timer INT enable %llx\n", value);
+ s->g_timer.reg.int_enb = value;
+ break;
+
+ case G_WSTAT:
+ s->g_timer.reg.wstat &= ~(value);
+ break;
+
+ case G_COMP0_ADD_INCR: case G_COMP1_ADD_INCR:
+ case G_COMP2_ADD_INCR: case G_COMP3_ADD_INCR:
+ index = GET_G_COMP_ADD_INCR_IDX(offset);
+ s->g_timer.reg.comp_add_incr[index] = value;
+ s->g_timer.reg.wstat |= G_WSTAT_COMP_ADDINCR(index);
+ break;
+
+ /* Local timers */
+ case L0_TCON: case L1_TCON:
+ lt_i = GET_L_TIMER_IDX(offset);
+ old_val = s->l_timer[lt_i].reg.tcon;
+
+ s->l_timer[lt_i].reg.wstat |= L_WSTAT_TCON_WRITE;
+ s->l_timer[lt_i].reg.tcon = value;
+
+ exynos4210_ltick_tx_begin(&s->l_timer[lt_i].tick_timer);
+ /* Stop local CNT */
+ if ((value & L_TCON_TICK_START) <
+ (old_val & L_TCON_TICK_START)) {
+ DPRINTF("local timer[%d] stop cnt\n", lt_i);
+ exynos4210_ltick_cnt_stop(&s->l_timer[lt_i].tick_timer);
+ }
+
+ /* Stop local INT */
+ if ((value & L_TCON_INT_START) <
+ (old_val & L_TCON_INT_START)) {
+ DPRINTF("local timer[%d] stop int\n", lt_i);
+ exynos4210_ltick_int_stop(&s->l_timer[lt_i].tick_timer);
+ }
+
+ /* Start local CNT */
+ if ((value & L_TCON_TICK_START) >
+ (old_val & L_TCON_TICK_START)) {
+ DPRINTF("local timer[%d] start cnt\n", lt_i);
+ exynos4210_ltick_cnt_start(&s->l_timer[lt_i].tick_timer);
+ }
+
+ /* Start local INT */
+ if ((value & L_TCON_INT_START) >
+ (old_val & L_TCON_INT_START)) {
+ DPRINTF("local timer[%d] start int\n", lt_i);
+ exynos4210_ltick_int_start(&s->l_timer[lt_i].tick_timer);
+ }
+ exynos4210_ltick_tx_commit(&s->l_timer[lt_i].tick_timer);
+
+ /* Start or Stop local FRC if TCON changed */
+ exynos4210_lfrc_tx_begin(&s->l_timer[lt_i]);
+ if ((value & L_TCON_FRC_START) >
+ (s->l_timer[lt_i].reg.tcon & L_TCON_FRC_START)) {
+ DPRINTF("local timer[%d] start frc\n", lt_i);
+ exynos4210_lfrc_start(&s->l_timer[lt_i]);
+ }
+ if ((value & L_TCON_FRC_START) <
+ (s->l_timer[lt_i].reg.tcon & L_TCON_FRC_START)) {
+ DPRINTF("local timer[%d] stop frc\n", lt_i);
+ exynos4210_lfrc_stop(&s->l_timer[lt_i]);
+ }
+ exynos4210_lfrc_tx_commit(&s->l_timer[lt_i]);
+ break;
+
+ case L0_TCNTB: case L1_TCNTB:
+ lt_i = GET_L_TIMER_IDX(offset);
+
+ /*
+ * TCNTB is updated to internal register only after CNT expired.
+ * Due to this we should reload timer to nearest moment when CNT is
+ * expired and then in event handler update tcntb to new TCNTB value.
+ */
+ exynos4210_ltick_tx_begin(&s->l_timer[lt_i].tick_timer);
+ exynos4210_ltick_set_cntb(&s->l_timer[lt_i].tick_timer, value,
+ s->l_timer[lt_i].tick_timer.icntb);
+ exynos4210_ltick_tx_commit(&s->l_timer[lt_i].tick_timer);
+
+ s->l_timer[lt_i].reg.wstat |= L_WSTAT_TCNTB_WRITE;
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_TCNTB] = value;
+
+#ifdef DEBUG_MCT
+ if (tcntb_min[lt_i] > value) {
+ tcntb_min[lt_i] = value;
+ }
+ if (tcntb_max[lt_i] < value) {
+ tcntb_max[lt_i] = value;
+ }
+ DPRINTF("local timer[%d] TCNTB write %llx; max=%x, min=%x\n",
+ lt_i, value, tcntb_max[lt_i], tcntb_min[lt_i]);
+#endif
+ break;
+
+ case L0_ICNTB: case L1_ICNTB:
+ lt_i = GET_L_TIMER_IDX(offset);
+
+ s->l_timer[lt_i].reg.wstat |= L_WSTAT_ICNTB_WRITE;
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB] = value &
+ ~L_ICNTB_MANUAL_UPDATE;
+
+ /*
+ * We need to avoid too small values for TCNTB*ICNTB. If not, IRQ event
+ * could raise too fast disallowing QEMU to execute target code.
+ */
+ if (s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB] *
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_TCNTB] < MCT_LT_CNT_LOW_LIMIT) {
+ if (!s->l_timer[lt_i].reg.cnt[L_REG_CNT_TCNTB]) {
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB] =
+ MCT_LT_CNT_LOW_LIMIT;
+ } else {
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB] =
+ MCT_LT_CNT_LOW_LIMIT /
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_TCNTB];
+ }
+ }
+
+ if (value & L_ICNTB_MANUAL_UPDATE) {
+ exynos4210_ltick_set_cntb(&s->l_timer[lt_i].tick_timer,
+ s->l_timer[lt_i].tick_timer.tcntb,
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB]);
+ }
+
+#ifdef DEBUG_MCT
+ if (icntb_min[lt_i] > value) {
+ icntb_min[lt_i] = value;
+ }
+ if (icntb_max[lt_i] < value) {
+ icntb_max[lt_i] = value;
+ }
+ DPRINTF("local timer[%d] ICNTB write %llx; max=%x, min=%x\n\n",
+ lt_i, value, icntb_max[lt_i], icntb_min[lt_i]);
+#endif
+ break;
+
+ case L0_FRCNTB: case L1_FRCNTB:
+ lt_i = GET_L_TIMER_IDX(offset);
+ DPRINTF("local timer[%d] FRCNTB write %llx\n", lt_i, value);
+
+ s->l_timer[lt_i].reg.wstat |= L_WSTAT_FRCCNTB_WRITE;
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_FRCCNTB] = value;
+
+ break;
+
+ case L0_TCNTO: case L1_TCNTO:
+ case L0_ICNTO: case L1_ICNTO:
+ case L0_FRCNTO: case L1_FRCNTO:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "exynos4210.mct: write to RO register " TARGET_FMT_plx,
+ offset);
+ break;
+
+ case L0_INT_CSTAT: case L1_INT_CSTAT:
+ lt_i = GET_L_TIMER_IDX(offset);
+
+ DPRINTF("local timer[%d] CSTAT write %llx\n", lt_i, value);
+
+ s->l_timer[lt_i].reg.int_cstat &= ~value;
+ if (!s->l_timer[lt_i].reg.int_cstat) {
+ qemu_irq_lower(s->l_timer[lt_i].irq);
+ }
+ break;
+
+ case L0_INT_ENB: case L1_INT_ENB:
+ lt_i = GET_L_TIMER_IDX(offset);
+ old_val = s->l_timer[lt_i].reg.int_enb;
+
+ /* Raise Local timer IRQ if cstat is pending */
+ if ((value & L_INT_INTENB_ICNTEIE) > (old_val & L_INT_INTENB_ICNTEIE)) {
+ if (s->l_timer[lt_i].reg.int_cstat & L_INT_CSTAT_INTCNT) {
+ qemu_irq_raise(s->l_timer[lt_i].irq);
+ }
+ }
+
+ s->l_timer[lt_i].reg.int_enb = value;
+
+ break;
+
+ case L0_WSTAT: case L1_WSTAT:
+ lt_i = GET_L_TIMER_IDX(offset);
+
+ s->l_timer[lt_i].reg.wstat &= ~value;
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIX "\n",
+ __func__, offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps exynos4210_mct_ops = {
+ .read = exynos4210_mct_read,
+ .write = exynos4210_mct_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/* MCT init */
+static void exynos4210_mct_init(Object *obj)
+{
+ int i;
+ Exynos4210MCTState *s = EXYNOS4210_MCT(obj);
+ SysBusDevice *dev = SYS_BUS_DEVICE(obj);
+
+ /* Global timer */
+ s->g_timer.ptimer_frc = ptimer_init(exynos4210_gfrc_event, s,
+ PTIMER_POLICY_DEFAULT);
+ memset(&s->g_timer.reg, 0, sizeof(struct gregs));
+
+ /* Local timers */
+ for (i = 0; i < 2; i++) {
+ s->l_timer[i].tick_timer.ptimer_tick =
+ ptimer_init(exynos4210_ltick_event, &s->l_timer[i],
+ PTIMER_POLICY_DEFAULT);
+ s->l_timer[i].ptimer_frc =
+ ptimer_init(exynos4210_lfrc_event, &s->l_timer[i],
+ PTIMER_POLICY_DEFAULT);
+ s->l_timer[i].id = i;
+ }
+
+ /* IRQs */
+ for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+ sysbus_init_irq(dev, &s->g_timer.irq[i]);
+ }
+ for (i = 0; i < 2; i++) {
+ sysbus_init_irq(dev, &s->l_timer[i].irq);
+ }
+
+ memory_region_init_io(&s->iomem, obj, &exynos4210_mct_ops, s,
+ "exynos4210-mct", MCT_SFR_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+}
+
+static void exynos4210_mct_finalize(Object *obj)
+{
+ int i;
+ Exynos4210MCTState *s = EXYNOS4210_MCT(obj);
+
+ ptimer_free(s->g_timer.ptimer_frc);
+
+ for (i = 0; i < 2; i++) {
+ ptimer_free(s->l_timer[i].tick_timer.ptimer_tick);
+ ptimer_free(s->l_timer[i].ptimer_frc);
+ }
+}
+
+static void exynos4210_mct_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = exynos4210_mct_reset;
+ dc->vmsd = &vmstate_exynos4210_mct_state;
+}
+
+static const TypeInfo exynos4210_mct_info = {
+ .name = TYPE_EXYNOS4210_MCT,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Exynos4210MCTState),
+ .instance_init = exynos4210_mct_init,
+ .instance_finalize = exynos4210_mct_finalize,
+ .class_init = exynos4210_mct_class_init,
+};
+
+static void exynos4210_mct_register_types(void)
+{
+ type_register_static(&exynos4210_mct_info);
+}
+
+type_init(exynos4210_mct_register_types)
diff --git a/hw/timer/exynos4210_pwm.c b/hw/timer/exynos4210_pwm.c
new file mode 100644
index 000000000..220088120
--- /dev/null
+++ b/hw/timer/exynos4210_pwm.c
@@ -0,0 +1,445 @@
+/*
+ * Samsung exynos4210 Pulse Width Modulation Timer
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ *
+ * Evgeny Voevodin <e.voevodin@samsung.com>
+ *
+ * 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/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/timer.h"
+#include "qemu/module.h"
+#include "hw/ptimer.h"
+
+#include "hw/arm/exynos4210.h"
+#include "hw/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 = (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 = (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 " TARGET_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 " TARGET_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_DEFAULT);
+ 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, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = 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)
diff --git a/hw/timer/grlib_gptimer.c b/hw/timer/grlib_gptimer.c
new file mode 100644
index 000000000..d51189010
--- /dev/null
+++ b/hw/timer/grlib_gptimer.c
@@ -0,0 +1,432 @@
+/*
+ * QEMU GRLIB GPTimer Emulator
+ *
+ * Copyright (c) 2010-2019 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/sparc/grlib.h"
+#include "hw/sysbus.h"
+#include "qemu/timer.h"
+#include "hw/irq.h"
+#include "hw/ptimer.h"
+#include "hw/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_NATIVE_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_DEFAULT);
+ 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 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),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void grlib_gptimer_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = grlib_gptimer_realize;
+ dc->reset = 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)
diff --git a/hw/timer/hpet.c b/hw/timer/hpet.c
new file mode 100644
index 000000000..9520471be
--- /dev/null
+++ b/hw/timer/hpet.c
@@ -0,0 +1,807 @@
+/*
+ * High Precision Event Timer emulation
+ *
+ * Copyright (c) 2007 Alexander Graf
+ * Copyright (c) 2008 IBM Corporation
+ *
+ * Authors: Beth Kon <bkon@us.ibm.com>
+ *
+ * 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/i386/pc.h"
+#include "hw/irq.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "qemu/timer.h"
+#include "hw/timer/hpet.h"
+#include "hw/sysbus.h"
+#include "hw/rtc/mc146818rtc.h"
+#include "hw/rtc/mc146818rtc_regs.h"
+#include "migration/vmstate.h"
+#include "hw/timer/i8254.h"
+#include "exec/address-spaces.h"
+#include "qom/object.h"
+
+//#define HPET_DEBUG
+#ifdef HPET_DEBUG
+#define DPRINTF printf
+#else
+#define DPRINTF(...)
+#endif
+
+#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 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.
+ */
+} HPETTimer;
+
+struct HPETState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+ uint64_t hpet_offset;
+ bool hpet_offset_saved;
+ qemu_irq irqs[HPET_NUM_IRQ_ROUTES];
+ uint32_t flags;
+ uint8_t rtc_irq_level;
+ qemu_irq pit_enabled;
+ uint8_t num_timers;
+ 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 ((int32_t)(b - a) < 0);
+}
+
+static uint32_t hpet_time_after64(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);
+}
+
+/*
+ * calculate diff between comparator value and current ticks
+ */
+static inline uint64_t hpet_calculate_diff(HPETTimer *t, uint64_t current)
+{
+
+ if (t->config & HPET_TN_32BIT) {
+ uint32_t diff, cmp;
+
+ cmp = (uint32_t)t->cmp;
+ diff = cmp - (uint32_t)current;
+ diff = (int32_t)diff > 0 ? diff : (uint32_t)1;
+ return (uint64_t)diff;
+ } else {
+ uint64_t diff, cmp;
+
+ cmp = t->cmp;
+ diff = cmp - current;
+ diff = (int64_t)diff > 0 ? diff : (uint64_t)1;
+ return diff;
+ }
+}
+
+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_enabled(timer) || !hpet_enabled(timer->state)) {
+ s->isr &= ~mask;
+ if (!timer_fsb_route(timer)) {
+ qemu_irq_lower(s->irqs[route]);
+ }
+ } else 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) {
+ s->isr |= mask;
+ qemu_irq_raise(s->irqs[route]);
+ } else {
+ s->isr &= ~mask;
+ qemu_irq_pulse(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);
+ }
+
+ return 0;
+}
+
+static int hpet_pre_load(void *opaque)
+{
+ HPETState *s = opaque;
+
+ /* version 1 only supports 3, later versions will load the actual value */
+ s->num_timers = HPET_MIN_TIMERS;
+ return 0;
+}
+
+static bool hpet_validate_num_timers(void *opaque, int version_id)
+{
+ HPETState *s = opaque;
+
+ if (s->num_timers < HPET_MIN_TIMERS) {
+ return false;
+ } else if (s->num_timers > HPET_MAX_TIMERS) {
+ return false;
+ }
+ return true;
+}
+
+static int hpet_post_load(void *opaque, int version_id)
+{
+ HPETState *s = opaque;
+
+ /* Recalculate the offset between the main counter and guest time */
+ if (!s->hpet_offset_saved) {
+ s->hpet_offset = ticks_to_ns(s->hpet_counter)
+ - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ }
+
+ /* Push number of timers into capability returned via HPET_ID */
+ s->capability &= ~HPET_ID_NUM_TIM_MASK;
+ s->capability |= (s->num_timers - 1) << HPET_ID_NUM_TIM_SHIFT;
+ hpet_cfg.hpet[s->hpet_id].event_timer_block_id = (uint32_t)s->capability;
+
+ /* Derive HPET_MSI_SUPPORT from the capability of the first timer. */
+ s->flags &= ~(1 << HPET_MSI_SUPPORT);
+ if (s->timer[0].config & HPET_TN_FSB_CAP) {
+ s->flags |= 1 << HPET_MSI_SUPPORT;
+ }
+ return 0;
+}
+
+static bool hpet_offset_needed(void *opaque)
+{
+ HPETState *s = opaque;
+
+ return hpet_enabled(s) && s->hpet_offset_saved;
+}
+
+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 = (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 = (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 = (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 = 1,
+ .pre_save = hpet_pre_save,
+ .pre_load = hpet_pre_load,
+ .post_load = hpet_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(config, HPETState),
+ VMSTATE_UINT64(isr, HPETState),
+ VMSTATE_UINT64(hpet_counter, HPETState),
+ VMSTATE_UINT8_V(num_timers, HPETState, 2),
+ VMSTATE_VALIDATE("num_timers in range", hpet_validate_num_timers),
+ VMSTATE_STRUCT_VARRAY_UINT8(timer, HPETState, num_timers, 0,
+ vmstate_hpet_timer, HPETTimer),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_hpet_rtc_irq_level,
+ &vmstate_hpet_offset,
+ NULL
+ }
+};
+
+/*
+ * timer expiration callback
+ */
+static void hpet_timer(void *opaque)
+{
+ HPETTimer *t = opaque;
+ uint64_t diff;
+
+ uint64_t period = t->period;
+ uint64_t cur_tick = hpet_get_ticks(t->state);
+
+ if (timer_is_periodic(t) && period != 0) {
+ if (t->config & HPET_TN_32BIT) {
+ while (hpet_time_after(cur_tick, t->cmp)) {
+ t->cmp = (uint32_t)(t->cmp + t->period);
+ }
+ } else {
+ while (hpet_time_after64(cur_tick, t->cmp)) {
+ t->cmp += period;
+ }
+ }
+ diff = hpet_calculate_diff(t, cur_tick);
+ timer_mod(t->qemu_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + (int64_t)ticks_to_ns(diff));
+ } else if (t->config & HPET_TN_32BIT && !timer_is_periodic(t)) {
+ if (t->wrap_flag) {
+ diff = hpet_calculate_diff(t, cur_tick);
+ timer_mod(t->qemu_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ (int64_t)ticks_to_ns(diff));
+ t->wrap_flag = 0;
+ }
+ }
+ update_irq(t, 1);
+}
+
+static void hpet_set_timer(HPETTimer *t)
+{
+ uint64_t diff;
+ uint32_t wrap_diff; /* how many ticks until we wrap? */
+ uint64_t cur_tick = hpet_get_ticks(t->state);
+
+ /* whenever new timer is being set up, make sure wrap_flag is 0 */
+ t->wrap_flag = 0;
+ diff = hpet_calculate_diff(t, cur_tick);
+
+ /* hpet spec says in one-shot 32-bit mode, generate an interrupt when
+ * counter wraps in addition to an interrupt with comparator match.
+ */
+ if (t->config & HPET_TN_32BIT && !timer_is_periodic(t)) {
+ wrap_diff = 0xffffffff - (uint32_t)cur_tick;
+ if (wrap_diff < (uint32_t)diff) {
+ diff = wrap_diff;
+ t->wrap_flag = 1;
+ }
+ }
+ timer_mod(t->qemu_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + (int64_t)ticks_to_ns(diff));
+}
+
+static void hpet_del_timer(HPETTimer *t)
+{
+ timer_del(t->qemu_timer);
+ update_irq(t, 0);
+}
+
+static uint64_t hpet_ram_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ HPETState *s = opaque;
+ uint64_t cur_tick, index;
+
+ DPRINTF("qemu: Enter hpet_ram_readl at %" PRIx64 "\n", addr);
+ index = addr;
+ /*address range of all TN regs*/
+ if (index >= 0x100 && index <= 0x3ff) {
+ uint8_t timer_id = (addr - 0x100) / 0x20;
+ HPETTimer *timer = &s->timer[timer_id];
+
+ if (timer_id > s->num_timers) {
+ DPRINTF("qemu: timer id out of range\n");
+ return 0;
+ }
+
+ switch ((addr - 0x100) % 0x20) {
+ case HPET_TN_CFG:
+ return timer->config;
+ case HPET_TN_CFG + 4: // Interrupt capabilities
+ return timer->config >> 32;
+ case HPET_TN_CMP: // comparator register
+ return timer->cmp;
+ case HPET_TN_CMP + 4:
+ return timer->cmp >> 32;
+ case HPET_TN_ROUTE:
+ return timer->fsb;
+ case HPET_TN_ROUTE + 4:
+ return timer->fsb >> 32;
+ default:
+ DPRINTF("qemu: invalid hpet_ram_readl\n");
+ break;
+ }
+ } else {
+ switch (index) {
+ case HPET_ID:
+ return s->capability;
+ case HPET_PERIOD:
+ return s->capability >> 32;
+ case HPET_CFG:
+ return s->config;
+ case HPET_CFG + 4:
+ DPRINTF("qemu: invalid HPET_CFG + 4 hpet_ram_readl\n");
+ return 0;
+ case HPET_COUNTER:
+ if (hpet_enabled(s)) {
+ cur_tick = hpet_get_ticks(s);
+ } else {
+ cur_tick = s->hpet_counter;
+ }
+ DPRINTF("qemu: reading counter = %" PRIx64 "\n", cur_tick);
+ return cur_tick;
+ case HPET_COUNTER + 4:
+ if (hpet_enabled(s)) {
+ cur_tick = hpet_get_ticks(s);
+ } else {
+ cur_tick = s->hpet_counter;
+ }
+ DPRINTF("qemu: reading counter + 4 = %" PRIx64 "\n", cur_tick);
+ return cur_tick >> 32;
+ case HPET_STATUS:
+ return s->isr;
+ default:
+ DPRINTF("qemu: invalid hpet_ram_readl\n");
+ break;
+ }
+ }
+ return 0;
+}
+
+static void hpet_ram_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ int i;
+ HPETState *s = opaque;
+ uint64_t old_val, new_val, val, index;
+
+ DPRINTF("qemu: Enter hpet_ram_writel at %" PRIx64 " = 0x%" PRIx64 "\n",
+ addr, value);
+ index = addr;
+ old_val = hpet_ram_read(opaque, addr, 4);
+ new_val = value;
+
+ /*address range of all TN regs*/
+ if (index >= 0x100 && index <= 0x3ff) {
+ uint8_t timer_id = (addr - 0x100) / 0x20;
+ HPETTimer *timer = &s->timer[timer_id];
+
+ DPRINTF("qemu: hpet_ram_writel timer_id = 0x%x\n", timer_id);
+ if (timer_id > s->num_timers) {
+ DPRINTF("qemu: timer id out of range\n");
+ return;
+ }
+ switch ((addr - 0x100) % 0x20) {
+ case HPET_TN_CFG:
+ DPRINTF("qemu: hpet_ram_writel HPET_TN_CFG\n");
+ if (activating_bit(old_val, new_val, HPET_TN_FSB_ENABLE)) {
+ update_irq(timer, 0);
+ }
+ val = hpet_fixup_reg(new_val, old_val, HPET_TN_CFG_WRITE_MASK);
+ timer->config = (timer->config & 0xffffffff00000000ULL) | val;
+ if (new_val & HPET_TN_32BIT) {
+ timer->cmp = (uint32_t)timer->cmp;
+ timer->period = (uint32_t)timer->period;
+ }
+ if (activating_bit(old_val, new_val, HPET_TN_ENABLE) &&
+ hpet_enabled(s)) {
+ hpet_set_timer(timer);
+ } else if (deactivating_bit(old_val, new_val, HPET_TN_ENABLE)) {
+ hpet_del_timer(timer);
+ }
+ break;
+ case HPET_TN_CFG + 4: // Interrupt capabilities
+ DPRINTF("qemu: invalid HPET_TN_CFG+4 write\n");
+ break;
+ case HPET_TN_CMP: // comparator register
+ DPRINTF("qemu: hpet_ram_writel HPET_TN_CMP\n");
+ if (timer->config & HPET_TN_32BIT) {
+ new_val = (uint32_t)new_val;
+ }
+ if (!timer_is_periodic(timer)
+ || (timer->config & HPET_TN_SETVAL)) {
+ timer->cmp = (timer->cmp & 0xffffffff00000000ULL) | new_val;
+ }
+ if (timer_is_periodic(timer)) {
+ /*
+ * FIXME: Clamp period to reasonable min value?
+ * Clamp period to reasonable max value
+ */
+ new_val &= (timer->config & HPET_TN_32BIT ? ~0u : ~0ull) >> 1;
+ timer->period =
+ (timer->period & 0xffffffff00000000ULL) | new_val;
+ }
+ timer->config &= ~HPET_TN_SETVAL;
+ if (hpet_enabled(s)) {
+ hpet_set_timer(timer);
+ }
+ break;
+ case HPET_TN_CMP + 4: // comparator register high order
+ DPRINTF("qemu: hpet_ram_writel HPET_TN_CMP + 4\n");
+ if (!timer_is_periodic(timer)
+ || (timer->config & HPET_TN_SETVAL)) {
+ timer->cmp = (timer->cmp & 0xffffffffULL) | new_val << 32;
+ } else {
+ /*
+ * FIXME: Clamp period to reasonable min value?
+ * Clamp period to reasonable max value
+ */
+ new_val &= (timer->config & HPET_TN_32BIT ? ~0u : ~0ull) >> 1;
+ timer->period =
+ (timer->period & 0xffffffffULL) | new_val << 32;
+ }
+ timer->config &= ~HPET_TN_SETVAL;
+ if (hpet_enabled(s)) {
+ hpet_set_timer(timer);
+ }
+ break;
+ case HPET_TN_ROUTE:
+ timer->fsb = (timer->fsb & 0xffffffff00000000ULL) | new_val;
+ break;
+ case HPET_TN_ROUTE + 4:
+ timer->fsb = (new_val << 32) | (timer->fsb & 0xffffffff);
+ break;
+ default:
+ DPRINTF("qemu: invalid hpet_ram_writel\n");
+ break;
+ }
+ return;
+ } else {
+ switch (index) {
+ case HPET_ID:
+ return;
+ case HPET_CFG:
+ val = hpet_fixup_reg(new_val, old_val, HPET_CFG_WRITE_MASK);
+ s->config = (s->config & 0xffffffff00000000ULL) | 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 ((&s->timer[i])->cmp != ~0ULL) {
+ 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]);
+ }
+ }
+ /* i8254 and RTC output pins are disabled
+ * when HPET is in legacy mode */
+ if (activating_bit(old_val, new_val, HPET_CFG_LEGACY)) {
+ 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)) {
+ 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_CFG + 4:
+ DPRINTF("qemu: invalid HPET_CFG+4 write\n");
+ break;
+ case HPET_STATUS:
+ val = new_val & s->isr;
+ for (i = 0; i < s->num_timers; i++) {
+ if (val & (1 << i)) {
+ update_irq(&s->timer[i], 0);
+ }
+ }
+ break;
+ case HPET_COUNTER:
+ if (hpet_enabled(s)) {
+ DPRINTF("qemu: Writing counter while HPET enabled!\n");
+ }
+ s->hpet_counter =
+ (s->hpet_counter & 0xffffffff00000000ULL) | value;
+ DPRINTF("qemu: HPET counter written. ctr = 0x%" PRIx64 " -> "
+ "%" PRIx64 "\n", value, s->hpet_counter);
+ break;
+ case HPET_COUNTER + 4:
+ if (hpet_enabled(s)) {
+ DPRINTF("qemu: Writing counter while HPET enabled!\n");
+ }
+ s->hpet_counter =
+ (s->hpet_counter & 0xffffffffULL) | (((uint64_t)value) << 32);
+ DPRINTF("qemu: HPET counter + 4 written. ctr = 0x%" PRIx64 " -> "
+ "%" PRIx64 "\n", value, s->hpet_counter);
+ break;
+ default:
+ DPRINTF("qemu: invalid hpet_ram_writel\n");
+ break;
+ }
+ }
+}
+
+static const MemoryRegionOps hpet_ram_ops = {
+ .read = hpet_ram_read,
+ .write = hpet_ram_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_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_cfg.hpet[s->hpet_id].event_timer_block_id = (uint32_t)s->capability;
+ hpet_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)) {
+ qemu_set_irq(s->irqs[0], level);
+ }
+ } else {
+ s->rtc_irq_level = level;
+ if (!hpet_in_legacy_mode(s)) {
+ 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);
+
+ /* HPET Area */
+ memory_region_init_io(&s->iomem, obj, &hpet_ram_ops, s, "hpet", HPET_LEN);
+ 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->intcap) {
+ warn_report("Hpet's intcap not initialized");
+ }
+ if (hpet_cfg.count == UINT8_MAX) {
+ /* first instance */
+ hpet_cfg.count = 0;
+ }
+
+ if (hpet_cfg.count == 8) {
+ error_setg(errp, "Only 8 instances of HPET is allowed");
+ return;
+ }
+
+ s->hpet_id = hpet_cfg.count++;
+
+ for (i = 0; i < HPET_NUM_IRQ_ROUTES; i++) {
+ sysbus_init_irq(sbd, &s->irqs[i]);
+ }
+
+ if (s->num_timers < HPET_MIN_TIMERS) {
+ s->num_timers = HPET_MIN_TIMERS;
+ } else if (s->num_timers > HPET_MAX_TIMERS) {
+ s->num_timers = HPET_MAX_TIMERS;
+ }
+ 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 main counter; 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 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),
+ DEFINE_PROP_BOOL("hpet-offset-saved", HPETState, hpet_offset_saved, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void hpet_device_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = hpet_realize;
+ dc->reset = 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)
diff --git a/hw/timer/i8254.c b/hw/timer/i8254.c
new file mode 100644
index 000000000..c8388ea43
--- /dev/null
+++ b/hw/timer/i8254.c
@@ -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/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"
+
+//#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;
+
+ 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;
+ }
+ }
+ 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 Property pit_properties[] = {
+ DEFINE_PROP_UINT32("iobase", PITCommonState, iobase, -1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pit_class_initfn(ObjectClass *klass, 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;
+ dc->reset = pit_reset;
+ device_class_set_props(dc, pit_properties);
+}
+
+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)
diff --git a/hw/timer/i8254_common.c b/hw/timer/i8254_common.c
new file mode 100644
index 000000000..050875b49
--- /dev/null
+++ b/hw/timer/i8254_common.c
@@ -0,0 +1,271 @@
+/*
+ * 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(ISADevice *dev, int channel, int val)
+{
+ PITCommonState *pit = PIT_COMMON(dev);
+ 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:
+ out = (d >= s->count);
+ break;
+ 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(ISADevice *dev, int channel, PITChannelInfo *info)
+{
+ PITCommonState *pit = PIT_COMMON(dev);
+ 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 = (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 = (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 void pit_common_class_init(ObjectClass *klass, 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;
+}
+
+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);
diff --git a/hw/timer/ibex_timer.c b/hw/timer/ibex_timer.c
new file mode 100644
index 000000000..66e1f8e48
--- /dev/null
+++ b/hw/timer/ibex_timer.c
@@ -0,0 +1,312 @@
+/*
+ * 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/irq.h"
+#include "hw/qdev-properties.h"
+#include "target/riscv/cpu.h"
+#include "migration/vmstate.h"
+
+REG32(CTRL, 0x00)
+ 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)
+{
+ CPUState *cs = qemu_get_cpu(0);
+ RISCVCPU *cpu = RISCV_CPU(cs);
+ 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 */
+ cpu->env.timecmp = value;
+
+ if (cpu->env.timecmp <= 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 = cpu->env.timecmp - 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(cpu->env.timer, 0x7FFFFFFFFFFFFFFF);
+ } else {
+ timer_mod(cpu->env.timer, 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);
+
+ CPUState *cpu = qemu_get_cpu(0);
+ CPURISCVState *env = cpu->env_ptr;
+ env->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+ &ibex_timer_cb, s);
+ env->timecmp = 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;
+ s->timer_intr_test = 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_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:
+ retvalue = s->timer_intr_test;
+ 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_CTRL:
+ s->timer_ctrl = val;
+ 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:
+ s->timer_intr_test = val;
+ if (s->timer_intr_enable &
+ s->timer_intr_test &
+ 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_NATIVE_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 = 1,
+ .minimum_version_id = 1,
+ .post_load = ibex_timer_post_load,
+ .fields = (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_UINT32(timer_intr_test, IbexTimerState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property ibex_timer_properties[] = {
+ DEFINE_PROP_UINT32("timebase-freq", IbexTimerState, timebase_freq, 10000),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+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, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = 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)
diff --git a/hw/timer/imx_epit.c b/hw/timer/imx_epit.c
new file mode 100644
index 000000000..ebd58254d
--- /dev/null
+++ b/hw/timer/imx_epit.c
@@ -0,0 +1,377 @@
+/*
+ * 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 <jcd@tribudubois.net>
+ *
+ * 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/irq.h"
+#include "hw/misc/imx_ccm.h"
+#include "qemu/module.h"
+#include "qemu/log.h"
+
+#ifndef DEBUG_IMX_EPIT
+#define DEBUG_IMX_EPIT 0
+#endif
+
+#define DPRINTF(fmt, args...) \
+ do { \
+ if (DEBUG_IMX_EPIT) { \
+ fprintf(stderr, "[%s]%s: " fmt , TYPE_IMX_EPIT, \
+ __func__, ##args); \
+ } \
+ } while (0)
+
+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 && (s->cr & CR_OCIEN) && (s->cr & CR_EN)) {
+ qemu_irq_raise(s->irq);
+ } else {
+ qemu_irq_lower(s->irq);
+ }
+}
+
+/*
+ * Must be called from within a ptimer_transaction_begin/commit block
+ * for both s->timer_cmp and s->timer_reload.
+ */
+static void imx_epit_set_freq(IMXEPITState *s)
+{
+ uint32_t clksrc;
+ uint32_t prescaler;
+
+ clksrc = extract32(s->cr, CR_CLKSRC_SHIFT, 2);
+ prescaler = 1 + extract32(s->cr, CR_PRESCALE_SHIFT, 12);
+
+ s->freq = imx_ccm_get_clock_frequency(s->ccm,
+ imx_epit_clocks[clksrc]) / prescaler;
+
+ DPRINTF("Setting ptimer frequency to %u\n", s->freq);
+
+ if (s->freq) {
+ ptimer_set_freq(s->timer_reload, s->freq);
+ ptimer_set_freq(s->timer_cmp, s->freq);
+ }
+}
+
+static void imx_epit_reset(DeviceState *dev)
+{
+ IMXEPITState *s = IMX_EPIT(dev);
+
+ /*
+ * Soft reset doesn't touch some bits; hard reset clears them
+ */
+ 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;
+ s->cnt = 0;
+ ptimer_transaction_begin(s->timer_cmp);
+ ptimer_transaction_begin(s->timer_reload);
+ /* stop both timers */
+ ptimer_stop(s->timer_cmp);
+ ptimer_stop(s->timer_reload);
+ /* compute new frequency */
+ imx_epit_set_freq(s);
+ /* 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);
+ if (s->freq && (s->cr & CR_EN)) {
+ /* if the timer is still enabled, restart it */
+ ptimer_run(s->timer_reload, 0);
+ }
+ ptimer_transaction_commit(s->timer_cmp);
+ ptimer_transaction_commit(s->timer_reload);
+}
+
+static uint32_t imx_epit_update_count(IMXEPITState *s)
+{
+ s->cnt = ptimer_get_count(s->timer_reload);
+
+ return s->cnt;
+}
+
+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 */
+ imx_epit_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_EPIT, __func__, offset);
+ break;
+ }
+
+ DPRINTF("(%s) = 0x%08x\n", imx_epit_reg_name(offset >> 2), reg_value);
+
+ return reg_value;
+}
+
+/* Must be called from ptimer_transaction_begin/commit block for s->timer_cmp */
+static void imx_epit_reload_compare_timer(IMXEPITState *s)
+{
+ if ((s->cr & (CR_EN | CR_OCIEN)) == (CR_EN | CR_OCIEN)) {
+ /* if the compare feature is on and timers are running */
+ uint32_t tmp = imx_epit_update_count(s);
+ uint64_t next;
+ if (tmp > s->cmp) {
+ /* It'll fire in this round of the timer */
+ next = tmp - s->cmp;
+ } else { /* catch it next time around */
+ next = tmp - s->cmp + ((s->cr & CR_RLD) ? EPIT_TIMER_MAX : s->lr);
+ }
+ ptimer_set_count(s->timer_cmp, next);
+ }
+}
+
+static void imx_epit_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ IMXEPITState *s = IMX_EPIT(opaque);
+ uint64_t oldcr;
+
+ DPRINTF("(%s, value = 0x%08x)\n", imx_epit_reg_name(offset >> 2),
+ (uint32_t)value);
+
+ switch (offset >> 2) {
+ case 0: /* CR */
+
+ oldcr = s->cr;
+ s->cr = value & 0x03ffffff;
+ if (s->cr & CR_SWR) {
+ /* handle the reset */
+ imx_epit_reset(DEVICE(s));
+ /*
+ * TODO: could we 'break' here? following operations appear
+ * to duplicate the work imx_epit_reset() already did.
+ */
+ }
+
+ ptimer_transaction_begin(s->timer_cmp);
+ ptimer_transaction_begin(s->timer_reload);
+
+ if (!(s->cr & CR_SWR)) {
+ imx_epit_set_freq(s);
+ }
+
+ if (s->freq && (s->cr & CR_EN) && !(oldcr & CR_EN)) {
+ if (s->cr & CR_ENMOD) {
+ if (s->cr & CR_RLD) {
+ ptimer_set_limit(s->timer_reload, s->lr, 1);
+ ptimer_set_limit(s->timer_cmp, s->lr, 1);
+ } else {
+ ptimer_set_limit(s->timer_reload, EPIT_TIMER_MAX, 1);
+ ptimer_set_limit(s->timer_cmp, EPIT_TIMER_MAX, 1);
+ }
+ }
+
+ imx_epit_reload_compare_timer(s);
+ ptimer_run(s->timer_reload, 0);
+ if (s->cr & CR_OCIEN) {
+ ptimer_run(s->timer_cmp, 0);
+ } else {
+ ptimer_stop(s->timer_cmp);
+ }
+ } else if (!(s->cr & CR_EN)) {
+ /* stop both timers */
+ ptimer_stop(s->timer_reload);
+ ptimer_stop(s->timer_cmp);
+ } else if (s->cr & CR_OCIEN) {
+ if (!(oldcr & CR_OCIEN)) {
+ imx_epit_reload_compare_timer(s);
+ ptimer_run(s->timer_cmp, 0);
+ }
+ } else {
+ ptimer_stop(s->timer_cmp);
+ }
+
+ ptimer_transaction_commit(s->timer_cmp);
+ ptimer_transaction_commit(s->timer_reload);
+ break;
+
+ case 1: /* SR - ACK*/
+ /* writing 1 to OCIF clear the OCIF bit */
+ if (value & 0x01) {
+ s->sr = 0;
+ imx_epit_update_int(s);
+ }
+ break;
+
+ case 2: /* LR - set ticks */
+ 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);
+ }
+
+ imx_epit_reload_compare_timer(s);
+ ptimer_transaction_commit(s->timer_cmp);
+ ptimer_transaction_commit(s->timer_reload);
+ break;
+
+ case 3: /* CMP */
+ s->cmp = value;
+
+ ptimer_transaction_begin(s->timer_cmp);
+ imx_epit_reload_compare_timer(s);
+ ptimer_transaction_commit(s->timer_cmp);
+
+ 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);
+
+ DPRINTF("sr was %d\n", s->sr);
+
+ s->sr = 1;
+ 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 = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(cr, IMXEPITState),
+ VMSTATE_UINT32(sr, IMXEPITState),
+ VMSTATE_UINT32(lr, IMXEPITState),
+ VMSTATE_UINT32(cmp, IMXEPITState),
+ VMSTATE_UINT32(cnt, IMXEPITState),
+ VMSTATE_UINT32(freq, 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);
+
+ DPRINTF("\n");
+
+ 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);
+
+ s->timer_reload = ptimer_init(imx_epit_reload, s, PTIMER_POLICY_DEFAULT);
+
+ s->timer_cmp = ptimer_init(imx_epit_cmp, s, PTIMER_POLICY_DEFAULT);
+}
+
+static void imx_epit_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = imx_epit_realize;
+ dc->reset = imx_epit_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)
diff --git a/hw/timer/imx_gpt.c b/hw/timer/imx_gpt.c
new file mode 100644
index 000000000..5c0d9a269
--- /dev/null
+++ b/hw/timer/imx_gpt.c
@@ -0,0 +1,583 @@
+/*
+ * 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 <jcd@tribudubois.net>
+ *
+ * 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/irq.h"
+#include "hw/timer/imx_gpt.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+#include "qemu/log.h"
+
+#ifndef DEBUG_IMX_GPT
+#define DEBUG_IMX_GPT 0
+#endif
+
+#define DPRINTF(fmt, args...) \
+ do { \
+ if (DEBUG_IMX_GPT) { \
+ fprintf(stderr, "[%s]%s: " fmt , TYPE_IMX_GPT, \
+ __func__, ##args); \
+ } \
+ } while (0)
+
+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 = (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 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 */
+};
+
+/* 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);
+
+ DPRINTF("Setting clksrc %d to frequency %d\n", 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;
+ }
+
+ DPRINTF("(%s) = 0x%08x\n", 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;
+
+ DPRINTF("(%s, value = 0x%08x)\n", 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);
+
+ DPRINTF("\n");
+
+ 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_DEFAULT);
+}
+
+static void imx_gpt_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = imx_gpt_realize;
+ dc->reset = 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 imx7_gpt_init(Object *obj)
+{
+ IMXGPTState *s = IMX_GPT(obj);
+
+ s->clocks = imx7_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 imx7_gpt_info = {
+ .name = TYPE_IMX7_GPT,
+ .parent = TYPE_IMX25_GPT,
+ .instance_init = imx7_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(&imx7_gpt_info);
+}
+
+type_init(imx_gpt_register_types)
diff --git a/hw/timer/meson.build b/hw/timer/meson.build
new file mode 100644
index 000000000..03092e2ce
--- /dev/null
+++ b/hw/timer/meson.build
@@ -0,0 +1,40 @@
+softmmu_ss.add(when: 'CONFIG_A9_GTIMER', if_true: files('a9gtimer.c'))
+softmmu_ss.add(when: 'CONFIG_ALLWINNER_A10_PIT', if_true: files('allwinner-a10-pit.c'))
+softmmu_ss.add(when: 'CONFIG_ALTERA_TIMER', if_true: files('altera_timer.c'))
+softmmu_ss.add(when: 'CONFIG_ARM_MPTIMER', if_true: files('arm_mptimer.c'))
+softmmu_ss.add(when: 'CONFIG_ARM_TIMER', if_true: files('arm_timer.c'))
+softmmu_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('armv7m_systick.c'))
+softmmu_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_timer.c'))
+softmmu_ss.add(when: 'CONFIG_CADENCE', if_true: files('cadence_ttc.c'))
+softmmu_ss.add(when: 'CONFIG_CMSDK_APB_DUALTIMER', if_true: files('cmsdk-apb-dualtimer.c'))
+softmmu_ss.add(when: 'CONFIG_CMSDK_APB_TIMER', if_true: files('cmsdk-apb-timer.c'))
+softmmu_ss.add(when: 'CONFIG_RENESAS_TMR', if_true: files('renesas_tmr.c'))
+softmmu_ss.add(when: 'CONFIG_RENESAS_CMT', if_true: files('renesas_cmt.c'))
+softmmu_ss.add(when: 'CONFIG_DIGIC', if_true: files('digic-timer.c'))
+softmmu_ss.add(when: 'CONFIG_ETRAXFS', if_true: files('etraxfs_timer.c'))
+softmmu_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_mct.c'))
+softmmu_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_pwm.c'))
+softmmu_ss.add(when: 'CONFIG_GRLIB', if_true: files('grlib_gptimer.c'))
+softmmu_ss.add(when: 'CONFIG_HPET', if_true: files('hpet.c'))
+softmmu_ss.add(when: 'CONFIG_I8254', if_true: files('i8254_common.c', 'i8254.c'))
+softmmu_ss.add(when: 'CONFIG_IMX', if_true: files('imx_epit.c'))
+softmmu_ss.add(when: 'CONFIG_IMX', if_true: files('imx_gpt.c'))
+softmmu_ss.add(when: 'CONFIG_MIPS_CPS', if_true: files('mips_gictimer.c'))
+softmmu_ss.add(when: 'CONFIG_MSF2', if_true: files('mss-timer.c'))
+softmmu_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_timer.c'))
+softmmu_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('nrf51_timer.c'))
+softmmu_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_gptimer.c'))
+softmmu_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_synctimer.c'))
+softmmu_ss.add(when: 'CONFIG_PXA2XX', if_true: files('pxa2xx_timer.c'))
+softmmu_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_systmr.c'))
+softmmu_ss.add(when: 'CONFIG_SH_TIMER', if_true: files('sh_timer.c'))
+softmmu_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_timer.c'))
+softmmu_ss.add(when: 'CONFIG_SSE_COUNTER', if_true: files('sse-counter.c'))
+softmmu_ss.add(when: 'CONFIG_SSE_TIMER', if_true: files('sse-timer.c'))
+softmmu_ss.add(when: 'CONFIG_STELLARIS_GPTM', if_true: files('stellaris-gptm.c'))
+softmmu_ss.add(when: 'CONFIG_STM32F2XX_TIMER', if_true: files('stm32f2xx_timer.c'))
+softmmu_ss.add(when: 'CONFIG_XILINX', if_true: files('xilinx_timer.c'))
+specific_ss.add(when: 'CONFIG_IBEX', if_true: files('ibex_timer.c'))
+softmmu_ss.add(when: 'CONFIG_SIFIVE_PWM', if_true: files('sifive_pwm.c'))
+
+specific_ss.add(when: 'CONFIG_AVR_TIMER16', if_true: files('avr_timer16.c'))
diff --git a/hw/timer/mips_gictimer.c b/hw/timer/mips_gictimer.c
new file mode 100644
index 000000000..2b0696d4a
--- /dev/null
+++ b/hw/timer/mips_gictimer.c
@@ -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;
+}
diff --git a/hw/timer/mss-timer.c b/hw/timer/mss-timer.c
new file mode 100644
index 000000000..fe0ca905f
--- /dev/null
+++ b/hw/timer/mss-timer.c
@@ -0,0 +1,311 @@
+/*
+ * Block model of System timer present in
+ * Microsemi's SmartFusion2 and SmartFusion SoCs.
+ *
+ * Copyright (c) 2017 Subbaraya Sundeep <sundeep.lkml@gmail.com>.
+ *
+ * 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/irq.h"
+#include "hw/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:
+ if (addr < R_TIM1_MAX) {
+ st->regs[addr] = value;
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ TYPE_MSS_TIMER": 64-bit mode not supported\n");
+ return;
+ }
+ break;
+ }
+ 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_DEFAULT);
+ 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 = (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 = (VMStateField[]) {
+ VMSTATE_UINT32(freq_hz, MSSTimerState),
+ VMSTATE_STRUCT_ARRAY(timers, MSSTimerState, NUM_TIMERS, 0,
+ vmstate_timers, struct Msf2Timer),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property mss_timer_properties[] = {
+ /* Libero GUI shows 100Mhz as default for clocks */
+ DEFINE_PROP_UINT32("clock-frequency", MSSTimerState, freq_hz,
+ 100 * 1000000),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void mss_timer_class_init(ObjectClass *klass, 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)
diff --git a/hw/timer/npcm7xx_timer.c b/hw/timer/npcm7xx_timer.c
new file mode 100644
index 000000000..32f5e021f
--- /dev/null
+++ b/hw/timer/npcm7xx_timer.c
@@ -0,0 +1,714 @@
+/*
+ * 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/irq.h"
+#include "hw/qdev-clock.h"
+#include "hw/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)
+{
+ 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)
+{
+ 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 = (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 = (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 = (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 = (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, 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);
diff --git a/hw/timer/nrf51_timer.c b/hw/timer/nrf51_timer.c
new file mode 100644
index 000000000..42be79c73
--- /dev/null
+++ b/hw/timer/nrf51_timer.c
@@ -0,0 +1,404 @@
+/*
+ * 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 <contrib@steffen-goertz.de>
+ * 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/irq.h"
+#include "hw/timer/nrf51_timer.h"
+#include "hw/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]);
+ s->update_counter_ns = now;
+ 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 = (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 Property nrf51_timer_properties[] = {
+ DEFINE_PROP_UINT8("id", NRF51TimerState, id, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void nrf51_timer_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = 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)
diff --git a/hw/timer/omap_gptimer.c b/hw/timer/omap_gptimer.c
new file mode 100644
index 000000000..c40719013
--- /dev/null
+++ b/hw/timer/omap_gptimer.c
@@ -0,0 +1,514 @@
+/*
+ * TI OMAP2 general purpose timers emulation.
+ *
+ * Copyright (C) 2007-2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * 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 or
+ * (at your option) any later version of the License.
+ *
+ * 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/irq.h"
+#include "qemu/timer.h"
+#include "hw/arm/omap.h"
+
+/* GP timers */
+struct omap_gp_timer_s {
+ MemoryRegion iomem;
+ qemu_irq irq;
+ qemu_irq wkup;
+ qemu_irq in;
+ qemu_irq out;
+ omap_clk clk;
+ QEMUTimer *timer;
+ QEMUTimer *match;
+ struct omap_target_agent_s *ta;
+
+ int in_val;
+ int out_val;
+ int64_t time;
+ int64_t rate;
+ int64_t ticks_per_sec;
+
+ int16_t config;
+ int status;
+ int it_ena;
+ int wu_ena;
+ int enable;
+ int inout;
+ int capt2;
+ int pt;
+ enum {
+ gpt_trigger_none, gpt_trigger_overflow, gpt_trigger_both
+ } trigger;
+ enum {
+ gpt_capture_none, gpt_capture_rising,
+ gpt_capture_falling, gpt_capture_both
+ } capture;
+ int scpwm;
+ int ce;
+ int pre;
+ int ptv;
+ int ar;
+ int st;
+ int posted;
+ uint32_t val;
+ uint32_t load_val;
+ uint32_t capture_val[2];
+ uint32_t match_val;
+ int capt_num;
+
+ uint16_t writeh; /* LSB */
+ uint16_t readh; /* MSB */
+};
+
+#define GPT_TCAR_IT (1 << 2)
+#define GPT_OVF_IT (1 << 1)
+#define GPT_MAT_IT (1 << 0)
+
+static inline void omap_gp_timer_intr(struct omap_gp_timer_s *timer, int it)
+{
+ if (timer->it_ena & it) {
+ if (!timer->status)
+ qemu_irq_raise(timer->irq);
+
+ timer->status |= it;
+ /* Or are the status bits set even when masked?
+ * i.e. is masking applied before or after the status register? */
+ }
+
+ if (timer->wu_ena & it)
+ qemu_irq_pulse(timer->wkup);
+}
+
+static inline void omap_gp_timer_out(struct omap_gp_timer_s *timer, int level)
+{
+ if (!timer->inout && timer->out_val != level) {
+ timer->out_val = level;
+ qemu_set_irq(timer->out, level);
+ }
+}
+
+static inline uint32_t omap_gp_timer_read(struct omap_gp_timer_s *timer)
+{
+ uint64_t distance;
+
+ if (timer->st && timer->rate) {
+ distance = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - timer->time;
+ distance = muldiv64(distance, timer->rate, timer->ticks_per_sec);
+
+ if (distance >= 0xffffffff - timer->val)
+ return 0xffffffff;
+ else
+ return timer->val + distance;
+ } else
+ return timer->val;
+}
+
+static inline void omap_gp_timer_sync(struct omap_gp_timer_s *timer)
+{
+ if (timer->st) {
+ timer->val = omap_gp_timer_read(timer);
+ timer->time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ }
+}
+
+static inline void omap_gp_timer_update(struct omap_gp_timer_s *timer)
+{
+ int64_t expires, matches;
+
+ if (timer->st && timer->rate) {
+ expires = muldiv64(0x100000000ll - timer->val,
+ timer->ticks_per_sec, timer->rate);
+ timer_mod(timer->timer, timer->time + expires);
+
+ if (timer->ce && timer->match_val >= timer->val) {
+ matches = muldiv64(timer->ticks_per_sec,
+ timer->match_val - timer->val, timer->rate);
+ timer_mod(timer->match, timer->time + matches);
+ } else
+ timer_del(timer->match);
+ } else {
+ timer_del(timer->timer);
+ timer_del(timer->match);
+ omap_gp_timer_out(timer, timer->scpwm);
+ }
+}
+
+static inline void omap_gp_timer_trigger(struct omap_gp_timer_s *timer)
+{
+ if (timer->pt)
+ /* TODO in overflow-and-match mode if the first event to
+ * occur is the match, don't toggle. */
+ omap_gp_timer_out(timer, !timer->out_val);
+ else
+ /* TODO inverted pulse on timer->out_val == 1? */
+ qemu_irq_pulse(timer->out);
+}
+
+static void omap_gp_timer_tick(void *opaque)
+{
+ struct omap_gp_timer_s *timer = (struct omap_gp_timer_s *) opaque;
+
+ if (!timer->ar) {
+ timer->st = 0;
+ timer->val = 0;
+ } else {
+ timer->val = timer->load_val;
+ timer->time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ }
+
+ if (timer->trigger == gpt_trigger_overflow ||
+ timer->trigger == gpt_trigger_both)
+ omap_gp_timer_trigger(timer);
+
+ omap_gp_timer_intr(timer, GPT_OVF_IT);
+ omap_gp_timer_update(timer);
+}
+
+static void omap_gp_timer_match(void *opaque)
+{
+ struct omap_gp_timer_s *timer = (struct omap_gp_timer_s *) opaque;
+
+ if (timer->trigger == gpt_trigger_both)
+ omap_gp_timer_trigger(timer);
+
+ omap_gp_timer_intr(timer, GPT_MAT_IT);
+}
+
+static void omap_gp_timer_input(void *opaque, int line, int on)
+{
+ struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) opaque;
+ int trigger;
+
+ switch (s->capture) {
+ default:
+ case gpt_capture_none:
+ trigger = 0;
+ break;
+ case gpt_capture_rising:
+ trigger = !s->in_val && on;
+ break;
+ case gpt_capture_falling:
+ trigger = s->in_val && !on;
+ break;
+ case gpt_capture_both:
+ trigger = (s->in_val == !on);
+ break;
+ }
+ s->in_val = on;
+
+ if (s->inout && trigger && s->capt_num < 2) {
+ s->capture_val[s->capt_num] = omap_gp_timer_read(s);
+
+ if (s->capt2 == s->capt_num ++)
+ omap_gp_timer_intr(s, GPT_TCAR_IT);
+ }
+}
+
+static void omap_gp_timer_clk_update(void *opaque, int line, int on)
+{
+ struct omap_gp_timer_s *timer = (struct omap_gp_timer_s *) opaque;
+
+ omap_gp_timer_sync(timer);
+ timer->rate = on ? omap_clk_getrate(timer->clk) : 0;
+ omap_gp_timer_update(timer);
+}
+
+static void omap_gp_timer_clk_setup(struct omap_gp_timer_s *timer)
+{
+ omap_clk_adduser(timer->clk,
+ qemu_allocate_irq(omap_gp_timer_clk_update, timer, 0));
+ timer->rate = omap_clk_getrate(timer->clk);
+}
+
+void omap_gp_timer_reset(struct omap_gp_timer_s *s)
+{
+ s->config = 0x000;
+ s->status = 0;
+ s->it_ena = 0;
+ s->wu_ena = 0;
+ s->inout = 0;
+ s->capt2 = 0;
+ s->capt_num = 0;
+ s->pt = 0;
+ s->trigger = gpt_trigger_none;
+ s->capture = gpt_capture_none;
+ s->scpwm = 0;
+ s->ce = 0;
+ s->pre = 0;
+ s->ptv = 0;
+ s->ar = 0;
+ s->st = 0;
+ s->posted = 1;
+ s->val = 0x00000000;
+ s->load_val = 0x00000000;
+ s->capture_val[0] = 0x00000000;
+ s->capture_val[1] = 0x00000000;
+ s->match_val = 0x00000000;
+ omap_gp_timer_update(s);
+}
+
+static uint32_t omap_gp_timer_readw(void *opaque, hwaddr addr)
+{
+ struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) opaque;
+
+ switch (addr) {
+ case 0x00: /* TIDR */
+ return 0x21;
+
+ case 0x10: /* TIOCP_CFG */
+ return s->config;
+
+ case 0x14: /* TISTAT */
+ /* ??? When's this bit reset? */
+ return 1; /* RESETDONE */
+
+ case 0x18: /* TISR */
+ return s->status;
+
+ case 0x1c: /* TIER */
+ return s->it_ena;
+
+ case 0x20: /* TWER */
+ return s->wu_ena;
+
+ case 0x24: /* TCLR */
+ return (s->inout << 14) |
+ (s->capt2 << 13) |
+ (s->pt << 12) |
+ (s->trigger << 10) |
+ (s->capture << 8) |
+ (s->scpwm << 7) |
+ (s->ce << 6) |
+ (s->pre << 5) |
+ (s->ptv << 2) |
+ (s->ar << 1) |
+ (s->st << 0);
+
+ case 0x28: /* TCRR */
+ return omap_gp_timer_read(s);
+
+ case 0x2c: /* TLDR */
+ return s->load_val;
+
+ case 0x30: /* TTGR */
+ return 0xffffffff;
+
+ case 0x34: /* TWPS */
+ return 0x00000000; /* No posted writes pending. */
+
+ case 0x38: /* TMAR */
+ return s->match_val;
+
+ case 0x3c: /* TCAR1 */
+ return s->capture_val[0];
+
+ case 0x40: /* TSICR */
+ return s->posted << 2;
+
+ case 0x44: /* TCAR2 */
+ return s->capture_val[1];
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static uint32_t omap_gp_timer_readh(void *opaque, hwaddr addr)
+{
+ struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) opaque;
+ uint32_t ret;
+
+ if (addr & 2)
+ return s->readh;
+ else {
+ ret = omap_gp_timer_readw(opaque, addr);
+ s->readh = ret >> 16;
+ return ret & 0xffff;
+ }
+}
+
+static void omap_gp_timer_write(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) opaque;
+
+ switch (addr) {
+ case 0x00: /* TIDR */
+ case 0x14: /* TISTAT */
+ case 0x34: /* TWPS */
+ case 0x3c: /* TCAR1 */
+ case 0x44: /* TCAR2 */
+ OMAP_RO_REG(addr);
+ break;
+
+ case 0x10: /* TIOCP_CFG */
+ s->config = value & 0x33d;
+ if (((value >> 3) & 3) == 3) /* IDLEMODE */
+ fprintf(stderr, "%s: illegal IDLEMODE value in TIOCP_CFG\n",
+ __func__);
+ if (value & 2) /* SOFTRESET */
+ omap_gp_timer_reset(s);
+ break;
+
+ case 0x18: /* TISR */
+ if (value & GPT_TCAR_IT)
+ s->capt_num = 0;
+ if (s->status && !(s->status &= ~value))
+ qemu_irq_lower(s->irq);
+ break;
+
+ case 0x1c: /* TIER */
+ s->it_ena = value & 7;
+ break;
+
+ case 0x20: /* TWER */
+ s->wu_ena = value & 7;
+ break;
+
+ case 0x24: /* TCLR */
+ omap_gp_timer_sync(s);
+ s->inout = (value >> 14) & 1;
+ s->capt2 = (value >> 13) & 1;
+ s->pt = (value >> 12) & 1;
+ s->trigger = (value >> 10) & 3;
+ if (s->capture == gpt_capture_none &&
+ ((value >> 8) & 3) != gpt_capture_none)
+ s->capt_num = 0;
+ s->capture = (value >> 8) & 3;
+ s->scpwm = (value >> 7) & 1;
+ s->ce = (value >> 6) & 1;
+ s->pre = (value >> 5) & 1;
+ s->ptv = (value >> 2) & 7;
+ s->ar = (value >> 1) & 1;
+ s->st = (value >> 0) & 1;
+ if (s->inout && s->trigger != gpt_trigger_none)
+ fprintf(stderr, "%s: GP timer pin must be an output "
+ "for this trigger mode\n", __func__);
+ if (!s->inout && s->capture != gpt_capture_none)
+ fprintf(stderr, "%s: GP timer pin must be an input "
+ "for this capture mode\n", __func__);
+ if (s->trigger == gpt_trigger_none)
+ omap_gp_timer_out(s, s->scpwm);
+ /* TODO: make sure this doesn't overflow 32-bits */
+ s->ticks_per_sec = NANOSECONDS_PER_SECOND << (s->pre ? s->ptv + 1 : 0);
+ omap_gp_timer_update(s);
+ break;
+
+ case 0x28: /* TCRR */
+ s->time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ s->val = value;
+ omap_gp_timer_update(s);
+ break;
+
+ case 0x2c: /* TLDR */
+ s->load_val = value;
+ break;
+
+ case 0x30: /* TTGR */
+ s->time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ s->val = s->load_val;
+ omap_gp_timer_update(s);
+ break;
+
+ case 0x38: /* TMAR */
+ omap_gp_timer_sync(s);
+ s->match_val = value;
+ omap_gp_timer_update(s);
+ break;
+
+ case 0x40: /* TSICR */
+ s->posted = (value >> 2) & 1;
+ if (value & 2) /* How much exactly are we supposed to reset? */
+ omap_gp_timer_reset(s);
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static void omap_gp_timer_writeh(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) opaque;
+
+ if (addr & 2)
+ omap_gp_timer_write(opaque, addr, (value << 16) | s->writeh);
+ else
+ s->writeh = (uint16_t) value;
+}
+
+static uint64_t omap_gp_timer_readfn(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ switch (size) {
+ case 1:
+ return omap_badwidth_read32(opaque, addr);
+ case 2:
+ return omap_gp_timer_readh(opaque, addr);
+ case 4:
+ return omap_gp_timer_readw(opaque, addr);
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static void omap_gp_timer_writefn(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ switch (size) {
+ case 1:
+ omap_badwidth_write32(opaque, addr, value);
+ break;
+ case 2:
+ omap_gp_timer_writeh(opaque, addr, value);
+ break;
+ case 4:
+ omap_gp_timer_write(opaque, addr, value);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static const MemoryRegionOps omap_gp_timer_ops = {
+ .read = omap_gp_timer_readfn,
+ .write = omap_gp_timer_writefn,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+struct omap_gp_timer_s *omap_gp_timer_init(struct omap_target_agent_s *ta,
+ qemu_irq irq, omap_clk fclk, omap_clk iclk)
+{
+ struct omap_gp_timer_s *s = g_new0(struct omap_gp_timer_s, 1);
+
+ s->ta = ta;
+ s->irq = irq;
+ s->clk = fclk;
+ s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, omap_gp_timer_tick, s);
+ s->match = timer_new_ns(QEMU_CLOCK_VIRTUAL, omap_gp_timer_match, s);
+ s->in = qemu_allocate_irq(omap_gp_timer_input, s, 0);
+ omap_gp_timer_reset(s);
+ omap_gp_timer_clk_setup(s);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_gp_timer_ops, s, "omap.gptimer",
+ omap_l4_region_size(ta, 0));
+ omap_l4_attach(ta, 0, &s->iomem);
+
+ return s;
+}
diff --git a/hw/timer/omap_synctimer.c b/hw/timer/omap_synctimer.c
new file mode 100644
index 000000000..72b997939
--- /dev/null
+++ b/hw/timer/omap_synctimer.c
@@ -0,0 +1,110 @@
+/*
+ * TI OMAP2 32kHz sync timer emulation.
+ *
+ * Copyright (C) 2007-2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * 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 or
+ * (at your option) any later version of the License.
+ *
+ * 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/timer.h"
+#include "hw/arm/omap.h"
+struct omap_synctimer_s {
+ MemoryRegion iomem;
+ uint32_t val;
+ uint16_t readh;
+};
+
+/* 32-kHz Sync Timer of the OMAP2 */
+static uint32_t omap_synctimer_read(struct omap_synctimer_s *s) {
+ return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), 0x8000,
+ NANOSECONDS_PER_SECOND);
+}
+
+void omap_synctimer_reset(struct omap_synctimer_s *s)
+{
+ s->val = omap_synctimer_read(s);
+}
+
+static uint32_t omap_synctimer_readw(void *opaque, hwaddr addr)
+{
+ struct omap_synctimer_s *s = (struct omap_synctimer_s *) opaque;
+
+ switch (addr) {
+ case 0x00: /* 32KSYNCNT_REV */
+ return 0x21;
+
+ case 0x10: /* CR */
+ return omap_synctimer_read(s) - s->val;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static uint32_t omap_synctimer_readh(void *opaque, hwaddr addr)
+{
+ struct omap_synctimer_s *s = (struct omap_synctimer_s *) opaque;
+ uint32_t ret;
+
+ if (addr & 2)
+ return s->readh;
+ else {
+ ret = omap_synctimer_readw(opaque, addr);
+ s->readh = ret >> 16;
+ return ret & 0xffff;
+ }
+}
+
+static uint64_t omap_synctimer_readfn(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ switch (size) {
+ case 1:
+ return omap_badwidth_read32(opaque, addr);
+ case 2:
+ return omap_synctimer_readh(opaque, addr);
+ case 4:
+ return omap_synctimer_readw(opaque, addr);
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static void omap_synctimer_writefn(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ OMAP_BAD_REG(addr);
+}
+
+static const MemoryRegionOps omap_synctimer_ops = {
+ .read = omap_synctimer_readfn,
+ .write = omap_synctimer_writefn,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+struct omap_synctimer_s *omap_synctimer_init(struct omap_target_agent_s *ta,
+ struct omap_mpu_state_s *mpu, omap_clk fclk, omap_clk iclk)
+{
+ struct omap_synctimer_s *s = g_malloc0(sizeof(*s));
+
+ omap_synctimer_reset(s);
+ memory_region_init_io(&s->iomem, NULL, &omap_synctimer_ops, s, "omap.synctimer",
+ omap_l4_region_size(ta, 0));
+ omap_l4_attach(ta, 0, &s->iomem);
+
+ return s;
+}
diff --git a/hw/timer/pxa2xx_timer.c b/hw/timer/pxa2xx_timer.c
new file mode 100644
index 000000000..2ae5ae321
--- /dev/null
+++ b/hw/timer/pxa2xx_timer.c
@@ -0,0 +1,621 @@
+/*
+ * 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/irq.h"
+#include "hw/qdev-properties.h"
+#include "qemu/timer.h"
+#include "sysemu/runstate.h"
+#include "hw/arm/pxa.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qom/object.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 */
+#define PXA27X_FREQ 3250000 /* 3.25 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;
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+ }
+}
+
+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 = (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 = (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 = (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 Property pxa25x_timer_dev_properties[] = {
+ DEFINE_PROP_UINT32("freq", PXA2xxTimerInfo, freq, PXA25X_FREQ),
+ DEFINE_PROP_BIT("tm4", PXA2xxTimerInfo, flags,
+ PXA2XX_TIMER_HAVE_TM4, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pxa25x_timer_dev_class_init(ObjectClass *klass, 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 Property pxa27x_timer_dev_properties[] = {
+ DEFINE_PROP_UINT32("freq", PXA2xxTimerInfo, freq, PXA27X_FREQ),
+ DEFINE_PROP_BIT("tm4", PXA2xxTimerInfo, flags,
+ PXA2XX_TIMER_HAVE_TM4, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pxa27x_timer_dev_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "PXA27x timer";
+ device_class_set_props(dc, pxa27x_timer_dev_properties);
+}
+
+static const TypeInfo pxa27x_timer_dev_info = {
+ .name = "pxa27x-timer",
+ .parent = TYPE_PXA2XX_TIMER,
+ .instance_size = sizeof(PXA2xxTimerInfo),
+ .class_init = pxa27x_timer_dev_class_init,
+};
+
+static void pxa2xx_timer_class_init(ObjectClass *oc, 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_register_static(&pxa27x_timer_dev_info);
+}
+
+type_init(pxa2xx_timer_register_types)
diff --git a/hw/timer/renesas_cmt.c b/hw/timer/renesas_cmt.c
new file mode 100644
index 000000000..2e0fd21a3
--- /dev/null
+++ b/hw/timer/renesas_cmt.c
@@ -0,0 +1,283 @@
+/*
+ * 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/irq.h"
+#include "hw/registerfields.h"
+#include "hw/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 = (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 Property rcmt_properties[] = {
+ DEFINE_PROP_UINT64("input-freq", RCMTState, input_freq, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void rcmt_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_rcmt;
+ dc->reset = 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)
diff --git a/hw/timer/renesas_tmr.c b/hw/timer/renesas_tmr.c
new file mode 100644
index 000000000..d96002e1e
--- /dev/null
+++ b/hw/timer/renesas_tmr.c
@@ -0,0 +1,493 @@
+/*
+ * 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/irq.h"
+#include "hw/registerfields.h"
+#include "hw/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 {
+ /* disble 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 = (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 Property rtmr_properties[] = {
+ DEFINE_PROP_UINT64("input-freq", RTMRState, input_freq, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void rtmr_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_rtmr;
+ dc->reset = 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)
diff --git a/hw/timer/sh_timer.c b/hw/timer/sh_timer.c
new file mode 100644
index 000000000..c72c327bf
--- /dev/null
+++ b/hw/timer/sh_timer.c
@@ -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 "exec/memory.h"
+#include "qemu/log.h"
+#include "hw/irq.h"
+#include "hw/sh4/sh.h"
+#include "hw/timer/tmu012.h"
+#include "hw/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_DEFAULT);
+
+ 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. */
+}
diff --git a/hw/timer/sifive_pwm.c b/hw/timer/sifive_pwm.c
new file mode 100644
index 000000000..c664480cc
--- /dev/null
+++ b/hw/timer/sifive_pwm.c
@@ -0,0 +1,468 @@
+/*
+ * SiFive PWM
+ *
+ * Copyright (c) 2020 Western Digital
+ *
+ * Author: Alistair Francis <alistair.francis@wdc.com>
+ *
+ * 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/irq.h"
+#include "hw/timer/sifive_pwm.h"
+#include "hw/qdev-properties.h"
+#include "hw/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_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_sifive_pwm = {
+ .name = TYPE_SIFIVE_PWM,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (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 Property sifive_pwm_properties[] = {
+ /* 0.5Ghz per spec after FSBL */
+ DEFINE_PROP_UINT64("clock-frequency", struct SiFivePwmState,
+ freq_hz, 500000000ULL),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+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, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = 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)
diff --git a/hw/timer/slavio_timer.c b/hw/timer/slavio_timer.c
new file mode 100644
index 000000000..03e33fc59
--- /dev/null
+++ b/hw/timer/slavio_timer.c
@@ -0,0 +1,450 @@
+/*
+ * 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/irq.h"
+#include "hw/ptimer.h"
+#include "hw/qdev-properties.h"
+#include "hw/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)
+
+struct SLAVIO_TIMERState {
+ SysBusDevice parent_obj;
+
+ uint32_t num_cpus;
+ uint32_t cputimer_mode;
+ CPUTimerState cputimer[MAX_CPUS + 1];
+};
+
+typedef struct TimerContext {
+ MemoryRegion iomem;
+ SLAVIO_TIMERState *s;
+ unsigned int timer_index; /* 0 for system, 1 ... MAX_CPUS for CPU timers */
+} TimerContext;
+
+#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_NATIVE_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 = (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 = (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 = g_malloc0(sizeof(TimerContext));
+ tc->s = s;
+ tc->timer_index = i;
+
+ s->cputimer[i].timer = ptimer_init(slavio_timer_irq, tc,
+ PTIMER_POLICY_DEFAULT);
+ 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 Property slavio_timer_properties[] = {
+ DEFINE_PROP_UINT32("num_cpus", SLAVIO_TIMERState, num_cpus, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void slavio_timer_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = 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,
+ .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)
diff --git a/hw/timer/sse-counter.c b/hw/timer/sse-counter.c
new file mode 100644
index 000000000..16c0e8ad1
--- /dev/null
+++ b/hw/timer/sse-counter.c
@@ -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/sysbus.h"
+#include "hw/registerfields.h"
+#include "hw/clock.h"
+#include "hw/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 = (VMStateField[]) {
+ VMSTATE_CLOCK(clk, SSECounter),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void sse_counter_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = sse_counter_realize;
+ dc->vmsd = &sse_counter_vmstate;
+ dc->reset = 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);
diff --git a/hw/timer/sse-timer.c b/hw/timer/sse-timer.c
new file mode 100644
index 000000000..f959cb9d6
--- /dev/null
+++ b/hw/timer/sse-timer.c
@@ -0,0 +1,471 @@
+/*
+ * 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/sysbus.h"
+#include "hw/irq.h"
+#include "hw/registerfields.h"
+#include "hw/clock.h"
+#include "hw/qdev-clock.h"
+#include "hw/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 writeable; 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 = (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 Property sse_timer_properties[] = {
+ DEFINE_PROP_LINK("counter", SSETimer, counter, TYPE_SSE_COUNTER, SSECounter *),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void sse_timer_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = sse_timer_realize;
+ dc->vmsd = &sse_timer_vmstate;
+ dc->reset = 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);
diff --git a/hw/timer/stellaris-gptm.c b/hw/timer/stellaris-gptm.c
new file mode 100644
index 000000000..fd71c79be
--- /dev/null
+++ b/hw/timer/stellaris-gptm.c
@@ -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/qdev-clock.h"
+#include "hw/timer/stellaris-gptm.h"
+
+static void gptm_update_irq(gptm_state *s)
+{
+ int level;
+ level = (s->state & s->mask) != 0;
+ qemu_set_irq(s->irq, level);
+}
+
+static void gptm_stop(gptm_state *s, int n)
+{
+ timer_del(s->timer[n]);
+}
+
+static void gptm_reload(gptm_state *s, int n, int reset)
+{
+ int64_t tick;
+ if (reset) {
+ tick = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ } else {
+ tick = s->tick[n];
+ }
+
+ if (s->config == 0) {
+ /* 32-bit CountDown. */
+ uint32_t count;
+ count = s->load[0] | (s->load[1] << 16);
+ tick += clock_ticks_to_ns(s->clk, count);
+ } else if (s->config == 1) {
+ /* 32-bit RTC. 1Hz tick. */
+ tick += NANOSECONDS_PER_SECOND;
+ } else if (s->mode[n] == 0xa) {
+ /* PWM mode. Not implemented. */
+ } else {
+ qemu_log_mask(LOG_UNIMP,
+ "GPTM: 16-bit timer mode unimplemented: 0x%x\n",
+ s->mode[n]);
+ return;
+ }
+ s->tick[n] = tick;
+ timer_mod(s->timer[n], tick);
+}
+
+static void gptm_tick(void *opaque)
+{
+ gptm_state **p = (gptm_state **)opaque;
+ gptm_state *s;
+ int n;
+
+ s = *p;
+ n = p - s->opaque;
+ if (s->config == 0) {
+ s->state |= 1;
+ if ((s->control & 0x20)) {
+ /* Output trigger. */
+ qemu_irq_pulse(s->trigger);
+ }
+ if (s->mode[0] & 1) {
+ /* One-shot. */
+ s->control &= ~1;
+ } else {
+ /* Periodic. */
+ gptm_reload(s, 0, 0);
+ }
+ } else if (s->config == 1) {
+ /* RTC. */
+ uint32_t match;
+ s->rtc++;
+ match = s->match[0] | (s->match[1] << 16);
+ if (s->rtc > match)
+ s->rtc = 0;
+ if (s->rtc == 0) {
+ s->state |= 8;
+ }
+ gptm_reload(s, 0, 0);
+ } else if (s->mode[n] == 0xa) {
+ /* PWM mode. Not implemented. */
+ } else {
+ qemu_log_mask(LOG_UNIMP,
+ "GPTM: 16-bit timer mode unimplemented: 0x%x\n",
+ s->mode[n]);
+ }
+ gptm_update_irq(s);
+}
+
+static uint64_t gptm_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ gptm_state *s = (gptm_state *)opaque;
+
+ switch (offset) {
+ case 0x00: /* CFG */
+ return s->config;
+ case 0x04: /* TAMR */
+ return s->mode[0];
+ case 0x08: /* TBMR */
+ return s->mode[1];
+ case 0x0c: /* CTL */
+ return s->control;
+ case 0x18: /* IMR */
+ return s->mask;
+ case 0x1c: /* RIS */
+ return s->state;
+ case 0x20: /* MIS */
+ return s->state & s->mask;
+ case 0x24: /* CR */
+ return 0;
+ case 0x28: /* TAILR */
+ return s->load[0] | ((s->config < 4) ? (s->load[1] << 16) : 0);
+ case 0x2c: /* TBILR */
+ return s->load[1];
+ case 0x30: /* TAMARCHR */
+ return s->match[0] | ((s->config < 4) ? (s->match[1] << 16) : 0);
+ case 0x34: /* TBMATCHR */
+ return s->match[1];
+ case 0x38: /* TAPR */
+ return s->prescale[0];
+ case 0x3c: /* TBPR */
+ return s->prescale[1];
+ case 0x40: /* TAPMR */
+ return s->match_prescale[0];
+ case 0x44: /* TBPMR */
+ return s->match_prescale[1];
+ case 0x48: /* TAR */
+ if (s->config == 1) {
+ return s->rtc;
+ }
+ qemu_log_mask(LOG_UNIMP,
+ "GPTM: read of TAR but timer read not supported\n");
+ return 0;
+ case 0x4c: /* TBR */
+ qemu_log_mask(LOG_UNIMP,
+ "GPTM: read of TBR but timer read not supported\n");
+ return 0;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "GPTM: read at bad offset 0x02%" HWADDR_PRIx "\n",
+ offset);
+ return 0;
+ }
+}
+
+static void gptm_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ gptm_state *s = (gptm_state *)opaque;
+ uint32_t oldval;
+
+ /*
+ * The timers should be disabled before changing the configuration.
+ * We take advantage of this and defer everything until the timer
+ * is enabled.
+ */
+ switch (offset) {
+ case 0x00: /* CFG */
+ s->config = value;
+ break;
+ case 0x04: /* TAMR */
+ s->mode[0] = value;
+ break;
+ case 0x08: /* TBMR */
+ s->mode[1] = value;
+ break;
+ case 0x0c: /* CTL */
+ oldval = s->control;
+ s->control = value;
+ /* TODO: Implement pause. */
+ if ((oldval ^ value) & 1) {
+ if (value & 1) {
+ gptm_reload(s, 0, 1);
+ } else {
+ gptm_stop(s, 0);
+ }
+ }
+ if (((oldval ^ value) & 0x100) && s->config >= 4) {
+ if (value & 0x100) {
+ gptm_reload(s, 1, 1);
+ } else {
+ gptm_stop(s, 1);
+ }
+ }
+ break;
+ case 0x18: /* IMR */
+ s->mask = value & 0x77;
+ gptm_update_irq(s);
+ break;
+ case 0x24: /* CR */
+ s->state &= ~value;
+ break;
+ case 0x28: /* TAILR */
+ s->load[0] = value & 0xffff;
+ if (s->config < 4) {
+ s->load[1] = value >> 16;
+ }
+ break;
+ case 0x2c: /* TBILR */
+ s->load[1] = value & 0xffff;
+ break;
+ case 0x30: /* TAMARCHR */
+ s->match[0] = value & 0xffff;
+ if (s->config < 4) {
+ s->match[1] = value >> 16;
+ }
+ break;
+ case 0x34: /* TBMATCHR */
+ s->match[1] = value >> 16;
+ break;
+ case 0x38: /* TAPR */
+ s->prescale[0] = value;
+ break;
+ case 0x3c: /* TBPR */
+ s->prescale[1] = value;
+ break;
+ case 0x40: /* TAPMR */
+ s->match_prescale[0] = value;
+ break;
+ case 0x44: /* TBPMR */
+ s->match_prescale[0] = value;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "GPTM: write at bad offset 0x02%" HWADDR_PRIx "\n",
+ offset);
+ }
+ gptm_update_irq(s);
+}
+
+static const MemoryRegionOps gptm_ops = {
+ .read = gptm_read,
+ .write = gptm_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_stellaris_gptm = {
+ .name = "stellaris_gptm",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(config, gptm_state),
+ VMSTATE_UINT32_ARRAY(mode, gptm_state, 2),
+ VMSTATE_UINT32(control, gptm_state),
+ VMSTATE_UINT32(state, gptm_state),
+ VMSTATE_UINT32(mask, gptm_state),
+ VMSTATE_UNUSED(8),
+ VMSTATE_UINT32_ARRAY(load, gptm_state, 2),
+ VMSTATE_UINT32_ARRAY(match, gptm_state, 2),
+ VMSTATE_UINT32_ARRAY(prescale, gptm_state, 2),
+ VMSTATE_UINT32_ARRAY(match_prescale, gptm_state, 2),
+ VMSTATE_UINT32(rtc, gptm_state),
+ VMSTATE_INT64_ARRAY(tick, gptm_state, 2),
+ VMSTATE_TIMER_PTR_ARRAY(timer, gptm_state, 2),
+ VMSTATE_CLOCK(clk, gptm_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void stellaris_gptm_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ gptm_state *s = STELLARIS_GPTM(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ sysbus_init_irq(sbd, &s->irq);
+ qdev_init_gpio_out(dev, &s->trigger, 1);
+
+ memory_region_init_io(&s->iomem, obj, &gptm_ops, s,
+ "gptm", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ s->opaque[0] = s->opaque[1] = s;
+
+ /*
+ * TODO: in an ideal world we would model the effects of changing
+ * the input clock frequency while the countdown timer is active.
+ * The best way to do this would be to convert the device to use
+ * ptimer instead of hand-rolling its own timer. This would also
+ * make it easy to implement reading the current count from the
+ * TAR and TBR registers.
+ */
+ s->clk = qdev_init_clock_in(dev, "clk", NULL, NULL, 0);
+}
+
+static void stellaris_gptm_realize(DeviceState *dev, Error **errp)
+{
+ gptm_state *s = STELLARIS_GPTM(dev);
+
+ if (!clock_has_source(s->clk)) {
+ error_setg(errp, "stellaris-gptm: clk must be connected");
+ return;
+ }
+
+ s->timer[0] = timer_new_ns(QEMU_CLOCK_VIRTUAL, gptm_tick, &s->opaque[0]);
+ s->timer[1] = timer_new_ns(QEMU_CLOCK_VIRTUAL, gptm_tick, &s->opaque[1]);
+}
+
+static void stellaris_gptm_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_stellaris_gptm;
+ dc->realize = stellaris_gptm_realize;
+}
+
+static const TypeInfo stellaris_gptm_info = {
+ .name = TYPE_STELLARIS_GPTM,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(gptm_state),
+ .instance_init = stellaris_gptm_init,
+ .class_init = stellaris_gptm_class_init,
+};
+
+static void stellaris_gptm_register_types(void)
+{
+ type_register_static(&stellaris_gptm_info);
+}
+
+type_init(stellaris_gptm_register_types)
diff --git a/hw/timer/stm32f2xx_timer.c b/hw/timer/stm32f2xx_timer.c
new file mode 100644
index 000000000..ba8694dcd
--- /dev/null
+++ b/hw/timer/stm32f2xx_timer.c
@@ -0,0 +1,347 @@
+/*
+ * STM32F2XX Timer
+ *
+ * Copyright (c) 2014 Alistair Francis <alistair@alistair23.me>
+ *
+ * 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/irq.h"
+#include "hw/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 = (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 Property stm32f2xx_timer_properties[] = {
+ DEFINE_PROP_UINT64("clock-frequency", struct STM32F2XXTimerState,
+ freq_hz, 1000000000),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+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, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = 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)
diff --git a/hw/timer/trace-events b/hw/timer/trace-events
new file mode 100644
index 000000000..3eccef838
--- /dev/null
+++ b/hw/timer/trace-events
@@ -0,0 +1,101 @@
+# 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, unsigned size, uint64_t value) "From 0x%" PRIx64 ": of size %u: 0x%" PRIx64
+
+# armv7m_systick.c
+systick_reload(void) "systick reload"
+systick_timer_tick(void) "systick reload"
+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"
+
+# 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
diff --git a/hw/timer/trace.h b/hw/timer/trace.h
new file mode 100644
index 000000000..5f72c441b
--- /dev/null
+++ b/hw/timer/trace.h
@@ -0,0 +1 @@
+#include "trace/trace-hw_timer.h"
diff --git a/hw/timer/xilinx_timer.c b/hw/timer/xilinx_timer.c
new file mode 100644
index 000000000..1eb927eb8
--- /dev/null
+++ b/hw/timer/xilinx_timer.c
@@ -0,0 +1,273 @@
+/*
+ * QEMU model of the Xilinx timer block.
+ *
+ * Copyright (c) 2009 Edgar E. Iglesias.
+ *
+ * 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/sysbus.h"
+#include "hw/irq.h"
+#include "hw/ptimer.h"
+#include "hw/qdev-properties.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"
+DECLARE_INSTANCE_CHECKER(struct timerblock, XILINX_TIMER,
+ TYPE_XILINX_TIMER)
+
+struct timerblock
+{
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ qemu_irq irq;
+ uint8_t one_timer_only;
+ uint32_t freq_hz;
+ struct xlx_timer *timers;
+};
+
+static inline unsigned int num_timers(struct timerblock *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(struct timerblock *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)
+{
+ struct timerblock *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)
+{
+ struct timerblock *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 = {
+ .read = timer_read,
+ .write = timer_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4
+ }
+};
+
+static void timer_hit(void *opaque)
+{
+ struct xlx_timer *xt = opaque;
+ struct timerblock *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)
+{
+ struct timerblock *t = XILINX_TIMER(dev);
+ unsigned int i;
+
+ /* 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_DEFAULT);
+ 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, "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)
+{
+ struct timerblock *t = XILINX_TIMER(obj);
+
+ /* All timers share a single irq line. */
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &t->irq);
+}
+
+static Property xilinx_timer_properties[] = {
+ DEFINE_PROP_UINT32("clock-frequency", struct timerblock, freq_hz,
+ 62 * 1000000),
+ DEFINE_PROP_UINT8("one-timer-only", struct timerblock, one_timer_only, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void xilinx_timer_class_init(ObjectClass *klass, 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(struct timerblock),
+ .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)