There is a timer wrap issue on dra7 for the ARM architected timer.
In a typical clock configuration the timer fails to wrap after 388 days.

To work around the issue, we need to use timer-ti-dm percpu timers instead.

Let's configure dmtimer3 and 4 as percpu timers by default, and warn about
the issue if the dtb is not configured properly.

Let's do this as a single patch so it can be backported to v5.8 and later
kernels easily. Note that this patch depends on earlier timer-ti-dm
systimer posted mode fixes, and a preparatory clockevent patch
"clocksource/drivers/timer-ti-dm: Prepare to handle dra7 timer wrap issue".

For more information, please see the errata for "AM572x Sitara Processors
Silicon Revisions 1.1, 2.0":

https://www.ti.com/lit/er/sprz429m/sprz429m.pdf

The concept is based on earlier reference patches done by Tero Kristo and
Keerthy.

Cc: Keerthy <j-keer...@ti.com>
Cc: Tero Kristo <kri...@kernel.org>
Signed-off-by: Tony Lindgren <t...@atomide.com>
---
 arch/arm/boot/dts/dra7-l4.dtsi             |  4 +-
 arch/arm/boot/dts/dra7.dtsi                | 20 ++++++
 drivers/clocksource/timer-ti-dm-systimer.c | 76 ++++++++++++++++++++++
 include/linux/cpuhotplug.h                 |  1 +
 4 files changed, 99 insertions(+), 2 deletions(-)

diff --git a/arch/arm/boot/dts/dra7-l4.dtsi b/arch/arm/boot/dts/dra7-l4.dtsi
--- a/arch/arm/boot/dts/dra7-l4.dtsi
+++ b/arch/arm/boot/dts/dra7-l4.dtsi
@@ -1168,7 +1168,7 @@ timer2: timer@0 {
                        };
                };
 
