xref: /openbmc/linux/arch/arm/kernel/smp_twd.c (revision d2912cb1)
1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2f32f4ce2SRussell King /*
3f32f4ce2SRussell King  *  linux/arch/arm/kernel/smp_twd.c
4f32f4ce2SRussell King  *
5f32f4ce2SRussell King  *  Copyright (C) 2002 ARM Ltd.
6f32f4ce2SRussell King  *  All Rights Reserved
7f32f4ce2SRussell King  */
8f32f4ce2SRussell King #include <linux/init.h>
9f32f4ce2SRussell King #include <linux/kernel.h>
105def51b0SLinus Walleij #include <linux/clk.h>
11a894fcc2SStephen Boyd #include <linux/cpu.h>
12f32f4ce2SRussell King #include <linux/delay.h>
13f32f4ce2SRussell King #include <linux/device.h>
145def51b0SLinus Walleij #include <linux/err.h>
15f32f4ce2SRussell King #include <linux/smp.h>
16f32f4ce2SRussell King #include <linux/jiffies.h>
17f32f4ce2SRussell King #include <linux/clockchips.h>
1892485104SMarc Zyngier #include <linux/interrupt.h>
19f32f4ce2SRussell King #include <linux/io.h>
20d8e03643SMarc Zyngier #include <linux/of_irq.h>
21d8e03643SMarc Zyngier #include <linux/of_address.h>
22f32f4ce2SRussell King 
23f32f4ce2SRussell King #include <asm/smp_twd.h>
24f32f4ce2SRussell King 
25f32f4ce2SRussell King /* set up by the platform code */
2692485104SMarc Zyngier static void __iomem *twd_base;
27f32f4ce2SRussell King 
285def51b0SLinus Walleij static struct clk *twd_clk;
29f32f4ce2SRussell King static unsigned long twd_timer_rate;
30a68becd1SLinus Walleij static DEFINE_PER_CPU(bool, percpu_setup_called);
31f32f4ce2SRussell King 
32a894fcc2SStephen Boyd static struct clock_event_device __percpu *twd_evt;
33e1b8c05dSRussell King static unsigned int twd_features =
34e1b8c05dSRussell King 		CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
3581e46f7bSMarc Zyngier static int twd_ppi;
3628af690aSMarc Zyngier 
twd_shutdown(struct clock_event_device * clk)375e253571SViresh Kumar static int twd_shutdown(struct clock_event_device *clk)
38f32f4ce2SRussell King {
395e253571SViresh Kumar 	writel_relaxed(0, twd_base + TWD_TIMER_CONTROL);
405e253571SViresh Kumar 	return 0;
41f32f4ce2SRussell King }
42f32f4ce2SRussell King 
twd_set_oneshot(struct clock_event_device * clk)435e253571SViresh Kumar static int twd_set_oneshot(struct clock_event_device *clk)
445e253571SViresh Kumar {
455e253571SViresh Kumar 	/* period set, and timer enabled in 'next_event' hook */
465e253571SViresh Kumar 	writel_relaxed(TWD_TIMER_CONTROL_IT_ENABLE | TWD_TIMER_CONTROL_ONESHOT,
475e253571SViresh Kumar 		       twd_base + TWD_TIMER_CONTROL);
485e253571SViresh Kumar 	return 0;
495e253571SViresh Kumar }
505e253571SViresh Kumar 
twd_set_periodic(struct clock_event_device * clk)515e253571SViresh Kumar static int twd_set_periodic(struct clock_event_device *clk)
525e253571SViresh Kumar {
535e253571SViresh Kumar 	unsigned long ctrl = TWD_TIMER_CONTROL_ENABLE |
545e253571SViresh Kumar 			     TWD_TIMER_CONTROL_IT_ENABLE |
555e253571SViresh Kumar 			     TWD_TIMER_CONTROL_PERIODIC;
565e253571SViresh Kumar 
575e253571SViresh Kumar 	writel_relaxed(DIV_ROUND_CLOSEST(twd_timer_rate, HZ),
585e253571SViresh Kumar 		       twd_base + TWD_TIMER_LOAD);
592e874ea3SBen Dooks 	writel_relaxed(ctrl, twd_base + TWD_TIMER_CONTROL);
605e253571SViresh Kumar 	return 0;
61f32f4ce2SRussell King }
62f32f4ce2SRussell King 
twd_set_next_event(unsigned long evt,struct clock_event_device * unused)63f32f4ce2SRussell King static int twd_set_next_event(unsigned long evt,
64f32f4ce2SRussell King 			struct clock_event_device *unused)
65f32f4ce2SRussell King {
662e874ea3SBen Dooks 	unsigned long ctrl = readl_relaxed(twd_base + TWD_TIMER_CONTROL);
67f32f4ce2SRussell King 
684c5158d4SRussell King 	ctrl |= TWD_TIMER_CONTROL_ENABLE;
694c5158d4SRussell King 
702e874ea3SBen Dooks 	writel_relaxed(evt, twd_base + TWD_TIMER_COUNTER);
712e874ea3SBen Dooks 	writel_relaxed(ctrl, twd_base + TWD_TIMER_CONTROL);
72f32f4ce2SRussell King 
73f32f4ce2SRussell King 	return 0;
74f32f4ce2SRussell King }
75f32f4ce2SRussell King 
76f32f4ce2SRussell King /*
77f32f4ce2SRussell King  * local_timer_ack: checks for a local timer interrupt.
78f32f4ce2SRussell King  *
79f32f4ce2SRussell King  * If a local timer interrupt has occurred, acknowledge and return 1.
80f32f4ce2SRussell King  * Otherwise, return 0.
81f32f4ce2SRussell King  */
twd_timer_ack(void)8292485104SMarc Zyngier static int twd_timer_ack(void)
83f32f4ce2SRussell King {
842e874ea3SBen Dooks 	if (readl_relaxed(twd_base + TWD_TIMER_INTSTAT)) {
852e874ea3SBen Dooks 		writel_relaxed(1, twd_base + TWD_TIMER_INTSTAT);
86f32f4ce2SRussell King 		return 1;
87f32f4ce2SRussell King 	}
88f32f4ce2SRussell King 
89f32f4ce2SRussell King 	return 0;
90f32f4ce2SRussell King }
91f32f4ce2SRussell King 
twd_timer_stop(void)92a894fcc2SStephen Boyd static void twd_timer_stop(void)
9328af690aSMarc Zyngier {
9406b96c8bSChristoph Lameter 	struct clock_event_device *clk = raw_cpu_ptr(twd_evt);
95a894fcc2SStephen Boyd 
965e253571SViresh Kumar 	twd_shutdown(clk);
9728af690aSMarc Zyngier 	disable_percpu_irq(clk->irq);
9828af690aSMarc Zyngier }
9928af690aSMarc Zyngier 
1002b25d9f6SMike Turquette /*
1012b25d9f6SMike Turquette  * Updates clockevent frequency when the cpu frequency changes.
1022b25d9f6SMike Turquette  * Called on the cpu that is changing frequency with interrupts disabled.
1032b25d9f6SMike Turquette  */
twd_update_frequency(void * new_rate)1042b25d9f6SMike Turquette static void twd_update_frequency(void *new_rate)
1052b25d9f6SMike Turquette {
1062b25d9f6SMike Turquette 	twd_timer_rate = *((unsigned long *) new_rate);
1072b25d9f6SMike Turquette 
10806b96c8bSChristoph Lameter 	clockevents_update_freq(raw_cpu_ptr(twd_evt), twd_timer_rate);
1092b25d9f6SMike Turquette }
1102b25d9f6SMike Turquette 
twd_rate_change(struct notifier_block * nb,unsigned long flags,void * data)1112b25d9f6SMike Turquette static int twd_rate_change(struct notifier_block *nb,
1122b25d9f6SMike Turquette 	unsigned long flags, void *data)
1132b25d9f6SMike Turquette {
1142b25d9f6SMike Turquette 	struct clk_notifier_data *cnd = data;
1152b25d9f6SMike Turquette 
1162b25d9f6SMike Turquette 	/*
1172b25d9f6SMike Turquette 	 * The twd clock events must be reprogrammed to account for the new
1182b25d9f6SMike Turquette 	 * frequency.  The timer is local to a cpu, so cross-call to the
1192b25d9f6SMike Turquette 	 * changing cpu.
1202b25d9f6SMike Turquette 	 */
1212b25d9f6SMike Turquette 	if (flags == POST_RATE_CHANGE)
122cbbe6f82SJason Liu 		on_each_cpu(twd_update_frequency,
1232b25d9f6SMike Turquette 				  (void *)&cnd->new_rate, 1);
1242b25d9f6SMike Turquette 
1252b25d9f6SMike Turquette 	return NOTIFY_OK;
1262b25d9f6SMike Turquette }
1272b25d9f6SMike Turquette 
1282b25d9f6SMike Turquette static struct notifier_block twd_clk_nb = {
1292b25d9f6SMike Turquette 	.notifier_call = twd_rate_change,
1302b25d9f6SMike Turquette };
1312b25d9f6SMike Turquette 
twd_clk_init(void)1322b25d9f6SMike Turquette static int twd_clk_init(void)
1332b25d9f6SMike Turquette {
13406b96c8bSChristoph Lameter 	if (twd_evt && raw_cpu_ptr(twd_evt) && !IS_ERR(twd_clk))
1352b25d9f6SMike Turquette 		return clk_notifier_register(twd_clk, &twd_clk_nb);
1362b25d9f6SMike Turquette 
1372b25d9f6SMike Turquette 	return 0;
1382b25d9f6SMike Turquette }
1392b25d9f6SMike Turquette core_initcall(twd_clk_init);
1402b25d9f6SMike Turquette 
twd_calibrate_rate(void)1418bd26e3aSPaul Gortmaker static void twd_calibrate_rate(void)
142f32f4ce2SRussell King {
14303399c1cSRussell King 	unsigned long count;
144f32f4ce2SRussell King 	u64 waitjiffies;
145f32f4ce2SRussell King 
146f32f4ce2SRussell King 	/*
147f32f4ce2SRussell King 	 * If this is the first time round, we need to work out how fast
148f32f4ce2SRussell King 	 * the timer ticks
149f32f4ce2SRussell King 	 */
150f32f4ce2SRussell King 	if (twd_timer_rate == 0) {
1514ed89f22SRussell King 		pr_info("Calibrating local timer... ");
152f32f4ce2SRussell King 
153f32f4ce2SRussell King 		/* Wait for a tick to start */
154f32f4ce2SRussell King 		waitjiffies = get_jiffies_64() + 1;
155f32f4ce2SRussell King 
156f32f4ce2SRussell King 		while (get_jiffies_64() < waitjiffies)
157f32f4ce2SRussell King 			udelay(10);
158f32f4ce2SRussell King 
159f32f4ce2SRussell King 		/* OK, now the tick has started, let's get the timer going */
160f32f4ce2SRussell King 		waitjiffies += 5;
161f32f4ce2SRussell King 
162f32f4ce2SRussell King 				 /* enable, no interrupt or reload */
1632e874ea3SBen Dooks 		writel_relaxed(0x1, twd_base + TWD_TIMER_CONTROL);
164f32f4ce2SRussell King 
165f32f4ce2SRussell King 				 /* maximum value */
1662e874ea3SBen Dooks 		writel_relaxed(0xFFFFFFFFU, twd_base + TWD_TIMER_COUNTER);
167f32f4ce2SRussell King 
168f32f4ce2SRussell King 		while (get_jiffies_64() < waitjiffies)
169f32f4ce2SRussell King 			udelay(10);
170f32f4ce2SRussell King 
1712e874ea3SBen Dooks 		count = readl_relaxed(twd_base + TWD_TIMER_COUNTER);
172f32f4ce2SRussell King 
173f32f4ce2SRussell King 		twd_timer_rate = (0xFFFFFFFFU - count) * (HZ / 5);
174f32f4ce2SRussell King 
1754ed89f22SRussell King 		pr_cont("%lu.%02luMHz.\n", twd_timer_rate / 1000000,
17690c5ffe5SVitaly Kuzmichev 			(twd_timer_rate / 10000) % 100);
177f32f4ce2SRussell King 	}
178f32f4ce2SRussell King }
179f32f4ce2SRussell King 
twd_handler(int irq,void * dev_id)18028af690aSMarc Zyngier static irqreturn_t twd_handler(int irq, void *dev_id)
18128af690aSMarc Zyngier {
182a894fcc2SStephen Boyd 	struct clock_event_device *evt = dev_id;
18328af690aSMarc Zyngier 
18428af690aSMarc Zyngier 	if (twd_timer_ack()) {
18528af690aSMarc Zyngier 		evt->event_handler(evt);
18628af690aSMarc Zyngier 		return IRQ_HANDLED;
18728af690aSMarc Zyngier 	}
18828af690aSMarc Zyngier 
18928af690aSMarc Zyngier 	return IRQ_NONE;
19028af690aSMarc Zyngier }
19128af690aSMarc Zyngier 
twd_get_clock(struct device_node * np)192bd603455SRob Herring static void twd_get_clock(struct device_node *np)
1935def51b0SLinus Walleij {
1945def51b0SLinus Walleij 	int err;
1955def51b0SLinus Walleij 
196bd603455SRob Herring 	if (np)
197bd603455SRob Herring 		twd_clk = of_clk_get(np, 0);
198bd603455SRob Herring 	else
199bd603455SRob Herring 		twd_clk = clk_get_sys("smp_twd", NULL);
200bd603455SRob Herring 
201bd603455SRob Herring 	if (IS_ERR(twd_clk)) {
202bd603455SRob Herring 		pr_err("smp_twd: clock not found %d\n", (int) PTR_ERR(twd_clk));
203bd603455SRob Herring 		return;
2045def51b0SLinus Walleij 	}
2055def51b0SLinus Walleij 
206bd603455SRob Herring 	err = clk_prepare_enable(twd_clk);
2075def51b0SLinus Walleij 	if (err) {
2082577cf24SLinus Walleij 		pr_err("smp_twd: clock failed to prepare+enable: %d\n", err);
209bd603455SRob Herring 		clk_put(twd_clk);
210bd603455SRob Herring 		return;
2115def51b0SLinus Walleij 	}
2125def51b0SLinus Walleij 
213bd603455SRob Herring 	twd_timer_rate = clk_get_rate(twd_clk);
2145def51b0SLinus Walleij }
2155def51b0SLinus Walleij 
216f32f4ce2SRussell King /*
217f32f4ce2SRussell King  * Setup the local clock events for a CPU.
218f32f4ce2SRussell King  */
twd_timer_setup(void)21947dcd356SOlof Johansson static void twd_timer_setup(void)
220f32f4ce2SRussell King {
22106b96c8bSChristoph Lameter 	struct clock_event_device *clk = raw_cpu_ptr(twd_evt);
222a68becd1SLinus Walleij 	int cpu = smp_processor_id();
22328af690aSMarc Zyngier 
224a68becd1SLinus Walleij 	/*
225a68becd1SLinus Walleij 	 * If the basic setup for this CPU has been done before don't
226a68becd1SLinus Walleij 	 * bother with the below.
227a68becd1SLinus Walleij 	 */
228a68becd1SLinus Walleij 	if (per_cpu(percpu_setup_called, cpu)) {
2292e874ea3SBen Dooks 		writel_relaxed(0, twd_base + TWD_TIMER_CONTROL);
230a894fcc2SStephen Boyd 		clockevents_register_device(clk);
231a68becd1SLinus Walleij 		enable_percpu_irq(clk->irq, 0);
232a894fcc2SStephen Boyd 		return;
233a68becd1SLinus Walleij 	}
234a68becd1SLinus Walleij 	per_cpu(percpu_setup_called, cpu) = true;
235a68becd1SLinus Walleij 
236f32f4ce2SRussell King 	twd_calibrate_rate();
237f32f4ce2SRussell King 
238a68becd1SLinus Walleij 	/*
239a68becd1SLinus Walleij 	 * The following is done once per CPU the first time .setup() is
240a68becd1SLinus Walleij 	 * called.
241a68becd1SLinus Walleij 	 */
2422e874ea3SBen Dooks 	writel_relaxed(0, twd_base + TWD_TIMER_CONTROL);
243c214455fSMarc Zyngier 
244f32f4ce2SRussell King 	clk->name = "local_timer";
245e1b8c05dSRussell King 	clk->features = twd_features;
246f32f4ce2SRussell King 	clk->rating = 350;
2475e253571SViresh Kumar 	clk->set_state_shutdown = twd_shutdown;
2485e253571SViresh Kumar 	clk->set_state_periodic = twd_set_periodic;
2495e253571SViresh Kumar 	clk->set_state_oneshot = twd_set_oneshot;
2505e253571SViresh Kumar 	clk->tick_resume = twd_shutdown;
251f32f4ce2SRussell King 	clk->set_next_event = twd_set_next_event;
25281e46f7bSMarc Zyngier 	clk->irq = twd_ppi;
253a894fcc2SStephen Boyd 	clk->cpumask = cpumask_of(cpu);
25428af690aSMarc Zyngier 
25554d15b1dSLinus Walleij 	clockevents_config_and_register(clk, twd_timer_rate,
25654d15b1dSLinus Walleij 					0xf, 0xffffffff);
25728af690aSMarc Zyngier 	enable_percpu_irq(clk->irq, 0);
25881e46f7bSMarc Zyngier }
25981e46f7bSMarc Zyngier 
twd_timer_starting_cpu(unsigned int cpu)26026b87688SRichard Cochran static int twd_timer_starting_cpu(unsigned int cpu)
261a894fcc2SStephen Boyd {
262a894fcc2SStephen Boyd 	twd_timer_setup();
26326b87688SRichard Cochran 	return 0;
26426b87688SRichard Cochran }
26526b87688SRichard Cochran 
twd_timer_dying_cpu(unsigned int cpu)26626b87688SRichard Cochran static int twd_timer_dying_cpu(unsigned int cpu)
26726b87688SRichard Cochran {
268a894fcc2SStephen Boyd 	twd_timer_stop();
26926b87688SRichard Cochran 	return 0;
270a894fcc2SStephen Boyd }
271a894fcc2SStephen Boyd 
twd_local_timer_common_register(struct device_node * np)272bd603455SRob Herring static int __init twd_local_timer_common_register(struct device_node *np)
27381e46f7bSMarc Zyngier {
27481e46f7bSMarc Zyngier 	int err;
27581e46f7bSMarc Zyngier 
276a894fcc2SStephen Boyd 	twd_evt = alloc_percpu(struct clock_event_device);
277d8e03643SMarc Zyngier 	if (!twd_evt) {
27881e46f7bSMarc Zyngier 		err = -ENOMEM;
279d8e03643SMarc Zyngier 		goto out_free;
28081e46f7bSMarc Zyngier 	}
28181e46f7bSMarc Zyngier 
28281e46f7bSMarc Zyngier 	err = request_percpu_irq(twd_ppi, twd_handler, "twd", twd_evt);
28381e46f7bSMarc Zyngier 	if (err) {
28481e46f7bSMarc Zyngier 		pr_err("twd: can't register interrupt %d (%d)\n", twd_ppi, err);
285d8e03643SMarc Zyngier 		goto out_free;
28681e46f7bSMarc Zyngier 	}
28781e46f7bSMarc Zyngier 
28826b87688SRichard Cochran 	cpuhp_setup_state_nocalls(CPUHP_AP_ARM_TWD_STARTING,
28973c1b41eSThomas Gleixner 				  "arm/timer/twd:starting",
29026b87688SRichard Cochran 				  twd_timer_starting_cpu, twd_timer_dying_cpu);
29181e46f7bSMarc Zyngier 
292bd603455SRob Herring 	twd_get_clock(np);
293194444c5SMarc Gonzalez 	if (!of_property_read_bool(np, "always-on"))
294e1b8c05dSRussell King 		twd_features |= CLOCK_EVT_FEAT_C3STOP;
295bd603455SRob Herring 
296a894fcc2SStephen Boyd 	/*
297a894fcc2SStephen Boyd 	 * Immediately configure the timer on the boot CPU, unless we need
298a894fcc2SStephen Boyd 	 * jiffies to be incrementing to calibrate the rate in which case
299a894fcc2SStephen Boyd 	 * setup the timer in late_time_init.
300a894fcc2SStephen Boyd 	 */
301a894fcc2SStephen Boyd 	if (twd_timer_rate)
302a894fcc2SStephen Boyd 		twd_timer_setup();
303a894fcc2SStephen Boyd 	else
304a894fcc2SStephen Boyd 		late_time_init = twd_timer_setup;
305a894fcc2SStephen Boyd 
30681e46f7bSMarc Zyngier 	return 0;
30781e46f7bSMarc Zyngier 
308d8e03643SMarc Zyngier out_free:
30981e46f7bSMarc Zyngier 	iounmap(twd_base);
310d8e03643SMarc Zyngier 	twd_base = NULL;
31181e46f7bSMarc Zyngier 	free_percpu(twd_evt);
312d8e03643SMarc Zyngier 
31381e46f7bSMarc Zyngier 	return err;
314f32f4ce2SRussell King }
315d8e03643SMarc Zyngier 
twd_local_timer_of_register(struct device_node * np)316dcbc0eddSDaniel Lezcano static int __init twd_local_timer_of_register(struct device_node *np)
317d8e03643SMarc Zyngier {
318d8e03643SMarc Zyngier 	int err;
319d8e03643SMarc Zyngier 
320d8e03643SMarc Zyngier 	twd_ppi = irq_of_parse_and_map(np, 0);
321d8e03643SMarc Zyngier 	if (!twd_ppi) {
322d8e03643SMarc Zyngier 		err = -EINVAL;
323d8e03643SMarc Zyngier 		goto out;
324d8e03643SMarc Zyngier 	}
325d8e03643SMarc Zyngier 
326d8e03643SMarc Zyngier 	twd_base = of_iomap(np, 0);
327d8e03643SMarc Zyngier 	if (!twd_base) {
328d8e03643SMarc Zyngier 		err = -ENOMEM;
329d8e03643SMarc Zyngier 		goto out;
330d8e03643SMarc Zyngier 	}
331d8e03643SMarc Zyngier 
332bd603455SRob Herring 	err = twd_local_timer_common_register(np);
333d8e03643SMarc Zyngier 
334d8e03643SMarc Zyngier out:
335d8e03643SMarc Zyngier 	WARN(err, "twd_local_timer_of_register failed (%d)\n", err);
336dcbc0eddSDaniel Lezcano 	return err;
337d8e03643SMarc Zyngier }
33817273395SDaniel Lezcano TIMER_OF_DECLARE(arm_twd_a9, "arm,cortex-a9-twd-timer", twd_local_timer_of_register);
33917273395SDaniel Lezcano TIMER_OF_DECLARE(arm_twd_a5, "arm,cortex-a5-twd-timer", twd_local_timer_of_register);
34017273395SDaniel Lezcano TIMER_OF_DECLARE(arm_twd_11mp, "arm,arm11mp-twd-timer", twd_local_timer_of_register);
341