summaryrefslogtreecommitdiff
path: root/drivers/clocksource/arm_global_timer.c
diff options
context:
space:
mode:
authorMarkus Schneider-Pargmann <msp@baylibre.com>2025-08-19 09:52:41 +0200
committerDaniel Lezcano <daniel.lezcano@linaro.org>2025-09-23 12:41:58 +0200
commit1c4b87c921fb158d853adcb8fd48c2dc07fc6f91 (patch)
tree09807b2a1f321cb33fded96111e0007341086f2f /drivers/clocksource/arm_global_timer.c
parent21b8a635f3b3d6a165fa257808ed381c13c72e9b (diff)
clocksource/drivers/arm_global_timer: Add auto-detection for initial prescaler values
am43xx has a clock tree where the global timer clock is an indirect child of the CPU clock used for frequency scaling: dpll_mpu_ck -- CPU/cpufreq | v dpll_mpu_m2_ck -- divider | v mpu_periphclk -- fixed divider by 2 used for global timer When CPU frequency changes, the global timer's clock notifier rejects the change because the hardcoded prescaler (1 or 2) cannot accommodate the frequency range across all CPU OPPs (300, 600, 720, 800, 1000 MHz). Add platform-specific prescaler auto-detection to solve this issue: - am43xx: prescaler = 50 (calculated as initial_freq/GCD of all OPP freqs) This allows the timer to work across all CPU frequencies after the fixed divider by 2. Tested on am4372-idk-evm. - zynq-7000: prescaler = 2 (preserves previous Kconfig default) - Other platforms: prescaler = 1 (previous default) The Kconfig option now defaults to 0 (auto-detection) but can still override the auto-detected value when set to a non-zero value, preserving existing customization workflows. Signed-off-by: Markus Schneider-Pargmann <msp@baylibre.com> Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org> Tested-by: Kevin Hilman <khilman@baylibre.com> Tested-by: Patrice Chotard <patrice.chotard@foss.st.com> Tested-by: Judith Mendez <jm@ti.com> Reviewed-by: Kevin Hilman <khilman@baylibre.com> Link: https://lore.kernel.org/r/20250819-topic-am43-arm-global-timer-v6-16-v2-1-6d082e2a5161@baylibre.com
Diffstat (limited to 'drivers/clocksource/arm_global_timer.c')
-rw-r--r--drivers/clocksource/arm_global_timer.c44
1 files changed, 39 insertions, 5 deletions
diff --git a/drivers/clocksource/arm_global_timer.c b/drivers/clocksource/arm_global_timer.c
index 2d86bbc2764a..5e3d6bb7e437 100644
--- a/drivers/clocksource/arm_global_timer.c
+++ b/drivers/clocksource/arm_global_timer.c
@@ -263,14 +263,13 @@ static void __init gt_delay_timer_init(void)
register_current_timer_delay(&gt_delay_timer);
}
-static int __init gt_clocksource_init(void)
+static int __init gt_clocksource_init(unsigned int psv)
{
writel(0, gt_base + GT_CONTROL);
writel(0, gt_base + GT_COUNTER0);
writel(0, gt_base + GT_COUNTER1);
/* set prescaler and enable timer on all the cores */
- writel(FIELD_PREP(GT_CONTROL_PRESCALER_MASK,
- CONFIG_ARM_GT_INITIAL_PRESCALER_VAL - 1) |
+ writel(FIELD_PREP(GT_CONTROL_PRESCALER_MASK, psv - 1) |
GT_CONTROL_TIMER_ENABLE, gt_base + GT_CONTROL);
#ifdef CONFIG_CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK
@@ -338,11 +337,45 @@ static int gt_clk_rate_change_cb(struct notifier_block *nb,
return NOTIFY_DONE;
}
+struct gt_prescaler_config {
+ const char *compatible;
+ unsigned long prescaler;
+};
+
+static const struct gt_prescaler_config gt_prescaler_configs[] = {
+ /*
+ * On am43 the global timer clock is a child of the clock used for CPU
+ * OPPs, so the initial prescaler has to be compatible with all OPPs
+ * which are 300, 600, 720, 800 and 1000 with a fixed divider of 2, this
+ * gives us a GCD of 10. Initial frequency is 1000, so the prescaler is
+ * 50.
+ */
+ { .compatible = "ti,am43", .prescaler = 50 },
+ { .compatible = "xlnx,zynq-7000", .prescaler = 2 },
+ { .compatible = NULL }
+};
+
+static unsigned long gt_get_initial_prescaler_value(struct device_node *np)
+{
+ const struct gt_prescaler_config *config;
+
+ if (CONFIG_ARM_GT_INITIAL_PRESCALER_VAL != 0)
+ return CONFIG_ARM_GT_INITIAL_PRESCALER_VAL;
+
+ for (config = gt_prescaler_configs; config->compatible; config++) {
+ if (of_machine_is_compatible(config->compatible))
+ return config->prescaler;
+ }
+
+ return 1;
+}
+
static int __init global_timer_of_register(struct device_node *np)
{
struct clk *gt_clk;
static unsigned long gt_clk_rate;
int err;
+ unsigned long psv;
/*
* In A9 r2p0 the comparators for each processor with the global timer
@@ -378,8 +411,9 @@ static int __init global_timer_of_register(struct device_node *np)
goto out_unmap;
}
+ psv = gt_get_initial_prescaler_value(np);
gt_clk_rate = clk_get_rate(gt_clk);
- gt_target_rate = gt_clk_rate / CONFIG_ARM_GT_INITIAL_PRESCALER_VAL;
+ gt_target_rate = gt_clk_rate / psv;
gt_clk_rate_change_nb.notifier_call =
gt_clk_rate_change_cb;
err = clk_notifier_register(gt_clk, &gt_clk_rate_change_nb);
@@ -404,7 +438,7 @@ static int __init global_timer_of_register(struct device_node *np)
}
/* Register and immediately configure the timer on the boot CPU */
- err = gt_clocksource_init();
+ err = gt_clocksource_init(psv);
if (err)
goto out_irq;