diff mbox series

[02/21] hw/timer: Make PERIPHCLK period configurable

Message ID 20250318130817.119636-3-corvin.koehne@gmail.com (mailing list archive)
State New
Headers show
Series Hi, | expand

Commit Message

Corvin Köhne March 18, 2025, 1:07 p.m. UTC
From: YannickV <Y.Vossen@beckhoff.com>

The a9 global timer and arm mp timer rely on the PERIPHCLK
as their clock source. The period of PERIPHCLK (denoted as N)
must be a multiple of the core CLK period, with N being equal
to or greater than two. However, the current implementation
does not take the PERIPHCLK period into account, leading to
unexpected behavior in systems where the application assumes
PERIPHCLK is clocked differently.

The property periphclk-period represents the period N, the CLK
is devided by to get the peripheral clock PERIPHCLK. We can now
configure clock properties for the a9 global timer and arm mp
timer. That ensures timers can behave according to the
applications needs.

The PERIPHCLK period can also be set via the command line, for
example for the a9 global timer:
-global driver=arm.cortex-a9-global-timer,
	       property=periphclk-period,value=2

Information can be found in the Zynq 7000 Soc Technical
Reference Manual under Timers.
https://docs.amd.com/r/en-US/ug585-zynq-7000-SoC-TRM

Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
---
 hw/timer/a9gtimer.c            | 19 ++++++++++++++++++-
 hw/timer/arm_mptimer.c         | 20 +++++++++++++++++++-
 include/hw/timer/a9gtimer.h    |  1 +
 include/hw/timer/arm_mptimer.h |  2 ++
 4 files changed, 40 insertions(+), 2 deletions(-)
diff mbox series

Patch

diff --git a/hw/timer/a9gtimer.c b/hw/timer/a9gtimer.c
index a1f5540e75..83aa75889e 100644
--- a/hw/timer/a9gtimer.c
+++ b/hw/timer/a9gtimer.c
@@ -27,6 +27,7 @@ 
 #include "hw/timer/a9gtimer.h"
 #include "migration/vmstate.h"
 #include "qapi/error.h"
+#include "qemu/error-report.h"
 #include "qemu/timer.h"
 #include "qemu/bitops.h"
 #include "qemu/log.h"
@@ -62,9 +63,17 @@  static inline int a9_gtimer_get_current_cpu(A9GTimerState *s)
 
 static inline uint64_t a9_gtimer_get_conv(A9GTimerState *s)
 {
+    /*
+     * Referring to the ARM-Cortex-A9 MPCore TRM
+     * 
+     * The a9 global timer relies on the PERIPHCLK as its clock source.
+     * The PERIPHCLK clock period must be configured as a multiple of the
+     * main clock CLK. The conversion from the qemu clock (1GHz) to a9
+     * gtimer ticks can be calculated like this:
+     */
     uint64_t prescale = extract32(s->control, R_CONTROL_PRESCALER_SHIFT,
                                   R_CONTROL_PRESCALER_LEN) + 1;
-    uint64_t ret = NANOSECONDS_PER_SECOND * prescale * 10;
+    uint64_t ret = NANOSECONDS_PER_SECOND * prescale * s->periphclk_period;
     return (uint32_t) (ret / s->cpu_clk_freq_hz);
 }
 
@@ -312,6 +321,12 @@  static void a9_gtimer_realize(DeviceState *dev, Error **errp)
     sysbus_init_mmio(sbd, &s->iomem);
     s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, a9_gtimer_update_no_sync, s);
 
