diff options
Diffstat (limited to 'arch/arm/mach-tegra/cpuidle-t2.c')
-rw-r--r-- | arch/arm/mach-tegra/cpuidle-t2.c | 413 |
1 files changed, 413 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/cpuidle-t2.c b/arch/arm/mach-tegra/cpuidle-t2.c new file mode 100644 index 00000000000..e5ff7c61f24 --- /dev/null +++ b/arch/arm/mach-tegra/cpuidle-t2.c | |||
@@ -0,0 +1,413 @@ | |||
1 | /* | ||
2 | * arch/arm/mach-tegra/cpuidle-t2.c | ||
3 | * | ||
4 | * CPU idle driver for Tegra2 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 | * You should have received a copy of the GNU General Public License along | ||
22 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
23 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
24 | */ | ||
25 | |||
26 | #include <linux/kernel.h> | ||
27 | #include <linux/cpu.h> | ||
28 | #include <linux/cpuidle.h> | ||
29 | #include <linux/debugfs.h> | ||
30 | #include <linux/delay.h> | ||
31 | #include <linux/init.h> | ||
32 | #include <linux/interrupt.h> | ||
33 | #include <linux/irq.h> | ||
34 | #include <linux/io.h> | ||
35 | #include <linux/sched.h> | ||
36 | #include <linux/seq_file.h> | ||
37 | #include <linux/slab.h> | ||
38 | #include <linux/smp.h> | ||
39 | #include <linux/suspend.h> | ||
40 | #include <linux/tick.h> | ||
41 | |||
42 | #include <asm/cpu_pm.h> | ||
43 | |||
44 | #include <mach/iomap.h> | ||
45 | #include <mach/irqs.h> | ||
46 | |||
47 | #include "cpuidle.h" | ||
48 | #include "gic.h" | ||
49 | #include "pm.h" | ||
50 | #include "sleep.h" | ||
51 | #include "timer.h" | ||
52 | |||
53 | static struct { | ||
54 | unsigned int cpu_ready_count[2]; | ||
55 | unsigned long long cpu_wants_lp2_time[2]; | ||
56 | unsigned long long in_lp2_time; | ||
57 | unsigned int both_idle_count; | ||
58 | unsigned int tear_down_count; | ||
59 | unsigned int lp2_count; | ||
60 | unsigned int lp2_completed_count; | ||
61 | unsigned int lp2_count_bin[32]; | ||
62 | unsigned int lp2_completed_count_bin[32]; | ||
63 | unsigned int lp2_int_count[NR_IRQS]; | ||
64 | unsigned int last_lp2_int_count[NR_IRQS]; | ||
65 | } idle_stats; | ||
66 | |||
67 | static inline unsigned int time_to_bin(unsigned int time) | ||
68 | { | ||
69 | return fls(time); | ||
70 | } | ||
71 | |||
72 | #ifdef CONFIG_SMP | ||
73 | |||
74 | #define CLK_RST_CONTROLLER_CLK_CPU_CMPLX 0x4C | ||
75 | #define CLK_RST_CONTROLLER_RST_CPU_CMPLX_CLR 0x344 | ||
76 | |||
77 | static void __iomem *clk_rst = IO_ADDRESS(TEGRA_CLK_RESET_BASE); | ||
78 | static void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE); | ||
79 | static s64 tegra_cpu1_wake_by_time = LLONG_MAX; | ||
80 | |||
81 | static int tegra2_reset_sleeping_cpu(int cpu) | ||
82 | { | ||
83 | int ret = 0; | ||
84 | |||
85 | BUG_ON(cpu == 0); | ||
86 | BUG_ON(cpu == smp_processor_id()); | ||
87 | tegra_pen_lock(); | ||
88 | |||
89 | if (readl(pmc + PMC_SCRATCH41) == CPU_RESETTABLE) | ||
90 | tegra2_cpu_reset(cpu); | ||
91 | else | ||
92 | ret = -EINVAL; | ||
93 | |||
94 | tegra_pen_unlock(); | ||
95 | |||
96 | return ret; | ||
97 | } | ||
98 | |||
99 | static void tegra2_wake_reset_cpu(int cpu) | ||
100 | { | ||
101 | u32 reg; | ||
102 | |||
103 | BUG_ON(cpu == 0); | ||
104 | BUG_ON(cpu == smp_processor_id()); | ||
105 | |||
106 | tegra_pen_lock(); | ||
107 | |||
108 | tegra2_cpu_clear_resettable(); | ||
109 | |||
110 | /* enable cpu clock on cpu */ | ||
111 | reg = readl(clk_rst + 0x4c); | ||
112 | writel(reg & ~(1 << (8 + cpu)), | ||
113 | clk_rst + CLK_RST_CONTROLLER_CLK_CPU_CMPLX); | ||
114 | |||
115 | /* take the CPU out of reset */ | ||
116 | reg = 0x1111 << cpu; | ||
117 | writel(reg, clk_rst + | ||
118 | CLK_RST_CONTROLLER_RST_CPU_CMPLX_CLR); | ||
119 | |||
120 | /* unhalt the cpu */ | ||
121 | flowctrl_writel(0, FLOW_CTRL_HALT_CPU(1)); | ||
122 | |||
123 | tegra_pen_unlock(); | ||
124 | } | ||
125 | |||
126 | static int tegra2_reset_other_cpus(int cpu) | ||
127 | { | ||
128 | int i; | ||
129 | int ret = 0; | ||
130 | |||
131 | BUG_ON(cpu != 0); | ||
132 | |||
133 | for_each_online_cpu(i) { | ||
134 | if (i != cpu) { | ||
135 | if (tegra2_reset_sleeping_cpu(i)) { | ||
136 | ret = -EBUSY; | ||
137 | break; | ||
138 | } | ||
139 | } | ||
140 | } | ||
141 | |||
142 | if (ret) { | ||
143 | for_each_online_cpu(i) { | ||
144 | if (i != cpu) | ||
145 | tegra2_wake_reset_cpu(i); | ||
146 | } | ||
147 | return ret; | ||
148 | } | ||
149 | |||
150 | return 0; | ||
151 | } | ||
152 | #else | ||
153 | static void tegra2_wake_reset_cpu(int cpu) | ||
154 | { | ||
155 | } | ||
156 | |||
157 | static int tegra2_reset_other_cpus(int cpu) | ||
158 | { | ||
159 | return 0; | ||
160 | } | ||
161 | #endif | ||
162 | |||
163 | bool tegra2_lp2_is_allowed(struct cpuidle_device *dev, | ||
164 | struct cpuidle_state *state) | ||
165 | { | ||
166 | s64 request = ktime_to_us(tick_nohz_get_sleep_length()); | ||
167 | |||
168 | if (request < state->target_residency) { | ||
169 | /* Not enough time left to enter LP2 */ | ||
170 | return false; | ||
171 | } | ||
172 | |||
173 | return true; | ||
174 | } | ||
175 | |||
176 | static inline void tegra2_lp3_fall_back(struct cpuidle_device *dev) | ||
177 | { | ||
178 | /* Not enough time left to enter LP2 */ | ||
179 | tegra_cpu_wfi(); | ||
180 | |||
181 | /* fall back here from LP2 path - tell cpuidle governor */ | ||
182 | dev->last_state = &dev->states[0]; | ||
183 | } | ||
184 | |||
185 | static int tegra2_idle_lp2_cpu_0(struct cpuidle_device *dev, | ||
186 | struct cpuidle_state *state, s64 request) | ||
187 | { | ||
188 | ktime_t entry_time; | ||
189 | ktime_t exit_time; | ||
190 | s64 wake_time; | ||
191 | bool sleep_completed = false; | ||
192 | int bin; | ||
193 | int i; | ||
194 | |||
195 | while (tegra2_cpu_is_resettable_soon()) | ||
196 | cpu_relax(); | ||
197 | |||
198 | if (tegra2_reset_other_cpus(dev->cpu)) | ||
199 | return 0; | ||
200 | |||
201 | idle_stats.both_idle_count++; | ||
202 | |||
203 | if (request < state->target_residency) { | ||
204 | tegra2_lp3_fall_back(dev); | ||
205 | return -EBUSY; | ||
206 | } | ||
207 | |||
208 | /* LP2 entry time */ | ||
209 | entry_time = ktime_get(); | ||
210 | |||
211 | /* LP2 initial targeted wake time */ | ||
212 | wake_time = ktime_to_us(entry_time) + request; | ||
213 | |||
214 | /* CPU0 must wake up before CPU1. */ | ||
215 | smp_rmb(); | ||
216 | wake_time = min_t(s64, wake_time, tegra_cpu1_wake_by_time); | ||
217 | |||
218 | /* LP2 actual targeted wake time */ | ||
219 | request = wake_time - ktime_to_us(entry_time); | ||
220 | BUG_ON(wake_time < 0LL); | ||
221 | |||
222 | idle_stats.tear_down_count++; | ||
223 | entry_time = ktime_get(); | ||
224 | |||
225 | if (request > state->target_residency) { | ||
226 | s64 sleep_time = request - tegra_lp2_exit_latency; | ||
227 | |||
228 | bin = time_to_bin((u32)request / 1000); | ||
229 | idle_stats.lp2_count++; | ||
230 | idle_stats.lp2_count_bin[bin]++; | ||
231 | |||
232 | clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu); | ||
233 | |||
234 | if (tegra_idle_lp2_last(sleep_time, 0) == 0) | ||
235 | sleep_completed = true; | ||
236 | else { | ||
237 | int irq = tegra_gic_pending_interrupt(); | ||
238 | idle_stats.lp2_int_count[irq]++; | ||
239 | } | ||
240 | |||
241 | clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu); | ||
242 | } | ||
243 | |||
244 | for_each_online_cpu(i) { | ||
245 | if (i != dev->cpu) | ||
246 | tegra2_wake_reset_cpu(i); | ||
247 | } | ||
248 | |||
249 | exit_time = ktime_get(); | ||
250 | if (sleep_completed) { | ||
251 | /* | ||
252 | * Stayed in LP2 for the full time until the next tick, | ||
253 | * adjust the exit latency based on measurement | ||
254 | */ | ||
255 | s64 actual_time = ktime_to_us(ktime_sub(exit_time, entry_time)); | ||
256 | long offset = (long)(actual_time - request); | ||
257 | int latency = tegra_lp2_exit_latency + offset / 16; | ||
258 | latency = clamp(latency, 0, 10000); | ||
259 | tegra_lp2_exit_latency = latency; | ||
260 | smp_wmb(); | ||
261 | |||
262 | idle_stats.lp2_completed_count++; | ||
263 | idle_stats.lp2_completed_count_bin[bin]++; | ||
264 | idle_stats.in_lp2_time += actual_time; | ||
265 | |||
266 | pr_debug("%lld %lld %ld %d\n", request, actual_time, | ||
267 | offset, bin); | ||
268 | } | ||
269 | |||
270 | return 0; | ||
271 | } | ||
272 | |||
273 | static void tegra2_idle_lp2_cpu_1(struct cpuidle_device *dev, | ||
274 | struct cpuidle_state *state, s64 request) | ||
275 | { | ||
276 | #ifdef CONFIG_SMP | ||
277 | struct tegra_twd_context twd_context; | ||
278 | |||
279 | if (request < tegra_lp2_exit_latency) { | ||
280 | tegra2_cpu_clear_resettable(); | ||
281 | tegra2_lp3_fall_back(dev); | ||
282 | return; | ||
283 | } | ||
284 | |||
285 | /* Save time this CPU must be awakened by. */ | ||
286 | tegra_cpu1_wake_by_time = ktime_to_us(ktime_get()) + request; | ||
287 | smp_wmb(); | ||
288 | |||
289 | clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu); | ||
290 | |||
291 | tegra_twd_suspend(&twd_context); | ||
292 | |||
293 | tegra2_sleep_wfi(PLAT_PHYS_OFFSET - PAGE_OFFSET); | ||
294 | |||
295 | tegra2_cpu_clear_resettable(); | ||
296 | |||
297 | tegra_cpu1_wake_by_time = LLONG_MAX; | ||
298 | |||
299 | tegra_twd_resume(&twd_context); | ||
300 | |||
301 | clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu); | ||
302 | #endif | ||
303 | } | ||
304 | |||
305 | void tegra2_idle_lp2(struct cpuidle_device *dev, | ||
306 | struct cpuidle_state *state) | ||
307 | { | ||
308 | s64 request = ktime_to_us(tick_nohz_get_sleep_length()); | ||
309 | bool last_cpu = tegra_set_cpu_in_lp2(dev->cpu); | ||
310 | |||
311 | cpu_pm_enter(); | ||
312 | |||
313 | if (dev->cpu == 0) { | ||
314 | if (last_cpu) { | ||
315 | if (tegra2_idle_lp2_cpu_0(dev, state, request) < 0) { | ||
316 | int i; | ||
317 | for_each_online_cpu(i) { | ||
318 | if (i != dev->cpu) | ||
319 | tegra2_wake_reset_cpu(i); | ||
320 | } | ||
321 | } | ||
322 | } else { | ||
323 | tegra2_lp3_fall_back(dev); | ||
324 | } | ||
325 | } else { | ||
326 | BUG_ON(last_cpu); | ||
327 | tegra2_idle_lp2_cpu_1(dev, state, request); | ||
328 | } | ||
329 | |||
330 | cpu_pm_exit(); | ||
331 | tegra_clear_cpu_in_lp2(dev->cpu); | ||
332 | } | ||
333 | |||
334 | void tegra2_cpu_idle_stats_lp2_ready(unsigned int cpu) | ||
335 | { | ||
336 | idle_stats.cpu_ready_count[cpu]++; | ||
337 | } | ||
338 | |||
339 | void tegra2_cpu_idle_stats_lp2_time(unsigned int cpu, s64 us) | ||
340 | { | ||
341 | idle_stats.cpu_wants_lp2_time[cpu] += us; | ||
342 | } | ||
343 | |||
344 | #ifdef CONFIG_DEBUG_FS | ||
345 | int tegra2_lp2_debug_show(struct seq_file *s, void *data) | ||
346 | { | ||
347 | int bin; | ||
348 | int i; | ||
349 | seq_printf(s, " cpu0 cpu1\n"); | ||
350 | seq_printf(s, "-------------------------------------------------\n"); | ||
351 | seq_printf(s, "cpu ready: %8u %8u\n", | ||
352 | idle_stats.cpu_ready_count[0], | ||
353 | idle_stats.cpu_ready_count[1]); | ||
354 | seq_printf(s, "both idle: %8u %7u%% %7u%%\n", | ||
355 | idle_stats.both_idle_count, | ||
356 | idle_stats.both_idle_count * 100 / | ||
357 | (idle_stats.cpu_ready_count[0] ?: 1), | ||
358 | idle_stats.both_idle_count * 100 / | ||
359 | (idle_stats.cpu_ready_count[1] ?: 1)); | ||
360 | seq_printf(s, "tear down: %8u %7u%%\n", idle_stats.tear_down_count, | ||
361 | idle_stats.tear_down_count * 100 / | ||
362 | (idle_stats.both_idle_count ?: 1)); | ||
363 | seq_printf(s, "lp2: %8u %7u%%\n", idle_stats.lp2_count, | ||
364 | idle_stats.lp2_count * 100 / | ||
365 | (idle_stats.both_idle_count ?: 1)); | ||
366 | seq_printf(s, "lp2 completed: %8u %7u%%\n", | ||
367 | idle_stats.lp2_completed_count, | ||
368 | idle_stats.lp2_completed_count * 100 / | ||
369 | (idle_stats.lp2_count ?: 1)); | ||
370 | |||
371 | seq_printf(s, "\n"); | ||
372 | seq_printf(s, "cpu ready time: %8llu %8llu ms\n", | ||
373 | div64_u64(idle_stats.cpu_wants_lp2_time[0], 1000), | ||
374 | div64_u64(idle_stats.cpu_wants_lp2_time[1], 1000)); | ||
375 | seq_printf(s, "lp2 time: %8llu ms %7d%% %7d%%\n", | ||
376 | div64_u64(idle_stats.in_lp2_time, 1000), | ||
377 | (int)div64_u64(idle_stats.in_lp2_time * 100, | ||
378 | idle_stats.cpu_wants_lp2_time[0] ?: 1), | ||
379 | (int)div64_u64(idle_stats.in_lp2_time * 100, | ||
380 | idle_stats.cpu_wants_lp2_time[1] ?: 1)); | ||
381 | |||
382 | seq_printf(s, "\n"); | ||
383 | seq_printf(s, "%19s %8s %8s %8s\n", "", "lp2", "comp", "%"); | ||
384 | seq_printf(s, "-------------------------------------------------\n"); | ||
385 | for (bin = 0; bin < 32; bin++) { | ||
386 | if (idle_stats.lp2_count_bin[bin] == 0) | ||
387 | continue; | ||
388 | seq_printf(s, "%6u - %6u ms: %8u %8u %7u%%\n", | ||
389 | 1 << (bin - 1), 1 << bin, | ||
390 | idle_stats.lp2_count_bin[bin], | ||
391 | idle_stats.lp2_completed_count_bin[bin], | ||
392 | idle_stats.lp2_completed_count_bin[bin] * 100 / | ||
393 | idle_stats.lp2_count_bin[bin]); | ||
394 | } | ||
395 | |||
396 | seq_printf(s, "\n"); | ||
397 | seq_printf(s, "%3s %20s %6s %10s\n", | ||
398 | "int", "name", "count", "last count"); | ||
399 | seq_printf(s, "--------------------------------------------\n"); | ||
400 | for (i = 0; i < NR_IRQS; i++) { | ||
401 | if (idle_stats.lp2_int_count[i] == 0) | ||
402 | continue; | ||
403 | seq_printf(s, "%3d %20s %6d %10d\n", | ||
404 | i, irq_to_desc(i)->action ? | ||
405 | irq_to_desc(i)->action->name ?: "???" : "???", | ||
406 | idle_stats.lp2_int_count[i], | ||
407 | idle_stats.lp2_int_count[i] - | ||
408 | idle_stats.last_lp2_int_count[i]); | ||
409 | idle_stats.last_lp2_int_count[i] = idle_stats.lp2_int_count[i]; | ||
410 | }; | ||
411 | return 0; | ||
412 | } | ||
413 | #endif | ||