diff options
Diffstat (limited to 'arch/arm/mach-tegra/cpuidle.c')
-rw-r--r-- | arch/arm/mach-tegra/cpuidle.c | 322 |
1 files changed, 322 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/cpuidle.c b/arch/arm/mach-tegra/cpuidle.c new file mode 100644 index 00000000000..47d5996e596 --- /dev/null +++ b/arch/arm/mach-tegra/cpuidle.c | |||
@@ -0,0 +1,322 @@ | |||
1 | /* | ||
2 | * arch/arm/mach-tegra/cpuidle.c | ||
3 | * | ||
4 | * CPU idle driver for Tegra CPUs | ||
5 | * | ||
6 | * Copyright (c) 2010-2011, NVIDIA Corporation. | ||
7 | * Copyright (c) 2011 Google, Inc. | ||
8 | * Author: Colin Cross <ccross@android.com> | ||
9 | * Gary King <gking@nvidia.com> | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or modify | ||
12 | * it under the terms of the GNU General Public License as published by | ||
13 | * the Free Software Foundation; either version 2 of the License, or | ||
14 | * (at your option) any later version. | ||
15 | * | ||
16 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
17 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
18 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
19 | * more details. | ||
20 | */ | ||
21 | |||
22 | #include <linux/kernel.h> | ||
23 | #include <linux/cpu.h> | ||
24 | #include <linux/cpuidle.h> | ||
25 | #include <linux/debugfs.h> | ||
26 | #include <linux/delay.h> | ||
27 | #include <linux/init.h> | ||
28 | #include <linux/interrupt.h> | ||
29 | #include <linux/irq.h> | ||
30 | #include <linux/io.h> | ||
31 | #include <linux/sched.h> | ||
32 | #include <linux/seq_file.h> | ||
33 | #include <linux/slab.h> | ||
34 | #include <linux/smp.h> | ||
35 | #include <linux/suspend.h> | ||
36 | #include <linux/tick.h> | ||
37 | |||
38 | #include <asm/cpu_pm.h> | ||
39 | |||
40 | #include <mach/iomap.h> | ||
41 | #include <mach/irqs.h> | ||
42 | |||
43 | #include <trace/events/power.h> | ||
44 | |||
45 | #include "cpuidle.h" | ||
46 | #include "pm.h" | ||
47 | #include "sleep.h" | ||
48 | |||
49 | int tegra_lp2_exit_latency; | ||
50 | static int tegra_lp2_power_off_time; | ||
51 | static unsigned int tegra_lp2_min_residency; | ||
52 | |||
53 | struct cpuidle_driver tegra_idle = { | ||
54 | .name = "tegra_idle", | ||
55 | .owner = THIS_MODULE, | ||
56 | }; | ||
57 | |||
58 | static DEFINE_PER_CPU(struct cpuidle_device *, idle_devices); | ||
59 | |||
60 | static int tegra_idle_enter_lp3(struct cpuidle_device *dev, | ||
61 | struct cpuidle_state *state) | ||
62 | { | ||
63 | ktime_t enter, exit; | ||
64 | s64 us; | ||
65 | |||
66 | trace_power_start(POWER_CSTATE, 1, dev->cpu); | ||
67 | |||
68 | local_irq_disable(); | ||
69 | local_fiq_disable(); | ||
70 | |||
71 | enter = ktime_get(); | ||
72 | |||
73 | tegra_cpu_wfi(); | ||
74 | |||
75 | exit = ktime_sub(ktime_get(), enter); | ||
76 | us = ktime_to_us(exit); | ||
77 | |||
78 | local_fiq_enable(); | ||
79 | local_irq_enable(); | ||
80 | return (int)us; | ||
81 | } | ||
82 | |||
83 | static bool lp2_in_idle __read_mostly = false; | ||
84 | |||
85 | #ifdef CONFIG_PM_SLEEP | ||
86 | static bool lp2_in_idle_modifiable __read_mostly = true; | ||
87 | static bool lp2_disabled_by_suspend; | ||
88 | |||
89 | void tegra_lp2_in_idle(bool enable) | ||
90 | { | ||
91 | /* If LP2 in idle is permanently disabled it can't be re-enabled. */ | ||
92 | if (lp2_in_idle_modifiable) { | ||
93 | lp2_in_idle = enable; | ||
94 | lp2_in_idle_modifiable = enable; | ||
95 | if (!enable) | ||
96 | pr_warn("LP2 in idle disabled\n"); | ||
97 | } | ||
98 | } | ||
99 | |||
100 | void tegra_lp2_update_target_residency(struct cpuidle_state *state) | ||
101 | { | ||
102 | state->target_residency = state->exit_latency + | ||
103 | tegra_lp2_power_off_time; | ||
104 | if (state->target_residency < tegra_lp2_min_residency) | ||
105 | state->target_residency = tegra_lp2_min_residency; | ||
106 | } | ||
107 | |||
108 | static int tegra_idle_enter_lp2(struct cpuidle_device *dev, | ||
109 | struct cpuidle_state *state) | ||
110 | { | ||
111 | ktime_t enter, exit; | ||
112 | s64 us; | ||
113 | |||
114 | if (!lp2_in_idle || lp2_disabled_by_suspend || | ||
115 | !tegra_lp2_is_allowed(dev, state)) { | ||
116 | dev->last_state = &dev->states[0]; | ||
117 | return tegra_idle_enter_lp3(dev, state); | ||
118 | } | ||
119 | |||
120 | local_irq_disable(); | ||
121 | enter = ktime_get(); | ||
122 | |||
123 | tegra_cpu_idle_stats_lp2_ready(dev->cpu); | ||
124 | tegra_idle_lp2(dev, state); | ||
125 | |||
126 | exit = ktime_sub(ktime_get(), enter); | ||
127 | us = ktime_to_us(exit); | ||
128 | |||
129 | local_irq_enable(); | ||
130 | |||
131 | /* cpu clockevents may have been reset by powerdown */ | ||
132 | hrtimer_peek_ahead_timers(); | ||
133 | |||
134 | smp_rmb(); | ||
135 | |||
136 | /* Update LP2 latency provided no fall back to LP3 */ | ||
137 | if (state == dev->last_state) { | ||
138 | tegra_lp2_set_global_latency(state); | ||
139 | tegra_lp2_update_target_residency(state); | ||
140 | } | ||
141 | tegra_cpu_idle_stats_lp2_time(dev->cpu, us); | ||
142 | |||
143 | return (int)us; | ||
144 | } | ||
145 | #endif | ||
146 | |||
147 | static int tegra_idle_prepare(struct cpuidle_device *dev) | ||
148 | { | ||
149 | #ifdef CONFIG_PM_SLEEP | ||
150 | if (lp2_in_idle) | ||
151 | dev->states[1].flags &= ~CPUIDLE_FLAG_IGNORE; | ||
152 | else | ||
153 | dev->states[1].flags |= CPUIDLE_FLAG_IGNORE; | ||
154 | #endif | ||
155 | |||
156 | return 0; | ||
157 | } | ||
158 | |||
159 | static int tegra_cpuidle_register_device(unsigned int cpu) | ||
160 | { | ||
161 | struct cpuidle_device *dev; | ||
162 | struct cpuidle_state *state; | ||
163 | |||
164 | dev = kzalloc(sizeof(*dev), GFP_KERNEL); | ||
165 | if (!dev) | ||
166 | return -ENOMEM; | ||
167 | |||
168 | dev->state_count = 0; | ||
169 | dev->cpu = cpu; | ||
170 | |||
171 | state = &dev->states[0]; | ||
172 | snprintf(state->name, CPUIDLE_NAME_LEN, "LP3"); | ||
173 | snprintf(state->desc, CPUIDLE_DESC_LEN, "CPU flow-controlled"); | ||
174 | state->exit_latency = 10; | ||
175 | state->target_residency = 10; | ||
176 | state->power_usage = 600; | ||
177 | state->flags = CPUIDLE_FLAG_TIME_VALID; | ||
178 | state->enter = tegra_idle_enter_lp3; | ||
179 | dev->safe_state = state; | ||
180 | dev->state_count++; | ||
181 | |||
182 | #ifdef CONFIG_PM_SLEEP | ||
183 | state = &dev->states[1]; | ||
184 | snprintf(state->name, CPUIDLE_NAME_LEN, "LP2"); | ||
185 | snprintf(state->desc, CPUIDLE_DESC_LEN, "CPU power-gate"); | ||
186 | state->exit_latency = tegra_cpu_power_good_time(); | ||
187 | |||
188 | state->target_residency = tegra_cpu_power_off_time() + | ||
189 | tegra_cpu_power_good_time(); | ||
190 | if (state->target_residency < tegra_lp2_min_residency) | ||
191 | state->target_residency = tegra_lp2_min_residency; | ||
192 | state->power_usage = 0; | ||
193 | state->flags = CPUIDLE_FLAG_TIME_VALID; | ||
194 | state->enter = tegra_idle_enter_lp2; | ||
195 | |||
196 | dev->power_specified = 1; | ||
197 | dev->safe_state = state; | ||
198 | dev->state_count++; | ||
199 | #endif | ||
200 | |||
201 | dev->prepare = tegra_idle_prepare; | ||
202 | |||
203 | if (cpuidle_register_device(dev)) { | ||
204 | pr_err("CPU%u: failed to register idle device\n", cpu); | ||
205 | kfree(dev); | ||
206 | return -EIO; | ||
207 | } | ||
208 | per_cpu(idle_devices, cpu) = dev; | ||
209 | return 0; | ||
210 | } | ||
211 | |||
212 | static int tegra_cpuidle_pm_notify(struct notifier_block *nb, | ||
213 | unsigned long event, void *dummy) | ||
214 | { | ||
215 | #ifdef CONFIG_PM_SLEEP | ||
216 | if (event == PM_SUSPEND_PREPARE) | ||
217 | lp2_disabled_by_suspend = true; | ||
218 | else if (event == PM_POST_SUSPEND) | ||
219 | lp2_disabled_by_suspend = false; | ||
220 | #endif | ||
221 | |||
222 | return NOTIFY_OK; | ||
223 | } | ||
224 | |||
225 | static struct notifier_block tegra_cpuidle_pm_notifier = { | ||
226 | .notifier_call = tegra_cpuidle_pm_notify, | ||
227 | }; | ||
228 | |||
229 | static int __init tegra_cpuidle_init(void) | ||
230 | { | ||
231 | unsigned int cpu; | ||
232 | int ret; | ||
233 | |||
234 | ret = cpuidle_register_driver(&tegra_idle); | ||
235 | if (ret) | ||
236 | return ret; | ||
237 | |||
238 | #ifdef CONFIG_PM_SLEEP | ||
239 | tegra_lp2_min_residency = tegra_cpu_lp2_min_residency(); | ||
240 | tegra_lp2_exit_latency = tegra_cpu_power_good_time(); | ||
241 | tegra_lp2_power_off_time = tegra_cpu_power_off_time(); | ||
242 | |||
243 | ret = tegra_cpudile_init_soc(); | ||
244 | if (ret) | ||
245 | return ret; | ||
246 | #endif | ||
247 | |||
248 | for_each_possible_cpu(cpu) { | ||
249 | if (tegra_cpuidle_register_device(cpu)) | ||
250 | pr_err("CPU%u: error initializing idle loop\n", cpu); | ||
251 | } | ||
252 | |||
253 | register_pm_notifier(&tegra_cpuidle_pm_notifier); | ||
254 | return 0; | ||
255 | } | ||
256 | |||
257 | static void __exit tegra_cpuidle_exit(void) | ||
258 | { | ||
259 | unregister_pm_notifier(&tegra_cpuidle_pm_notifier); | ||
260 | cpuidle_unregister_driver(&tegra_idle); | ||
261 | } | ||
262 | |||
263 | module_init(tegra_cpuidle_init); | ||
264 | module_exit(tegra_cpuidle_exit); | ||
265 | |||
266 | static int lp2_in_idle_set(const char *arg, const struct kernel_param *kp) | ||
267 | { | ||
268 | #ifdef CONFIG_PM_SLEEP | ||
269 | int ret; | ||
270 | |||
271 | /* If LP2 in idle is permanently disabled it can't be re-enabled. */ | ||
272 | if (lp2_in_idle_modifiable) { | ||
273 | ret = param_set_bool(arg, kp); | ||
274 | return ret; | ||
275 | } | ||
276 | #endif | ||
277 | return -ENODEV; | ||
278 | } | ||
279 | |||
280 | static int lp2_in_idle_get(char *buffer, const struct kernel_param *kp) | ||
281 | { | ||
282 | return param_get_bool(buffer, kp); | ||
283 | } | ||
284 | |||
285 | static struct kernel_param_ops lp2_in_idle_ops = { | ||
286 | .set = lp2_in_idle_set, | ||
287 | .get = lp2_in_idle_get, | ||
288 | }; | ||
289 | module_param_cb(lp2_in_idle, &lp2_in_idle_ops, &lp2_in_idle, 0644); | ||
290 | |||
291 | #if defined(CONFIG_DEBUG_FS) && defined(CONFIG_PM_SLEEP) | ||
292 | static int tegra_lp2_debug_open(struct inode *inode, struct file *file) | ||
293 | { | ||
294 | return single_open(file, tegra_lp2_debug_show, inode->i_private); | ||
295 | } | ||
296 | |||
297 | static const struct file_operations tegra_lp2_debug_ops = { | ||
298 | .open = tegra_lp2_debug_open, | ||
299 | .read = seq_read, | ||
300 | .llseek = seq_lseek, | ||
301 | .release = single_release, | ||
302 | }; | ||
303 | |||
304 | static int __init tegra_cpuidle_debug_init(void) | ||
305 | { | ||
306 | struct dentry *dir; | ||
307 | struct dentry *d; | ||
308 | |||
309 | dir = debugfs_create_dir("cpuidle", NULL); | ||
310 | if (!dir) | ||
311 | return -ENOMEM; | ||
312 | |||
313 | d = debugfs_create_file("lp2", S_IRUGO, dir, NULL, | ||
314 | &tegra_lp2_debug_ops); | ||
315 | if (!d) | ||
316 | return -ENOMEM; | ||
317 | |||
318 | return 0; | ||
319 | } | ||
320 | |||
321 | late_initcall(tegra_cpuidle_debug_init); | ||
322 | #endif | ||