+    if (s->periphclk_period < 2) {
+        error_report("Invalid periphclk-period (%lu), must be >= 2",
+                     s->periphclk_period);
+        exit(1);
+    }
+
     for (i = 0; i < s->num_cpu; i++) {
         A9GTimerPerCPU *gtb = &s->per_cpu[i];
 
@@ -377,6 +392,8 @@  static const Property a9_gtimer_properties[] = {
     DEFINE_PROP_UINT64("cpu-freq", A9GTimerState, cpu_clk_freq_hz,
                        NANOSECONDS_PER_SECOND),
     DEFINE_PROP_UINT32("num-cpu", A9GTimerState, num_cpu, 0),
+    DEFINE_PROP_UINT64("periphclk-period", A9GTimerState,
+                       periphclk_period, 10),
 };
 
 static void a9_gtimer_class_init(ObjectClass *klass, void *data)
diff --git a/hw/timer/arm_mptimer.c b/hw/timer/arm_mptimer.c
index a748b6ab1a..767413c77a 100644
--- a/hw/timer/arm_mptimer.c
+++ b/hw/timer/arm_mptimer.c
@@ -27,6 +27,7 @@ 
 #include "hw/timer/arm_mptimer.h"
 #include "migration/vmstate.h"
 #include "qapi/error.h"
+#include "qemu/error-report.h"
 #include "qemu/module.h"
 #include "hw/core/cpu.h"
 
@@ -61,8 +62,16 @@  static inline void timerblock_update_irq(TimerBlock *tb)
 /* Return conversion factor from mpcore timer ticks to qemu timer ticks.  */
 static inline uint32_t timerblock_scale(TimerBlock *tb, uint32_t control)
 {
+    /*
+     * Referring to the ARM-Cortex-A9 MPCore TRM
+     * 
+     * The arm mp timer relies on the PERIPHCLK as its clock source.
+     * The PERIPHCLK clock period must be configured as a multiple of the
+     * main clock CLK. The conversion from the qemu clock (1GHz) to arm mp
+     * timer ticks can be calculated like this:
+     */
     uint64_t prescale = (((control >> 8) & 0xff) + 1);
-    uint64_t ret = NANOSECONDS_PER_SECOND * prescale * 10;
+    uint64_t ret = NANOSECONDS_PER_SECOND * prescale * tb->periphclk_period;
     return (uint32_t) (ret / tb->freq_hz);
 }
 
@@ -273,6 +282,12 @@  static void arm_mptimer_realize(DeviceState *dev, Error **errp)
     for (i = 0; i < s->num_cpu; i++) {
         TimerBlock *tb = &s->timerblock[i];
         tb->freq_hz = s->clk_freq_hz;
+        if (s->periphclk_period < 2) {
+            error_report("Invalid periphclk-period (%lu), must be >= 2",
+                         s->periphclk_period);
+            exit(1);
+        }
+        tb->periphclk_period = s->periphclk_period;
         tb->timer = ptimer_init(timerblock_tick, tb, PTIMER_POLICY);
         sysbus_init_irq(sbd, &tb->irq);
         memory_region_init_io(&tb->iomem, OBJECT(s), &timerblock_ops, tb,
@@ -288,6 +303,7 @@  static const VMStateDescription vmstate_timerblock = {
     .fields = (const VMStateField[]) {
         VMSTATE_UINT32(control, TimerBlock),
         VMSTATE_UINT64(freq_hz, TimerBlock),
+        VMSTATE_UINT64(periphclk_period, TimerBlock),
         VMSTATE_UINT32(status, TimerBlock),
         VMSTATE_PTIMER(timer, TimerBlock),
         VMSTATE_END_OF_LIST()
@@ -309,6 +325,8 @@  static const Property arm_mptimer_properties[] = {
     DEFINE_PROP_UINT64("clk-freq", ARMMPTimerState, clk_freq_hz,
                        NANOSECONDS_PER_SECOND),
     DEFINE_PROP_UINT32("num-cpu", ARMMPTimerState, num_cpu, 0),
+    DEFINE_PROP_UINT64("periphclk-period", ARMMPTimerState,
+                       periphclk_period, 10),
 };
 
 static void arm_mptimer_class_init(ObjectClass *klass, void *data)
diff --git a/include/hw/timer/a9gtimer.h b/include/hw/timer/a9gtimer.h
index 8ce507a793..edb51f91e3 100644
--- a/include/hw/timer/a9gtimer.h
+++ b/include/hw/timer/a9gtimer.h
@@ -77,6 +77,7 @@  struct A9GTimerState {
     MemoryRegion iomem;
     /* static props */
     uint64_t cpu_clk_freq_hz;
+    uint64_t periphclk_period;
     uint32_t num_cpu;
 
     QEMUTimer *timer;
diff --git a/include/hw/timer/arm_mptimer.h b/include/hw/timer/arm_mptimer.h
index 8b936cceac..2c4cb5c1c3 100644
--- a/include/hw/timer/arm_mptimer.h
+++ b/include/hw/timer/arm_mptimer.h
@@ -32,6 +32,7 @@  typedef struct {
     uint32_t status;
     struct ptimer_state *timer;
     uint64_t freq_hz;
+    uint64_t periphclk_period;
     qemu_irq irq;
     MemoryRegion iomem;
 } TimerBlock;
@@ -45,6 +46,7 @@  struct ARMMPTimerState {
     /*< public >*/
 
     uint64_t clk_freq_hz;
+    uint64_t periphclk_period;
     uint32_t num_cpu;
     TimerBlock timerblock[ARM_MPTIMER_MAX_CPUS];
     MemoryRegion iomem;