diff options
author | Shawn Guo <shawn.guo@linaro.org> | 2012-12-04 14:55:15 (GMT) |
---|---|---|
committer | Shawn Guo <shawn.guo@linaro.org> | 2013-01-30 13:09:31 (GMT) |
commit | e5f9dec8ff5ff3f6254412abed1f68d758f6616b (patch) | |
tree | 05f293a11ec605b1380b2c7bfb0c3895abeddb08 /arch/arm/mach-imx/cpuidle-imx6q.c | |
parent | 12bb344074cfc50482121c76571e3cda07c4bc09 (diff) | |
download | linux-e5f9dec8ff5ff3f6254412abed1f68d758f6616b.tar.xz |
ARM: imx6q: support WAIT mode using cpuidle
Add WAIT mode (ARM core clock gating) support to imx6q cpuidle driver.
As WAIT mode is broken on imx6q TO 1.0 and 1.1, it only enables the
support for revision 1.2 with chicken bit set.
Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
Diffstat (limited to 'arch/arm/mach-imx/cpuidle-imx6q.c')
-rw-r--r-- | arch/arm/mach-imx/cpuidle-imx6q.c | 73 |
1 files changed, 71 insertions, 2 deletions
diff --git a/arch/arm/mach-imx/cpuidle-imx6q.c b/arch/arm/mach-imx/cpuidle-imx6q.c index 83facc9..d533e26 100644 --- a/arch/arm/mach-imx/cpuidle-imx6q.c +++ b/arch/arm/mach-imx/cpuidle-imx6q.c @@ -6,21 +6,90 @@ * published by the Free Software Foundation. */ +#include <linux/clockchips.h> #include <linux/cpuidle.h> #include <linux/module.h> #include <asm/cpuidle.h> +#include <asm/proc-fns.h> +#include "common.h" #include "cpuidle.h" +static atomic_t master = ATOMIC_INIT(0); +static DEFINE_SPINLOCK(master_lock); + +static int imx6q_enter_wait(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + int cpu = dev->cpu; + + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &cpu); + + if (atomic_inc_return(&master) == num_online_cpus()) { + /* + * With this lock, we prevent other cpu to exit and enter + * this function again and become the master. + */ + if (!spin_trylock(&master_lock)) + goto idle; + imx6q_set_lpm(WAIT_UNCLOCKED); + cpu_do_idle(); + imx6q_set_lpm(WAIT_CLOCKED); + spin_unlock(&master_lock); + goto done; + } + +idle: + cpu_do_idle(); +done: + atomic_dec(&master); + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &cpu); + + return index; +} + +/* + * For each cpu, setup the broadcast timer because local timer + * stops for the states other than WFI. + */ +static void imx6q_setup_broadcast_timer(void *arg) +{ + int cpu = smp_processor_id(); + + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ON, &cpu); +} + static struct cpuidle_driver imx6q_cpuidle_driver = { .name = "imx6q_cpuidle", .owner = THIS_MODULE, .en_core_tk_irqen = 1, - .states[0] = ARM_CPUIDLE_WFI_STATE, - .state_count = 1, + .states = { + /* WFI */ + ARM_CPUIDLE_WFI_STATE, + /* WAIT */ + { + .exit_latency = 50, + .target_residency = 75, + .flags = CPUIDLE_FLAG_TIME_VALID, + .enter = imx6q_enter_wait, + .name = "WAIT", + .desc = "Clock off", + }, + }, + .state_count = 2, + .safe_state_index = 0, }; int __init imx6q_cpuidle_init(void) { + /* Need to enable SCU standby for entering WAIT modes */ + imx_scu_standby_enable(); + + /* Set chicken bit to get a reliable WAIT mode support */ + imx6q_set_chicken_bit(); + + /* Configure the broadcast timer on each cpu */ + on_each_cpu(imx6q_setup_broadcast_timer, NULL, 1); + return imx_cpuidle_init(&imx6q_cpuidle_driver); } |