diff options
Diffstat (limited to 'drivers/cpuidle')
-rw-r--r-- | drivers/cpuidle/Kconfig | 6 | ||||
-rw-r--r-- | drivers/cpuidle/Makefile | 2 | ||||
-rw-r--r-- | drivers/cpuidle/cpuidle-calxeda.c | 57 | ||||
-rw-r--r-- | drivers/cpuidle/cpuidle-kirkwood.c | 29 | ||||
-rw-r--r-- | drivers/cpuidle/cpuidle.c | 153 | ||||
-rw-r--r-- | drivers/cpuidle/driver.c | 31 |
6 files changed, 141 insertions, 137 deletions
diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig index 071e2c3eec4f..c4cc27e5c8a5 100644 --- a/drivers/cpuidle/Kconfig +++ b/drivers/cpuidle/Kconfig @@ -39,10 +39,4 @@ config CPU_IDLE_CALXEDA help Select this to enable cpuidle on Calxeda processors. -config CPU_IDLE_KIRKWOOD - bool "CPU Idle Driver for Kirkwood processors" - depends on ARCH_KIRKWOOD - help - Select this to enable cpuidle on Kirkwood processors. - endif diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile index 24c6e7d945ed..0d8bd55e776f 100644 --- a/drivers/cpuidle/Makefile +++ b/drivers/cpuidle/Makefile @@ -6,4 +6,4 @@ obj-y += cpuidle.o driver.o governor.o sysfs.o governors/ obj-$(CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED) += coupled.o obj-$(CONFIG_CPU_IDLE_CALXEDA) += cpuidle-calxeda.o -obj-$(CONFIG_CPU_IDLE_KIRKWOOD) += cpuidle-kirkwood.o +obj-$(CONFIG_ARCH_KIRKWOOD) += cpuidle-kirkwood.o diff --git a/drivers/cpuidle/cpuidle-calxeda.c b/drivers/cpuidle/cpuidle-calxeda.c index e1aab38c5a8d..223379169cb0 100644 --- a/drivers/cpuidle/cpuidle-calxeda.c +++ b/drivers/cpuidle/cpuidle-calxeda.c @@ -1,7 +1,7 @@ /* * Copyright 2012 Calxeda, Inc. * - * Based on arch/arm/plat-mxc/cpuidle.c: + * Based on arch/arm/plat-mxc/cpuidle.c: #v3.7 * Copyright 2012 Freescale Semiconductor, Inc. * Copyright 2012 Linaro Ltd. * @@ -16,6 +16,8 @@ * * You should have received a copy of the GNU General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. + * + * Maintainer: Rob Herring <rob.herring@calxeda.com> */ #include <linux/cpuidle.h> @@ -35,8 +37,6 @@ extern void highbank_set_cpu_jump(int cpu, void *jump_addr); extern void *scu_base_addr; -static struct cpuidle_device __percpu *calxeda_idle_cpuidle_devices; - static inline unsigned int get_auxcr(void) { unsigned int val; @@ -85,22 +85,8 @@ static int calxeda_pwrdown_idle(struct cpuidle_device *dev, return index; } -static void calxeda_idle_cpuidle_devices_uninit(void) -{ - int i; - struct cpuidle_device *dev; - - for_each_possible_cpu(i) { - dev = per_cpu_ptr(calxeda_idle_cpuidle_devices, i); - cpuidle_unregister_device(dev); - } - - free_percpu(calxeda_idle_cpuidle_devices); -} - static struct cpuidle_driver calxeda_idle_driver = { .name = "calxeda_idle", - .en_core_tk_irqen = 1, .states = { ARM_CPUIDLE_WFI_STATE, { @@ -118,44 +104,9 @@ static struct cpuidle_driver calxeda_idle_driver = { static int __init calxeda_cpuidle_init(void) { - int cpu_id; - int ret; - struct cpuidle_device *dev; - struct cpuidle_driver *drv = &calxeda_idle_driver; - if (!of_machine_is_compatible("calxeda,highbank")) return -ENODEV; - ret = cpuidle_register_driver(drv); - if (ret) - return ret; - - calxeda_idle_cpuidle_devices = alloc_percpu(struct cpuidle_device); - if (calxeda_idle_cpuidle_devices == NULL) { - ret = -ENOMEM; - goto unregister_drv; - } - - /* initialize state data for each cpuidle_device */ - for_each_possible_cpu(cpu_id) { - dev = per_cpu_ptr(calxeda_idle_cpuidle_devices, cpu_id); - dev->cpu = cpu_id; - dev->state_count = drv->state_count; - - ret = cpuidle_register_device(dev); - if (ret) { - pr_err("Failed to register cpu %u, error: %d\n", - cpu_id, ret); - goto uninit; - } - } - - return 0; - -uninit: - calxeda_idle_cpuidle_devices_uninit(); -unregister_drv: - cpuidle_unregister_driver(drv); - return ret; + return cpuidle_register(&calxeda_idle_driver, NULL); } module_init(calxeda_cpuidle_init); diff --git a/drivers/cpuidle/cpuidle-kirkwood.c b/drivers/cpuidle/cpuidle-kirkwood.c index 670aa1e55cd6..521b0a7fdd89 100644 --- a/drivers/cpuidle/cpuidle-kirkwood.c +++ b/drivers/cpuidle/cpuidle-kirkwood.c @@ -1,6 +1,4 @@ /* - * arch/arm/mach-kirkwood/cpuidle.c - * * CPU idle Marvell Kirkwood SoCs * * This file is licensed under the terms of the GNU General Public @@ -11,6 +9,9 @@ * to implement two idle states - * #1 wait-for-interrupt * #2 wait-for-interrupt and DDR self refresh + * + * Maintainer: Jason Cooper <jason@lakedaemon.net> + * Maintainer: Andrew Lunn <andrew@lunn.ch> */ #include <linux/kernel.h> @@ -41,7 +42,6 @@ static int kirkwood_enter_idle(struct cpuidle_device *dev, static struct cpuidle_driver kirkwood_idle_driver = { .name = "kirkwood_idle", .owner = THIS_MODULE, - .en_core_tk_irqen = 1, .states[0] = ARM_CPUIDLE_WFI_STATE, .states[1] = { .enter = kirkwood_enter_idle, @@ -53,9 +53,6 @@ static struct cpuidle_driver kirkwood_idle_driver = { }, .state_count = KIRKWOOD_MAX_STATES, }; -static struct cpuidle_device *device; - -static DEFINE_PER_CPU(struct cpuidle_device, kirkwood_cpuidle_device); /* Initialize CPU idle by registering the idle states */ static int kirkwood_cpuidle_probe(struct platform_device *pdev) @@ -66,26 +63,16 @@ static int kirkwood_cpuidle_probe(struct platform_device *pdev) if (res == NULL) return -EINVAL; - ddr_operation_base = devm_request_and_ioremap(&pdev->dev, res); - if (!ddr_operation_base) - return -EADDRNOTAVAIL; + ddr_operation_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ddr_operation_base)) + return PTR_ERR(ddr_operation_base); - device = &per_cpu(kirkwood_cpuidle_device, smp_processor_id()); - device->state_count = KIRKWOOD_MAX_STATES; - - cpuidle_register_driver(&kirkwood_idle_driver); - if (cpuidle_register_device(device)) { - pr_err("kirkwood_init_cpuidle: Failed registering\n"); - return -EIO; - } - return 0; + return cpuidle_register(&kirkwood_idle_driver, NULL); } int kirkwood_cpuidle_remove(struct platform_device *pdev) { - cpuidle_unregister_device(device); - cpuidle_unregister_driver(&kirkwood_idle_driver); - + cpuidle_unregister(&kirkwood_idle_driver); return 0; } diff --git a/drivers/cpuidle/cpuidle.c b/drivers/cpuidle/cpuidle.c index eba69290e074..c3a93fece819 100644 --- a/drivers/cpuidle/cpuidle.c +++ b/drivers/cpuidle/cpuidle.c @@ -8,6 +8,7 @@ * This code is licenced under the GPL. */ +#include <linux/clockchips.h> #include <linux/kernel.h> #include <linux/mutex.h> #include <linux/sched.h> @@ -23,6 +24,7 @@ #include "cpuidle.h" DEFINE_PER_CPU(struct cpuidle_device *, cpuidle_devices); +DEFINE_PER_CPU(struct cpuidle_device, cpuidle_dev); DEFINE_MUTEX(cpuidle_lock); LIST_HEAD(cpuidle_detected_devices); @@ -42,24 +44,6 @@ void disable_cpuidle(void) static int __cpuidle_register_device(struct cpuidle_device *dev); -static inline int cpuidle_enter(struct cpuidle_device *dev, - struct cpuidle_driver *drv, int index) -{ - struct cpuidle_state *target_state = &drv->states[index]; - return target_state->enter(dev, drv, index); -} - -static inline int cpuidle_enter_tk(struct cpuidle_device *dev, - struct cpuidle_driver *drv, int index) -{ - return cpuidle_wrap_enter(dev, drv, index, cpuidle_enter); -} - -typedef int (*cpuidle_enter_t)(struct cpuidle_device *dev, - struct cpuidle_driver *drv, int index); - -static cpuidle_enter_t cpuidle_enter_ops; - /** * cpuidle_play_dead - cpu off-lining * @@ -89,11 +73,27 @@ int cpuidle_play_dead(void) * @next_state: index into drv->states of the state to enter */ int cpuidle_enter_state(struct cpuidle_device *dev, struct cpuidle_driver *drv, - int next_state) + int index) { int entered_state; - entered_state = cpuidle_enter_ops(dev, drv, next_state); + struct cpuidle_state *target_state = &drv->states[index]; + ktime_t time_start, time_end; + s64 diff; + + time_start = ktime_get(); + + entered_state = target_state->enter(dev, drv, index); + + time_end = ktime_get(); + + local_irq_enable(); + + diff = ktime_to_us(ktime_sub(time_end, time_start)); + if (diff > INT_MAX) + diff = INT_MAX; + + dev->last_residency = (int) diff; if (entered_state >= 0) { /* Update cpuidle counters */ @@ -146,12 +146,20 @@ int cpuidle_idle_call(void) trace_cpu_idle_rcuidle(next_state, dev->cpu); + if (drv->states[next_state].flags & CPUIDLE_FLAG_TIMER_STOP) + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, + &dev->cpu); + if (cpuidle_state_is_coupled(dev, drv, next_state)) entered_state = cpuidle_enter_state_coupled(dev, drv, next_state); else entered_state = cpuidle_enter_state(dev, drv, next_state); + if (drv->states[next_state].flags & CPUIDLE_FLAG_TIMER_STOP) + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, + &dev->cpu); + trace_cpu_idle_rcuidle(PWR_EVENT_EXIT, dev->cpu); /* give the governor an opportunity to reflect on the outcome */ @@ -222,37 +230,6 @@ void cpuidle_resume(void) mutex_unlock(&cpuidle_lock); } -/** - * cpuidle_wrap_enter - performs timekeeping and irqen around enter function - * @dev: pointer to a valid cpuidle_device object - * @drv: pointer to a valid cpuidle_driver object - * @index: index of the target cpuidle state. - */ -int cpuidle_wrap_enter(struct cpuidle_device *dev, - struct cpuidle_driver *drv, int index, - int (*enter)(struct cpuidle_device *dev, - struct cpuidle_driver *drv, int index)) -{ - ktime_t time_start, time_end; - s64 diff; - - time_start = ktime_get(); - - index = enter(dev, drv, index); - - time_end = ktime_get(); - - local_irq_enable(); - - diff = ktime_to_us(ktime_sub(time_end, time_start)); - if (diff > INT_MAX) - diff = INT_MAX; - - dev->last_residency = (int) diff; - - return index; -} - #ifdef CONFIG_ARCH_HAS_CPU_RELAX static int poll_idle(struct cpuidle_device *dev, struct cpuidle_driver *drv, int index) @@ -324,9 +301,6 @@ int cpuidle_enable_device(struct cpuidle_device *dev) return ret; } - cpuidle_enter_ops = drv->en_core_tk_irqen ? - cpuidle_enter_tk : cpuidle_enter; - poll_idle_init(drv); ret = cpuidle_add_device_sysfs(dev); @@ -480,6 +454,77 @@ void cpuidle_unregister_device(struct cpuidle_device *dev) EXPORT_SYMBOL_GPL(cpuidle_unregister_device); +/** + * cpuidle_unregister: unregister a driver and the devices. This function + * can be used only if the driver has been previously registered through + * the cpuidle_register function. + * + * @drv: a valid pointer to a struct cpuidle_driver + */ +void cpuidle_unregister(struct cpuidle_driver *drv) +{ + int cpu; + struct cpuidle_device *device; + + for_each_possible_cpu(cpu) { + device = &per_cpu(cpuidle_dev, cpu); + cpuidle_unregister_device(device); + } + + cpuidle_unregister_driver(drv); +} +EXPORT_SYMBOL_GPL(cpuidle_unregister); + +/** + * cpuidle_register: registers the driver and the cpu devices with the + * coupled_cpus passed as parameter. This function is used for all common + * initialization pattern there are in the arch specific drivers. The + * devices is globally defined in this file. + * + * @drv : a valid pointer to a struct cpuidle_driver + * @coupled_cpus: a cpumask for the coupled states + * + * Returns 0 on success, < 0 otherwise + */ +int cpuidle_register(struct cpuidle_driver *drv, + const struct cpumask *const coupled_cpus) +{ + int ret, cpu; + struct cpuidle_device *device; + + ret = cpuidle_register_driver(drv); + if (ret) { + pr_err("failed to register cpuidle driver\n"); + return ret; + } + + for_each_possible_cpu(cpu) { + device = &per_cpu(cpuidle_dev, cpu); + device->cpu = cpu; + +#ifdef CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED + /* + * On multiplatform for ARM, the coupled idle states could + * enabled in the kernel even if the cpuidle driver does not + * use it. Note, coupled_cpus is a struct copy. + */ + if (coupled_cpus) + device->coupled_cpus = *coupled_cpus; +#endif + ret = cpuidle_register_device(device); + if (!ret) + continue; + + pr_err("Failed to register cpuidle device for cpu%d\n", cpu); + + cpuidle_unregister(drv); + break; + } + + return ret; +} +EXPORT_SYMBOL_GPL(cpuidle_register); + #ifdef CONFIG_SMP static void smp_callback(void *v) diff --git a/drivers/cpuidle/driver.c b/drivers/cpuidle/driver.c index 422c7b69ba7c..8dfaaae94444 100644 --- a/drivers/cpuidle/driver.c +++ b/drivers/cpuidle/driver.c @@ -11,6 +11,8 @@ #include <linux/mutex.h> #include <linux/module.h> #include <linux/cpuidle.h> +#include <linux/cpumask.h> +#include <linux/clockchips.h> #include "cpuidle.h" @@ -19,9 +21,28 @@ DEFINE_SPINLOCK(cpuidle_driver_lock); static void __cpuidle_set_cpu_driver(struct cpuidle_driver *drv, int cpu); static struct cpuidle_driver * __cpuidle_get_cpu_driver(int cpu); -static void __cpuidle_driver_init(struct cpuidle_driver *drv) +static void cpuidle_setup_broadcast_timer(void *arg) { + int cpu = smp_processor_id(); + clockevents_notify((long)(arg), &cpu); +} + +static void __cpuidle_driver_init(struct cpuidle_driver *drv, int cpu) +{ + int i; + drv->refcnt = 0; + + for (i = drv->state_count - 1; i >= 0 ; i--) { + + if (!(drv->states[i].flags & CPUIDLE_FLAG_TIMER_STOP)) + continue; + + drv->bctimer = 1; + on_each_cpu_mask(get_cpu_mask(cpu), cpuidle_setup_broadcast_timer, + (void *)CLOCK_EVT_NOTIFY_BROADCAST_ON, 1); + break; + } } static int __cpuidle_register_driver(struct cpuidle_driver *drv, int cpu) @@ -35,7 +56,7 @@ static int __cpuidle_register_driver(struct cpuidle_driver *drv, int cpu) if (__cpuidle_get_cpu_driver(cpu)) return -EBUSY; - __cpuidle_driver_init(drv); + __cpuidle_driver_init(drv, cpu); __cpuidle_set_cpu_driver(drv, cpu); @@ -49,6 +70,12 @@ static void __cpuidle_unregister_driver(struct cpuidle_driver *drv, int cpu) if (!WARN_ON(drv->refcnt > 0)) __cpuidle_set_cpu_driver(NULL, cpu); + + if (drv->bctimer) { + drv->bctimer = 0; + on_each_cpu_mask(get_cpu_mask(cpu), cpuidle_setup_broadcast_timer, + (void *)CLOCK_EVT_NOTIFY_BROADCAST_OFF, 1); + } } #ifdef CONFIG_CPU_IDLE_MULTIPLE_DRIVERS |