diff options
author | 2023-10-10 11:40:56 +0000 | |
---|---|---|
committer | 2023-10-10 11:40:56 +0000 | |
commit | e02cda008591317b1625707ff8e115a4841aa889 (patch) | |
tree | aee302e3cf8b59ec2d32ec481be3d1afddfc8968 /hw/timer | |
parent | cc668e6b7e0ffd8c9d130513d12053cf5eda1d3b (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')
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, >b->irq); + memory_region_init_io(>b->iomem, OBJECT(dev), &a9_gtimer_ops, gtb, + "a9gtimer per cpu", 0x20); + sysbus_init_mmio(sbd, >b->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) |