aboutsummaryrefslogtreecommitdiff
path: root/hw/timer/mips_gictimer.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/timer/mips_gictimer.c')
-rw-r--r--hw/timer/mips_gictimer.c142
1 files changed, 142 insertions, 0 deletions
diff --git a/hw/timer/mips_gictimer.c b/hw/timer/mips_gictimer.c
new file mode 100644
index 0000000000..3698889475
--- /dev/null
+++ b/hw/timer/mips_gictimer.c
@@ -0,0 +1,142 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2016 Imagination Technologies
+ */
+
+#include "qemu/osdep.h"
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "qemu/timer.h"
+#include "hw/timer/mips_gictimer.h"
+
+#define TIMER_PERIOD 10 /* 10 ns period for 100 Mhz frequency */
+
+static void gic_vptimer_update(MIPSGICTimerState *gictimer,
+ uint32_t vp_index, uint64_t now)
+{
+ uint64_t next;
+ uint32_t wait;
+
+ wait = gictimer->vptimers[vp_index].comparelo - gictimer->sh_counterlo -
+ (uint32_t)(now / TIMER_PERIOD);
+ next = now + (uint64_t)wait * TIMER_PERIOD;
+
+ timer_mod(gictimer->vptimers[vp_index].qtimer, next);
+}
+
+static void gic_vptimer_expire(MIPSGICTimerState *gictimer, uint32_t vp_index,
+ uint64_t now)
+{
+ if (gictimer->countstop) {
+ /* timer stopped */
+ return;
+ }
+ gictimer->cb(gictimer->opaque, vp_index);
+ gic_vptimer_update(gictimer, vp_index, now);
+}
+
+static void gic_vptimer_cb(void *opaque)
+{
+ MIPSGICTimerVPState *vptimer = opaque;
+ MIPSGICTimerState *gictimer = vptimer->gictimer;
+ gic_vptimer_expire(gictimer, vptimer->vp_index,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
+}
+
+uint32_t mips_gictimer_get_sh_count(MIPSGICTimerState *gictimer)
+{
+ int i;
+ if (gictimer->countstop) {
+ return gictimer->sh_counterlo;
+ } else {
+ uint64_t now;
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ for (i = 0; i < gictimer->num_vps; i++) {
+ if (timer_pending(gictimer->vptimers[i].qtimer)
+ && timer_expired(gictimer->vptimers[i].qtimer, now)) {
+ /* The timer has already expired. */
+ gic_vptimer_expire(gictimer, i, now);
+ }
+ }
+ return gictimer->sh_counterlo + (uint32_t)(now / TIMER_PERIOD);
+ }
+}
+
+void mips_gictimer_store_sh_count(MIPSGICTimerState *gictimer, uint64_t count)
+{
+ int i;
+ uint64_t now;
+
+ if (gictimer->countstop || !gictimer->vptimers[0].qtimer) {
+ gictimer->sh_counterlo = count;
+ } else {
+ /* Store new count register */
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ gictimer->sh_counterlo = count - (uint32_t)(now / TIMER_PERIOD);
+ /* Update timer timer */
+ for (i = 0; i < gictimer->num_vps; i++) {
+ gic_vptimer_update(gictimer, i, now);
+ }
+ }
+}
+
+uint32_t mips_gictimer_get_vp_compare(MIPSGICTimerState *gictimer,
+ uint32_t vp_index)
+{
+ return gictimer->vptimers[vp_index].comparelo;
+}
+
+void mips_gictimer_store_vp_compare(MIPSGICTimerState *gictimer,
+ uint32_t vp_index, uint64_t compare)
+{
+ gictimer->vptimers[vp_index].comparelo = (uint32_t) compare;
+ gic_vptimer_update(gictimer, vp_index,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
+}
+
+uint8_t mips_gictimer_get_countstop(MIPSGICTimerState *gictimer)
+{
+ return gictimer->countstop;
+}
+
+void mips_gictimer_start_count(MIPSGICTimerState *gictimer)
+{
+ gictimer->countstop = 0;
+ mips_gictimer_store_sh_count(gictimer, gictimer->sh_counterlo);
+}
+
+void mips_gictimer_stop_count(MIPSGICTimerState *gictimer)
+{
+ int i;
+
+ gictimer->countstop = 1;
+ /* Store the current value */
+ gictimer->sh_counterlo +=
+ (uint32_t)(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / TIMER_PERIOD);
+ for (i = 0; i < gictimer->num_vps; i++) {
+ timer_del(gictimer->vptimers[i].qtimer);
+ }
+}
+
+MIPSGICTimerState *mips_gictimer_init(void *opaque, uint32_t nvps,
+ MIPSGICTimerCB *cb)
+{
+ int i;
+ MIPSGICTimerState *gictimer = g_new(MIPSGICTimerState, 1);
+ gictimer->vptimers = g_new(MIPSGICTimerVPState, nvps);
+ gictimer->countstop = 1;
+ gictimer->num_vps = nvps;
+ gictimer->opaque = opaque;
+ gictimer->cb = cb;
+ for (i = 0; i < nvps; i++) {
+ gictimer->vptimers[i].gictimer = gictimer;
+ gictimer->vptimers[i].vp_index = i;
+ gictimer->vptimers[i].qtimer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+ &gic_vptimer_cb,
+ &gictimer->vptimers[i]);
+ }
+ return gictimer;
+}