| /* | 
 |  * This file is subject to the terms and conditions of the GNU General Public | 
 |  * License.  See the file "COPYING" in the main directory of this archive | 
 |  * for more details. | 
 |  * | 
 |  * Copyright (C) 2016 Imagination Technologies | 
 |  */ | 
 |  | 
 | #include "qemu/osdep.h" | 
 | #include "qemu/timer.h" | 
 | #include "hw/timer/mips_gictimer.h" | 
 |  | 
 | #define TIMER_PERIOD 10 /* 10 ns period for 100 Mhz frequency */ | 
 |  | 
 | uint32_t mips_gictimer_get_freq(MIPSGICTimerState *gic) | 
 | { | 
 |     return NANOSECONDS_PER_SECOND / TIMER_PERIOD; | 
 | } | 
 |  | 
 | static void gic_vptimer_update(MIPSGICTimerState *gictimer, | 
 |                                    uint32_t vp_index, uint64_t now) | 
 | { | 
 |     uint64_t next; | 
 |     uint32_t wait; | 
 |  | 
 |     wait = gictimer->vptimers[vp_index].comparelo - gictimer->sh_counterlo - | 
 |            (uint32_t)(now / TIMER_PERIOD); | 
 |     next = now + (uint64_t)wait * TIMER_PERIOD; | 
 |  | 
 |     timer_mod(gictimer->vptimers[vp_index].qtimer, next); | 
 | } | 
 |  | 
 | static void gic_vptimer_expire(MIPSGICTimerState *gictimer, uint32_t vp_index, | 
 |                                uint64_t now) | 
 | { | 
 |     if (gictimer->countstop) { | 
 |         /* timer stopped */ | 
 |         return; | 
 |     } | 
 |     gictimer->cb(gictimer->opaque, vp_index); | 
 |     gic_vptimer_update(gictimer, vp_index, now); | 
 | } | 
 |  | 
 | static void gic_vptimer_cb(void *opaque) | 
 | { | 
 |     MIPSGICTimerVPState *vptimer = opaque; | 
 |     MIPSGICTimerState *gictimer = vptimer->gictimer; | 
 |     gic_vptimer_expire(gictimer, vptimer->vp_index, | 
 |                        qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)); | 
 | } | 
 |  | 
 | uint32_t mips_gictimer_get_sh_count(MIPSGICTimerState *gictimer) | 
 | { | 
 |     int i; | 
 |     if (gictimer->countstop) { | 
 |         return gictimer->sh_counterlo; | 
 |     } else { | 
 |         uint64_t now; | 
 |         now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); | 
 |         for (i = 0; i < gictimer->num_vps; i++) { | 
 |             if (timer_pending(gictimer->vptimers[i].qtimer) | 
 |                 && timer_expired(gictimer->vptimers[i].qtimer, now)) { | 
 |                 /* The timer has already expired.  */ | 
 |                 gic_vptimer_expire(gictimer, i, now); | 
 |             } | 
 |         } | 
 |         return gictimer->sh_counterlo + (uint32_t)(now / TIMER_PERIOD); | 
 |     } | 
 | } | 
 |  | 
 | void mips_gictimer_store_sh_count(MIPSGICTimerState *gictimer, uint64_t count) | 
 | { | 
 |     int i; | 
 |     uint64_t now; | 
 |  | 
 |     if (gictimer->countstop || !gictimer->vptimers[0].qtimer) { | 
 |         gictimer->sh_counterlo = count; | 
 |     } else { | 
 |         /* Store new count register */ | 
 |         now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); | 
 |         gictimer->sh_counterlo = count - (uint32_t)(now / TIMER_PERIOD); | 
 |         /* Update timer timer */ | 
 |         for (i = 0; i < gictimer->num_vps; i++) { | 
 |             gic_vptimer_update(gictimer, i, now); | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | uint32_t mips_gictimer_get_vp_compare(MIPSGICTimerState *gictimer, | 
 |                                       uint32_t vp_index) | 
 | { | 
 |     return gictimer->vptimers[vp_index].comparelo; | 
 | } | 
 |  | 
 | void mips_gictimer_store_vp_compare(MIPSGICTimerState *gictimer, | 
 |                                     uint32_t vp_index, uint64_t compare) | 
 | { | 
 |     gictimer->vptimers[vp_index].comparelo = (uint32_t) compare; | 
 |     gic_vptimer_update(gictimer, vp_index, | 
 |                        qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)); | 
 | } | 
 |  | 
 | uint8_t mips_gictimer_get_countstop(MIPSGICTimerState *gictimer) | 
 | { | 
 |     return gictimer->countstop; | 
 | } | 
 |  | 
 | void mips_gictimer_start_count(MIPSGICTimerState *gictimer) | 
 | { | 
 |     gictimer->countstop = 0; | 
 |     mips_gictimer_store_sh_count(gictimer, gictimer->sh_counterlo); | 
 | } | 
 |  | 
 | void mips_gictimer_stop_count(MIPSGICTimerState *gictimer) | 
 | { | 
 |     int i; | 
 |  | 
 |     gictimer->countstop = 1; | 
 |     /* Store the current value */ | 
 |     gictimer->sh_counterlo += | 
 |         (uint32_t)(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / TIMER_PERIOD); | 
 |     for (i = 0; i < gictimer->num_vps; i++) { | 
 |         timer_del(gictimer->vptimers[i].qtimer); | 
 |     } | 
 | } | 
 |  | 
 | MIPSGICTimerState *mips_gictimer_init(void *opaque, uint32_t nvps, | 
 |                                       MIPSGICTimerCB *cb) | 
 | { | 
 |     int i; | 
 |     MIPSGICTimerState *gictimer = g_new(MIPSGICTimerState, 1); | 
 |     gictimer->vptimers = g_new(MIPSGICTimerVPState, nvps); | 
 |     gictimer->countstop = 1; | 
 |     gictimer->num_vps = nvps; | 
 |     gictimer->opaque = opaque; | 
 |     gictimer->cb = cb; | 
 |     for (i = 0; i < nvps; i++) { | 
 |         gictimer->vptimers[i].gictimer = gictimer; | 
 |         gictimer->vptimers[i].vp_index = i; | 
 |         gictimer->vptimers[i].qtimer = timer_new_ns(QEMU_CLOCK_VIRTUAL, | 
 |                                             &gic_vptimer_cb, | 
 |                                             &gictimer->vptimers[i]); | 
 |     } | 
 |     return gictimer; | 
 | } |