diff options
Diffstat (limited to 'arch/arm/mach-imx/cpuidle-imx6q.c')
-rw-r--r-- | arch/arm/mach-imx/cpuidle-imx6q.c | 95 |
1 files changed, 95 insertions, 0 deletions
diff --git a/arch/arm/mach-imx/cpuidle-imx6q.c b/arch/arm/mach-imx/cpuidle-imx6q.c new file mode 100644 index 000000000000..d533e2695f0e --- /dev/null +++ b/arch/arm/mach-imx/cpuidle-imx6q.c | |||
@@ -0,0 +1,95 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2012 Freescale Semiconductor, Inc. | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License version 2 as | ||
6 | * published by the Free Software Foundation. | ||
7 | */ | ||
8 | |||
9 | #include <linux/clockchips.h> | ||
10 | #include <linux/cpuidle.h> | ||
11 | #include <linux/module.h> | ||
12 | #include <asm/cpuidle.h> | ||
13 | #include <asm/proc-fns.h> | ||
14 | |||
15 | #include "common.h" | ||
16 | #include "cpuidle.h" | ||
17 | |||
18 | static atomic_t master = ATOMIC_INIT(0); | ||
19 | static DEFINE_SPINLOCK(master_lock); | ||
20 | |||
21 | static int imx6q_enter_wait(struct cpuidle_device *dev, | ||
22 | struct cpuidle_driver *drv, int index) | ||
23 | { | ||
24 | int cpu = dev->cpu; | ||
25 | |||
26 | clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &cpu); | ||
27 | |||
28 | if (atomic_inc_return(&master) == num_online_cpus()) { | ||
29 | /* | ||
30 | * With this lock, we prevent other cpu to exit and enter | ||
31 | * this function again and become the master. | ||
32 | */ | ||
33 | if (!spin_trylock(&master_lock)) | ||
34 | goto idle; | ||
35 | imx6q_set_lpm(WAIT_UNCLOCKED); | ||
36 | cpu_do_idle(); | ||
37 | imx6q_set_lpm(WAIT_CLOCKED); | ||
38 | spin_unlock(&master_lock); | ||
39 | goto done; | ||
40 | } | ||
41 | |||
42 | idle: | ||
43 | cpu_do_idle(); | ||
44 | done: | ||
45 | atomic_dec(&master); | ||
46 | clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &cpu); | ||
47 | |||
48 | return index; | ||
49 | } | ||
50 | |||
51 | /* | ||
52 | * For each cpu, setup the broadcast timer because local timer | ||
53 | * stops for the states other than WFI. | ||
54 | */ | ||
55 | static void imx6q_setup_broadcast_timer(void *arg) | ||
56 | { | ||
57 | int cpu = smp_processor_id(); | ||
58 | |||
59 | clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ON, &cpu); | ||
60 | } | ||
61 | |||
62 | static struct cpuidle_driver imx6q_cpuidle_driver = { | ||
63 | .name = "imx6q_cpuidle", | ||
64 | .owner = THIS_MODULE, | ||
65 | .en_core_tk_irqen = 1, | ||
66 | .states = { | ||
67 | /* WFI */ | ||
68 | ARM_CPUIDLE_WFI_STATE, | ||
69 | /* WAIT */ | ||
70 | { | ||
71 | .exit_latency = 50, | ||
72 | .target_residency = 75, | ||
73 | .flags = CPUIDLE_FLAG_TIME_VALID, | ||
74 | .enter = imx6q_enter_wait, | ||
75 | .name = "WAIT", | ||
76 | .desc = "Clock off", | ||
77 | }, | ||
78 | }, | ||
79 | .state_count = 2, | ||
80 | .safe_state_index = 0, | ||
81 | }; | ||
82 | |||
83 | int __init imx6q_cpuidle_init(void) | ||
84 | { | ||
85 | /* Need to enable SCU standby for entering WAIT modes */ | ||
86 | imx_scu_standby_enable(); | ||
87 | |||
88 | /* Set chicken bit to get a reliable WAIT mode support */ | ||
89 | imx6q_set_chicken_bit(); | ||
90 | |||
91 | /* Configure the broadcast timer on each cpu */ | ||
92 | on_each_cpu(imx6q_setup_broadcast_timer, NULL, 1); | ||
93 | |||
94 | return imx_cpuidle_init(&imx6q_cpuidle_driver); | ||
95 | } | ||