From: Xinyu Chen Date: Mon, 5 Mar 2012 08:17:52 +0000 (+0800) Subject: ENGR00176068-2 smp_twd: reconfigure clockevents after cpufreq change X-Git-Tag: v3.0.35-fsl_4.1.0~1523 X-Git-Url: https://git.karo-electronics.de/?a=commitdiff_plain;h=eff372004530a00ccd87a5a4ece4ed8308687e48;p=karo-tx-linux.git ENGR00176068-2 smp_twd: reconfigure clockevents after cpufreq change After a cpufreq transition, update the clockevent's frequency by fetching the new clock rate from the clock framework and reprogram the next clock event. Signed-off-by: Xinyu Chen Signed-off-by: Colin Cross Acked-by: Thomas Gleixner Acked-by: Rob Herring Acked-by: Santosh Shilimkar Signed-off-by: Linus Walleij Signed-off-by: Russell King --- diff --git a/arch/arm/kernel/smp_twd.c b/arch/arm/kernel/smp_twd.c index 01c186222f3b..983f8cc078cb 100644 --- a/arch/arm/kernel/smp_twd.c +++ b/arch/arm/kernel/smp_twd.c @@ -17,15 +17,21 @@ #include #include #include +#include +#include +#include #include #include /* set up by the platform code */ void __iomem *twd_base; +static struct clk *twd_clk; static unsigned long twd_timer_rate; +static struct clock_event_device __percpu **twd_evt; + static void twd_set_mode(enum clock_event_mode mode, struct clock_event_device *clk) { @@ -80,6 +86,55 @@ int twd_timer_ack(void) return 0; } +static struct clk *twd_get_clock(void) +{ + return clk_get_sys("smp_twd", NULL); +} + +#ifdef CONFIG_CPU_FREQ +/* + * Updates clockevent frequency when the cpu frequency changes. + * Called on the cpu that is changing frequency with interrupts disabled. + */ +static void twd_update_frequency(void *data) +{ + twd_timer_rate = clk_get_rate(twd_clk); + + clockevents_update_freq(*__this_cpu_ptr(twd_evt), twd_timer_rate); +} + +static int twd_cpufreq_transition(struct notifier_block *nb, + unsigned long state, void *data) +{ + struct cpufreq_freqs *freqs = data; + + /* + * The twd clock events must be reprogrammed to account for the new + * frequency. The timer is local to a cpu, so cross-call to the + * changing cpu. + */ + if (state == CPUFREQ_POSTCHANGE || state == CPUFREQ_RESUMECHANGE) + smp_call_function_single(freqs->cpu, twd_update_frequency, + NULL, 1); + + return NOTIFY_OK; +} + +static struct notifier_block twd_cpufreq_nb = { + .notifier_call = twd_cpufreq_transition, +}; + +static int twd_cpufreq_init(void) +{ + if (twd_evt && *__this_cpu_ptr(twd_evt) && !IS_ERR(twd_clk)) + return cpufreq_register_notifier(&twd_cpufreq_nb, + CPUFREQ_TRANSITION_NOTIFIER); + + return 0; +} +core_initcall(twd_cpufreq_init); + +#endif static void __cpuinit twd_calibrate_rate(void) { unsigned long count; @@ -124,7 +179,24 @@ static void __cpuinit twd_calibrate_rate(void) */ void __cpuinit twd_timer_setup(struct clock_event_device *clk) { - twd_calibrate_rate(); + struct clock_event_device **this_cpu_clk; + + if (!twd_evt) { + + twd_evt = alloc_percpu(struct clock_event_device *); + if (!twd_evt) { + pr_err("twd: can't allocate memory\n"); + return; + } + } + + if (!twd_clk) + twd_clk = twd_get_clock(); + + if (!IS_ERR_OR_NULL(twd_clk)) + twd_timer_rate = clk_get_rate(twd_clk); + else + twd_calibrate_rate(); clk->name = "local_timer"; clk->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT | @@ -132,12 +204,12 @@ void __cpuinit twd_timer_setup(struct clock_event_device *clk) clk->rating = 350; clk->set_mode = twd_set_mode; clk->set_next_event = twd_set_next_event; - clk->shift = 20; - clk->mult = div_sc(twd_timer_rate, NSEC_PER_SEC, clk->shift); - clk->max_delta_ns = clockevent_delta2ns(0xffffffff, clk); - clk->min_delta_ns = clockevent_delta2ns(0xf, clk); - clockevents_register_device(clk); + this_cpu_clk = __this_cpu_ptr(twd_evt); + *this_cpu_clk = clk; + + clockevents_config_and_register(clk, twd_timer_rate, + 0xf, 0xffffffff); /* Make sure our local interrupt controller has this enabled */ gic_enable_ppi(clk->irq);