-               target-module@34000 {                   /* 0x48034000, ap 7 
46.0 */
+               timer3_target: target-module@34000 {    /* 0x48034000, ap 7 
46.0 */
                        compatible = "ti,sysc-omap4-timer", "ti,sysc";
                        reg = <0x34000 0x4>,
                              <0x34010 0x4>;
@@ -1195,7 +1195,7 @@ timer3: timer@0 {
                        };
                };
 
-               target-module@36000 {                   /* 0x48036000, ap 9 
4e.0 */
+               timer4_target: target-module@36000 {    /* 0x48036000, ap 9 
4e.0 */
                        compatible = "ti,sysc-omap4-timer", "ti,sysc";
                        reg = <0x36000 0x4>,
                              <0x36010 0x4>;
diff --git a/arch/arm/boot/dts/dra7.dtsi b/arch/arm/boot/dts/dra7.dtsi
--- a/arch/arm/boot/dts/dra7.dtsi
+++ b/arch/arm/boot/dts/dra7.dtsi
@@ -46,6 +46,7 @@ aliases {
 
        timer {
                compatible = "arm,armv7-timer";
+               status = "disabled";    /* See ARM architected timer wrap 
erratum i940 */
                interrupts = <GIC_PPI 13 (GIC_CPU_MASK_SIMPLE(2) | 
IRQ_TYPE_LEVEL_LOW)>,
                             <GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(2) | 
IRQ_TYPE_LEVEL_LOW)>,
                             <GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(2) | 
IRQ_TYPE_LEVEL_LOW)>,
@@ -1241,3 +1242,22 @@ timer@0 {
                assigned-clock-parents = <&sys_32k_ck>;
        };
 };
+
+/* Local timers, see ARM architected timer wrap erratum i940 */
+&timer3_target {
+       ti,no-reset-on-init;
+       ti,no-idle;
+       timer@0 {
+               assigned-clocks = <&l4per_clkctrl DRA7_L4PER_TIMER3_CLKCTRL 24>;
+               assigned-clock-parents = <&timer_sys_clk_div>;
+       };
+};
+
+&timer4_target {
+       ti,no-reset-on-init;
+       ti,no-idle;
+       timer@0 {
+               assigned-clocks = <&l4per_clkctrl DRA7_L4PER_TIMER4_CLKCTRL 24>;
+               assigned-clock-parents = <&timer_sys_clk_div>;
+       };
+};
diff --git a/drivers/clocksource/timer-ti-dm-systimer.c 
b/drivers/clocksource/timer-ti-dm-systimer.c
--- a/drivers/clocksource/timer-ti-dm-systimer.c
+++ b/drivers/clocksource/timer-ti-dm-systimer.c
@@ -2,6 +2,7 @@
 #include <linux/clk.h>
 #include <linux/clocksource.h>
 #include <linux/clockchips.h>
+#include <linux/cpuhotplug.h>
 #include <linux/interrupt.h>
 #include <linux/io.h>
 #include <linux/iopoll.h>
@@ -634,6 +635,78 @@ static int __init dmtimer_clockevent_init(struct 
device_node *np)
        return error;
 }
 
+/* Dmtimer as percpu timer. See dra7 ARM architected timer wrap erratum i940 */
+static DEFINE_PER_CPU(struct dmtimer_clockevent, dmtimer_percpu_timer);
+
+static int __init dmtimer_percpu_timer_init(struct device_node *np, int cpu)
+{
+       struct dmtimer_clockevent *clkevt;
+       int error;
+
+       if (!cpu_possible(cpu))
+               return -EINVAL;
+
+       if (!of_property_read_bool(np->parent, "ti,no-reset-on-init") ||
+           !of_property_read_bool(np->parent, "ti,no-idle"))
+               pr_warn("Incomplete dtb for percpu dmtimer %pOF\n", np->parent);
+
+       clkevt = per_cpu_ptr(&dmtimer_percpu_timer, cpu);
+
+       error = dmtimer_clkevt_init_common(clkevt, np, CLOCK_EVT_FEAT_ONESHOT,
+                                          cpumask_of(cpu), "percpu-dmtimer",
+                                          500);
+       if (error)
+               return error;
+
+       return 0;
+}
+
+/* See TRM for timer internal resynch latency */
+static int omap_dmtimer_starting_cpu(unsigned int cpu)
+{
+       struct dmtimer_clockevent *clkevt = per_cpu_ptr(&dmtimer_percpu_timer, 
cpu);
+       struct clock_event_device *dev = &clkevt->dev;
+       struct dmtimer_systimer *t = &clkevt->t;
+
+       clockevents_config_and_register(dev, t->rate, 3, ULONG_MAX);
+       irq_force_affinity(dev->irq, cpumask_of(cpu));
+
+       return 0;
+}
+
+static int __init dmtimer_percpu_timer_startup(void)
+{
+       struct dmtimer_clockevent *clkevt = per_cpu_ptr(&dmtimer_percpu_timer, 
0);
+       struct dmtimer_systimer *t = &clkevt->t;
+
+       if (t->sysc) {
+               cpuhp_setup_state(CPUHP_AP_TI_GP_TIMER_STARTING,
+                                 "clockevents/omap/gptimer:starting",
+                                 omap_dmtimer_starting_cpu, NULL);
+       }
+
+       return 0;
+}
+subsys_initcall(dmtimer_percpu_timer_startup);
+
+static int __init dmtimer_percpu_quirk_init(struct device_node *np, u32 pa)
+{
+       struct device_node *arm_timer;
+
+       arm_timer = of_find_compatible_node(NULL, NULL, "arm,armv7-timer");
+       if (of_device_is_available(arm_timer)) {
+               pr_warn_once("ARM architected timer wrap issue i940 
detected\n");
+               return 0;
+       }
+
+       if (pa == 0x48034000)           /* dra7 dmtimer3 */
+               return dmtimer_percpu_timer_init(np, 0);
+       else if (pa == 0x48036000)      /* dra7 dmtimer4 */
+               return dmtimer_percpu_timer_init(np, 1);
+
+       return 0;
+}
+
 /* Clocksource */
 static struct dmtimer_clocksource *
 to_dmtimer_clocksource(struct clocksource *cs)
@@ -767,6 +840,9 @@ static int __init dmtimer_systimer_init(struct device_node 
*np)
        if (clockevent == pa)
                return dmtimer_clockevent_init(np);
 
+       if (of_machine_is_compatible("ti,dra7"))
+               return dmtimer_percpu_quirk_init(np, pa);
+
        return 0;
 }
 
diff --git a/include/linux/cpuhotplug.h b/include/linux/cpuhotplug.h
--- a/include/linux/cpuhotplug.h
+++ b/include/linux/cpuhotplug.h
@@ -135,6 +135,7 @@ enum cpuhp_state {
        CPUHP_AP_RISCV_TIMER_STARTING,
        CPUHP_AP_CLINT_TIMER_STARTING,
        CPUHP_AP_CSKY_TIMER_STARTING,
+       CPUHP_AP_TI_GP_TIMER_STARTING,
        CPUHP_AP_HYPERV_TIMER_STARTING,
        CPUHP_AP_KVM_STARTING,
        CPUHP_AP_KVM_ARM_VGIC_INIT_STARTING,
-- 
2.30.1

Reply via email to