/* * 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; }