diff options
author | Jonas Aaberg <jonas.aberg@stericsson.com> | 2011-09-22 15:20:44 +0200 |
---|---|---|
committer | Robert Marklund <robert.marklund@stericsson.com> | 2011-10-05 13:01:12 +0200 |
commit | 8364f08ab5f8dd3217ee2f2d4b8393c186e4d507 (patch) | |
tree | 1fa8a23aa32f890bf776b1e087ead7d408cb99b7 | |
parent | 6bbed941d76cd37f9319c8b8c08d8ea642cb9370 (diff) |
Reset to linux-next patch:
commit 774959368b9b567685179682cebfcf63e540b17b
Author: Colin Cross <ccross@android.com>
Date: Thu May 19 17:24:51 2011 -0700
ARM: smp_twd: Reconfigure clockevents after cpufreq change
Change-Id: If31593fe23c00df528af597e6fd2baaf0c102753
Signed-off-by: Jonas Aaberg <jonas.aberg@stericsson.com>
Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32132
-rw-r--r-- | arch/arm/include/asm/smp_twd.h | 12 | ||||
-rw-r--r-- | arch/arm/kernel/smp_twd.c | 184 | ||||
-rw-r--r-- | arch/arm/mach-ux500/localtimer.c | 11 |
3 files changed, 73 insertions, 134 deletions
diff --git a/arch/arm/include/asm/smp_twd.h b/arch/arm/include/asm/smp_twd.h index 51197634059..fed9981fba0 100644 --- a/arch/arm/include/asm/smp_twd.h +++ b/arch/arm/include/asm/smp_twd.h @@ -17,24 +17,12 @@ #define TWD_TIMER_CONTROL_ONESHOT (0 << 1) #define TWD_TIMER_CONTROL_PERIODIC (1 << 1) #define TWD_TIMER_CONTROL_IT_ENABLE (1 << 2) -#define TWD_TIMER_CONTROL_PRESCALE_MASK (0xFF << 8) struct clock_event_device; extern void __iomem *twd_base; -void twd_timer_stop(void); int twd_timer_ack(void); void twd_timer_setup(struct clock_event_device *); -/* - * Use this setup function on systems that support cpufreq. - * periphclk_prescaler is the fixed divider value between the cpu - * clock and the PERIPHCLK clock that feeds the TWD. target_rate should be - * low enough that the prescaler can accurately reach the target rate from the - * lowest cpu frequency, but high enough to give a reasonable timer accuracy. - */ -void twd_timer_setup_scalable(struct clock_event_device *, - unsigned long target_rate, unsigned int periphclk_prescaler); - #endif diff --git a/arch/arm/kernel/smp_twd.c b/arch/arm/kernel/smp_twd.c index 70fa99d740e..36c12ef9499 100644 --- a/arch/arm/kernel/smp_twd.c +++ b/arch/arm/kernel/smp_twd.c @@ -10,14 +10,17 @@ */ #include <linux/init.h> #include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/cpufreq.h> #include <linux/delay.h> #include <linux/device.h> +#include <linux/err.h> #include <linux/smp.h> #include <linux/jiffies.h> #include <linux/clockchips.h> #include <linux/irq.h> #include <linux/io.h> -#include <linux/cpufreq.h> +#include <linux/percpu.h> #include <asm/smp_twd.h> #include <asm/hardware/gic.h> @@ -25,27 +28,21 @@ /* set up by the platform code */ void __iomem *twd_base; +static struct clk *twd_clk; static unsigned long twd_timer_rate; -static unsigned long twd_periphclk_prescaler; -static unsigned long twd_target_rate; - -static DEFINE_PER_CPU(unsigned long, twd_ctrl); -static DEFINE_PER_CPU(unsigned long, twd_load); +static DEFINE_PER_CPU(struct clock_event_device *, twd_ce); static void twd_set_mode(enum clock_event_mode mode, struct clock_event_device *clk) { unsigned long ctrl; - int this_cpu = smp_processor_id(); - - __raw_writel(per_cpu(twd_load, this_cpu), - twd_base + TWD_TIMER_LOAD); switch (mode) { case CLOCK_EVT_MODE_PERIODIC: /* timer load already set up */ ctrl = TWD_TIMER_CONTROL_ENABLE | TWD_TIMER_CONTROL_IT_ENABLE | TWD_TIMER_CONTROL_PERIODIC; + __raw_writel(twd_timer_rate / HZ, twd_base + TWD_TIMER_LOAD); break; case CLOCK_EVT_MODE_ONESHOT: /* period set, and timer enabled in 'next_event' hook */ @@ -57,22 +54,18 @@ static void twd_set_mode(enum clock_event_mode mode, ctrl = 0; } - ctrl |= per_cpu(twd_ctrl, this_cpu) & TWD_TIMER_CONTROL_PRESCALE_MASK; - __raw_writel(ctrl, twd_base + TWD_TIMER_CONTROL); - per_cpu(twd_ctrl, this_cpu) = ctrl; } static int twd_set_next_event(unsigned long evt, struct clock_event_device *unused) { - int this_cpu = smp_processor_id(); + unsigned long ctrl = __raw_readl(twd_base + TWD_TIMER_CONTROL); - per_cpu(twd_ctrl, this_cpu) |= TWD_TIMER_CONTROL_ENABLE; + ctrl |= TWD_TIMER_CONTROL_ENABLE; __raw_writel(evt, twd_base + TWD_TIMER_COUNTER); - __raw_writel(per_cpu(twd_ctrl, this_cpu), - twd_base + TWD_TIMER_CONTROL); + __raw_writel(ctrl, twd_base + TWD_TIMER_CONTROL); return 0; } @@ -93,50 +86,51 @@ int twd_timer_ack(void) return 0; } +#ifdef CONFIG_CPU_FREQ + /* - * must be called with interrupts disabled and on the cpu that is being changed + * Updates clockevent frequency when the cpu frequency changes. + * Called on the cpu that is changing frequency with interrupts disabled. */ -static void twd_update_cpu_frequency(unsigned long new_rate) +static void twd_update_frequency(void *data) { - int prescaler; - int this_cpu = smp_processor_id(); + twd_timer_rate = clk_get_rate(twd_clk); - BUG_ON(twd_periphclk_prescaler == 0 || twd_target_rate == 0); - - twd_timer_rate = new_rate / twd_periphclk_prescaler; - - prescaler = DIV_ROUND_UP(twd_timer_rate, twd_target_rate); - prescaler = clamp(prescaler - 1, 0, 0xFF); - - per_cpu(twd_ctrl, this_cpu) &= ~TWD_TIMER_CONTROL_PRESCALE_MASK; - per_cpu(twd_ctrl, this_cpu) |= prescaler << 8; - __raw_writel(per_cpu(twd_ctrl, this_cpu), - twd_base + TWD_TIMER_CONTROL); + clockevents_update_freq(__get_cpu_var(twd_ce), twd_timer_rate); } -static void twd_update_cpu_frequency_on_cpu(void *data) +static int twd_cpufreq_transition(struct notifier_block *nb, + unsigned long state, void *data) { - struct cpufreq_freqs *freq = 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); - twd_update_cpu_frequency(freq->new * 1000); + return NOTIFY_OK; } -static int twd_cpufreq_notifier(struct notifier_block *nb, - unsigned long event, void *data) -{ - struct cpufreq_freqs *freq = data; +static struct notifier_block twd_cpufreq_nb = { + .notifier_call = twd_cpufreq_transition, +}; - if (event == CPUFREQ_RESUMECHANGE || - (event == CPUFREQ_PRECHANGE && freq->new > freq->old) || - (event == CPUFREQ_POSTCHANGE && freq->new < freq->old)) - on_each_cpu(twd_update_cpu_frequency_on_cpu, freq, 1); +static int twd_cpufreq_init(void) +{ + if (!IS_ERR_OR_NULL(twd_clk)) + return cpufreq_register_notifier(&twd_cpufreq_nb, + CPUFREQ_TRANSITION_NOTIFIER); return 0; } +core_initcall(twd_cpufreq_init); -static struct notifier_block twd_cpufreq_notifier_block = { - .notifier_call = twd_cpufreq_notifier, -}; +#endif static void __cpuinit twd_calibrate_rate(void) { @@ -160,11 +154,7 @@ static void __cpuinit twd_calibrate_rate(void) waitjiffies += 5; /* enable, no interrupt or reload */ - __raw_writel(TWD_TIMER_CONTROL_ENABLE, - twd_base + TWD_TIMER_CONTROL); - - per_cpu(twd_ctrl, smp_processor_id()) = - TWD_TIMER_CONTROL_ENABLE; + __raw_writel(0x1, twd_base + TWD_TIMER_CONTROL); /* maximum value */ __raw_writel(0xFFFFFFFFU, twd_base + TWD_TIMER_COUNTER); @@ -181,82 +171,52 @@ static void __cpuinit twd_calibrate_rate(void) } } +static struct clk *twd_get_clock(void) +{ + struct clk *clk; + int err; + + clk = clk_get_sys("smp_twd", NULL); + if (IS_ERR(clk)) { + pr_err("smp_twd: clock not found: %d\n", (int)PTR_ERR(clk)); + return clk; + } + + err = clk_enable(clk); + if (err) { + pr_err("smp_twd: clock failed to enable: %d\n", err); + clk_put(clk); + return ERR_PTR(err); + } + + return clk; +} + /* * Setup the local clock events for a CPU. */ -static void __cpuinit __twd_timer_setup(struct clock_event_device *clk, - unsigned long target_rate, unsigned int periphclk_prescaler) +void __cpuinit twd_timer_setup(struct clock_event_device *clk) { - unsigned long cpu_rate; - unsigned long twd_tick_rate; - - twd_calibrate_rate(); - - if (target_rate && periphclk_prescaler) { - cpu_rate = twd_timer_rate * periphclk_prescaler; - twd_target_rate = target_rate; - twd_periphclk_prescaler = periphclk_prescaler; - twd_update_cpu_frequency(cpu_rate); - twd_tick_rate = twd_target_rate; - } else { - twd_tick_rate = twd_timer_rate; - } + if (!twd_clk) + twd_clk = twd_get_clock(); - per_cpu(twd_load, smp_processor_id()) = twd_tick_rate / HZ; + if (!IS_ERR_OR_NULL(twd_clk)) + twd_timer_rate = clk_get_rate(twd_clk); + else + twd_calibrate_rate(); clk->name = "local_timer"; -#if defined(CONFIG_GENERIC_CLOCKEVENTS_BROADCAST) && \ - defined(CONFIG_LOCAL_TIMERS) clk->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_C3STOP; -#else - clk->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT; -#endif 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_tick_rate, NSEC_PER_SEC, clk->shift); - clk->max_delta_ns = clockevent_delta2ns(0xffffffff, clk); - clk->min_delta_ns = clockevent_delta2ns(0xf, clk); /* Make sure our local interrupt controller has this enabled */ gic_enable_ppi(clk->irq); - clockevents_register_device(clk); -} - -void __cpuinit twd_timer_setup_scalable(struct clock_event_device *clk, - unsigned long target_rate, unsigned int periphclk_prescaler) -{ - __twd_timer_setup(clk, target_rate, periphclk_prescaler); -} - -void __cpuinit twd_timer_setup(struct clock_event_device *clk) -{ - __twd_timer_setup(clk, 0, 0); -} - -static int twd_timer_setup_cpufreq(void) -{ - if (twd_periphclk_prescaler) - cpufreq_register_notifier(&twd_cpufreq_notifier_block, - CPUFREQ_TRANSITION_NOTIFIER); - - return 0; -} -arch_initcall(twd_timer_setup_cpufreq); + __get_cpu_var(twd_ce) = clk; -#ifdef CONFIG_HOTPLUG_CPU -/* - * take a local timer down - */ -void twd_timer_stop(void) -{ - int this_cpu = smp_processor_id(); - per_cpu(twd_ctrl, this_cpu) &= ~(TWD_TIMER_CONTROL_ENABLE | - TWD_TIMER_CONTROL_IT_ENABLE); - __raw_writel(per_cpu(twd_ctrl, this_cpu), - twd_base + TWD_TIMER_CONTROL); + clockevents_config_and_register(clk, twd_timer_rate, + 0xf, 0xffffffff); } -#endif diff --git a/arch/arm/mach-ux500/localtimer.c b/arch/arm/mach-ux500/localtimer.c index 30d4f3e1557..5ba113309a0 100644 --- a/arch/arm/mach-ux500/localtimer.c +++ b/arch/arm/mach-ux500/localtimer.c @@ -18,21 +18,12 @@ #include <asm/smp_twd.h> #include <asm/localtimer.h> -#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST -void smp_timer_broadcast(const struct cpumask *mask); -#endif - /* * Setup the local clock events for a CPU. */ int __cpuinit local_timer_setup(struct clock_event_device *evt) { evt->irq = IRQ_LOCALTIMER; - -#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST - evt->broadcast = smp_timer_broadcast; -#endif - - twd_timer_setup_scalable(evt, 2500 * 1000, 2); + twd_timer_setup(evt); return 0; } |