From dd5fa6df90fc2264287670e8bb7ae3b46fd479da Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Thu, 10 Feb 2011 02:04:45 -0800 Subject: [PATCH 1/9] cpu_pm: Add cpu power management notifiers During some CPU power modes entered during idle, hotplug and suspend, peripherals located in the CPU power domain, such as the GIC, localtimers, and VFP, may be powered down. Add a notifier chain that allows drivers for those peripherals to be notified before and after they may be reset. Notified drivers can include VFP co-processor, interrupt controller and it's PM extensions, local CPU timers context save/restore which shouldn't be interrupted. Hence CPU PM event APIs must be called with interrupts disabled. Signed-off-by: Colin Cross Signed-off-by: Santosh Shilimkar Reviewed-by: Kevin Hilman Tested-and-Acked-by: Shawn Guo Tested-by: Kevin Hilman Tested-by: Vishwanath BS Conflicts: kernel/power/Kconfig --- include/linux/cpu_pm.h | 109 ++++++++++++++++++++++ kernel/Makefile | 1 + kernel/cpu_pm.c | 200 +++++++++++++++++++++++++++++++++++++++++ kernel/power/Kconfig | 4 + 4 files changed, 314 insertions(+) create mode 100644 include/linux/cpu_pm.h create mode 100644 kernel/cpu_pm.c diff --git a/include/linux/cpu_pm.h b/include/linux/cpu_pm.h new file mode 100644 index 000000000000..455b233dd3b1 --- /dev/null +++ b/include/linux/cpu_pm.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2011 Google, Inc. + * + * Author: + * Colin Cross + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _LINUX_CPU_PM_H +#define _LINUX_CPU_PM_H + +#include +#include + +/* + * When a CPU goes to a low power state that turns off power to the CPU's + * power domain, the contents of some blocks (floating point coprocessors, + * interrupt controllers, caches, timers) in the same power domain can + * be lost. The cpm_pm notifiers provide a method for platform idle, suspend, + * and hotplug implementations to notify the drivers for these blocks that + * they may be reset. + * + * All cpu_pm notifications must be called with interrupts disabled. + * + * The notifications are split into two classes: CPU notifications and CPU + * cluster notifications. + * + * CPU notifications apply to a single CPU and must be called on the affected + * CPU. They are used to save per-cpu context for affected blocks. + * + * CPU cluster notifications apply to all CPUs in a single power domain. They + * are used to save any global context for affected blocks, and must be called + * after all the CPUs in the power domain have been notified of the low power + * state. + */ + +/* + * Event codes passed as unsigned long val to notifier calls + */ +enum cpu_pm_event { + /* A single cpu is entering a low power state */ + CPU_PM_ENTER, + + /* A single cpu failed to enter a low power state */ + CPU_PM_ENTER_FAILED, + + /* A single cpu is exiting a low power state */ + CPU_PM_EXIT, + + /* A cpu power domain is entering a low power state */ + CPU_CLUSTER_PM_ENTER, + + /* A cpu power domain failed to enter a low power state */ + CPU_CLUSTER_PM_ENTER_FAILED, + + /* A cpu power domain is exiting a low power state */ + CPU_CLUSTER_PM_EXIT, +}; + +#ifdef CONFIG_CPU_PM +int cpu_pm_register_notifier(struct notifier_block *nb); +int cpu_pm_unregister_notifier(struct notifier_block *nb); +int cpu_pm_enter(void); +int cpu_pm_exit(void); +int cpu_cluster_pm_enter(void); +int cpu_cluster_pm_exit(void); + +#else + +static inline int cpu_pm_register_notifier(struct notifier_block *nb) +{ + return 0; +} + +static inline int cpu_pm_unregister_notifier(struct notifier_block *nb) +{ + return 0; +} + +static inline int cpu_pm_enter(void) +{ + return 0; +} + +static inline int cpu_pm_exit(void) +{ + return 0; +} + +static inline int cpu_cluster_pm_enter(void) +{ + return 0; +} + +static inline int cpu_cluster_pm_exit(void) +{ + return 0; +} +#endif +#endif diff --git a/kernel/Makefile b/kernel/Makefile index 2d64cfcc8b42..e4fd98bb7459 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -101,6 +101,7 @@ obj-$(CONFIG_RING_BUFFER) += trace/ obj-$(CONFIG_TRACEPOINTS) += trace/ obj-$(CONFIG_SMP) += sched_cpupri.o obj-$(CONFIG_IRQ_WORK) += irq_work.o +obj-$(CONFIG_CPU_PM) += cpu_pm.o obj-$(CONFIG_PERF_EVENTS) += events/ diff --git a/kernel/cpu_pm.c b/kernel/cpu_pm.c new file mode 100644 index 000000000000..4d1ff4acd04b --- /dev/null +++ b/kernel/cpu_pm.c @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2011 Google, Inc. + * + * Author: + * Colin Cross + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include + +static DEFINE_RWLOCK(cpu_pm_notifier_lock); +static RAW_NOTIFIER_HEAD(cpu_pm_notifier_chain); + +static int cpu_pm_notify(enum cpu_pm_event event, int nr_to_call, int *nr_calls) +{ + int ret; + + ret = __raw_notifier_call_chain(&cpu_pm_notifier_chain, event, NULL, + nr_to_call, nr_calls); + + return notifier_to_errno(ret); +} + +/** + * cpu_pm_register_notifier - register a driver with cpu_pm + * @nb: notifier block to register + * + * Add a driver to a list of drivers that are notified about + * CPU and CPU cluster low power entry and exit. + * + * This function may sleep, and has the same return conditions as + * raw_notifier_chain_register. + */ +int cpu_pm_register_notifier(struct notifier_block *nb) +{ + unsigned long flags; + int ret; + + write_lock_irqsave(&cpu_pm_notifier_lock, flags); + ret = raw_notifier_chain_register(&cpu_pm_notifier_chain, nb); + write_unlock_irqrestore(&cpu_pm_notifier_lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(cpu_pm_register_notifier); + +/** + * cpu_pm_unregister_notifier - unregister a driver with cpu_pm + * @nb: notifier block to be unregistered + * + * Remove a driver from the CPU PM notifier list. + * + * This function may sleep, and has the same return conditions as + * raw_notifier_chain_unregister. + */ +int cpu_pm_unregister_notifier(struct notifier_block *nb) +{ + unsigned long flags; + int ret; + + write_lock_irqsave(&cpu_pm_notifier_lock, flags); + ret = raw_notifier_chain_unregister(&cpu_pm_notifier_chain, nb); + write_unlock_irqrestore(&cpu_pm_notifier_lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(cpu_pm_unregister_notifier); + +/** + * cpm_pm_enter - CPU low power entry notifier + * + * Notifies listeners that a single CPU is entering a low power state that may + * cause some blocks in the same power domain as the cpu to reset. + * + * Must be called on the affected CPU with interrupts disabled. Platform is + * responsible for ensuring that cpu_pm_enter is not called twice on the same + * CPU before cpu_pm_exit is called. Notified drivers can include VFP + * co-processor, interrupt controller and it's PM extensions, local CPU + * timers context save/restore which shouldn't be interrupted. Hence it + * must be called with interrupts disabled. + * + * Return conditions are same as __raw_notifier_call_chain. + */ +int cpu_pm_enter(void) +{ + int nr_calls; + int ret = 0; + + read_lock(&cpu_pm_notifier_lock); + ret = cpu_pm_notify(CPU_PM_ENTER, -1, &nr_calls); + if (ret) + /* + * Inform listeners (nr_calls - 1) about failure of CPU PM + * PM entry who are notified earlier to prepare for it. + */ + cpu_pm_notify(CPU_PM_ENTER_FAILED, nr_calls - 1, NULL); + read_unlock(&cpu_pm_notifier_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(cpu_pm_enter); + +/** + * cpm_pm_exit - CPU low power exit notifier + * + * Notifies listeners that a single CPU is exiting a low power state that may + * have caused some blocks in the same power domain as the cpu to reset. + * + * Notified drivers can include VFP co-processor, interrupt controller + * and it's PM extensions, local CPU timers context save/restore which + * shouldn't be interrupted. Hence it must be called with interrupts disabled. + * + * Return conditions are same as __raw_notifier_call_chain. + */ +int cpu_pm_exit(void) +{ + int ret; + + read_lock(&cpu_pm_notifier_lock); + ret = cpu_pm_notify(CPU_PM_EXIT, -1, NULL); + read_unlock(&cpu_pm_notifier_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(cpu_pm_exit); + +/** + * cpm_cluster_pm_enter - CPU cluster low power entry notifier + * + * Notifies listeners that all cpus in a power domain are entering a low power + * state that may cause some blocks in the same power domain to reset. + * + * Must be called after cpu_pm_enter has been called on all cpus in the power + * domain, and before cpu_pm_exit has been called on any cpu in the power + * domain. Notified drivers can include VFP co-processor, interrupt controller + * and it's PM extensions, local CPU timers context save/restore which + * shouldn't be interrupted. Hence it must be called with interrupts disabled. + * + * Must be called with interrupts disabled. + * + * Return conditions are same as __raw_notifier_call_chain. + */ +int cpu_cluster_pm_enter(void) +{ + int nr_calls; + int ret = 0; + + read_lock(&cpu_pm_notifier_lock); + ret = cpu_pm_notify(CPU_CLUSTER_PM_ENTER, -1, &nr_calls); + if (ret) + /* + * Inform listeners (nr_calls - 1) about failure of CPU cluster + * PM entry who are notified earlier to prepare for it. + */ + cpu_pm_notify(CPU_CLUSTER_PM_ENTER_FAILED, nr_calls - 1, NULL); + read_unlock(&cpu_pm_notifier_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(cpu_cluster_pm_enter); + +/** + * cpm_cluster_pm_exit - CPU cluster low power exit notifier + * + * Notifies listeners that all cpus in a power domain are exiting form a + * low power state that may have caused some blocks in the same power domain + * to reset. + * + * Must be called after cpu_pm_exit has been called on all cpus in the power + * domain, and before cpu_pm_exit has been called on any cpu in the power + * domain. Notified drivers can include VFP co-processor, interrupt controller + * and it's PM extensions, local CPU timers context save/restore which + * shouldn't be interrupted. Hence it must be called with interrupts disabled. + * + * Return conditions are same as __raw_notifier_call_chain. + */ +int cpu_cluster_pm_exit(void) +{ + int ret; + + read_lock(&cpu_pm_notifier_lock); + ret = cpu_pm_notify(CPU_CLUSTER_PM_EXIT, -1, NULL); + read_unlock(&cpu_pm_notifier_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(cpu_cluster_pm_exit); diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig index d50bb90e1c1f..f16f2ff20881 100644 --- a/kernel/power/Kconfig +++ b/kernel/power/Kconfig @@ -295,6 +295,10 @@ config PM_RUNTIME_CLK def_bool y depends on PM_RUNTIME && HAVE_CLK +config CPU_PM + bool + depends on SUSPEND || CPU_IDLE + config SUSPEND_TIME bool "Log time spent in suspend" ---help--- From 1ba5cc0dbba2c232dbc5bb9fd3aca58832df13ff Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Fri, 22 Jul 2011 14:57:09 -0700 Subject: [PATCH 2/9] cpu_pm: call notifiers during suspend Implements syscore_ops in cpu_pm to call the cpu and cpu cluster notifiers during suspend and resume, allowing drivers receiving the notifications to avoid implementing syscore_ops. Signed-off-by: Colin Cross Signed-off-by: Santosh Shilimkar Reviewed-by: Kevin Hilman Tested-and-Acked-by: Shawn Guo Tested-by: Vishwanath BS --- kernel/cpu_pm.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/kernel/cpu_pm.c b/kernel/cpu_pm.c index 4d1ff4acd04b..249152e15308 100644 --- a/kernel/cpu_pm.c +++ b/kernel/cpu_pm.c @@ -20,6 +20,7 @@ #include #include #include +#include static DEFINE_RWLOCK(cpu_pm_notifier_lock); static RAW_NOTIFIER_HEAD(cpu_pm_notifier_chain); @@ -198,3 +199,35 @@ int cpu_cluster_pm_exit(void) return ret; } EXPORT_SYMBOL_GPL(cpu_cluster_pm_exit); + +#ifdef CONFIG_PM +static int cpu_pm_suspend(void) +{ + int ret; + + ret = cpu_pm_enter(); + if (ret) + return ret; + + ret = cpu_cluster_pm_enter(); + return ret; +} + +static void cpu_pm_resume(void) +{ + cpu_cluster_pm_exit(); + cpu_pm_exit(); +} + +static struct syscore_ops cpu_pm_syscore_ops = { + .suspend = cpu_pm_suspend, + .resume = cpu_pm_resume, +}; + +static int cpu_pm_init(void) +{ + register_syscore_ops(&cpu_pm_syscore_ops); + return 0; +} +core_initcall(cpu_pm_init); +#endif From 6cf5d1c9738185bd1e77f648dde45f7fc809bec1 Mon Sep 17 00:00:00 2001 From: Santosh Shilimkar Date: Sat, 10 Sep 2011 11:30:28 +0530 Subject: [PATCH 3/9] ARM: Enable CPU_PM notifiers on ARM machines. Signed-off-by: Santosh Shilimkar Tested-and-Acked-by: Shawn Guo Tested-by: Vishwanath BS --- arch/arm/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 663138dc7d2f..6d78f3af4ee6 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -29,6 +29,7 @@ config ARM select HAVE_GENERIC_HARDIRQS select HAVE_SPARSE_IRQ select GENERIC_IRQ_SHOW + select CPU_PM if (SUSPEND || CPU_IDLE) help The ARM series is a line of low-power-consumption RISC chip designs licensed by ARM Ltd and targeted at embedded applications and From 80e4f17cc859e0229a60e97ff778bf1f1e99dd64 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Thu, 10 Feb 2011 02:08:32 -0800 Subject: [PATCH 4/9] ARM: vfp: Use cpu pm notifiers to save vfp state When the cpu is powered down in a low power mode, the vfp registers may be reset. This patch uses CPU_PM_ENTER and CPU_PM_EXIT notifiers to save and restore the cpu's vfp registers. Signed-off-by: Colin Cross Signed-off-by: Santosh Shilimkar Reviewed-by: Kevin Hilman Tested-and-Acked-by: Shawn Guo Tested-by: Vishwanath BS --- arch/arm/vfp/vfpmodule.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/arch/arm/vfp/vfpmodule.c b/arch/arm/vfp/vfpmodule.c index 53cb06fc3165..bfd1e2f40de4 100644 --- a/arch/arm/vfp/vfpmodule.c +++ b/arch/arm/vfp/vfpmodule.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -403,9 +404,7 @@ static void vfp_enable(void *unused) set_copro_access(access | CPACC_FULL(10) | CPACC_FULL(11)); } -#ifdef CONFIG_PM -#include - +#ifdef CONFIG_CPU_PM static int vfp_pm_suspend(void) { struct thread_info *ti = current_thread_info(); @@ -439,19 +438,33 @@ static void vfp_pm_resume(void) fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_EN); } -static struct syscore_ops vfp_pm_syscore_ops = { - .suspend = vfp_pm_suspend, - .resume = vfp_pm_resume, +static int vfp_cpu_pm_notifier(struct notifier_block *self, unsigned long cmd, + void *v) +{ + switch (cmd) { + case CPU_PM_ENTER: + vfp_pm_suspend(); + break; + case CPU_PM_ENTER_FAILED: + case CPU_PM_EXIT: + vfp_pm_resume(); + break; + } + return NOTIFY_OK; +} + +static struct notifier_block vfp_cpu_pm_notifier_block = { + .notifier_call = vfp_cpu_pm_notifier, }; static void vfp_pm_init(void) { - register_syscore_ops(&vfp_pm_syscore_ops); + cpu_pm_register_notifier(&vfp_cpu_pm_notifier_block); } #else static inline void vfp_pm_init(void) { } -#endif /* CONFIG_PM */ +#endif /* CONFIG_CPU_PM */ void vfp_sync_hwstate(struct thread_info *thread) { From 3006cb821a6596d592cf43537a9b1993581a07d5 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Fri, 20 May 2011 12:19:58 +0100 Subject: [PATCH 5/9] ARM: mm: fix racy ASID rollover broadcast on SMP platforms If ASID rollover is detected on a CPU in an SMP system, a synchronous IPI call is made to force the secondaries to reallocate their current ASIDs. There is a problem where a CPU may be interrupted in the cpu_switch_mm code with the context ID held in r1. After servicing the IPI, the context ID register will be updated with an ASID from the previous generation, polluting the TLB for when that ASID becomes valid in the new generation. This patch disables interrupts during cpu_switch_mm for SMP systems, preventing incoming rollover broadcasts from being serviced while the register state is inconsistent. Additionally, the context resetting code is modified to call cpu_switch_mm, rather than setting the context ID register directly, so that the TTBR always agrees with the ASID. Acked-by: Catalin Marinas Signed-off-by: Will Deacon --- arch/arm/include/asm/proc-fns.h | 14 ++++++++++++++ arch/arm/mm/context.c | 3 +-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/arch/arm/include/asm/proc-fns.h b/arch/arm/include/asm/proc-fns.h index 8ec535e11fd7..35c3fc93b88f 100644 --- a/arch/arm/include/asm/proc-fns.h +++ b/arch/arm/include/asm/proc-fns.h @@ -97,8 +97,22 @@ extern void cpu_resume(void); #ifdef CONFIG_MMU +#ifdef CONFIG_SMP + +#define cpu_switch_mm(pgd, mm) \ + ({ \ + unsigned long flags; \ + local_irq_save(flags); \ + cpu_do_switch_mm(virt_to_phys(pgd), mm); \ + local_irq_restore(flags); \ + }) + +#else /* SMP */ + #define cpu_switch_mm(pgd,mm) cpu_do_switch_mm(virt_to_phys(pgd),mm) +#endif + #define cpu_get_pgd() \ ({ \ unsigned long pg; \ diff --git a/arch/arm/mm/context.c b/arch/arm/mm/context.c index b0ee9ba3cfab..131f381712c9 100644 --- a/arch/arm/mm/context.c +++ b/arch/arm/mm/context.c @@ -99,8 +99,7 @@ static void reset_context(void *info) set_mm_context(mm, asid); /* set the new ASID */ - asm("mcr p15, 0, %0, c13, c0, 1\n" : : "r" (mm->context.id)); - isb(); + cpu_switch_mm(mm->pgd, mm); } #else From 4a6481fc378a90f42d453ad634f8aca1ce1a4d61 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Mon, 16 May 2011 16:21:54 -0700 Subject: [PATCH 6/9] ARM: smp_twd: Reconfigure clockevents after cpufreq change The localtimer's clock changes with the cpu clock. After a cpufreq transition, update the clockevent's frequency and reprogram the next clock event. Adds a clock called "smp_twd" that is used to determine the twd frequency, which can also be used at init time to avoid calibrating the twd frequency. Signed-off-by: Colin Cross Acked-by: Santosh Shilimkar Cc: Thomas Gleixner Cc: Russell King Cc: Rob Herring --- arch/arm/kernel/smp_twd.c | 69 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/arch/arm/kernel/smp_twd.c b/arch/arm/kernel/smp_twd.c index 2c277d40cee6..27761cacd7b0 100644 --- a/arch/arm/kernel/smp_twd.c +++ b/arch/arm/kernel/smp_twd.c @@ -10,21 +10,29 @@ */ #include #include +#include +#include #include #include +#include #include #include #include #include #include +#include #include #include +#define TWD_MIN_RANGE 4 + /* set up by the platform code */ void __iomem *twd_base; +static struct clk *twd_clk; static unsigned long twd_timer_rate; +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) @@ -80,6 +88,49 @@ int twd_timer_ack(void) return 0; } +/* + * 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_reconfigure(__get_cpu_var(twd_ce), twd_timer_rate, + TWD_MIN_RANGE); +} + +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 (!IS_ERR_OR_NULL(twd_clk)) + return cpufreq_register_notifier(&twd_cpufreq_nb, + CPUFREQ_TRANSITION_NOTIFIER); + + return 0; +} +core_initcall(twd_cpufreq_init); + static void __cpuinit twd_calibrate_rate(void) { unsigned long count; @@ -124,7 +175,16 @@ static void __cpuinit twd_calibrate_rate(void) */ void __cpuinit twd_timer_setup(struct clock_event_device *clk) { - twd_calibrate_rate(); + if (twd_clk == NULL) { + twd_clk = clk_get_sys("smp_twd", NULL); + if (IS_ERR_OR_NULL(twd_clk)) + pr_warn("%s: no clock found\n", __func__); + } + + 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,13 +192,16 @@ 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); + + clockevents_calc_mult_shift(clk, twd_timer_rate, TWD_MIN_RANGE); + 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); + __get_cpu_var(twd_ce) = clk; + clockevents_register_device(clk); } From 702f53a01986ff6c4dea1c15de51d34f386e85e7 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Sat, 29 Oct 2011 14:06:07 -0700 Subject: [PATCH 7/9] ARM: smp_twd: use clockevents_update_freq to update frequency clockevents_reconfigure was an older api that doesn't handle updating the max time between ticks when the frequency changes. Under some conditions, the boot value of max_delta_ns scaled by the mult/shift values for the current frequency can result in a value of 0x200000004 selected as the number of cycles to program for a long tick, which gets wrapped to 0x4. Also switch to the matching clockevents_config_and_register function to register the clockevent, which handles converting the min/max ticks to ns during init. Change-Id: I6ca659c309e7bb031cdb1954767b5aa7a022ff44 Signed-off-by: Colin Cross --- arch/arm/kernel/smp_twd.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/arch/arm/kernel/smp_twd.c b/arch/arm/kernel/smp_twd.c index 27761cacd7b0..350a125f8cd6 100644 --- a/arch/arm/kernel/smp_twd.c +++ b/arch/arm/kernel/smp_twd.c @@ -25,8 +25,6 @@ #include #include -#define TWD_MIN_RANGE 4 - /* set up by the platform code */ void __iomem *twd_base; @@ -96,8 +94,7 @@ static void twd_update_frequency(void *data) { twd_timer_rate = clk_get_rate(twd_clk); - clockevents_reconfigure(__get_cpu_var(twd_ce), twd_timer_rate, - TWD_MIN_RANGE); + clockevents_update_freq(__get_cpu_var(twd_ce), twd_timer_rate); } static int twd_cpufreq_transition(struct notifier_block *nb, @@ -193,15 +190,10 @@ void __cpuinit twd_timer_setup(struct clock_event_device *clk) clk->set_mode = twd_set_mode; clk->set_next_event = twd_set_next_event; - clockevents_calc_mult_shift(clk, twd_timer_rate, TWD_MIN_RANGE); - - 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); __get_cpu_var(twd_ce) = clk; - clockevents_register_device(clk); + clockevents_config_and_register(clk, twd_timer_rate, 0xf, 0xffffffff); } From 9f70ef65c4132afb1258390280f579acd32ad747 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Wed, 20 Jul 2011 14:18:46 +0100 Subject: [PATCH 8/9] ARM: twd: register clockevents device before enabling PPI The smp_twd clockevents driver currently enables the local timer PPI before the clockevents device is registered. This can lead to a kernel panic if a spurious timer interrupt is generated before registration has completed since the kernel will treat it as an IPI timer. This patch moves the clockevents device registration before the IRQ unmasking so that we can always handle timer interrupts once they can occur. Acked-by: Marc Zyngier Signed-off-by: Will Deacon --- arch/arm/kernel/smp_twd.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/arm/kernel/smp_twd.c b/arch/arm/kernel/smp_twd.c index 350a125f8cd6..5d9e308a0909 100644 --- a/arch/arm/kernel/smp_twd.c +++ b/arch/arm/kernel/smp_twd.c @@ -190,10 +190,10 @@ void __cpuinit twd_timer_setup(struct clock_event_device *clk) clk->set_mode = twd_set_mode; clk->set_next_event = twd_set_next_event; - /* Make sure our local interrupt controller has this enabled */ - gic_enable_ppi(clk->irq); - __get_cpu_var(twd_ce) = 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); } From d1a4be5e640aad647e5b0a758b6cba6987a0f0ca Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Fri, 20 Jan 2012 12:24:47 +0100 Subject: [PATCH 9/9] ARM: 7297/1: smp_twd: make sure timer is stopped before registering it On secondary CPUs, the Timer Control Register is not reset to a sane value before the timer is registered, and the TRM doesn't seem to indicate any reset value either. In some cases, the kernel will take an interrupt too early, depending on what junk was present in the registers at reset time. The fix is to set the Timer Control Register to 0 before registering the clock_event_device and enabling the interrupt. Problem seen on VE (Cortex A5) and Tegra. Signed-off-by: Marc Zyngier Signed-off-by: Russell King --- arch/arm/kernel/smp_twd.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/kernel/smp_twd.c b/arch/arm/kernel/smp_twd.c index 5d9e308a0909..82347d53a877 100644 --- a/arch/arm/kernel/smp_twd.c +++ b/arch/arm/kernel/smp_twd.c @@ -183,6 +183,8 @@ void __cpuinit twd_timer_setup(struct clock_event_device *clk) else twd_calibrate_rate(); + __raw_writel(0, twd_base + TWD_TIMER_CONTROL); + clk->name = "local_timer"; clk->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_C3STOP;