summaryrefslogtreecommitdiff
path: root/hw/timer
diff options
context:
space:
mode:
authorPaolo Bonzini <pbonzini@redhat.com>2013-02-05 14:38:25 +0100
committerPaolo Bonzini <pbonzini@redhat.com>2013-04-08 18:13:14 +0200
commit3bd884511f8dc44a01e32878b2972443a16db70d (patch)
tree97bf4af7947076a7e23f905a370a157e422fd967 /hw/timer
parent47934d0aadc075b05ce2d9e8a44fa6a46edd1afa (diff)
downloadqemu-3bd884511f8dc44a01e32878b2972443a16db70d.tar.gz
hw: move timer devices to hw/timer/, configure with default-configs/
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Diffstat (limited to 'hw/timer')
-rw-r--r--hw/timer/Makefile.objs16
-rw-r--r--hw/timer/arm_mptimer.c309
-rw-r--r--hw/timer/etraxfs_timer.c351
-rw-r--r--hw/timer/exynos4210_mct.c1482
-rw-r--r--hw/timer/exynos4210_pwm.c422
-rw-r--r--hw/timer/exynos4210_rtc.c592
-rw-r--r--hw/timer/grlib_gptimer.c404
-rw-r--r--hw/timer/imx_timer.c689
-rw-r--r--hw/timer/lm32_timer.c230
-rw-r--r--hw/timer/milkymist-sysctl.c338
-rw-r--r--hw/timer/omap_gptimer.c488
-rw-r--r--hw/timer/omap_synctimer.c102
-rw-r--r--hw/timer/pxa2xx_timer.c583
-rw-r--r--hw/timer/sh_timer.c333
-rw-r--r--hw/timer/slavio_timer.c435
-rw-r--r--hw/timer/tusb6010.c813
16 files changed, 7587 insertions, 0 deletions
diff --git a/hw/timer/Makefile.objs b/hw/timer/Makefile.objs
index a1ef26cf34..e4bd17fbb7 100644
--- a/hw/timer/Makefile.objs
+++ b/hw/timer/Makefile.objs
@@ -8,5 +8,21 @@ common-obj-$(CONFIG_PL031) += pl031.o
common-obj-$(CONFIG_PUV3) += puv3_ost.o
common-obj-$(CONFIG_TWL92230) += twl92230.o
common-obj-$(CONFIG_XILINX) += xilinx_timer.o
+common-obj-$(CONFIG_SLAVIO) += slavio_timer.o
+common-obj-$(CONFIG_ETRAXFS) += etraxfs_timer.o
+common-obj-$(CONFIG_GRLIB) += grlib_gptimer.o
+common-obj-$(CONFIG_IMX) += imx_timer.o
+common-obj-$(CONFIG_LM32) += lm32_timer.o
+common-obj-$(CONFIG_MILKYMIST) += milkymist-sysctl.o
+obj-$(CONFIG_EXYNOS4) += exynos4210_mct.o
+obj-$(CONFIG_EXYNOS4) += exynos4210_pwm.o
+obj-$(CONFIG_EXYNOS4) += exynos4210_rtc.o
+obj-$(CONFIG_OMAP) += omap_gptimer.o
+obj-$(CONFIG_OMAP) += omap_synctimer.o
+obj-$(CONFIG_PXA2XX) += pxa2xx_timer.o
+obj-$(CONFIG_SH4) += sh_timer.o
+obj-$(CONFIG_TUSB6010) += tusb6010.o
+
+obj-$(CONFIG_ARM_MPTIMER) += arm_mptimer.o
obj-$(CONFIG_MC146818RTC) += mc146818rtc.o
diff --git a/hw/timer/arm_mptimer.c b/hw/timer/arm_mptimer.c
new file mode 100644
index 0000000000..317f5e43ed
--- /dev/null
+++ b/hw/timer/arm_mptimer.c
@@ -0,0 +1,309 @@
+/*
+ * 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 "hw/sysbus.h"
+#include "qemu/timer.h"
+
+/* This device implements the per-cpu private timer and watchdog block
+ * which is used in both the ARM11MPCore and Cortex-A9MP.
+ */
+
+#define MAX_CPUS 4
+
+/* State of a single timer or watchdog block */
+typedef struct {
+ uint32_t count;
+ uint32_t load;
+ uint32_t control;
+ uint32_t status;
+ int64_t tick;
+ QEMUTimer *timer;
+ qemu_irq irq;
+ MemoryRegion iomem;
+} TimerBlock;
+
+typedef struct {
+ SysBusDevice busdev;
+ uint32_t num_cpu;
+ TimerBlock timerblock[MAX_CPUS];
+ MemoryRegion iomem;
+} ARMMPTimerState;
+
+static inline int get_current_cpu(ARMMPTimerState *s)
+{
+ CPUState *cpu_single_cpu = ENV_GET_CPU(cpu_single_env);
+
+ if (cpu_single_cpu->cpu_index >= s->num_cpu) {
+ hw_error("arm_mptimer: num-cpu %d but this cpu is %d!\n",
+ s->num_cpu, cpu_single_cpu->cpu_index);
+ }
+ return cpu_single_cpu->cpu_index;
+}
+
+static inline void timerblock_update_irq(TimerBlock *tb)
+{
+ qemu_set_irq(tb->irq, tb->status);
+}
+
+/* Return conversion factor from mpcore timer ticks to qemu timer ticks. */
+static inline uint32_t timerblock_scale(TimerBlock *tb)
+{
+ return (((tb->control >> 8) & 0xff) + 1) * 10;
+}
+
+static void timerblock_reload(TimerBlock *tb, int restart)
+{
+ if (tb->count == 0) {
+ return;
+ }
+ if (restart) {
+ tb->tick = qemu_get_clock_ns(vm_clock);
+ }
+ tb->tick += (int64_t)tb->count * timerblock_scale(tb);
+ qemu_mod_timer(tb->timer, tb->tick);
+}
+
+static void timerblock_tick(void *opaque)
+{
+ TimerBlock *tb = (TimerBlock *)opaque;
+ tb->status = 1;
+ if (tb->control & 2) {
+ tb->count = tb->load;
+ timerblock_reload(tb, 0);
+ } else {
+ tb->count = 0;
+ }
+ timerblock_update_irq(tb);
+}
+
+static uint64_t timerblock_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ TimerBlock *tb = (TimerBlock *)opaque;
+ int64_t val;
+ switch (addr) {
+ case 0: /* Load */
+ return tb->load;
+ case 4: /* Counter. */
+ if (((tb->control & 1) == 0) || (tb->count == 0)) {
+ return 0;
+ }
+ /* Slow and ugly, but hopefully won't happen too often. */
+ val = tb->tick - qemu_get_clock_ns(vm_clock);
+ val /= timerblock_scale(tb);
+ if (val < 0) {
+ val = 0;
+ }
+ return val;
+ 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;
+ int64_t old;
+ switch (addr) {
+ case 0: /* Load */
+ tb->load = value;
+ /* Fall through. */
+ case 4: /* Counter. */
+ if ((tb->control & 1) && tb->count) {
+ /* Cancel the previous timer. */
+ qemu_del_timer(tb->timer);
+ }
+ tb->count = value;
+ if (tb->control & 1) {
+ timerblock_reload(tb, 1);
+ }
+ break;
+ case 8: /* Control. */
+ old = tb->control;
+ tb->control = value;
+ if (((old & 1) == 0) && (value & 1)) {
+ if (tb->count == 0 && (tb->control & 2)) {
+ tb->count = tb->load;
+ }
+ timerblock_reload(tb, 1);
+ }
+ 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->count = 0;
+ tb->load = 0;
+ tb->control = 0;
+ tb->status = 0;
+ tb->tick = 0;
+ if (tb->timer) {
+ qemu_del_timer(tb->timer);
+ }
+}
+
+static void arm_mptimer_reset(DeviceState *dev)
+{
+ ARMMPTimerState *s =
+ FROM_SYSBUS(ARMMPTimerState, SYS_BUS_DEVICE(dev));
+ int i;
+ for (i = 0; i < ARRAY_SIZE(s->timerblock); i++) {
+ timerblock_reset(&s->timerblock[i]);
+ }
+}
+
+static int arm_mptimer_init(SysBusDevice *dev)
+{
+ ARMMPTimerState *s = FROM_SYSBUS(ARMMPTimerState, dev);
+ int i;
+ if (s->num_cpu < 1 || s->num_cpu > MAX_CPUS) {
+ hw_error("%s: num-cpu must be between 1 and %d\n", __func__, MAX_CPUS);
+ }
+ /* 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.
+ */
+ memory_region_init_io(&s->iomem, &arm_thistimer_ops, s,
+ "arm_mptimer_timer", 0x20);
+ sysbus_init_mmio(dev, &s->iomem);
+ for (i = 0; i < s->num_cpu; i++) {
+ TimerBlock *tb = &s->timerblock[i];
+ tb->timer = qemu_new_timer_ns(vm_clock, timerblock_tick, tb);
+ sysbus_init_irq(dev, &tb->irq);
+ memory_region_init_io(&tb->iomem, &timerblock_ops, tb,
+ "arm_mptimer_timerblock", 0x20);
+ sysbus_init_mmio(dev, &tb->iomem);
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_timerblock = {
+ .name = "arm_mptimer_timerblock",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(count, TimerBlock),
+ VMSTATE_UINT32(load, TimerBlock),
+ VMSTATE_UINT32(control, TimerBlock),
+ VMSTATE_UINT32(status, TimerBlock),
+ VMSTATE_INT64(tick, TimerBlock),
+ VMSTATE_TIMER(timer, TimerBlock),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_arm_mptimer = {
+ .name = "arm_mptimer",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_VARRAY_UINT32(timerblock, ARMMPTimerState, num_cpu,
+ 2, 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);
+ SysBusDeviceClass *sbc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sbc->init = arm_mptimer_init;
+ dc->vmsd = &vmstate_arm_mptimer;
+ dc->reset = arm_mptimer_reset;
+ dc->no_user = 1;
+ dc->props = arm_mptimer_properties;
+}
+
+static const TypeInfo arm_mptimer_info = {
+ .name = "arm_mptimer",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ARMMPTimerState),
+ .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/etraxfs_timer.c b/hw/timer/etraxfs_timer.c
new file mode 100644
index 0000000000..3cd9476bb1
--- /dev/null
+++ b/hw/timer/etraxfs_timer.c
@@ -0,0 +1,351 @@
+/*
+ * 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 "hw/sysbus.h"
+#include "sysemu/sysemu.h"
+#include "qemu/timer.h"
+#include "hw/ptimer.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
+
+struct etrax_timer {
+ SysBusDevice busdev;
+ MemoryRegion mmio;
+ qemu_irq irq;
+ qemu_irq nmi;
+
+ QEMUBH *bh_t0;
+ QEMUBH *bh_t1;
+ QEMUBH *bh_wd;
+ 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)
+{
+ struct etrax_timer *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_get_clock_ns(vm_clock) / 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(struct etrax_timer *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_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;
+ }
+}
+
+static void timer_update_irq(struct etrax_timer *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)
+{
+ struct etrax_timer *t = opaque;
+ t->r_intr |= 1;
+ timer_update_irq(t);
+}
+
+static void timer1_hit(void *opaque)
+{
+ struct etrax_timer *t = opaque;
+ t->r_intr |= 2;
+ timer_update_irq(t);
+}
+
+static void watchdog_hit(void *opaque)
+{
+ struct etrax_timer *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();
+
+ t->wd_hits++;
+}
+
+static inline void timer_watchdog_update(struct etrax_timer *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_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;
+}
+
+static void
+timer_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ struct etrax_timer *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(void *opaque)
+{
+ struct etrax_timer *t = opaque;
+
+ ptimer_stop(t->ptimer_t0);
+ ptimer_stop(t->ptimer_t1);
+ ptimer_stop(t->ptimer_wd);
+ t->rw_wd_ctrl = 0;
+ t->r_intr = 0;
+ t->rw_intr_mask = 0;
+ qemu_irq_lower(t->irq);
+}
+
+static int etraxfs_timer_init(SysBusDevice *dev)
+{
+ struct etrax_timer *t = FROM_SYSBUS(typeof (*t), dev);
+
+ t->bh_t0 = qemu_bh_new(timer0_hit, t);
+ t->bh_t1 = qemu_bh_new(timer1_hit, t);
+ t->bh_wd = qemu_bh_new(watchdog_hit, t);
+ t->ptimer_t0 = ptimer_init(t->bh_t0);
+ t->ptimer_t1 = ptimer_init(t->bh_t1);
+ t->ptimer_wd = ptimer_init(t->bh_wd);
+
+ sysbus_init_irq(dev, &t->irq);
+ sysbus_init_irq(dev, &t->nmi);
+
+ memory_region_init_io(&t->mmio, &timer_ops, t, "etraxfs-timer", 0x5c);
+ sysbus_init_mmio(dev, &t->mmio);
+ qemu_register_reset(etraxfs_timer_reset, t);
+ return 0;
+}
+
+static void etraxfs_timer_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sdc->init = etraxfs_timer_init;
+}
+
+static const TypeInfo etraxfs_timer_info = {
+ .name = "etraxfs,timer",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof (struct etrax_timer),
+ .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 0000000000..87ce75b643
--- /dev/null
+++ b/hw/timer/exynos4210_mct.c
@@ -0,0 +1,1482 @@
+/*
+ * 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 "hw/sysbus.h"
+#include "qemu/timer.h"
+#include "qemu-common.h"
+#include "hw/ptimer.h"
+
+#include "hw/arm/exynos4210.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_NIRQ 6
+#define MCT_SFR_SIZE 0x444
+
+#define MCT_GT_CMP_NUM 4
+
+#define MCT_GT_MAX_VAL UINT64_MAX
+
+#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;
+
+typedef struct Exynos4210MCTState {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+
+ /* Registers */
+ uint32_t reg_mct_cfg;
+
+ Exynos4210MCTLT l_timer[2];
+ Exynos4210MCTGT g_timer;
+
+ uint32_t freq; /* all timers tick frequency, TCLK */
+} Exynos4210MCTState;
+
+/*** VMState ***/
+static const VMStateDescription vmstate_tick_timer = {
+ .name = "exynos4210.mct.tick_timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 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,
+ .minimum_version_id_old = 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,
+ .minimum_version_id_old = 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,
+ .minimum_version_id_old = 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,
+ .minimum_version_id_old = 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,
+ .minimum_version_id_old = 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.
+ */
+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
+ */
+static void exynos4210_gfrc_stop(Exynos4210MCTGT *s)
+{
+ DPRINTF("global timer frc stop\n");
+
+ ptimer_stop(s->ptimer_frc);
+}
+
+/*
+ * Start global FRC timer
+ */
+static void exynos4210_gfrc_start(Exynos4210MCTGT *s)
+{
+ DPRINTF("global timer frc start\n");
+
+ ptimer_run(s->ptimer_frc, 1);
+}
+
+/*
+ * 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
+ */
+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[%d] 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.
+ */
+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
+ */
+static void exynos4210_lfrc_start(Exynos4210MCTLT *s)
+{
+ ptimer_run(s->ptimer_frc, 1);
+}
+
+/*
+ * Stop local FRC timer
+ */
+static void exynos4210_lfrc_stop(Exynos4210MCTLT *s)
+{
+ ptimer_stop(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.
+ */
+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.
+ */
+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;
+ }
+}
+
+/*
+ * 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
+ */
+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 = 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 = 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_cnt_stop(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_get_clock_ns(vm_clock);
+ 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);
+}
+
+/* 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=%dHz\n", s->freq);
+
+ /* global timer */
+ ptimer_set_freq(s->g_timer.ptimer_frc, s->freq);
+
+ /* local timer */
+ ptimer_set_freq(s->l_timer[0].tick_timer.ptimer_tick, s->freq);
+ ptimer_set_freq(s->l_timer[0].ptimer_frc, s->freq);
+ ptimer_set_freq(s->l_timer[1].tick_timer.ptimer_tick, s->freq);
+ 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 = (Exynos4210MCTState *)d;
+ uint32_t i;
+
+ s->reg_mct_cfg = 0;
+
+ /* global timer */
+ memset(&s->g_timer.reg, 0, sizeof(s->g_timer.reg));
+ exynos4210_gfrc_stop(&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;
+ ptimer_stop(s->l_timer[i].ptimer_frc);
+
+ 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;
+ 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;
+ 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:
+ hw_error("exynos4210.mct: bad read offset "
+ TARGET_FMT_plx "\n", 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_restart(s);
+ 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_restart(s);
+ 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);
+
+ /* Start FRC if transition from disabled to enabled */
+ if ((value & G_TCON_TIMER_ENABLE) > (old_val &
+ G_TCON_TIMER_ENABLE)) {
+ exynos4210_gfrc_start(&s->g_timer);
+ }
+ 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);
+ }
+ }
+ 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;
+
+ /* 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);
+ }
+
+ /* Start or Stop local FRC if TCON changed */
+ 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]);
+ }
+ break;
+
+ case L0_TCNTB: case L1_TCNTB:
+
+ lt_i = GET_L_TIMER_IDX(offset);
+ index = GET_L_TIMER_CNT_REG_IDX(offset, lt_i);
+
+ /*
+ * 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_set_cntb(&s->l_timer[lt_i].tick_timer, value,
+ s->l_timer[lt_i].tick_timer.icntb);
+
+ 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);
+ index = GET_L_TIMER_CNT_REG_IDX(offset, lt_i);
+
+ 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);
+ index = GET_L_TIMER_CNT_REG_IDX(offset, lt_i);
+
+ 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:
+ fprintf(stderr, "\n[exynos4210.mct: write to RO register "
+ TARGET_FMT_plx "]\n\n", 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:
+ hw_error("exynos4210.mct: bad write offset "
+ TARGET_FMT_plx "\n", offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps exynos4210_mct_ops = {
+ .read = exynos4210_mct_read,
+ .write = exynos4210_mct_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/* MCT init */
+static int exynos4210_mct_init(SysBusDevice *dev)
+{
+ int i;
+ Exynos4210MCTState *s = FROM_SYSBUS(Exynos4210MCTState, dev);
+ QEMUBH *bh[2];
+
+ /* Global timer */
+ bh[0] = qemu_bh_new(exynos4210_gfrc_event, s);
+ s->g_timer.ptimer_frc = ptimer_init(bh[0]);
+ memset(&s->g_timer.reg, 0, sizeof(struct gregs));
+
+ /* Local timers */
+ for (i = 0; i < 2; i++) {
+ bh[0] = qemu_bh_new(exynos4210_ltick_event, &s->l_timer[i]);
+ bh[1] = qemu_bh_new(exynos4210_lfrc_event, &s->l_timer[i]);
+ s->l_timer[i].tick_timer.ptimer_tick = ptimer_init(bh[0]);
+ s->l_timer[i].ptimer_frc = ptimer_init(bh[1]);
+ 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, &exynos4210_mct_ops, s, "exynos4210-mct",
+ MCT_SFR_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ return 0;
+}
+
+static void exynos4210_mct_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = exynos4210_mct_init;
+ dc->reset = exynos4210_mct_reset;
+ dc->vmsd = &vmstate_exynos4210_mct_state;
+}
+
+static const TypeInfo exynos4210_mct_info = {
+ .name = "exynos4210.mct",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Exynos4210MCTState),
+ .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 0000000000..185ccb9a74
--- /dev/null
+++ b/hw/timer/exynos4210_pwm.c
@@ -0,0 +1,422 @@
+/*
+ * 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 "hw/sysbus.h"
+#include "qemu/timer.h"
+#include "qemu-common.h"
+#include "hw/ptimer.h"
+
+#include "hw/arm/exynos4210.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;
+
+
+typedef struct Exynos4210PWMState {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+
+ uint32_t reg_tcfg[2];
+ uint32_t reg_tcon;
+ uint32_t reg_tint_cstat;
+
+ Exynos4210PWM timer[EXYNOS4210_PWM_TIMERS_NUM];
+
+} Exynos4210PWMState;
+
+/*** VMState ***/
+static const VMStateDescription vmstate_exynos4210_pwm = {
+ .name = "exynos4210.pwm.pwm",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 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,
+ .minimum_version_id_old = 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
+ */
+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=%dHz\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 %d 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 %d 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 %d 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:
+ fprintf(stderr,
+ "[exynos4210.pwm: bad read offset " TARGET_FMT_plx "]\n",
+ 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++) {
+ exynos4210_pwm_update_freq(s, s->timer[i].id);
+ }
+ break;
+
+ case TCON:
+ for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
+ 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);
+ }
+ }
+ 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:
+ fprintf(stderr,
+ "[exynos4210.pwm: bad write offset " TARGET_FMT_plx "]\n",
+ offset);
+ break;
+
+ }
+}
+
+/*
+ * Set default values to timer fields and registers
+ */
+static void exynos4210_pwm_reset(DeviceState *d)
+{
+ Exynos4210PWMState *s = (Exynos4210PWMState *)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;
+
+ exynos4210_pwm_update_freq(s, s->timer[i].id);
+ ptimer_stop(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 int exynos4210_pwm_init(SysBusDevice *dev)
+{
+ Exynos4210PWMState *s = FROM_SYSBUS(Exynos4210PWMState, dev);
+ int i;
+ QEMUBH *bh;
+
+ for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
+ bh = qemu_bh_new(exynos4210_pwm_tick, &s->timer[i]);
+ sysbus_init_irq(dev, &s->timer[i].irq);
+ s->timer[i].ptimer = ptimer_init(bh);
+ s->timer[i].id = i;
+ s->timer[i].parent = s;
+ }
+
+ memory_region_init_io(&s->iomem, &exynos4210_pwm_ops, s, "exynos4210-pwm",
+ EXYNOS4210_PWM_REG_MEM_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ return 0;
+}
+
+static void exynos4210_pwm_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = exynos4210_pwm_init;
+ dc->reset = exynos4210_pwm_reset;
+ dc->vmsd = &vmstate_exynos4210_pwm_state;
+}
+
+static const TypeInfo exynos4210_pwm_info = {
+ .name = "exynos4210.pwm",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Exynos4210PWMState),
+ .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/exynos4210_rtc.c b/hw/timer/exynos4210_rtc.c
new file mode 100644
index 0000000000..bceee44cb2
--- /dev/null
+++ b/hw/timer/exynos4210_rtc.c
@@ -0,0 +1,592 @@
+/*
+ * Samsung exynos4210 Real Time Clock
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * Ogurtsov Oleg <o.ogurtsov@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/>.
+ *
+ */
+
+/* Description:
+ * Register RTCCON:
+ * CLKSEL Bit[1] not used
+ * CLKOUTEN Bit[9] not used
+ */
+
+#include "hw/sysbus.h"
+#include "qemu/timer.h"
+#include "qemu-common.h"
+#include "hw/ptimer.h"
+
+#include "hw/hw.h"
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+
+#include "hw/arm/exynos4210.h"
+
+#define DEBUG_RTC 0
+
+#if DEBUG_RTC
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stdout, "RTC: [%24s:%5d] " fmt, __func__, __LINE__, \
+ ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+#define EXYNOS4210_RTC_REG_MEM_SIZE 0x0100
+
+#define INTP 0x0030
+#define RTCCON 0x0040
+#define TICCNT 0x0044
+#define RTCALM 0x0050
+#define ALMSEC 0x0054
+#define ALMMIN 0x0058
+#define ALMHOUR 0x005C
+#define ALMDAY 0x0060
+#define ALMMON 0x0064
+#define ALMYEAR 0x0068
+#define BCDSEC 0x0070
+#define BCDMIN 0x0074
+#define BCDHOUR 0x0078
+#define BCDDAY 0x007C
+#define BCDDAYWEEK 0x0080
+#define BCDMON 0x0084
+#define BCDYEAR 0x0088
+#define CURTICNT 0x0090
+
+#define TICK_TIMER_ENABLE 0x0100
+#define TICNT_THRESHHOLD 2
+
+
+#define RTC_ENABLE 0x0001
+
+#define INTP_TICK_ENABLE 0x0001
+#define INTP_ALM_ENABLE 0x0002
+
+#define ALARM_INT_ENABLE 0x0040
+
+#define RTC_BASE_FREQ 32768
+
+typedef struct Exynos4210RTCState {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+
+ /* registers */
+ uint32_t reg_intp;
+ uint32_t reg_rtccon;
+ uint32_t reg_ticcnt;
+ uint32_t reg_rtcalm;
+ uint32_t reg_almsec;
+ uint32_t reg_almmin;
+ uint32_t reg_almhour;
+ uint32_t reg_almday;
+ uint32_t reg_almmon;
+ uint32_t reg_almyear;
+ uint32_t reg_curticcnt;
+
+ ptimer_state *ptimer; /* tick timer */
+ ptimer_state *ptimer_1Hz; /* clock timer */
+ uint32_t freq;
+
+ qemu_irq tick_irq; /* Time Tick Generator irq */
+ qemu_irq alm_irq; /* alarm irq */
+
+ struct tm current_tm; /* current time */
+} Exynos4210RTCState;
+
+#define TICCKSEL(value) ((value & (0x0F << 4)) >> 4)
+
+/*** VMState ***/
+static const VMStateDescription vmstate_exynos4210_rtc_state = {
+ .name = "exynos4210.rtc",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(reg_intp, Exynos4210RTCState),
+ VMSTATE_UINT32(reg_rtccon, Exynos4210RTCState),
+ VMSTATE_UINT32(reg_ticcnt, Exynos4210RTCState),
+ VMSTATE_UINT32(reg_rtcalm, Exynos4210RTCState),
+ VMSTATE_UINT32(reg_almsec, Exynos4210RTCState),
+ VMSTATE_UINT32(reg_almmin, Exynos4210RTCState),
+ VMSTATE_UINT32(reg_almhour, Exynos4210RTCState),
+ VMSTATE_UINT32(reg_almday, Exynos4210RTCState),
+ VMSTATE_UINT32(reg_almmon, Exynos4210RTCState),
+ VMSTATE_UINT32(reg_almyear, Exynos4210RTCState),
+ VMSTATE_UINT32(reg_curticcnt, Exynos4210RTCState),
+ VMSTATE_PTIMER(ptimer, Exynos4210RTCState),
+ VMSTATE_PTIMER(ptimer_1Hz, Exynos4210RTCState),
+ VMSTATE_UINT32(freq, Exynos4210RTCState),
+ VMSTATE_INT32(current_tm.tm_sec, Exynos4210RTCState),
+ VMSTATE_INT32(current_tm.tm_min, Exynos4210RTCState),
+ VMSTATE_INT32(current_tm.tm_hour, Exynos4210RTCState),
+ VMSTATE_INT32(current_tm.tm_wday, Exynos4210RTCState),
+ VMSTATE_INT32(current_tm.tm_mday, Exynos4210RTCState),
+ VMSTATE_INT32(current_tm.tm_mon, Exynos4210RTCState),
+ VMSTATE_INT32(current_tm.tm_year, Exynos4210RTCState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+#define BCD3DIGITS(x) \
+ ((uint32_t)to_bcd((uint8_t)(x % 100)) + \
+ ((uint32_t)to_bcd((uint8_t)((x % 1000) / 100)) << 8))
+
+static void check_alarm_raise(Exynos4210RTCState *s)
+{
+ unsigned int alarm_raise = 0;
+ struct tm stm = s->current_tm;
+
+ if ((s->reg_rtcalm & 0x01) &&
+ (to_bcd((uint8_t)stm.tm_sec) == (uint8_t)s->reg_almsec)) {
+ alarm_raise = 1;
+ }
+ if ((s->reg_rtcalm & 0x02) &&
+ (to_bcd((uint8_t)stm.tm_min) == (uint8_t)s->reg_almmin)) {
+ alarm_raise = 1;
+ }
+ if ((s->reg_rtcalm & 0x04) &&
+ (to_bcd((uint8_t)stm.tm_hour) == (uint8_t)s->reg_almhour)) {
+ alarm_raise = 1;
+ }
+ if ((s->reg_rtcalm & 0x08) &&
+ (to_bcd((uint8_t)stm.tm_mday) == (uint8_t)s->reg_almday)) {
+ alarm_raise = 1;
+ }
+ if ((s->reg_rtcalm & 0x10) &&
+ (to_bcd((uint8_t)stm.tm_mon) == (uint8_t)s->reg_almmon)) {
+ alarm_raise = 1;
+ }
+ if ((s->reg_rtcalm & 0x20) &&
+ (BCD3DIGITS(stm.tm_year) == s->reg_almyear)) {
+ alarm_raise = 1;
+ }
+
+ if (alarm_raise) {
+ DPRINTF("ALARM IRQ\n");
+ /* set irq status */
+ s->reg_intp |= INTP_ALM_ENABLE;
+ qemu_irq_raise(s->alm_irq);
+ }
+}
+
+/*
+ * RTC update frequency
+ * Parameters:
+ * reg_value - current RTCCON register or his new value
+ */
+static void exynos4210_rtc_update_freq(Exynos4210RTCState *s,
+ uint32_t reg_value)
+{
+ uint32_t freq;
+
+ freq = s->freq;
+ /* set frequncy for time generator */
+ s->freq = RTC_BASE_FREQ / (1 << TICCKSEL(reg_value));
+
+ if (freq != s->freq) {
+ ptimer_set_freq(s->ptimer, s->freq);
+ DPRINTF("freq=%dHz\n", s->freq);
+ }
+}
+
+/* month is between 0 and 11. */
+static int get_days_in_month(int month, int year)
+{
+ static const int days_tab[12] = {
+ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+ };
+ int d;
+ if ((unsigned)month >= 12) {
+ return 31;
+ }
+ d = days_tab[month];
+ if (month == 1) {
+ if ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) {
+ d++;
+ }
+ }
+ return d;
+}
+
+/* update 'tm' to the next second */
+static void rtc_next_second(struct tm *tm)
+{
+ int days_in_month;
+
+ tm->tm_sec++;
+ if ((unsigned)tm->tm_sec >= 60) {
+ tm->tm_sec = 0;
+ tm->tm_min++;
+ if ((unsigned)tm->tm_min >= 60) {
+ tm->tm_min = 0;
+ tm->tm_hour++;
+ if ((unsigned)tm->tm_hour >= 24) {
+ tm->tm_hour = 0;
+ /* next day */
+ tm->tm_wday++;
+ if ((unsigned)tm->tm_wday >= 7) {
+ tm->tm_wday = 0;
+ }
+ days_in_month = get_days_in_month(tm->tm_mon,
+ tm->tm_year + 1900);
+ tm->tm_mday++;
+ if (tm->tm_mday < 1) {
+ tm->tm_mday = 1;
+ } else if (tm->tm_mday > days_in_month) {
+ tm->tm_mday = 1;
+ tm->tm_mon++;
+ if (tm->tm_mon >= 12) {
+ tm->tm_mon = 0;
+ tm->tm_year++;
+ }
+ }
+ }
+ }
+ }
+}
+
+/*
+ * tick handler
+ */
+static void exynos4210_rtc_tick(void *opaque)
+{
+ Exynos4210RTCState *s = (Exynos4210RTCState *)opaque;
+
+ DPRINTF("TICK IRQ\n");
+ /* set irq status */
+ s->reg_intp |= INTP_TICK_ENABLE;
+ /* raise IRQ */
+ qemu_irq_raise(s->tick_irq);
+
+ /* restart timer */
+ ptimer_set_count(s->ptimer, s->reg_ticcnt);
+ ptimer_run(s->ptimer, 1);
+}
+
+/*
+ * 1Hz clock handler
+ */
+static void exynos4210_rtc_1Hz_tick(void *opaque)
+{
+ Exynos4210RTCState *s = (Exynos4210RTCState *)opaque;
+
+ rtc_next_second(&s->current_tm);
+ /* DPRINTF("1Hz tick\n"); */
+
+ /* raise IRQ */
+ if (s->reg_rtcalm & ALARM_INT_ENABLE) {
+ check_alarm_raise(s);
+ }
+
+ ptimer_set_count(s->ptimer_1Hz, RTC_BASE_FREQ);
+ ptimer_run(s->ptimer_1Hz, 1);
+}
+
+/*
+ * RTC Read
+ */
+static uint64_t exynos4210_rtc_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ uint32_t value = 0;
+ Exynos4210RTCState *s = (Exynos4210RTCState *)opaque;
+
+ switch (offset) {
+ case INTP:
+ value = s->reg_intp;
+ break;
+ case RTCCON:
+ value = s->reg_rtccon;
+ break;
+ case TICCNT:
+ value = s->reg_ticcnt;
+ break;
+ case RTCALM:
+ value = s->reg_rtcalm;
+ break;
+ case ALMSEC:
+ value = s->reg_almsec;
+ break;
+ case ALMMIN:
+ value = s->reg_almmin;
+ break;
+ case ALMHOUR:
+ value = s->reg_almhour;
+ break;
+ case ALMDAY:
+ value = s->reg_almday;
+ break;
+ case ALMMON:
+ value = s->reg_almmon;
+ break;
+ case ALMYEAR:
+ value = s->reg_almyear;
+ break;
+
+ case BCDSEC:
+ value = (uint32_t)to_bcd((uint8_t)s->current_tm.tm_sec);
+ break;
+ case BCDMIN:
+ value = (uint32_t)to_bcd((uint8_t)s->current_tm.tm_min);
+ break;
+ case BCDHOUR:
+ value = (uint32_t)to_bcd((uint8_t)s->current_tm.tm_hour);
+ break;
+ case BCDDAYWEEK:
+ value = (uint32_t)to_bcd((uint8_t)s->current_tm.tm_wday);
+ break;
+ case BCDDAY:
+ value = (uint32_t)to_bcd((uint8_t)s->current_tm.tm_mday);
+ break;
+ case BCDMON:
+ value = (uint32_t)to_bcd((uint8_t)s->current_tm.tm_mon + 1);
+ break;
+ case BCDYEAR:
+ value = BCD3DIGITS(s->current_tm.tm_year);
+ break;
+
+ case CURTICNT:
+ s->reg_curticcnt = ptimer_get_count(s->ptimer);
+ value = s->reg_curticcnt;
+ break;
+
+ default:
+ fprintf(stderr,
+ "[exynos4210.rtc: bad read offset " TARGET_FMT_plx "]\n",
+ offset);
+ break;
+ }
+ return value;
+}
+
+/*
+ * RTC Write
+ */
+static void exynos4210_rtc_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ Exynos4210RTCState *s = (Exynos4210RTCState *)opaque;
+
+ switch (offset) {
+ case INTP:
+ if (value & INTP_ALM_ENABLE) {
+ qemu_irq_lower(s->alm_irq);
+ s->reg_intp &= (~INTP_ALM_ENABLE);
+ }
+ if (value & INTP_TICK_ENABLE) {
+ qemu_irq_lower(s->tick_irq);
+ s->reg_intp &= (~INTP_TICK_ENABLE);
+ }
+ break;
+ case RTCCON:
+ if (value & RTC_ENABLE) {
+ exynos4210_rtc_update_freq(s, value);
+ }
+ if ((value & RTC_ENABLE) > (s->reg_rtccon & RTC_ENABLE)) {
+ /* clock timer */
+ ptimer_set_count(s->ptimer_1Hz, RTC_BASE_FREQ);
+ ptimer_run(s->ptimer_1Hz, 1);
+ DPRINTF("run clock timer\n");
+ }
+ if ((value & RTC_ENABLE) < (s->reg_rtccon & RTC_ENABLE)) {
+ /* tick timer */
+ ptimer_stop(s->ptimer);
+ /* clock timer */
+ ptimer_stop(s->ptimer_1Hz);
+ DPRINTF("stop all timers\n");
+ }
+ if (value & RTC_ENABLE) {
+ if ((value & TICK_TIMER_ENABLE) >
+ (s->reg_rtccon & TICK_TIMER_ENABLE) &&
+ (s->reg_ticcnt)) {
+ ptimer_set_count(s->ptimer, s->reg_ticcnt);
+ ptimer_run(s->ptimer, 1);
+ DPRINTF("run tick timer\n");
+ }
+ if ((value & TICK_TIMER_ENABLE) <
+ (s->reg_rtccon & TICK_TIMER_ENABLE)) {
+ ptimer_stop(s->ptimer);
+ }
+ }
+ s->reg_rtccon = value;
+ break;
+ case TICCNT:
+ if (value > TICNT_THRESHHOLD) {
+ s->reg_ticcnt = value;
+ } else {
+ fprintf(stderr,
+ "[exynos4210.rtc: bad TICNT value %u ]\n",
+ (uint32_t)value);
+ }
+ break;
+
+ case RTCALM:
+ s->reg_rtcalm = value;
+ break;
+ case ALMSEC:
+ s->reg_almsec = (value & 0x7f);
+ break;
+ case ALMMIN:
+ s->reg_almmin = (value & 0x7f);
+ break;
+ case ALMHOUR:
+ s->reg_almhour = (value & 0x3f);
+ break;
+ case ALMDAY:
+ s->reg_almday = (value & 0x3f);
+ break;
+ case ALMMON:
+ s->reg_almmon = (value & 0x1f);
+ break;
+ case ALMYEAR:
+ s->reg_almyear = (value & 0x0fff);
+ break;
+
+ case BCDSEC:
+ if (s->reg_rtccon & RTC_ENABLE) {
+ s->current_tm.tm_sec = (int)from_bcd((uint8_t)value);
+ }
+ break;
+ case BCDMIN:
+ if (s->reg_rtccon & RTC_ENABLE) {
+ s->current_tm.tm_min = (int)from_bcd((uint8_t)value);
+ }
+ break;
+ case BCDHOUR:
+ if (s->reg_rtccon & RTC_ENABLE) {
+ s->current_tm.tm_hour = (int)from_bcd((uint8_t)value);
+ }
+ break;
+ case BCDDAYWEEK:
+ if (s->reg_rtccon & RTC_ENABLE) {
+ s->current_tm.tm_wday = (int)from_bcd((uint8_t)value);
+ }
+ break;
+ case BCDDAY:
+ if (s->reg_rtccon & RTC_ENABLE) {
+ s->current_tm.tm_mday = (int)from_bcd((uint8_t)value);
+ }
+ break;
+ case BCDMON:
+ if (s->reg_rtccon & RTC_ENABLE) {
+ s->current_tm.tm_mon = (int)from_bcd((uint8_t)value) - 1;
+ }
+ break;
+ case BCDYEAR:
+ if (s->reg_rtccon & RTC_ENABLE) {
+ /* 3 digits */
+ s->current_tm.tm_year = (int)from_bcd((uint8_t)value) +
+ (int)from_bcd((uint8_t)((value >> 8) & 0x0f)) * 100;
+ }
+ break;
+
+ default:
+ fprintf(stderr,
+ "[exynos4210.rtc: bad write offset " TARGET_FMT_plx "]\n",
+ offset);
+ break;
+
+ }
+}
+
+/*
+ * Set default values to timer fields and registers
+ */
+static void exynos4210_rtc_reset(DeviceState *d)
+{
+ Exynos4210RTCState *s = (Exynos4210RTCState *)d;
+
+ qemu_get_timedate(&s->current_tm, 0);
+
+ DPRINTF("Get time from host: %d-%d-%d %2d:%02d:%02d\n",
+ s->current_tm.tm_year, s->current_tm.tm_mon, s->current_tm.tm_mday,
+ s->current_tm.tm_hour, s->current_tm.tm_min, s->current_tm.tm_sec);
+
+ s->reg_intp = 0;
+ s->reg_rtccon = 0;
+ s->reg_ticcnt = 0;
+ s->reg_rtcalm = 0;
+ s->reg_almsec = 0;
+ s->reg_almmin = 0;
+ s->reg_almhour = 0;
+ s->reg_almday = 0;
+ s->reg_almmon = 0;
+ s->reg_almyear = 0;
+
+ s->reg_curticcnt = 0;
+
+ exynos4210_rtc_update_freq(s, s->reg_rtccon);
+ ptimer_stop(s->ptimer);
+ ptimer_stop(s->ptimer_1Hz);
+}
+
+static const MemoryRegionOps exynos4210_rtc_ops = {
+ .read = exynos4210_rtc_read,
+ .write = exynos4210_rtc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/*
+ * RTC timer initialization
+ */
+static int exynos4210_rtc_init(SysBusDevice *dev)
+{
+ Exynos4210RTCState *s = FROM_SYSBUS(Exynos4210RTCState, dev);
+ QEMUBH *bh;
+
+ bh = qemu_bh_new(exynos4210_rtc_tick, s);
+ s->ptimer = ptimer_init(bh);
+ ptimer_set_freq(s->ptimer, RTC_BASE_FREQ);
+ exynos4210_rtc_update_freq(s, 0);
+
+ bh = qemu_bh_new(exynos4210_rtc_1Hz_tick, s);
+ s->ptimer_1Hz = ptimer_init(bh);
+ ptimer_set_freq(s->ptimer_1Hz, RTC_BASE_FREQ);
+
+ sysbus_init_irq(dev, &s->alm_irq);
+ sysbus_init_irq(dev, &s->tick_irq);
+
+ memory_region_init_io(&s->iomem, &exynos4210_rtc_ops, s, "exynos4210-rtc",
+ EXYNOS4210_RTC_REG_MEM_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ return 0;
+}
+
+static void exynos4210_rtc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = exynos4210_rtc_init;
+ dc->reset = exynos4210_rtc_reset;
+ dc->vmsd = &vmstate_exynos4210_rtc_state;
+}
+
+static const TypeInfo exynos4210_rtc_info = {
+ .name = "exynos4210.rtc",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Exynos4210RTCState),
+ .class_init = exynos4210_rtc_class_init,
+};
+
+static void exynos4210_rtc_register_types(void)
+{
+ type_register_static(&exynos4210_rtc_info);
+}
+
+type_init(exynos4210_rtc_register_types)
diff --git a/hw/timer/grlib_gptimer.c b/hw/timer/grlib_gptimer.c
new file mode 100644
index 0000000000..7043a34684
--- /dev/null
+++ b/hw/timer/grlib_gptimer.c
@@ -0,0 +1,404 @@
+/*
+ * QEMU GRLIB GPTimer Emulator
+ *
+ * Copyright (c) 2010-2011 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 "hw/sysbus.h"
+#include "qemu/timer.h"
+#include "hw/ptimer.h"
+
+#include "trace.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
+
+typedef struct GPTimer GPTimer;
+typedef struct GPTimerUnit GPTimerUnit;
+
+struct GPTimer {
+ QEMUBH *bh;
+ struct ptimer_state *ptimer;
+
+ qemu_irq irq;
+ int id;
+ GPTimerUnit *unit;
+
+ /* registers */
+ uint32_t counter;
+ uint32_t reload;
+ uint32_t config;
+};
+
+struct GPTimerUnit {
+ SysBusDevice busdev;
+ 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_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 + 1);
+
+ ptimer_set_count(timer->ptimer, timer->counter + 1);
+ ptimer_run(timer->ptimer, 1);
+}
+
+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_set_freq(unit->timers[i].ptimer, value);
+ }
+}
+
+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);
+ unit->timers[id].counter = value;
+ grlib_gptimer_enable(&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. */
+
+ 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;
+ 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 = container_of(d, GPTimerUnit, busdev.qdev);
+ int i = 0;
+
+ assert(unit != NULL);
+
+ unit->scaler = 0;
+ unit->reload = 0;
+ unit->config = 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_stop(timer->ptimer);
+ ptimer_set_count(timer->ptimer, 0);
+ ptimer_set_freq(timer->ptimer, unit->freq_hz);
+ }
+}
+
+static int grlib_gptimer_init(SysBusDevice *dev)
+{
+ GPTimerUnit *unit = FROM_SYSBUS(typeof(*unit), dev);
+ unsigned int i;
+
+ 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->bh = qemu_bh_new(grlib_gptimer_hit, timer);
+ timer->ptimer = ptimer_init(timer->bh);
+ timer->id = i;
+
+ /* One IRQ line for each timer */
+ sysbus_init_irq(dev, &timer->irq);
+
+ ptimer_set_freq(timer->ptimer, unit->freq_hz);
+ }
+
+ memory_region_init_io(&unit->iomem, &grlib_gptimer_ops, unit, "gptimer",
+ UNIT_REG_SIZE + GPTIMER_REG_SIZE * unit->nr_timers);
+
+ sysbus_init_mmio(dev, &unit->iomem);
+ return 0;
+}
+
+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);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = grlib_gptimer_init;
+ dc->reset = grlib_gptimer_reset;
+ dc->props = grlib_gptimer_properties;
+}
+
+static const TypeInfo grlib_gptimer_info = {
+ .name = "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/imx_timer.c b/hw/timer/imx_timer.c
new file mode 100644
index 0000000000..03197e3f54
--- /dev/null
+++ b/hw/timer/imx_timer.c
@@ -0,0 +1,689 @@
+/*
+ * IMX31 Timer
+ *
+ * Copyright (c) 2008 OK Labs
+ * Copyright (c) 2011 NICTA Pty Ltd
+ * Originally written by Hans Jiang
+ * Updated by Peter Chubb
+ *
+ * This code is licensed under GPL version 2 or later. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "hw/hw.h"
+#include "qemu/timer.h"
+#include "hw/ptimer.h"
+#include "hw/sysbus.h"
+#include "hw/arm/imx.h"
+
+//#define DEBUG_TIMER 1
+#ifdef DEBUG_TIMER
+# define DPRINTF(fmt, args...) \
+ do { printf("imx_timer: " fmt , ##args); } while (0)
+#else
+# define DPRINTF(fmt, args...) do {} while (0)
+#endif
+
+/*
+ * Define to 1 for messages about attempts to
+ * access unimplemented registers or similar.
+ */
+#define DEBUG_IMPLEMENTATION 1
+#if DEBUG_IMPLEMENTATION
+# define IPRINTF(fmt, args...) \
+ do { fprintf(stderr, "imx_timer: " fmt, ##args); } while (0)
+#else
+# define IPRINTF(fmt, args...) do {} while (0)
+#endif
+
+/*
+ * GPT : General purpose timer
+ *
+ * This timer counts up continuously while it is enabled, resetting itself
+ * to 0 when it reaches TIMER_MAX (in freerun mode) or when it
+ * reaches the value of ocr1 (in periodic mode). WE simulate this using a
+ * QEMU ptimer counting down from ocr1 and reloading from ocr1 in
+ * periodic mode, or counting from ocr1 to zero, then TIMER_MAX - ocr1.
+ * waiting_rov is set when counting from TIMER_MAX.
+ *
+ * In the real hardware, there are three comparison registers that can
+ * trigger interrupts, and compare channel 1 can be used to
+ * force-reset the timer. However, this is a `bare-bones'
+ * implementation: only what Linux 3.x uses has been implemented
+ * (free-running timer from 0 to OCR1 or TIMER_MAX) .
+ */
+
+
+#define TIMER_MAX 0XFFFFFFFFUL
+
+/* Control register. Not all of these bits have any effect (yet) */
+#define GPT_CR_EN (1 << 0) /* GPT Enable */
+#define GPT_CR_ENMOD (1 << 1) /* GPT Enable Mode */
+#define GPT_CR_DBGEN (1 << 2) /* GPT Debug mode enable */
+#define GPT_CR_WAITEN (1 << 3) /* GPT Wait Mode Enable */
+#define GPT_CR_DOZEN (1 << 4) /* GPT Doze mode enable */
+#define GPT_CR_STOPEN (1 << 5) /* GPT Stop Mode Enable */
+#define GPT_CR_CLKSRC_SHIFT (6)
+#define GPT_CR_CLKSRC_MASK (0x7)
+
+#define GPT_CR_FRR (1 << 9) /* Freerun or Restart */
+#define GPT_CR_SWR (1 << 15) /* Software Reset */
+#define GPT_CR_IM1 (3 << 16) /* Input capture channel 1 mode (2 bits) */
+#define GPT_CR_IM2 (3 << 18) /* Input capture channel 2 mode (2 bits) */
+#define GPT_CR_OM1 (7 << 20) /* Output Compare Channel 1 Mode (3 bits) */
+#define GPT_CR_OM2 (7 << 23) /* Output Compare Channel 2 Mode (3 bits) */
+#define GPT_CR_OM3 (7 << 26) /* Output Compare Channel 3 Mode (3 bits) */
+#define GPT_CR_FO1 (1 << 29) /* Force Output Compare Channel 1 */
+#define GPT_CR_FO2 (1 << 30) /* Force Output Compare Channel 2 */
+#define GPT_CR_FO3 (1 << 31) /* Force Output Compare Channel 3 */
+
+#define GPT_SR_OF1 (1 << 0)
+#define GPT_SR_ROV (1 << 5)
+
+#define GPT_IR_OF1IE (1 << 0)
+#define GPT_IR_ROVIE (1 << 5)
+
+typedef struct {
+ SysBusDevice busdev;
+ ptimer_state *timer;
+ MemoryRegion iomem;
+ DeviceState *ccm;
+
+ uint32_t cr;
+ uint32_t pr;
+ uint32_t sr;
+ uint32_t ir;
+ uint32_t ocr1;
+ uint32_t cnt;
+
+ uint32_t waiting_rov;
+ qemu_irq irq;
+} IMXTimerGState;
+
+static const VMStateDescription vmstate_imx_timerg = {
+ .name = "imx-timerg",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(cr, IMXTimerGState),
+ VMSTATE_UINT32(pr, IMXTimerGState),
+ VMSTATE_UINT32(sr, IMXTimerGState),
+ VMSTATE_UINT32(ir, IMXTimerGState),
+ VMSTATE_UINT32(ocr1, IMXTimerGState),
+ VMSTATE_UINT32(cnt, IMXTimerGState),
+ VMSTATE_UINT32(waiting_rov, IMXTimerGState),
+ VMSTATE_PTIMER(timer, IMXTimerGState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const IMXClk imx_timerg_clocks[] = {
+ NOCLK, /* 000 No clock source */
+ IPG, /* 001 ipg_clk, 532MHz*/
+ IPG, /* 010 ipg_clk_highfreq */
+ NOCLK, /* 011 not defined */
+ CLK_32k, /* 100 ipg_clk_32k */
+ NOCLK, /* 101 not defined */
+ NOCLK, /* 110 not defined */
+ NOCLK, /* 111 not defined */
+};
+
+
+static void imx_timerg_set_freq(IMXTimerGState *s)
+{
+ int clksrc;
+ uint32_t freq;
+
+ clksrc = (s->cr >> GPT_CR_CLKSRC_SHIFT) & GPT_CR_CLKSRC_MASK;
+ freq = imx_clock_frequency(s->ccm, imx_timerg_clocks[clksrc]) / (1 + s->pr);
+
+ DPRINTF("Setting gtimer clksrc %d to frequency %d\n", clksrc, freq);
+ if (freq) {
+ ptimer_set_freq(s->timer, freq);
+ }
+}
+
+static void imx_timerg_update(IMXTimerGState *s)
+{
+ uint32_t flags = s->sr & s->ir & (GPT_SR_OF1 | GPT_SR_ROV);
+
+ DPRINTF("g-timer SR: %s %s IR=%s %s, %s\n",
+ s->sr & GPT_SR_OF1 ? "OF1" : "",
+ s->sr & GPT_SR_ROV ? "ROV" : "",
+ s->ir & GPT_SR_OF1 ? "OF1" : "",
+ s->ir & GPT_SR_ROV ? "ROV" : "",
+ s->cr & GPT_CR_EN ? "CR_EN" : "Not Enabled");
+
+
+ qemu_set_irq(s->irq, (s->cr & GPT_CR_EN) && flags);
+}
+
+static uint32_t imx_timerg_update_counts(IMXTimerGState *s)
+{
+ uint64_t target = s->waiting_rov ? TIMER_MAX : s->ocr1;
+ uint64_t cnt = ptimer_get_count(s->timer);
+ s->cnt = target - cnt;
+ return s->cnt;
+}
+
+static void imx_timerg_reload(IMXTimerGState *s, uint32_t timeout)
+{
+ uint64_t diff_cnt;
+
+ if (!(s->cr & GPT_CR_FRR)) {
+ IPRINTF("IMX_timerg_reload --- called in reset-mode\n");
+ return;
+ }
+
+ /*
+ * For small timeouts, qemu sometimes runs too slow.
+ * Better deliver a late interrupt than none.
+ *
+ * In Reset mode (FRR bit clear)
+ * the ptimer reloads itself from OCR1;
+ * in free-running mode we need to fake
+ * running from 0 to ocr1 to TIMER_MAX
+ */
+ if (timeout > s->cnt) {
+ diff_cnt = timeout - s->cnt;
+ } else {
+ diff_cnt = 0;
+ }
+ ptimer_set_count(s->timer, diff_cnt);
+}
+
+static uint64_t imx_timerg_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ IMXTimerGState *s = (IMXTimerGState *)opaque;
+
+ DPRINTF("g-read(offset=%x)", offset >> 2);
+ switch (offset >> 2) {
+ case 0: /* Control Register */
+ DPRINTF(" cr = %x\n", s->cr);
+ return s->cr;
+
+ case 1: /* prescaler */
+ DPRINTF(" pr = %x\n", s->pr);
+ return s->pr;
+
+ case 2: /* Status Register */
+ DPRINTF(" sr = %x\n", s->sr);
+ return s->sr;
+
+ case 3: /* Interrupt Register */
+ DPRINTF(" ir = %x\n", s->ir);
+ return s->ir;
+
+ case 4: /* Output Compare Register 1 */
+ DPRINTF(" ocr1 = %x\n", s->ocr1);
+ return s->ocr1;
+
+
+ case 9: /* cnt */
+ imx_timerg_update_counts(s);
+ DPRINTF(" cnt = %x\n", s->cnt);
+ return s->cnt;
+ }
+
+ IPRINTF("imx_timerg_read: Bad offset %x\n",
+ (int)offset >> 2);
+ return 0;
+}
+
+static void imx_timerg_reset(DeviceState *dev)
+{
+ IMXTimerGState *s = container_of(dev, IMXTimerGState, busdev.qdev);
+
+ /*
+ * Soft reset doesn't touch some bits; hard reset clears them
+ */
+ s->cr &= ~(GPT_CR_EN|GPT_CR_DOZEN|GPT_CR_WAITEN|GPT_CR_DBGEN);
+ s->sr = 0;
+ s->pr = 0;
+ s->ir = 0;
+ s->cnt = 0;
+ s->ocr1 = TIMER_MAX;
+ ptimer_stop(s->timer);
+ ptimer_set_limit(s->timer, TIMER_MAX, 1);
+ imx_timerg_set_freq(s);
+}
+
+static void imx_timerg_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ IMXTimerGState *s = (IMXTimerGState *)opaque;
+ DPRINTF("g-write(offset=%x, value = 0x%x)\n", (unsigned int)offset >> 2,
+ (unsigned int)value);
+
+ switch (offset >> 2) {
+ case 0: {
+ uint32_t oldcr = s->cr;
+ /* CR */
+ if (value & GPT_CR_SWR) { /* force reset */
+ value &= ~GPT_CR_SWR;
+ imx_timerg_reset(&s->busdev.qdev);
+ imx_timerg_update(s);
+ }
+
+ s->cr = value & ~0x7c00;
+ imx_timerg_set_freq(s);
+ if ((oldcr ^ value) & GPT_CR_EN) {
+ if (value & GPT_CR_EN) {
+ if (value & GPT_CR_ENMOD) {
+ ptimer_set_count(s->timer, s->ocr1);
+ s->cnt = 0;
+ }
+ ptimer_run(s->timer,
+ (value & GPT_CR_FRR) && (s->ocr1 != TIMER_MAX));
+ } else {
+ ptimer_stop(s->timer);
+ };
+ }
+ return;
+ }
+
+ case 1: /* Prescaler */
+ s->pr = value & 0xfff;
+ imx_timerg_set_freq(s);
+ return;
+
+ case 2: /* SR */
+ /*
+ * No point in implementing the status register bits to do with
+ * external interrupt sources.
+ */
+ value &= GPT_SR_OF1 | GPT_SR_ROV;
+ s->sr &= ~value;
+ imx_timerg_update(s);
+ return;
+
+ case 3: /* IR -- interrupt register */
+ s->ir = value & 0x3f;
+ imx_timerg_update(s);
+ return;
+
+ case 4: /* OCR1 -- output compare register */
+ /* In non-freerun mode, reset count when this register is written */
+ if (!(s->cr & GPT_CR_FRR)) {
+ s->waiting_rov = 0;
+ ptimer_set_limit(s->timer, value, 1);
+ } else {
+ imx_timerg_update_counts(s);
+ if (value > s->cnt) {
+ s->waiting_rov = 0;
+ imx_timerg_reload(s, value);
+ } else {
+ s->waiting_rov = 1;
+ imx_timerg_reload(s, TIMER_MAX - s->cnt);
+ }
+ }
+ s->ocr1 = value;
+ return;
+
+ default:
+ IPRINTF("imx_timerg_write: Bad offset %x\n",
+ (int)offset >> 2);
+ }
+}
+
+static void imx_timerg_timeout(void *opaque)
+{
+ IMXTimerGState *s = (IMXTimerGState *)opaque;
+
+ DPRINTF("imx_timerg_timeout, waiting rov=%d\n", s->waiting_rov);
+ if (s->cr & GPT_CR_FRR) {
+ /*
+ * Free running timer from 0 -> TIMERMAX
+ * Generates interrupt at TIMER_MAX and at cnt==ocr1
+ * If ocr1 == TIMER_MAX, then no need to reload timer.
+ */
+ if (s->ocr1 == TIMER_MAX) {
+ DPRINTF("s->ocr1 == TIMER_MAX, FRR\n");
+ s->sr |= GPT_SR_OF1 | GPT_SR_ROV;
+ imx_timerg_update(s);
+ return;
+ }
+
+ if (s->waiting_rov) {
+ /*
+ * We were waiting for cnt==TIMER_MAX
+ */
+ s->sr |= GPT_SR_ROV;
+ s->waiting_rov = 0;
+ s->cnt = 0;
+ imx_timerg_reload(s, s->ocr1);
+ } else {
+ /* Must have got a cnt==ocr1 timeout. */
+ s->sr |= GPT_SR_OF1;
+ s->cnt = s->ocr1;
+ s->waiting_rov = 1;
+ imx_timerg_reload(s, TIMER_MAX);
+ }
+ imx_timerg_update(s);
+ return;
+ }
+
+ s->sr |= GPT_SR_OF1;
+ imx_timerg_update(s);
+}
+
+static const MemoryRegionOps imx_timerg_ops = {
+ .read = imx_timerg_read,
+ .write = imx_timerg_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+
+static int imx_timerg_init(SysBusDevice *dev)
+{
+ IMXTimerGState *s = FROM_SYSBUS(IMXTimerGState, dev);
+ QEMUBH *bh;
+
+ sysbus_init_irq(dev, &s->irq);
+ memory_region_init_io(&s->iomem, &imx_timerg_ops,
+ s, "imxg-timer",
+ 0x00001000);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ bh = qemu_bh_new(imx_timerg_timeout, s);
+ s->timer = ptimer_init(bh);
+
+ /* Hard reset resets extra bits in CR */
+ s->cr = 0;
+ return 0;
+}
+
+
+
+/*
+ * EPIT: Enhanced periodic interrupt timer
+ */
+
+#define CR_EN (1 << 0)
+#define CR_ENMOD (1 << 1)
+#define CR_OCIEN (1 << 2)
+#define CR_RLD (1 << 3)
+#define CR_PRESCALE_SHIFT (4)
+#define CR_PRESCALE_MASK (0xfff)
+#define CR_SWR (1 << 16)
+#define CR_IOVW (1 << 17)
+#define CR_DBGEN (1 << 18)
+#define CR_EPIT (1 << 19)
+#define CR_DOZEN (1 << 20)
+#define CR_STOPEN (1 << 21)
+#define CR_CLKSRC_SHIFT (24)
+#define CR_CLKSRC_MASK (0x3 << CR_CLKSRC_SHIFT)
+
+
+/*
+ * Exact clock frequencies vary from board to board.
+ * These are typical.
+ */
+static const IMXClk imx_timerp_clocks[] = {
+ 0, /* disabled */
+ IPG, /* ipg_clk, ~532MHz */
+ IPG, /* ipg_clk_highfreq */
+ CLK_32k, /* ipg_clk_32k -- ~32kHz */
+};
+
+typedef struct {
+ SysBusDevice busdev;
+ ptimer_state *timer;
+ MemoryRegion iomem;
+ DeviceState *ccm;
+
+ uint32_t cr;
+ uint32_t lr;
+ uint32_t cmp;
+
+ uint32_t freq;
+ int int_level;
+ qemu_irq irq;
+} IMXTimerPState;
+
+/*
+ * Update interrupt status
+ */
+static void imx_timerp_update(IMXTimerPState *s)
+{
+ if (s->int_level && (s->cr & CR_OCIEN)) {
+ qemu_irq_raise(s->irq);
+ } else {
+ qemu_irq_lower(s->irq);
+ }
+}
+
+static void imx_timerp_reset(DeviceState *dev)
+{
+ IMXTimerPState *s = container_of(dev, IMXTimerPState, busdev.qdev);
+
+ s->cr = 0;
+ s->lr = TIMER_MAX;
+ s->int_level = 0;
+ s->cmp = 0;
+ ptimer_stop(s->timer);
+ ptimer_set_count(s->timer, TIMER_MAX);
+}
+
+static uint64_t imx_timerp_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ IMXTimerPState *s = (IMXTimerPState *)opaque;
+
+ DPRINTF("p-read(offset=%x)", offset >> 2);
+ switch (offset >> 2) {
+ case 0: /* Control Register */
+ DPRINTF("cr %x\n", s->cr);
+ return s->cr;
+
+ case 1: /* Status Register */
+ DPRINTF("int_level %x\n", s->int_level);
+ return s->int_level;
+
+ case 2: /* LR - ticks*/
+ DPRINTF("lr %x\n", s->lr);
+ return s->lr;
+
+ case 3: /* CMP */
+ DPRINTF("cmp %x\n", s->cmp);
+ return s->cmp;
+
+ case 4: /* CNT */
+ return ptimer_get_count(s->timer);
+ }
+ IPRINTF("imx_timerp_read: Bad offset %x\n",
+ (int)offset >> 2);
+ return 0;
+}
+
+static void set_timerp_freq(IMXTimerPState *s)
+{
+ int clksrc;
+ unsigned prescaler;
+ uint32_t freq;
+
+ clksrc = (s->cr & CR_CLKSRC_MASK) >> CR_CLKSRC_SHIFT;
+ prescaler = 1 + ((s->cr >> CR_PRESCALE_SHIFT) & CR_PRESCALE_MASK);
+ freq = imx_clock_frequency(s->ccm, imx_timerp_clocks[clksrc]) / prescaler;
+
+ s->freq = freq;
+ DPRINTF("Setting ptimer frequency to %u\n", freq);
+
+ if (freq) {
+ ptimer_set_freq(s->timer, freq);
+ }
+}
+
+static void imx_timerp_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ IMXTimerPState *s = (IMXTimerPState *)opaque;
+ DPRINTF("p-write(offset=%x, value = %x)\n", (unsigned int)offset >> 2,
+ (unsigned int)value);
+
+ switch (offset >> 2) {
+ case 0: /* CR */
+ if (value & CR_SWR) {
+ imx_timerp_reset(&s->busdev.qdev);
+ value &= ~CR_SWR;
+ }
+ s->cr = value & 0x03ffffff;
+ set_timerp_freq(s);
+
+ if (s->freq && (s->cr & CR_EN)) {
+ if (!(s->cr & CR_ENMOD)) {
+ ptimer_set_count(s->timer, s->lr);
+ }
+ ptimer_run(s->timer, 0);
+ } else {
+ ptimer_stop(s->timer);
+ }
+ break;
+
+ case 1: /* SR - ACK*/
+ s->int_level = 0;
+ imx_timerp_update(s);
+ break;
+
+ case 2: /* LR - set ticks */
+ s->lr = value;
+ ptimer_set_limit(s->timer, value, !!(s->cr & CR_IOVW));
+ break;
+
+ case 3: /* CMP */
+ s->cmp = value;
+ if (value) {
+ IPRINTF(
+ "Values for EPIT comparison other than zero not supported\n"
+ );
+ }
+ break;
+
+ default:
+ IPRINTF("imx_timerp_write: Bad offset %x\n",
+ (int)offset >> 2);
+ }
+}
+
+static void imx_timerp_tick(void *opaque)
+{
+ IMXTimerPState *s = (IMXTimerPState *)opaque;
+
+ DPRINTF("imxp tick\n");
+ if (!(s->cr & CR_RLD)) {
+ ptimer_set_count(s->timer, TIMER_MAX);
+ }
+ s->int_level = 1;
+ imx_timerp_update(s);
+}
+
+void imx_timerp_create(const hwaddr addr,
+ qemu_irq irq,
+ DeviceState *ccm)
+{
+ IMXTimerPState *pp;
+ DeviceState *dev;
+
+ dev = sysbus_create_simple("imx_timerp", addr, irq);
+ pp = container_of(dev, IMXTimerPState, busdev.qdev);
+ pp->ccm = ccm;
+}
+
+static const MemoryRegionOps imx_timerp_ops = {
+ .read = imx_timerp_read,
+ .write = imx_timerp_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_imx_timerp = {
+ .name = "imx-timerp",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(cr, IMXTimerPState),
+ VMSTATE_UINT32(lr, IMXTimerPState),
+ VMSTATE_UINT32(cmp, IMXTimerPState),
+ VMSTATE_UINT32(freq, IMXTimerPState),
+ VMSTATE_INT32(int_level, IMXTimerPState),
+ VMSTATE_PTIMER(timer, IMXTimerPState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int imx_timerp_init(SysBusDevice *dev)
+{
+ IMXTimerPState *s = FROM_SYSBUS(IMXTimerPState, dev);
+ QEMUBH *bh;
+
+ DPRINTF("imx_timerp_init\n");
+
+ sysbus_init_irq(dev, &s->irq);
+ memory_region_init_io(&s->iomem, &imx_timerp_ops,
+ s, "imxp-timer",
+ 0x00001000);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ bh = qemu_bh_new(imx_timerp_tick, s);
+ s->timer = ptimer_init(bh);
+
+ return 0;
+}
+
+
+void imx_timerg_create(const hwaddr addr,
+ qemu_irq irq,
+ DeviceState *ccm)
+{
+ IMXTimerGState *pp;
+ DeviceState *dev;
+
+ dev = sysbus_create_simple("imx_timerg", addr, irq);
+ pp = container_of(dev, IMXTimerGState, busdev.qdev);
+ pp->ccm = ccm;
+}
+
+static void imx_timerg_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+ k->init = imx_timerg_init;
+ dc->vmsd = &vmstate_imx_timerg;
+ dc->reset = imx_timerg_reset;
+ dc->desc = "i.MX general timer";
+}
+
+static void imx_timerp_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+ k->init = imx_timerp_init;
+ dc->vmsd = &vmstate_imx_timerp;
+ dc->reset = imx_timerp_reset;
+ dc->desc = "i.MX periodic timer";
+}
+
+static const TypeInfo imx_timerp_info = {
+ .name = "imx_timerp",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IMXTimerPState),
+ .class_init = imx_timerp_class_init,
+};
+
+static const TypeInfo imx_timerg_info = {
+ .name = "imx_timerg",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IMXTimerGState),
+ .class_init = imx_timerg_class_init,
+};
+
+static void imx_timer_register_types(void)
+{
+ type_register_static(&imx_timerp_info);
+ type_register_static(&imx_timerg_info);
+}
+
+type_init(imx_timer_register_types)
diff --git a/hw/timer/lm32_timer.c b/hw/timer/lm32_timer.c
new file mode 100644
index 0000000000..e06fac7082
--- /dev/null
+++ b/hw/timer/lm32_timer.c
@@ -0,0 +1,230 @@
+/*
+ * QEMU model of the LatticeMico32 timer block.
+ *
+ * Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ *
+ * 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 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/>.
+ *
+ *
+ * Specification available at:
+ * http://www.latticesemi.com/documents/mico32timer.pdf
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "qemu/timer.h"
+#include "hw/ptimer.h"
+#include "qemu/error-report.h"
+
+#define DEFAULT_FREQUENCY (50*1000000)
+
+enum {
+ R_SR = 0,
+ R_CR,
+ R_PERIOD,
+ R_SNAPSHOT,
+ R_MAX
+};
+
+enum {
+ SR_TO = (1 << 0),
+ SR_RUN = (1 << 1),
+};
+
+enum {
+ CR_ITO = (1 << 0),
+ CR_CONT = (1 << 1),
+ CR_START = (1 << 2),
+ CR_STOP = (1 << 3),
+};
+
+struct LM32TimerState {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+
+ QEMUBH *bh;
+ ptimer_state *ptimer;
+
+ qemu_irq irq;
+ uint32_t freq_hz;
+
+ uint32_t regs[R_MAX];
+};
+typedef struct LM32TimerState LM32TimerState;
+
+static void timer_update_irq(LM32TimerState *s)
+{
+ int state = (s->regs[R_SR] & SR_TO) && (s->regs[R_CR] & CR_ITO);
+
+ trace_lm32_timer_irq_state(state);
+ qemu_set_irq(s->irq, state);
+}
+
+static uint64_t timer_read(void *opaque, hwaddr addr, unsigned size)
+{
+ LM32TimerState *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr) {
+ case R_SR:
+ case R_CR:
+ case R_PERIOD:
+ r = s->regs[addr];
+ break;
+ case R_SNAPSHOT:
+ r = (uint32_t)ptimer_get_count(s->ptimer);
+ break;
+ default:
+ error_report("lm32_timer: read access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+
+ trace_lm32_timer_memory_read(addr << 2, r);
+ return r;
+}
+
+static void timer_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ LM32TimerState *s = opaque;
+
+ trace_lm32_timer_memory_write(addr, value);
+
+ addr >>= 2;
+ switch (addr) {
+ case R_SR:
+ s->regs[R_SR] &= ~SR_TO;
+ break;
+ case R_CR:
+ s->regs[R_CR] = value;
+ if (s->regs[R_CR] & CR_START) {
+ ptimer_run(s->ptimer, 1);
+ }
+ if (s->regs[R_CR] & CR_STOP) {
+ ptimer_stop(s->ptimer);
+ }
+ break;
+ case R_PERIOD:
+ s->regs[R_PERIOD] = value;
+ ptimer_set_count(s->ptimer, value);
+ break;
+ case R_SNAPSHOT:
+ error_report("lm32_timer: write access to read only register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ default:
+ error_report("lm32_timer: write access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+ timer_update_irq(s);
+}
+
+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)
+{
+ LM32TimerState *s = opaque;
+
+ trace_lm32_timer_hit();
+
+ s->regs[R_SR] |= SR_TO;
+
+ if (s->regs[R_CR] & CR_CONT) {
+ ptimer_set_count(s->ptimer, s->regs[R_PERIOD]);
+ ptimer_run(s->ptimer, 1);
+ }
+ timer_update_irq(s);
+}
+
+static void timer_reset(DeviceState *d)
+{
+ LM32TimerState *s = container_of(d, LM32TimerState, busdev.qdev);
+ int i;
+
+ for (i = 0; i < R_MAX; i++) {
+ s->regs[i] = 0;
+ }
+ ptimer_stop(s->ptimer);
+}
+
+static int lm32_timer_init(SysBusDevice *dev)
+{
+ LM32TimerState *s = FROM_SYSBUS(typeof(*s), dev);
+
+ sysbus_init_irq(dev, &s->irq);
+
+ s->bh = qemu_bh_new(timer_hit, s);
+ s->ptimer = ptimer_init(s->bh);
+ ptimer_set_freq(s->ptimer, s->freq_hz);
+
+ memory_region_init_io(&s->iomem, &timer_ops, s, "timer", R_MAX * 4);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_lm32_timer = {
+ .name = "lm32-timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PTIMER(ptimer, LM32TimerState),
+ VMSTATE_UINT32(freq_hz, LM32TimerState),
+ VMSTATE_UINT32_ARRAY(regs, LM32TimerState, R_MAX),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property lm32_timer_properties[] = {
+ DEFINE_PROP_UINT32("frequency", LM32TimerState, freq_hz, DEFAULT_FREQUENCY),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void lm32_timer_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = lm32_timer_init;
+ dc->reset = timer_reset;
+ dc->vmsd = &vmstate_lm32_timer;
+ dc->props = lm32_timer_properties;
+}
+
+static const TypeInfo lm32_timer_info = {
+ .name = "lm32-timer",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(LM32TimerState),
+ .class_init = lm32_timer_class_init,
+};
+
+static void lm32_timer_register_types(void)
+{
+ type_register_static(&lm32_timer_info);
+}
+
+type_init(lm32_timer_register_types)
diff --git a/hw/timer/milkymist-sysctl.c b/hw/timer/milkymist-sysctl.c
new file mode 100644
index 0000000000..e083a280c4
--- /dev/null
+++ b/hw/timer/milkymist-sysctl.c
@@ -0,0 +1,338 @@
+/*
+ * QEMU model of the Milkymist System Controller.
+ *
+ * Copyright (c) 2010-2012 Michael Walle <michael@walle.cc>
+ *
+ * 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 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/>.
+ *
+ *
+ * Specification available at:
+ * http://www.milkymist.org/socdoc/sysctl.pdf
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "sysemu/sysemu.h"
+#include "trace.h"
+#include "qemu/timer.h"
+#include "hw/ptimer.h"
+#include "qemu/error-report.h"
+
+enum {
+ CTRL_ENABLE = (1<<0),
+ CTRL_AUTORESTART = (1<<1),
+};
+
+enum {
+ ICAP_READY = (1<<0),
+};
+
+enum {
+ R_GPIO_IN = 0,
+ R_GPIO_OUT,
+ R_GPIO_INTEN,
+ R_TIMER0_CONTROL = 4,
+ R_TIMER0_COMPARE,
+ R_TIMER0_COUNTER,
+ R_TIMER1_CONTROL = 8,
+ R_TIMER1_COMPARE,
+ R_TIMER1_COUNTER,
+ R_ICAP = 16,
+ R_DBG_SCRATCHPAD = 20,
+ R_DBG_WRITE_LOCK,
+ R_CLK_FREQUENCY = 29,
+ R_CAPABILITIES,
+ R_SYSTEM_ID,
+ R_MAX
+};
+
+struct MilkymistSysctlState {
+ SysBusDevice busdev;
+ MemoryRegion regs_region;
+
+ QEMUBH *bh0;
+ QEMUBH *bh1;
+ ptimer_state *ptimer0;
+ ptimer_state *ptimer1;
+
+ uint32_t freq_hz;
+ uint32_t capabilities;
+ uint32_t systemid;
+ uint32_t strappings;
+
+ uint32_t regs[R_MAX];
+
+ qemu_irq gpio_irq;
+ qemu_irq timer0_irq;
+ qemu_irq timer1_irq;
+};
+typedef struct MilkymistSysctlState MilkymistSysctlState;
+
+static void sysctl_icap_write(MilkymistSysctlState *s, uint32_t value)
+{
+ trace_milkymist_sysctl_icap_write(value);
+ switch (value & 0xffff) {
+ case 0x000e:
+ qemu_system_shutdown_request();
+ break;
+ }
+}
+
+static uint64_t sysctl_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MilkymistSysctlState *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr) {
+ case R_TIMER0_COUNTER:
+ r = (uint32_t)ptimer_get_count(s->ptimer0);
+ /* milkymist timer counts up */
+ r = s->regs[R_TIMER0_COMPARE] - r;
+ break;
+ case R_TIMER1_COUNTER:
+ r = (uint32_t)ptimer_get_count(s->ptimer1);
+ /* milkymist timer counts up */
+ r = s->regs[R_TIMER1_COMPARE] - r;
+ break;
+ case R_GPIO_IN:
+ case R_GPIO_OUT:
+ case R_GPIO_INTEN:
+ case R_TIMER0_CONTROL:
+ case R_TIMER0_COMPARE:
+ case R_TIMER1_CONTROL:
+ case R_TIMER1_COMPARE:
+ case R_ICAP:
+ case R_DBG_SCRATCHPAD:
+ case R_DBG_WRITE_LOCK:
+ case R_CLK_FREQUENCY:
+ case R_CAPABILITIES:
+ case R_SYSTEM_ID:
+ r = s->regs[addr];
+ break;
+
+ default:
+ error_report("milkymist_sysctl: read access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+
+ trace_milkymist_sysctl_memory_read(addr << 2, r);
+
+ return r;
+}
+
+static void sysctl_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ MilkymistSysctlState *s = opaque;
+
+ trace_milkymist_sysctl_memory_write(addr, value);
+
+ addr >>= 2;
+ switch (addr) {
+ case R_GPIO_OUT:
+ case R_GPIO_INTEN:
+ case R_TIMER0_COUNTER:
+ case R_TIMER1_COUNTER:
+ case R_DBG_SCRATCHPAD:
+ s->regs[addr] = value;
+ break;
+ case R_TIMER0_COMPARE:
+ ptimer_set_limit(s->ptimer0, value, 0);
+ s->regs[addr] = value;
+ break;
+ case R_TIMER1_COMPARE:
+ ptimer_set_limit(s->ptimer1, value, 0);
+ s->regs[addr] = value;
+ break;
+ case R_TIMER0_CONTROL:
+ s->regs[addr] = value;
+ if (s->regs[R_TIMER0_CONTROL] & CTRL_ENABLE) {
+ trace_milkymist_sysctl_start_timer0();
+ ptimer_set_count(s->ptimer0,
+ s->regs[R_TIMER0_COMPARE] - s->regs[R_TIMER0_COUNTER]);
+ ptimer_run(s->ptimer0, 0);
+ } else {
+ trace_milkymist_sysctl_stop_timer0();
+ ptimer_stop(s->ptimer0);
+ }
+ break;
+ case R_TIMER1_CONTROL:
+ s->regs[addr] = value;
+ if (s->regs[R_TIMER1_CONTROL] & CTRL_ENABLE) {
+ trace_milkymist_sysctl_start_timer1();
+ ptimer_set_count(s->ptimer1,
+ s->regs[R_TIMER1_COMPARE] - s->regs[R_TIMER1_COUNTER]);
+ ptimer_run(s->ptimer1, 0);
+ } else {
+ trace_milkymist_sysctl_stop_timer1();
+ ptimer_stop(s->ptimer1);
+ }
+ break;
+ case R_ICAP:
+ sysctl_icap_write(s, value);
+ break;
+ case R_DBG_WRITE_LOCK:
+ s->regs[addr] = 1;
+ break;
+ case R_SYSTEM_ID:
+ qemu_system_reset_request();
+ break;
+
+ case R_GPIO_IN:
+ case R_CLK_FREQUENCY:
+ case R_CAPABILITIES:
+ error_report("milkymist_sysctl: write to read-only register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+
+ default:
+ error_report("milkymist_sysctl: write access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+}
+
+static const MemoryRegionOps sysctl_mmio_ops = {
+ .read = sysctl_read,
+ .write = sysctl_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void timer0_hit(void *opaque)
+{
+ MilkymistSysctlState *s = opaque;
+
+ if (!(s->regs[R_TIMER0_CONTROL] & CTRL_AUTORESTART)) {
+ s->regs[R_TIMER0_CONTROL] &= ~CTRL_ENABLE;
+ trace_milkymist_sysctl_stop_timer0();
+ ptimer_stop(s->ptimer0);
+ }
+
+ trace_milkymist_sysctl_pulse_irq_timer0();
+ qemu_irq_pulse(s->timer0_irq);
+}
+
+static void timer1_hit(void *opaque)
+{
+ MilkymistSysctlState *s = opaque;
+
+ if (!(s->regs[R_TIMER1_CONTROL] & CTRL_AUTORESTART)) {
+ s->regs[R_TIMER1_CONTROL] &= ~CTRL_ENABLE;
+ trace_milkymist_sysctl_stop_timer1();
+ ptimer_stop(s->ptimer1);
+ }
+
+ trace_milkymist_sysctl_pulse_irq_timer1();
+ qemu_irq_pulse(s->timer1_irq);
+}
+
+static void milkymist_sysctl_reset(DeviceState *d)
+{
+ MilkymistSysctlState *s =
+ container_of(d, MilkymistSysctlState, busdev.qdev);
+ int i;
+
+ for (i = 0; i < R_MAX; i++) {
+ s->regs[i] = 0;
+ }
+
+ ptimer_stop(s->ptimer0);
+ ptimer_stop(s->ptimer1);
+
+ /* defaults */
+ s->regs[R_ICAP] = ICAP_READY;
+ s->regs[R_SYSTEM_ID] = s->systemid;
+ s->regs[R_CLK_FREQUENCY] = s->freq_hz;
+ s->regs[R_CAPABILITIES] = s->capabilities;
+ s->regs[R_GPIO_IN] = s->strappings;
+}
+
+static int milkymist_sysctl_init(SysBusDevice *dev)
+{
+ MilkymistSysctlState *s = FROM_SYSBUS(typeof(*s), dev);
+
+ sysbus_init_irq(dev, &s->gpio_irq);
+ sysbus_init_irq(dev, &s->timer0_irq);
+ sysbus_init_irq(dev, &s->timer1_irq);
+
+ s->bh0 = qemu_bh_new(timer0_hit, s);
+ s->bh1 = qemu_bh_new(timer1_hit, s);
+ s->ptimer0 = ptimer_init(s->bh0);
+ s->ptimer1 = ptimer_init(s->bh1);
+ ptimer_set_freq(s->ptimer0, s->freq_hz);
+ ptimer_set_freq(s->ptimer1, s->freq_hz);
+
+ memory_region_init_io(&s->regs_region, &sysctl_mmio_ops, s,
+ "milkymist-sysctl", R_MAX * 4);
+ sysbus_init_mmio(dev, &s->regs_region);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_milkymist_sysctl = {
+ .name = "milkymist-sysctl",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, MilkymistSysctlState, R_MAX),
+ VMSTATE_PTIMER(ptimer0, MilkymistSysctlState),
+ VMSTATE_PTIMER(ptimer1, MilkymistSysctlState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property milkymist_sysctl_properties[] = {
+ DEFINE_PROP_UINT32("frequency", MilkymistSysctlState,
+ freq_hz, 80000000),
+ DEFINE_PROP_UINT32("capabilities", MilkymistSysctlState,
+ capabilities, 0x00000000),
+ DEFINE_PROP_UINT32("systemid", MilkymistSysctlState,
+ systemid, 0x10014d31),
+ DEFINE_PROP_UINT32("gpio_strappings", MilkymistSysctlState,
+ strappings, 0x00000001),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void milkymist_sysctl_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = milkymist_sysctl_init;
+ dc->reset = milkymist_sysctl_reset;
+ dc->vmsd = &vmstate_milkymist_sysctl;
+ dc->props = milkymist_sysctl_properties;
+}
+
+static const TypeInfo milkymist_sysctl_info = {
+ .name = "milkymist-sysctl",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MilkymistSysctlState),
+ .class_init = milkymist_sysctl_class_init,
+};
+
+static void milkymist_sysctl_register_types(void)
+{
+ type_register_static(&milkymist_sysctl_info);
+}
+
+type_init(milkymist_sysctl_register_types)
diff --git a/hw/timer/omap_gptimer.c b/hw/timer/omap_gptimer.c
new file mode 100644
index 0000000000..9b0e9dd567
--- /dev/null
+++ b/hw/timer/omap_gptimer.c
@@ -0,0 +1,488 @@
+/*
+ * 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 "hw/hw.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_get_clock_ns(vm_clock) - 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_get_clock_ns(vm_clock);
+ }
+}
+
+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);
+ qemu_mod_timer(timer->timer, timer->time + expires);
+
+ if (timer->ce && timer->match_val >= timer->val) {
+ matches = muldiv64(timer->match_val - timer->val,
+ timer->ticks_per_sec, timer->rate);
+ qemu_mod_timer(timer->match, timer->time + matches);
+ } else
+ qemu_del_timer(timer->match);
+ } else {
+ qemu_del_timer(timer->timer);
+ qemu_del_timer(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_get_clock_ns(vm_clock);
+ }
+
+ 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_irqs(omap_gp_timer_clk_update, timer, 1)[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",
+ __FUNCTION__);
+ 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", __FUNCTION__);
+ if (!s->inout && s->capture != gpt_capture_none)
+ fprintf(stderr, "%s: GP timer pin must be an input "
+ "for this capture mode\n", __FUNCTION__);
+ 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 = get_ticks_per_sec() << (s->pre ? s->ptv + 1 : 0);
+ omap_gp_timer_update(s);
+ break;
+
+ case 0x28: /* TCRR */
+ s->time = qemu_get_clock_ns(vm_clock);
+ s->val = value;
+ omap_gp_timer_update(s);
+ break;
+
+ case 0x2c: /* TLDR */
+ s->load_val = value;
+ break;
+
+ case 0x30: /* TTGR */
+ s->time = qemu_get_clock_ns(vm_clock);
+ 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)
+ return omap_gp_timer_write(opaque, addr, (value << 16) | s->writeh);
+ else
+ s->writeh = (uint16_t) value;
+}
+
+static const MemoryRegionOps omap_gp_timer_ops = {
+ .old_mmio = {
+ .read = {
+ omap_badwidth_read32,
+ omap_gp_timer_readh,
+ omap_gp_timer_readw,
+ },
+ .write = {
+ omap_badwidth_write32,
+ omap_gp_timer_writeh,
+ omap_gp_timer_write,
+ },
+ },
+ .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 = (struct omap_gp_timer_s *)
+ g_malloc0(sizeof(struct omap_gp_timer_s));
+
+ s->ta = ta;
+ s->irq = irq;
+ s->clk = fclk;
+ s->timer = qemu_new_timer_ns(vm_clock, omap_gp_timer_tick, s);
+ s->match = qemu_new_timer_ns(vm_clock, omap_gp_timer_match, s);
+ s->in = qemu_allocate_irqs(omap_gp_timer_input, s, 1)[0];
+ omap_gp_timer_reset(s);
+ omap_gp_timer_clk_setup(s);
+
+ memory_region_init_io(&s->iomem, &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 0000000000..a24f35c277
--- /dev/null
+++ b/hw/timer/omap_synctimer.c
@@ -0,0 +1,102 @@
+/*
+ * 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 "hw/hw.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_get_clock_ns(vm_clock), 0x8000, get_ticks_per_sec());
+}
+
+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 void omap_synctimer_write(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ OMAP_BAD_REG(addr);
+}
+
+static const MemoryRegionOps omap_synctimer_ops = {
+ .old_mmio = {
+ .read = {
+ omap_badwidth_read32,
+ omap_synctimer_readh,
+ omap_synctimer_readw,
+ },
+ .write = {
+ omap_badwidth_write32,
+ omap_synctimer_write,
+ omap_synctimer_write,
+ },
+ },
+ .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, &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 0000000000..8ea2416dd7
--- /dev/null
+++ b/hw/timer/pxa2xx_timer.c
@@ -0,0 +1,583 @@
+/*
+ * Intel XScale PXA255/270 OS Timers.
+ *
+ * Copyright (c) 2006 Openedhand Ltd.
+ * Copyright (c) 2006 Thorsten Zitterell
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/hw.h"
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+#include "hw/arm/pxa.h"
+#include "hw/sysbus.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,
+};
+
+typedef struct PXA2xxTimerInfo PXA2xxTimerInfo;
+
+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 busdev;
+ 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, get_ticks_per_sec());
+
+ for (i = 0; i < 4; i ++) {
+ new_qemu = now_qemu + muldiv64((uint32_t) (s->timer[i].value - now_vm),
+ get_ticks_per_sec(), s->freq);
+ qemu_mod_timer(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;
+
+ if (s->tm4[n].control & (1 << 7))
+ counter = n;
+ else
+ counter = counters[n];
+
+ if (!s->tm4[counter].freq) {
+ qemu_del_timer(s->tm4[n].tm.qtimer);
+ return;
+ }
+
+ now_vm = s->tm4[counter].clock + muldiv64(now_qemu -
+ s->tm4[counter].lastload,
+ s->tm4[counter].freq, get_ticks_per_sec());
+
+ new_qemu = now_qemu + muldiv64((uint32_t) (s->tm4[n].tm.value - now_vm),
+ get_ticks_per_sec(), s->tm4[counter].freq);
+ qemu_mod_timer(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_get_clock_ns(vm_clock) -
+ s->lastload, s->freq, get_ticks_per_sec());
+ 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_get_clock_ns(vm_clock) -
+ s->tm4[tm - 1].lastload,
+ s->tm4[tm - 1].freq, get_ticks_per_sec());
+ 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_get_clock_ns(vm_clock) -
+ s->tm4[tm].lastload, s->tm4[tm].freq, get_ticks_per_sec());
+ 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:
+ badreg:
+ hw_error("pxa2xx_timer_read: Bad offset " REG_FMT "\n", 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_get_clock_ns(vm_clock));
+ 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_get_clock_ns(vm_clock), tm);
+ break;
+ case OSCR:
+ s->oldclock = s->clock;
+ s->lastload = qemu_get_clock_ns(vm_clock);
+ 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_get_clock_ns(vm_clock);
+ 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_get_clock_ns(vm_clock), 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_get_clock_ns(vm_clock), tm);
+ }
+ break;
+ default:
+ badreg:
+ hw_error("pxa2xx_timer_write: Bad offset " REG_FMT "\n", offset);
+ }
+}
+
+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();
+ }
+}
+
+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_get_clock_ns(vm_clock), 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_get_clock_ns(vm_clock);
+ 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 int pxa2xx_timer_init(SysBusDevice *dev)
+{
+ int i;
+ PXA2xxTimerInfo *s;
+
+ s = FROM_SYSBUS(PXA2xxTimerInfo, dev);
+ s->irq_enabled = 0;
+ s->oldclock = 0;
+ s->clock = 0;
+ s->lastload = qemu_get_clock_ns(vm_clock);
+ s->reset3 = 0;
+
+ for (i = 0; i < 4; i ++) {
+ s->timer[i].value = 0;
+ sysbus_init_irq(dev, &s->timer[i].irq);
+ s->timer[i].info = s;
+ s->timer[i].num = i;
+ s->timer[i].qtimer = qemu_new_timer_ns(vm_clock,
+ pxa2xx_timer_tick, &s->timer[i]);
+ }
+ if (s->flags & (1 << PXA2XX_TIMER_HAVE_TM4)) {
+ sysbus_init_irq(dev, &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 = qemu_new_timer_ns(vm_clock,
+ pxa2xx_timer_tick4, &s->tm4[i]);
+ }
+ }
+
+ memory_region_init_io(&s->iomem, &pxa2xx_timer_ops, s,
+ "pxa2xx-timer", 0x00001000);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_pxa2xx_timer0_regs = {
+ .name = "pxa2xx_timer0",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .minimum_version_id_old = 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,
+ .minimum_version_id_old = 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,
+ .minimum_version_id_old = 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);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = pxa2xx_timer_init;
+ dc->desc = "PXA25x timer";
+ dc->vmsd = &vmstate_pxa2xx_timer_regs;
+ dc->props = pxa25x_timer_dev_properties;
+}
+
+static const TypeInfo pxa25x_timer_dev_info = {
+ .name = "pxa25x-timer",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .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);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = pxa2xx_timer_init;
+ dc->desc = "PXA27x timer";
+ dc->vmsd = &vmstate_pxa2xx_timer_regs;
+ dc->props = pxa27x_timer_dev_properties;
+}
+
+static const TypeInfo pxa27x_timer_dev_info = {
+ .name = "pxa27x-timer",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PXA2xxTimerInfo),
+ .class_init = pxa27x_timer_dev_class_init,
+};
+
+static void pxa2xx_timer_register_types(void)
+{
+ 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/sh_timer.c b/hw/timer/sh_timer.c
new file mode 100644
index 0000000000..f92ff4f73f
--- /dev/null
+++ b/hw/timer/sh_timer.c
@@ -0,0 +1,333 @@
+/*
+ * 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 "hw/hw.h"
+#include "hw/sh4/sh.h"
+#include "qemu/timer.h"
+#include "exec/address-spaces.h"
+#include "hw/ptimer.h"
+
+//#define DEBUG_TIMER
+
+#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;
+} sh_timer_state;
+
+/* Check all active timers, and schedule the next timer interrupt. */
+
+static void sh_timer_update(sh_timer_state *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)
+{
+ sh_timer_state *s = (sh_timer_state *)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;
+ default:
+ hw_error("sh_timer_read: Bad offset %x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void sh_timer_write(void *opaque, hwaddr offset,
+ uint32_t value)
+{
+ sh_timer_state *s = (sh_timer_state *)opaque;
+ int freq;
+
+ switch (offset >> 2) {
+ case OFFSET_TCOR:
+ s->tcor = value;
+ ptimer_set_limit(s->timer, s->tcor, 0);
+ break;
+ case OFFSET_TCNT:
+ s->tcnt = value;
+ ptimer_set_count(s->timer, s->tcnt);
+ break;
+ case OFFSET_TCR:
+ if (s->enabled) {
+ /* 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);
+ }
+ 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;
+ default: hw_error("sh_timer_write: Reserved TPSC value\n"); break;
+ }
+ switch ((value & TIMER_TCR_CKEG) >> 3) {
+ case 0: break;
+ case 1:
+ case 2:
+ case 3: if (s->feat & TIMER_FEAT_EXTCLK) break;
+ default: hw_error("sh_timer_write: Reserved CKEG value\n"); break;
+ }
+ switch ((value & TIMER_TCR_ICPE) >> 6) {
+ case 0: break;
+ case 2:
+ case 3: if (s->feat & TIMER_FEAT_CAPT) break;
+ default: hw_error("sh_timer_write: Reserved ICPE value\n"); break;
+ }
+ if ((value & TIMER_TCR_UNF) == 0)
+ s->int_level = 0;
+
+ value &= ~TIMER_TCR_UNF;
+
+ if ((value & TIMER_TCR_ICPF) && (!(s->feat & TIMER_FEAT_CAPT)))
+ hw_error("sh_timer_write: Reserved ICPF value\n");
+
+ value &= ~TIMER_TCR_ICPF; /* capture not supported */
+
+ if (value & TIMER_TCR_RESERVED)
+ hw_error("sh_timer_write: Reserved TCR bits set\n");
+ 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);
+ }
+ break;
+ case OFFSET_TCPR:
+ if (s->feat & TIMER_FEAT_CAPT) {
+ s->tcpr = value;
+ break;
+ }
+ default:
+ hw_error("sh_timer_write: Bad offset %x\n", (int)offset);
+ }
+ sh_timer_update(s);
+}
+
+static void sh_timer_start_stop(void *opaque, int enable)
+{
+ sh_timer_state *s = (sh_timer_state *)opaque;
+
+#ifdef DEBUG_TIMER
+ printf("sh_timer_start_stop %d (%d)\n", enable, s->enabled);
+#endif
+
+ if (s->enabled && !enable) {
+ ptimer_stop(s->timer);
+ }
+ if (!s->enabled && enable) {
+ ptimer_run(s->timer, 0);
+ }
+ s->enabled = !!enable;
+
+#ifdef DEBUG_TIMER
+ printf("sh_timer_start_stop done %d\n", s->enabled);
+#endif
+}
+
+static void sh_timer_tick(void *opaque)
+{
+ sh_timer_state *s = (sh_timer_state *)opaque;
+ s->int_level = s->enabled;
+ sh_timer_update(s);
+}
+
+static void *sh_timer_init(uint32_t freq, int feat, qemu_irq irq)
+{
+ sh_timer_state *s;
+ QEMUBH *bh;
+
+ s = (sh_timer_state *)g_malloc0(sizeof(sh_timer_state));
+ s->freq = freq;
+ s->feat = feat;
+ s->tcor = 0xffffffff;
+ s->tcnt = 0xffffffff;
+ s->tcpr = 0xdeadbeef;
+ s->tcr = 0;
+ s->enabled = 0;
+ s->irq = irq;
+
+ bh = qemu_bh_new(sh_timer_tick, s);
+ s->timer = ptimer_init(bh);
+
+ 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 = (tmu012_state *)opaque;
+
+#ifdef DEBUG_TIMER
+ printf("tmu012_read 0x%lx\n", (unsigned long) offset);
+#endif
+
+ if (offset >= 0x20) {
+ if (!(s->feat & TMU012_FEAT_3CHAN))
+ hw_error("tmu012_write: Bad channel offset %x\n", (int)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;
+
+ hw_error("tmu012_write: Bad offset %x\n", (int)offset);
+ return 0;
+}
+
+static void tmu012_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ tmu012_state *s = (tmu012_state *)opaque;
+
+#ifdef DEBUG_TIMER
+ printf("tmu012_write 0x%lx 0x%08x\n", (unsigned long) offset, value);
+#endif
+
+ if (offset >= 0x20) {
+ if (!(s->feat & TMU012_FEAT_3CHAN))
+ hw_error("tmu012_write: Bad channel offset %x\n", (int)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))
+ hw_error("tmu012_write: Bad channel\n");
+
+ 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 = (tmu012_state *)g_malloc0(sizeof(tmu012_state));
+ 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, &tmu012_ops, s,
+ "timer", 0x100000000ULL);
+
+ memory_region_init_alias(&s->iomem_p4, "timer-p4",
+ &s->iomem, 0, 0x1000);
+ memory_region_add_subregion(sysmem, P4ADDR(base), &s->iomem_p4);
+
+ memory_region_init_alias(&s->iomem_a7, "timer-a7",
+ &s->iomem, 0, 0x1000);
+ memory_region_add_subregion(sysmem, A7ADDR(base), &s->iomem_a7);
+ /* ??? Save/restore. */
+}
diff --git a/hw/timer/slavio_timer.c b/hw/timer/slavio_timer.c
new file mode 100644
index 0000000000..1145a87603
--- /dev/null
+++ b/hw/timer/slavio_timer.c
@@ -0,0 +1,435 @@
+/*
+ * 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 "hw/sparc/sun4m.h"
+#include "qemu/timer.h"
+#include "hw/ptimer.h"
+#include "hw/sysbus.h"
+#include "trace.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 running;
+ uint64_t limit;
+} CPUTimerState;
+
+typedef struct SLAVIO_TIMERState {
+ SysBusDevice busdev;
+ uint32_t num_cpus;
+ uint32_t cputimer_mode;
+ CPUTimerState cputimer[MAX_CPUS + 1];
+} SLAVIO_TIMERState;
+
+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->running;
+ } 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:
+ 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->timer) {
+ 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);
+ }
+ }
+ }
+ 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_set_count(t->timer, LIMIT_TO_PERIODS(t->limit - count));
+ } else {
+ trace_slavio_timer_mem_writel_counter_invalid();
+ }
+ break;
+ case TIMER_COUNTER_NORST:
+ // set limit without resetting counter
+ t->limit = val & TIMER_MAX_COUNT32;
+ 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);
+ }
+ break;
+ case TIMER_STATUS:
+ if (slavio_timer_is_user(tc)) {
+ // start/stop user counter
+ if ((val & 1) && !t->running) {
+ trace_slavio_timer_mem_writel_status_start(timer_index);
+ ptimer_run(t->timer, 0);
+ t->running = 1;
+ } else if (!(val & 1) && t->running) {
+ trace_slavio_timer_mem_writel_status_stop(timer_index);
+ ptimer_stop(t->timer);
+ t->running = 0;
+ }
+ }
+ 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];
+
+ // 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
+ ptimer_stop(curr_timer->timer);
+ curr_timer->running = 0;
+ // 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
+ // stop the user timer if it is running
+ if (curr_timer->running) {
+ ptimer_stop(curr_timer->timer);
+ }
+ // start the counter
+ ptimer_run(curr_timer->timer, 0);
+ curr_timer->running = 1;
+ // clear this processors user timer bit in config
+ // register
+ s->cputimer_mode &= ~processor;
+ trace_slavio_timer_mem_writel_mode_counter(timer_index);
+ }
+ }
+ }
+ } 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 = 4,
+ },
+};
+
+static const VMStateDescription vmstate_timer = {
+ .name ="timer",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .minimum_version_id_old = 3,
+ .fields = (VMStateField []) {
+ VMSTATE_UINT64(limit, CPUTimerState),
+ VMSTATE_UINT32(count, CPUTimerState),
+ VMSTATE_UINT32(counthigh, CPUTimerState),
+ VMSTATE_UINT32(reached, CPUTimerState),
+ VMSTATE_UINT32(running, 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,
+ .minimum_version_id_old = 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 = container_of(d, SLAVIO_TIMERState, busdev.qdev);
+ 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_set_limit(curr_timer->timer,
+ LIMIT_TO_PERIODS(TIMER_MAX_COUNT32), 1);
+ ptimer_run(curr_timer->timer, 0);
+ curr_timer->running = 1;
+ }
+ }
+ s->cputimer_mode = 0;
+}
+
+static int slavio_timer_init1(SysBusDevice *dev)
+{
+ SLAVIO_TIMERState *s = FROM_SYSBUS(SLAVIO_TIMERState, dev);
+ QEMUBH *bh;
+ 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;
+
+ bh = qemu_bh_new(slavio_timer_irq, tc);
+ s->cputimer[i].timer = ptimer_init(bh);
+ ptimer_set_period(s->cputimer[i].timer, TIMER_PERIOD);
+
+ size = i == 0 ? SYS_TIMER_SIZE : CPU_TIMER_SIZE;
+ snprintf(timer_name, sizeof(timer_name), "timer-%i", i);
+ memory_region_init_io(&tc->iomem, &slavio_timer_mem_ops, tc,
+ timer_name, size);
+ sysbus_init_mmio(dev, &tc->iomem);
+
+ sysbus_init_irq(dev, &s->cputimer[i].irq);
+ }
+
+ return 0;
+}
+
+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);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = slavio_timer_init1;
+ dc->reset = slavio_timer_reset;
+ dc->vmsd = &vmstate_slavio_timer;
+ dc->props = slavio_timer_properties;
+}
+
+static const TypeInfo slavio_timer_info = {
+ .name = "slavio_timer",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SLAVIO_TIMERState),
+ .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/tusb6010.c b/hw/timer/tusb6010.c
new file mode 100644
index 0000000000..533938a9b4
--- /dev/null
+++ b/hw/timer/tusb6010.c
@@ -0,0 +1,813 @@
+/*
+ * Texas Instruments TUSB6010 emulation.
+ * Based on reverse-engineering of a linux driver.
+ *
+ * Copyright (C) 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) version 3 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-common.h"
+#include "qemu/timer.h"
+#include "hw/usb.h"
+#include "hw/arm/omap.h"
+#include "hw/irq.h"
+#include "hw/arm/devices.h"
+#include "hw/sysbus.h"
+
+typedef struct TUSBState {
+ SysBusDevice busdev;
+ MemoryRegion iomem[2];
+ qemu_irq irq;
+ MUSBState *musb;
+ QEMUTimer *otg_timer;
+ QEMUTimer *pwr_timer;
+
+ int power;
+ uint32_t scratch;
+ uint16_t test_reset;
+ uint32_t prcm_config;
+ uint32_t prcm_mngmt;
+ uint16_t otg_status;
+ uint32_t dev_config;
+ int host_mode;
+ uint32_t intr;
+ uint32_t intr_ok;
+ uint32_t mask;
+ uint32_t usbip_intr;
+ uint32_t usbip_mask;
+ uint32_t gpio_intr;
+ uint32_t gpio_mask;
+ uint32_t gpio_config;
+ uint32_t dma_intr;
+ uint32_t dma_mask;
+ uint32_t dma_map;
+ uint32_t dma_config;
+ uint32_t ep0_config;
+ uint32_t rx_config[15];
+ uint32_t tx_config[15];
+ uint32_t wkup_mask;
+ uint32_t pullup[2];
+ uint32_t control_config;
+ uint32_t otg_timer_val;
+} TUSBState;
+
+#define TUSB_DEVCLOCK 60000000 /* 60 MHz */
+
+#define TUSB_VLYNQ_CTRL 0x004
+
+/* Mentor Graphics OTG core registers. */
+#define TUSB_BASE_OFFSET 0x400
+
+/* FIFO registers, 32-bit. */
+#define TUSB_FIFO_BASE 0x600
+
+/* Device System & Control registers, 32-bit. */
+#define TUSB_SYS_REG_BASE 0x800
+
+#define TUSB_DEV_CONF (TUSB_SYS_REG_BASE + 0x000)
+#define TUSB_DEV_CONF_USB_HOST_MODE (1 << 16)
+#define TUSB_DEV_CONF_PROD_TEST_MODE (1 << 15)
+#define TUSB_DEV_CONF_SOFT_ID (1 << 1)
+#define TUSB_DEV_CONF_ID_SEL (1 << 0)
+
+#define TUSB_PHY_OTG_CTRL_ENABLE (TUSB_SYS_REG_BASE + 0x004)
+#define TUSB_PHY_OTG_CTRL (TUSB_SYS_REG_BASE + 0x008)
+#define TUSB_PHY_OTG_CTRL_WRPROTECT (0xa5 << 24)
+#define TUSB_PHY_OTG_CTRL_O_ID_PULLUP (1 << 23)
+#define TUSB_PHY_OTG_CTRL_O_VBUS_DET_EN (1 << 19)
+#define TUSB_PHY_OTG_CTRL_O_SESS_END_EN (1 << 18)
+#define TUSB_PHY_OTG_CTRL_TESTM2 (1 << 17)
+#define TUSB_PHY_OTG_CTRL_TESTM1 (1 << 16)
+#define TUSB_PHY_OTG_CTRL_TESTM0 (1 << 15)
+#define TUSB_PHY_OTG_CTRL_TX_DATA2 (1 << 14)
+#define TUSB_PHY_OTG_CTRL_TX_GZ2 (1 << 13)
+#define TUSB_PHY_OTG_CTRL_TX_ENABLE2 (1 << 12)
+#define TUSB_PHY_OTG_CTRL_DM_PULLDOWN (1 << 11)
+#define TUSB_PHY_OTG_CTRL_DP_PULLDOWN (1 << 10)
+#define TUSB_PHY_OTG_CTRL_OSC_EN (1 << 9)
+#define TUSB_PHY_OTG_CTRL_PHYREF_CLK(v) (((v) & 3) << 7)
+#define TUSB_PHY_OTG_CTRL_PD (1 << 6)
+#define TUSB_PHY_OTG_CTRL_PLL_ON (1 << 5)
+#define TUSB_PHY_OTG_CTRL_EXT_RPU (1 << 4)
+#define TUSB_PHY_OTG_CTRL_PWR_GOOD (1 << 3)
+#define TUSB_PHY_OTG_CTRL_RESET (1 << 2)
+#define TUSB_PHY_OTG_CTRL_SUSPENDM (1 << 1)
+#define TUSB_PHY_OTG_CTRL_CLK_MODE (1 << 0)
+
+/* OTG status register */
+#define TUSB_DEV_OTG_STAT (TUSB_SYS_REG_BASE + 0x00c)
+#define TUSB_DEV_OTG_STAT_PWR_CLK_GOOD (1 << 8)
+#define TUSB_DEV_OTG_STAT_SESS_END (1 << 7)
+#define TUSB_DEV_OTG_STAT_SESS_VALID (1 << 6)
+#define TUSB_DEV_OTG_STAT_VBUS_VALID (1 << 5)
+#define TUSB_DEV_OTG_STAT_VBUS_SENSE (1 << 4)
+#define TUSB_DEV_OTG_STAT_ID_STATUS (1 << 3)
+#define TUSB_DEV_OTG_STAT_HOST_DISCON (1 << 2)
+#define TUSB_DEV_OTG_STAT_LINE_STATE (3 << 0)
+#define TUSB_DEV_OTG_STAT_DP_ENABLE (1 << 1)
+#define TUSB_DEV_OTG_STAT_DM_ENABLE (1 << 0)
+
+#define TUSB_DEV_OTG_TIMER (TUSB_SYS_REG_BASE + 0x010)
+#define TUSB_DEV_OTG_TIMER_ENABLE (1 << 31)
+#define TUSB_DEV_OTG_TIMER_VAL(v) ((v) & 0x07ffffff)
+#define TUSB_PRCM_REV (TUSB_SYS_REG_BASE + 0x014)
+
+/* PRCM configuration register */
+#define TUSB_PRCM_CONF (TUSB_SYS_REG_BASE + 0x018)
+#define TUSB_PRCM_CONF_SFW_CPEN (1 << 24)
+#define TUSB_PRCM_CONF_SYS_CLKSEL(v) (((v) & 3) << 16)
+
+/* PRCM management register */
+#define TUSB_PRCM_MNGMT (TUSB_SYS_REG_BASE + 0x01c)
+#define TUSB_PRCM_MNGMT_SRP_FIX_TMR(v) (((v) & 0xf) << 25)
+#define TUSB_PRCM_MNGMT_SRP_FIX_EN (1 << 24)
+#define TUSB_PRCM_MNGMT_VBUS_VAL_TMR(v) (((v) & 0xf) << 20)
+#define TUSB_PRCM_MNGMT_VBUS_VAL_FLT_EN (1 << 19)
+#define TUSB_PRCM_MNGMT_DFT_CLK_DIS (1 << 18)
+#define TUSB_PRCM_MNGMT_VLYNQ_CLK_DIS (1 << 17)
+#define TUSB_PRCM_MNGMT_OTG_SESS_END_EN (1 << 10)
+#define TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN (1 << 9)
+#define TUSB_PRCM_MNGMT_OTG_ID_PULLUP (1 << 8)
+#define TUSB_PRCM_MNGMT_15_SW_EN (1 << 4)
+#define TUSB_PRCM_MNGMT_33_SW_EN (1 << 3)
+#define TUSB_PRCM_MNGMT_5V_CPEN (1 << 2)
+#define TUSB_PRCM_MNGMT_PM_IDLE (1 << 1)
+#define TUSB_PRCM_MNGMT_DEV_IDLE (1 << 0)
+
+/* Wake-up source clear and mask registers */
+#define TUSB_PRCM_WAKEUP_SOURCE (TUSB_SYS_REG_BASE + 0x020)
+#define TUSB_PRCM_WAKEUP_CLEAR (TUSB_SYS_REG_BASE + 0x028)
+#define TUSB_PRCM_WAKEUP_MASK (TUSB_SYS_REG_BASE + 0x02c)
+#define TUSB_PRCM_WAKEUP_RESERVED_BITS (0xffffe << 13)
+#define TUSB_PRCM_WGPIO_7 (1 << 12)
+#define TUSB_PRCM_WGPIO_6 (1 << 11)
+#define TUSB_PRCM_WGPIO_5 (1 << 10)
+#define TUSB_PRCM_WGPIO_4 (1 << 9)
+#define TUSB_PRCM_WGPIO_3 (1 << 8)
+#define TUSB_PRCM_WGPIO_2 (1 << 7)
+#define TUSB_PRCM_WGPIO_1 (1 << 6)
+#define TUSB_PRCM_WGPIO_0 (1 << 5)
+#define TUSB_PRCM_WHOSTDISCON (1 << 4) /* Host disconnect */
+#define TUSB_PRCM_WBUS (1 << 3) /* USB bus resume */
+#define TUSB_PRCM_WNORCS (1 << 2) /* NOR chip select */
+#define TUSB_PRCM_WVBUS (1 << 1) /* OTG PHY VBUS */
+#define TUSB_PRCM_WID (1 << 0) /* OTG PHY ID detect */
+
+#define TUSB_PULLUP_1_CTRL (TUSB_SYS_REG_BASE + 0x030)
+#define TUSB_PULLUP_2_CTRL (TUSB_SYS_REG_BASE + 0x034)
+#define TUSB_INT_CTRL_REV (TUSB_SYS_REG_BASE + 0x038)
+#define TUSB_INT_CTRL_CONF (TUSB_SYS_REG_BASE + 0x03c)
+#define TUSB_USBIP_INT_SRC (TUSB_SYS_REG_BASE + 0x040)
+#define TUSB_USBIP_INT_SET (TUSB_SYS_REG_BASE + 0x044)
+#define TUSB_USBIP_INT_CLEAR (TUSB_SYS_REG_BASE + 0x048)
+#define TUSB_USBIP_INT_MASK (TUSB_SYS_REG_BASE + 0x04c)
+#define TUSB_DMA_INT_SRC (TUSB_SYS_REG_BASE + 0x050)
+#define TUSB_DMA_INT_SET (TUSB_SYS_REG_BASE + 0x054)
+#define TUSB_DMA_INT_CLEAR (TUSB_SYS_REG_BASE + 0x058)
+#define TUSB_DMA_INT_MASK (TUSB_SYS_REG_BASE + 0x05c)
+#define TUSB_GPIO_INT_SRC (TUSB_SYS_REG_BASE + 0x060)
+#define TUSB_GPIO_INT_SET (TUSB_SYS_REG_BASE + 0x064)
+#define TUSB_GPIO_INT_CLEAR (TUSB_SYS_REG_BASE + 0x068)
+#define TUSB_GPIO_INT_MASK (TUSB_SYS_REG_BASE + 0x06c)
+
+/* NOR flash interrupt source registers */
+#define TUSB_INT_SRC (TUSB_SYS_REG_BASE + 0x070)
+#define TUSB_INT_SRC_SET (TUSB_SYS_REG_BASE + 0x074)
+#define TUSB_INT_SRC_CLEAR (TUSB_SYS_REG_BASE + 0x078)
+#define TUSB_INT_MASK (TUSB_SYS_REG_BASE + 0x07c)
+#define TUSB_INT_SRC_TXRX_DMA_DONE (1 << 24)
+#define TUSB_INT_SRC_USB_IP_CORE (1 << 17)
+#define TUSB_INT_SRC_OTG_TIMEOUT (1 << 16)
+#define TUSB_INT_SRC_VBUS_SENSE_CHNG (1 << 15)
+#define TUSB_INT_SRC_ID_STATUS_CHNG (1 << 14)
+#define TUSB_INT_SRC_DEV_WAKEUP (1 << 13)
+#define TUSB_INT_SRC_DEV_READY (1 << 12)
+#define TUSB_INT_SRC_USB_IP_TX (1 << 9)
+#define TUSB_INT_SRC_USB_IP_RX (1 << 8)
+#define TUSB_INT_SRC_USB_IP_VBUS_ERR (1 << 7)
+#define TUSB_INT_SRC_USB_IP_VBUS_REQ (1 << 6)
+#define TUSB_INT_SRC_USB_IP_DISCON (1 << 5)
+#define TUSB_INT_SRC_USB_IP_CONN (1 << 4)
+#define TUSB_INT_SRC_USB_IP_SOF (1 << 3)
+#define TUSB_INT_SRC_USB_IP_RST_BABBLE (1 << 2)
+#define TUSB_INT_SRC_USB_IP_RESUME (1 << 1)
+#define TUSB_INT_SRC_USB_IP_SUSPEND (1 << 0)
+
+#define TUSB_GPIO_REV (TUSB_SYS_REG_BASE + 0x080)
+#define TUSB_GPIO_CONF (TUSB_SYS_REG_BASE + 0x084)
+#define TUSB_DMA_CTRL_REV (TUSB_SYS_REG_BASE + 0x100)
+#define TUSB_DMA_REQ_CONF (TUSB_SYS_REG_BASE + 0x104)
+#define TUSB_EP0_CONF (TUSB_SYS_REG_BASE + 0x108)
+#define TUSB_EP_IN_SIZE (TUSB_SYS_REG_BASE + 0x10c)
+#define TUSB_DMA_EP_MAP (TUSB_SYS_REG_BASE + 0x148)
+#define TUSB_EP_OUT_SIZE (TUSB_SYS_REG_BASE + 0x14c)
+#define TUSB_EP_MAX_PACKET_SIZE_OFFSET (TUSB_SYS_REG_BASE + 0x188)
+#define TUSB_SCRATCH_PAD (TUSB_SYS_REG_BASE + 0x1c4)
+#define TUSB_WAIT_COUNT (TUSB_SYS_REG_BASE + 0x1c8)
+#define TUSB_PROD_TEST_RESET (TUSB_SYS_REG_BASE + 0x1d8)
+
+#define TUSB_DIDR1_LO (TUSB_SYS_REG_BASE + 0x1f8)
+#define TUSB_DIDR1_HI (TUSB_SYS_REG_BASE + 0x1fc)
+
+/* Device System & Control register bitfields */
+#define TUSB_INT_CTRL_CONF_INT_RLCYC(v) (((v) & 0x7) << 18)
+#define TUSB_INT_CTRL_CONF_INT_POLARITY (1 << 17)
+#define TUSB_INT_CTRL_CONF_INT_MODE (1 << 16)
+#define TUSB_GPIO_CONF_DMAREQ(v) (((v) & 0x3f) << 24)
+#define TUSB_DMA_REQ_CONF_BURST_SIZE(v) (((v) & 3) << 26)
+#define TUSB_DMA_REQ_CONF_DMA_RQ_EN(v) (((v) & 0x3f) << 20)
+#define TUSB_DMA_REQ_CONF_DMA_RQ_ASR(v) (((v) & 0xf) << 16)
+#define TUSB_EP0_CONFIG_SW_EN (1 << 8)
+#define TUSB_EP0_CONFIG_DIR_TX (1 << 7)
+#define TUSB_EP0_CONFIG_XFR_SIZE(v) ((v) & 0x7f)
+#define TUSB_EP_CONFIG_SW_EN (1 << 31)
+#define TUSB_EP_CONFIG_XFR_SIZE(v) ((v) & 0x7fffffff)
+#define TUSB_PROD_TEST_RESET_VAL 0xa596
+
+static void tusb_intr_update(TUSBState *s)
+{
+ if (s->control_config & TUSB_INT_CTRL_CONF_INT_POLARITY)
+ qemu_set_irq(s->irq, s->intr & ~s->mask & s->intr_ok);
+ else
+ qemu_set_irq(s->irq, (!(s->intr & ~s->mask)) & s->intr_ok);
+}
+
+static void tusb_usbip_intr_update(TUSBState *s)
+{
+ /* TX interrupt in the MUSB */
+ if (s->usbip_intr & 0x0000ffff & ~s->usbip_mask)
+ s->intr |= TUSB_INT_SRC_USB_IP_TX;
+ else
+ s->intr &= ~TUSB_INT_SRC_USB_IP_TX;
+
+ /* RX interrupt in the MUSB */
+ if (s->usbip_intr & 0xffff0000 & ~s->usbip_mask)
+ s->intr |= TUSB_INT_SRC_USB_IP_RX;
+ else
+ s->intr &= ~TUSB_INT_SRC_USB_IP_RX;
+
+ /* XXX: What about TUSB_INT_SRC_USB_IP_CORE? */
+
+ tusb_intr_update(s);
+}
+
+static void tusb_dma_intr_update(TUSBState *s)
+{
+ if (s->dma_intr & ~s->dma_mask)
+ s->intr |= TUSB_INT_SRC_TXRX_DMA_DONE;
+ else
+ s->intr &= ~TUSB_INT_SRC_TXRX_DMA_DONE;
+
+ tusb_intr_update(s);
+}
+
+static void tusb_gpio_intr_update(TUSBState *s)
+{
+ /* TODO: How is this signalled? */
+}
+
+extern CPUReadMemoryFunc * const musb_read[];
+extern CPUWriteMemoryFunc * const musb_write[];
+
+static uint32_t tusb_async_readb(void *opaque, hwaddr addr)
+{
+ TUSBState *s = (TUSBState *) opaque;
+
+ switch (addr & 0xfff) {
+ case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff):
+ return musb_read[0](s->musb, addr & 0x1ff);
+
+ case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff):
+ return musb_read[0](s->musb, 0x20 + ((addr >> 3) & 0x3c));
+ }
+
+ printf("%s: unknown register at %03x\n",
+ __FUNCTION__, (int) (addr & 0xfff));
+ return 0;
+}
+
+static uint32_t tusb_async_readh(void *opaque, hwaddr addr)
+{
+ TUSBState *s = (TUSBState *) opaque;
+
+ switch (addr & 0xfff) {
+ case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff):
+ return musb_read[1](s->musb, addr & 0x1ff);
+
+ case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff):
+ return musb_read[1](s->musb, 0x20 + ((addr >> 3) & 0x3c));
+ }
+
+ printf("%s: unknown register at %03x\n",
+ __FUNCTION__, (int) (addr & 0xfff));
+ return 0;
+}
+
+static uint32_t tusb_async_readw(void *opaque, hwaddr addr)
+{
+ TUSBState *s = (TUSBState *) opaque;
+ int offset = addr & 0xfff;
+ int epnum;
+ uint32_t ret;
+
+ switch (offset) {
+ case TUSB_DEV_CONF:
+ return s->dev_config;
+
+ case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff):
+ return musb_read[2](s->musb, offset & 0x1ff);
+
+ case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff):
+ return musb_read[2](s->musb, 0x20 + ((addr >> 3) & 0x3c));
+
+ case TUSB_PHY_OTG_CTRL_ENABLE:
+ case TUSB_PHY_OTG_CTRL:
+ return 0x00; /* TODO */
+
+ case TUSB_DEV_OTG_STAT:
+ ret = s->otg_status;
+#if 0
+ if (!(s->prcm_mngmt & TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN))
+ ret &= ~TUSB_DEV_OTG_STAT_VBUS_VALID;
+#endif
+ return ret;
+ case TUSB_DEV_OTG_TIMER:
+ return s->otg_timer_val;
+
+ case TUSB_PRCM_REV:
+ return 0x20;
+ case TUSB_PRCM_CONF:
+ return s->prcm_config;
+ case TUSB_PRCM_MNGMT:
+ return s->prcm_mngmt;
+ case TUSB_PRCM_WAKEUP_SOURCE:
+ case TUSB_PRCM_WAKEUP_CLEAR: /* TODO: What does this one return? */
+ return 0x00000000;
+ case TUSB_PRCM_WAKEUP_MASK:
+ return s->wkup_mask;
+
+ case TUSB_PULLUP_1_CTRL:
+ return s->pullup[0];
+ case TUSB_PULLUP_2_CTRL:
+ return s->pullup[1];
+
+ case TUSB_INT_CTRL_REV:
+ return 0x20;
+ case TUSB_INT_CTRL_CONF:
+ return s->control_config;
+
+ case TUSB_USBIP_INT_SRC:
+ case TUSB_USBIP_INT_SET: /* TODO: What do these two return? */
+ case TUSB_USBIP_INT_CLEAR:
+ return s->usbip_intr;
+ case TUSB_USBIP_INT_MASK:
+ return s->usbip_mask;
+
+ case TUSB_DMA_INT_SRC:
+ case TUSB_DMA_INT_SET: /* TODO: What do these two return? */
+ case TUSB_DMA_INT_CLEAR:
+ return s->dma_intr;
+ case TUSB_DMA_INT_MASK:
+ return s->dma_mask;
+
+ case TUSB_GPIO_INT_SRC: /* TODO: What do these two return? */
+ case TUSB_GPIO_INT_SET:
+ case TUSB_GPIO_INT_CLEAR:
+ return s->gpio_intr;
+ case TUSB_GPIO_INT_MASK:
+ return s->gpio_mask;
+
+ case TUSB_INT_SRC:
+ case TUSB_INT_SRC_SET: /* TODO: What do these two return? */
+ case TUSB_INT_SRC_CLEAR:
+ return s->intr;
+ case TUSB_INT_MASK:
+ return s->mask;
+
+ case TUSB_GPIO_REV:
+ return 0x30;
+ case TUSB_GPIO_CONF:
+ return s->gpio_config;
+
+ case TUSB_DMA_CTRL_REV:
+ return 0x30;
+ case TUSB_DMA_REQ_CONF:
+ return s->dma_config;
+ case TUSB_EP0_CONF:
+ return s->ep0_config;
+ case TUSB_EP_IN_SIZE ... (TUSB_EP_IN_SIZE + 0x3b):
+ epnum = (offset - TUSB_EP_IN_SIZE) >> 2;
+ return s->tx_config[epnum];
+ case TUSB_DMA_EP_MAP:
+ return s->dma_map;
+ case TUSB_EP_OUT_SIZE ... (TUSB_EP_OUT_SIZE + 0x3b):
+ epnum = (offset - TUSB_EP_OUT_SIZE) >> 2;
+ return s->rx_config[epnum];
+ case TUSB_EP_MAX_PACKET_SIZE_OFFSET ...
+ (TUSB_EP_MAX_PACKET_SIZE_OFFSET + 0x3b):
+ return 0x00000000; /* TODO */
+ case TUSB_WAIT_COUNT:
+ return 0x00; /* TODO */
+
+ case TUSB_SCRATCH_PAD:
+ return s->scratch;
+
+ case TUSB_PROD_TEST_RESET:
+ return s->test_reset;
+
+ /* DIE IDs */
+ case TUSB_DIDR1_LO:
+ return 0xa9453c59;
+ case TUSB_DIDR1_HI:
+ return 0x54059adf;
+ }
+
+ printf("%s: unknown register at %03x\n", __FUNCTION__, offset);
+ return 0;
+}
+
+static void tusb_async_writeb(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ TUSBState *s = (TUSBState *) opaque;
+
+ switch (addr & 0xfff) {
+ case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff):
+ musb_write[0](s->musb, addr & 0x1ff, value);
+ break;
+
+ case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff):
+ musb_write[0](s->musb, 0x20 + ((addr >> 3) & 0x3c), value);
+ break;
+
+ default:
+ printf("%s: unknown register at %03x\n",
+ __FUNCTION__, (int) (addr & 0xfff));
+ return;
+ }
+}
+
+static void tusb_async_writeh(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ TUSBState *s = (TUSBState *) opaque;
+
+ switch (addr & 0xfff) {
+ case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff):
+ musb_write[1](s->musb, addr & 0x1ff, value);
+ break;
+
+ case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff):
+ musb_write[1](s->musb, 0x20 + ((addr >> 3) & 0x3c), value);
+ break;
+
+ default:
+ printf("%s: unknown register at %03x\n",
+ __FUNCTION__, (int) (addr & 0xfff));
+ return;
+ }
+}
+
+static void tusb_async_writew(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ TUSBState *s = (TUSBState *) opaque;
+ int offset = addr & 0xfff;
+ int epnum;
+
+ switch (offset) {
+ case TUSB_VLYNQ_CTRL:
+ break;
+
+ case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff):
+ musb_write[2](s->musb, offset & 0x1ff, value);
+ break;
+
+ case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff):
+ musb_write[2](s->musb, 0x20 + ((addr >> 3) & 0x3c), value);
+ break;
+
+ case TUSB_DEV_CONF:
+ s->dev_config = value;
+ s->host_mode = (value & TUSB_DEV_CONF_USB_HOST_MODE);
+ if (value & TUSB_DEV_CONF_PROD_TEST_MODE)
+ hw_error("%s: Product Test mode not allowed\n", __FUNCTION__);
+ break;
+
+ case TUSB_PHY_OTG_CTRL_ENABLE:
+ case TUSB_PHY_OTG_CTRL:
+ return; /* TODO */
+ case TUSB_DEV_OTG_TIMER:
+ s->otg_timer_val = value;
+ if (value & TUSB_DEV_OTG_TIMER_ENABLE)
+ qemu_mod_timer(s->otg_timer, qemu_get_clock_ns(vm_clock) +
+ muldiv64(TUSB_DEV_OTG_TIMER_VAL(value),
+ get_ticks_per_sec(), TUSB_DEVCLOCK));
+ else
+ qemu_del_timer(s->otg_timer);
+ break;
+
+ case TUSB_PRCM_CONF:
+ s->prcm_config = value;
+ break;
+ case TUSB_PRCM_MNGMT:
+ s->prcm_mngmt = value;
+ break;
+ case TUSB_PRCM_WAKEUP_CLEAR:
+ break;
+ case TUSB_PRCM_WAKEUP_MASK:
+ s->wkup_mask = value;
+ break;
+
+ case TUSB_PULLUP_1_CTRL:
+ s->pullup[0] = value;
+ break;
+ case TUSB_PULLUP_2_CTRL:
+ s->pullup[1] = value;
+ break;
+ case TUSB_INT_CTRL_CONF:
+ s->control_config = value;
+ tusb_intr_update(s);
+ break;
+
+ case TUSB_USBIP_INT_SET:
+ s->usbip_intr |= value;
+ tusb_usbip_intr_update(s);
+ break;
+ case TUSB_USBIP_INT_CLEAR:
+ s->usbip_intr &= ~value;
+ tusb_usbip_intr_update(s);
+ musb_core_intr_clear(s->musb, ~value);
+ break;
+ case TUSB_USBIP_INT_MASK:
+ s->usbip_mask = value;
+ tusb_usbip_intr_update(s);
+ break;
+
+ case TUSB_DMA_INT_SET:
+ s->dma_intr |= value;
+ tusb_dma_intr_update(s);
+ break;
+ case TUSB_DMA_INT_CLEAR:
+ s->dma_intr &= ~value;
+ tusb_dma_intr_update(s);
+ break;
+ case TUSB_DMA_INT_MASK:
+ s->dma_mask = value;
+ tusb_dma_intr_update(s);
+ break;
+
+ case TUSB_GPIO_INT_SET:
+ s->gpio_intr |= value;
+ tusb_gpio_intr_update(s);
+ break;
+ case TUSB_GPIO_INT_CLEAR:
+ s->gpio_intr &= ~value;
+ tusb_gpio_intr_update(s);
+ break;
+ case TUSB_GPIO_INT_MASK:
+ s->gpio_mask = value;
+ tusb_gpio_intr_update(s);
+ break;
+
+ case TUSB_INT_SRC_SET:
+ s->intr |= value;
+ tusb_intr_update(s);
+ break;
+ case TUSB_INT_SRC_CLEAR:
+ s->intr &= ~value;
+ tusb_intr_update(s);
+ break;
+ case TUSB_INT_MASK:
+ s->mask = value;
+ tusb_intr_update(s);
+ break;
+
+ case TUSB_GPIO_CONF:
+ s->gpio_config = value;
+ break;
+ case TUSB_DMA_REQ_CONF:
+ s->dma_config = value;
+ break;
+ case TUSB_EP0_CONF:
+ s->ep0_config = value & 0x1ff;
+ musb_set_size(s->musb, 0, TUSB_EP0_CONFIG_XFR_SIZE(value),
+ value & TUSB_EP0_CONFIG_DIR_TX);
+ break;
+ case TUSB_EP_IN_SIZE ... (TUSB_EP_IN_SIZE + 0x3b):
+ epnum = (offset - TUSB_EP_IN_SIZE) >> 2;
+ s->tx_config[epnum] = value;
+ musb_set_size(s->musb, epnum + 1, TUSB_EP_CONFIG_XFR_SIZE(value), 1);
+ break;
+ case TUSB_DMA_EP_MAP:
+ s->dma_map = value;
+ break;
+ case TUSB_EP_OUT_SIZE ... (TUSB_EP_OUT_SIZE + 0x3b):
+ epnum = (offset - TUSB_EP_OUT_SIZE) >> 2;
+ s->rx_config[epnum] = value;
+ musb_set_size(s->musb, epnum + 1, TUSB_EP_CONFIG_XFR_SIZE(value), 0);
+ break;
+ case TUSB_EP_MAX_PACKET_SIZE_OFFSET ...
+ (TUSB_EP_MAX_PACKET_SIZE_OFFSET + 0x3b):
+ return; /* TODO */
+ case TUSB_WAIT_COUNT:
+ return; /* TODO */
+
+ case TUSB_SCRATCH_PAD:
+ s->scratch = value;
+ break;
+
+ case TUSB_PROD_TEST_RESET:
+ s->test_reset = value;
+ break;
+
+ default:
+ printf("%s: unknown register at %03x\n", __FUNCTION__, offset);
+ return;
+ }
+}
+
+static const MemoryRegionOps tusb_async_ops = {
+ .old_mmio = {
+ .read = { tusb_async_readb, tusb_async_readh, tusb_async_readw, },
+ .write = { tusb_async_writeb, tusb_async_writeh, tusb_async_writew, },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void tusb_otg_tick(void *opaque)
+{
+ TUSBState *s = (TUSBState *) opaque;
+
+ s->otg_timer_val = 0;
+ s->intr |= TUSB_INT_SRC_OTG_TIMEOUT;
+ tusb_intr_update(s);
+}
+
+static void tusb_power_tick(void *opaque)
+{
+ TUSBState *s = (TUSBState *) opaque;
+
+ if (s->power) {
+ s->intr_ok = ~0;
+ tusb_intr_update(s);
+ }
+}
+
+static void tusb_musb_core_intr(void *opaque, int source, int level)
+{
+ TUSBState *s = (TUSBState *) opaque;
+ uint16_t otg_status = s->otg_status;
+
+ switch (source) {
+ case musb_set_vbus:
+ if (level)
+ otg_status |= TUSB_DEV_OTG_STAT_VBUS_VALID;
+ else
+ otg_status &= ~TUSB_DEV_OTG_STAT_VBUS_VALID;
+
+ /* XXX: only if TUSB_PHY_OTG_CTRL_OTG_VBUS_DET_EN set? */
+ /* XXX: only if TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN set? */
+ if (s->otg_status != otg_status) {
+ s->otg_status = otg_status;
+ s->intr |= TUSB_INT_SRC_VBUS_SENSE_CHNG;
+ tusb_intr_update(s);
+ }
+ break;
+
+ case musb_set_session:
+ /* XXX: only if TUSB_PHY_OTG_CTRL_OTG_SESS_END_EN set? */
+ /* XXX: only if TUSB_PRCM_MNGMT_OTG_SESS_END_EN set? */
+ if (level) {
+ s->otg_status |= TUSB_DEV_OTG_STAT_SESS_VALID;
+ s->otg_status &= ~TUSB_DEV_OTG_STAT_SESS_END;
+ } else {
+ s->otg_status &= ~TUSB_DEV_OTG_STAT_SESS_VALID;
+ s->otg_status |= TUSB_DEV_OTG_STAT_SESS_END;
+ }
+
+ /* XXX: some IRQ or anything? */
+ break;
+
+ case musb_irq_tx:
+ case musb_irq_rx:
+ s->usbip_intr = musb_core_intr_get(s->musb);
+ /* Fall through. */
+ default:
+ if (level)
+ s->intr |= 1 << source;
+ else
+ s->intr &= ~(1 << source);
+ tusb_intr_update(s);
+ break;
+ }
+}
+
+static void tusb6010_power(TUSBState *s, int on)
+{
+ if (!on) {
+ s->power = 0;
+ } else if (!s->power && on) {
+ s->power = 1;
+ /* Pull the interrupt down after TUSB6010 comes up. */
+ s->intr_ok = 0;
+ tusb_intr_update(s);
+ qemu_mod_timer(s->pwr_timer,
+ qemu_get_clock_ns(vm_clock) + get_ticks_per_sec() / 2);
+ }
+}
+
+static void tusb6010_irq(void *opaque, int source, int level)
+{
+ if (source) {
+ tusb_musb_core_intr(opaque, source - 1, level);
+ } else {
+ tusb6010_power(opaque, level);
+ }
+}
+
+static void tusb6010_reset(DeviceState *dev)
+{
+ TUSBState *s = FROM_SYSBUS(TUSBState, SYS_BUS_DEVICE(dev));
+ int i;
+
+ s->test_reset = TUSB_PROD_TEST_RESET_VAL;
+ s->host_mode = 0;
+ s->dev_config = 0;
+ s->otg_status = 0; /* !TUSB_DEV_OTG_STAT_ID_STATUS means host mode */
+ s->power = 0;
+ s->mask = 0xffffffff;
+ s->intr = 0x00000000;
+ s->otg_timer_val = 0;
+ s->scratch = 0;
+ s->prcm_config = 0;
+ s->prcm_mngmt = 0;
+ s->intr_ok = 0;
+ s->usbip_intr = 0;
+ s->usbip_mask = 0;
+ s->gpio_intr = 0;
+ s->gpio_mask = 0;
+ s->gpio_config = 0;
+ s->dma_intr = 0;
+ s->dma_mask = 0;
+ s->dma_map = 0;
+ s->dma_config = 0;
+ s->ep0_config = 0;
+ s->wkup_mask = 0;
+ s->pullup[0] = s->pullup[1] = 0;
+ s->control_config = 0;
+ for (i = 0; i < 15; i++) {
+ s->rx_config[i] = s->tx_config[i] = 0;
+ }
+ musb_reset(s->musb);
+}
+
+static int tusb6010_init(SysBusDevice *dev)
+{
+ TUSBState *s = FROM_SYSBUS(TUSBState, dev);
+ s->otg_timer = qemu_new_timer_ns(vm_clock, tusb_otg_tick, s);
+ s->pwr_timer = qemu_new_timer_ns(vm_clock, tusb_power_tick, s);
+ memory_region_init_io(&s->iomem[1], &tusb_async_ops, s, "tusb-async",
+ UINT32_MAX);
+ sysbus_init_mmio(dev, &s->iomem[0]);
+ sysbus_init_mmio(dev, &s->iomem[1]);
+ sysbus_init_irq(dev, &s->irq);
+ qdev_init_gpio_in(&dev->qdev, tusb6010_irq, musb_irq_max + 1);
+ s->musb = musb_init(&dev->qdev, 1);
+ return 0;
+}
+
+static void tusb6010_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = tusb6010_init;
+ dc->reset = tusb6010_reset;
+}
+
+static const TypeInfo tusb6010_info = {
+ .name = "tusb6010",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(TUSBState),
+ .class_init = tusb6010_class_init,
+};
+
+static void tusb6010_register_types(void)
+{
+ type_register_static(&tusb6010_info);
+}
+
+type_init(tusb6010_register_types)