Commit | Line | Data |
---|---|---|
d457ef35 JL |
1 | /* |
2 | * CPU complex suspend & resume functions for Tegra SoCs | |
3 | * | |
4 | * Copyright (c) 2009-2012, NVIDIA Corporation. All rights reserved. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms and conditions of the GNU General Public License, | |
8 | * version 2, as published by the Free Software Foundation. | |
9 | * | |
10 | * This program is distributed in the hope it will be useful, but WITHOUT | |
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
13 | * more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
17 | */ | |
18 | ||
a0524acc | 19 | #include <linux/clk/tegra.h> |
d457ef35 | 20 | #include <linux/cpumask.h> |
d552920a | 21 | #include <linux/cpu_pm.h> |
a0524acc | 22 | #include <linux/delay.h> |
d552920a | 23 | #include <linux/err.h> |
a0524acc TR |
24 | #include <linux/io.h> |
25 | #include <linux/kernel.h> | |
1ff6bbfd | 26 | #include <linux/slab.h> |
a0524acc TR |
27 | #include <linux/spinlock.h> |
28 | #include <linux/suspend.h> | |
d552920a | 29 | |
304664ea | 30 | #include <soc/tegra/fuse.h> |
7232398a TR |
31 | #include <soc/tegra/pm.h> |
32 | #include <soc/tegra/pmc.h> | |
304664ea | 33 | |
d552920a | 34 | #include <asm/cacheflush.h> |
d552920a JL |
35 | #include <asm/idmap.h> |
36 | #include <asm/proc-fns.h> | |
a0524acc TR |
37 | #include <asm/smp_plat.h> |
38 | #include <asm/suspend.h> | |
d552920a | 39 | #include <asm/tlbflush.h> |
d457ef35 | 40 | |
d552920a | 41 | #include "flowctrl.h" |
a0524acc | 42 | #include "iomap.h" |
a0524acc TR |
43 | #include "pm.h" |
44 | #include "reset.h" | |
d552920a | 45 | #include "sleep.h" |
d552920a | 46 | |
d457ef35 | 47 | #ifdef CONFIG_PM_SLEEP |
d457ef35 | 48 | static DEFINE_SPINLOCK(tegra_lp2_lock); |
95872f42 JL |
49 | static u32 iram_save_size; |
50 | static void *iram_save_addr; | |
51 | struct tegra_lp1_iram tegra_lp1_iram; | |
d552920a | 52 | void (*tegra_tear_down_cpu)(void); |
95872f42 JL |
53 | void (*tegra_sleep_core_finish)(unsigned long v2p); |
54 | static int (*tegra_sleep_func)(unsigned long v2p); | |
d457ef35 | 55 | |
bf91add4 JL |
56 | static void tegra_tear_down_cpu_init(void) |
57 | { | |
304664ea | 58 | switch (tegra_get_chip_id()) { |
bf91add4 JL |
59 | case TEGRA20: |
60 | if (IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC)) | |
61 | tegra_tear_down_cpu = tegra20_tear_down_cpu; | |
62 | break; | |
63 | case TEGRA30: | |
b573ad9f | 64 | case TEGRA114: |
f0c4ac13 | 65 | case TEGRA124: |
b573ad9f | 66 | if (IS_ENABLED(CONFIG_ARCH_TEGRA_3x_SOC) || |
f0c4ac13 JL |
67 | IS_ENABLED(CONFIG_ARCH_TEGRA_114_SOC) || |
68 | IS_ENABLED(CONFIG_ARCH_TEGRA_124_SOC)) | |
bf91add4 JL |
69 | tegra_tear_down_cpu = tegra30_tear_down_cpu; |
70 | break; | |
71 | } | |
72 | } | |
73 | ||
d552920a JL |
74 | /* |
75 | * restore_cpu_complex | |
76 | * | |
77 | * restores cpu clock setting, clears flow controller | |
78 | * | |
79 | * Always called on CPU 0. | |
80 | */ | |
81 | static void restore_cpu_complex(void) | |
82 | { | |
83 | int cpu = smp_processor_id(); | |
84 | ||
85 | BUG_ON(cpu != 0); | |
86 | ||
87 | #ifdef CONFIG_SMP | |
88 | cpu = cpu_logical_map(cpu); | |
89 | #endif | |
90 | ||
91 | /* Restore the CPU clock settings */ | |
92 | tegra_cpu_clock_resume(); | |
93 | ||
94 | flowctrl_cpu_suspend_exit(cpu); | |
d552920a JL |
95 | } |
96 | ||
97 | /* | |
98 | * suspend_cpu_complex | |
99 | * | |
100 | * saves pll state for use by restart_plls, prepares flow controller for | |
101 | * transition to suspend state | |
102 | * | |
103 | * Must always be called on cpu 0. | |
104 | */ | |
105 | static void suspend_cpu_complex(void) | |
106 | { | |
107 | int cpu = smp_processor_id(); | |
108 | ||
109 | BUG_ON(cpu != 0); | |
110 | ||
111 | #ifdef CONFIG_SMP | |
112 | cpu = cpu_logical_map(cpu); | |
113 | #endif | |
114 | ||
115 | /* Save the CPU clock settings */ | |
116 | tegra_cpu_clock_suspend(); | |
117 | ||
118 | flowctrl_cpu_suspend_enter(cpu); | |
d552920a JL |
119 | } |
120 | ||
8f6a0b65 | 121 | void tegra_clear_cpu_in_lp2(void) |
d457ef35 | 122 | { |
8f6a0b65 | 123 | int phy_cpu_id = cpu_logical_map(smp_processor_id()); |
d457ef35 JL |
124 | u32 *cpu_in_lp2 = tegra_cpu_lp2_mask; |
125 | ||
126 | spin_lock(&tegra_lp2_lock); | |
127 | ||
128 | BUG_ON(!(*cpu_in_lp2 & BIT(phy_cpu_id))); | |
129 | *cpu_in_lp2 &= ~BIT(phy_cpu_id); | |
130 | ||
131 | spin_unlock(&tegra_lp2_lock); | |
132 | } | |
133 | ||
8f6a0b65 | 134 | bool tegra_set_cpu_in_lp2(void) |
d457ef35 | 135 | { |
8f6a0b65 | 136 | int phy_cpu_id = cpu_logical_map(smp_processor_id()); |
d457ef35 JL |
137 | bool last_cpu = false; |
138 | cpumask_t *cpu_lp2_mask = tegra_cpu_lp2_mask; | |
139 | u32 *cpu_in_lp2 = tegra_cpu_lp2_mask; | |
140 | ||
141 | spin_lock(&tegra_lp2_lock); | |
142 | ||
143 | BUG_ON((*cpu_in_lp2 & BIT(phy_cpu_id))); | |
144 | *cpu_in_lp2 |= BIT(phy_cpu_id); | |
145 | ||
146 | if ((phy_cpu_id == 0) && cpumask_equal(cpu_lp2_mask, cpu_online_mask)) | |
147 | last_cpu = true; | |
304664ea | 148 | else if (tegra_get_chip_id() == TEGRA20 && phy_cpu_id == 1) |
5c1350bd | 149 | tegra20_cpu_set_resettable_soon(); |
d457ef35 JL |
150 | |
151 | spin_unlock(&tegra_lp2_lock); | |
152 | return last_cpu; | |
153 | } | |
d552920a | 154 | |
2058842e AB |
155 | int tegra_cpu_do_idle(void) |
156 | { | |
157 | return cpu_do_idle(); | |
158 | } | |
159 | ||
d552920a JL |
160 | static int tegra_sleep_cpu(unsigned long v2p) |
161 | { | |
6affb482 | 162 | setup_mm_for_reboot(); |
d552920a JL |
163 | tegra_sleep_cpu_finish(v2p); |
164 | ||
165 | /* should never here */ | |
166 | BUG(); | |
167 | ||
168 | return 0; | |
169 | } | |
170 | ||
7232398a TR |
171 | static void tegra_pm_set(enum tegra_suspend_mode mode) |
172 | { | |
173 | u32 value; | |
174 | ||
175 | switch (tegra_get_chip_id()) { | |
176 | case TEGRA20: | |
177 | case TEGRA30: | |
178 | break; | |
179 | default: | |
180 | /* Turn off CRAIL */ | |
181 | value = flowctrl_read_cpu_csr(0); | |
182 | value &= ~FLOW_CTRL_CSR_ENABLE_EXT_MASK; | |
183 | value |= FLOW_CTRL_CSR_ENABLE_EXT_CRAIL; | |
184 | flowctrl_write_cpu_csr(0, value); | |
185 | break; | |
186 | } | |
187 | ||
188 | tegra_pmc_enter_suspend_mode(mode); | |
189 | } | |
190 | ||
4d82d058 | 191 | void tegra_idle_lp2_last(void) |
d552920a | 192 | { |
7232398a | 193 | tegra_pm_set(TEGRA_SUSPEND_LP2); |
d552920a JL |
194 | |
195 | cpu_cluster_pm_enter(); | |
196 | suspend_cpu_complex(); | |
d552920a JL |
197 | |
198 | cpu_suspend(PHYS_OFFSET - PAGE_OFFSET, &tegra_sleep_cpu); | |
199 | ||
d552920a JL |
200 | restore_cpu_complex(); |
201 | cpu_cluster_pm_exit(); | |
202 | } | |
c8c2e606 JL |
203 | |
204 | enum tegra_suspend_mode tegra_pm_validate_suspend_mode( | |
205 | enum tegra_suspend_mode mode) | |
206 | { | |
c8c2e606 | 207 | /* |
95872f42 | 208 | * The Tegra devices support suspending to LP1 or lower currently. |
c8c2e606 | 209 | */ |
95872f42 JL |
210 | if (mode > TEGRA_SUSPEND_LP1) |
211 | return TEGRA_SUSPEND_LP1; | |
c8c2e606 JL |
212 | |
213 | return mode; | |
214 | } | |
215 | ||
95872f42 JL |
216 | static int tegra_sleep_core(unsigned long v2p) |
217 | { | |
218 | setup_mm_for_reboot(); | |
219 | tegra_sleep_core_finish(v2p); | |
220 | ||
221 | /* should never here */ | |
222 | BUG(); | |
223 | ||
224 | return 0; | |
225 | } | |
226 | ||
227 | /* | |
228 | * tegra_lp1_iram_hook | |
229 | * | |
230 | * Hooking the address of LP1 reset vector and SDRAM self-refresh code in | |
231 | * SDRAM. These codes not be copied to IRAM in this fuction. We need to | |
232 | * copy these code to IRAM before LP0/LP1 suspend and restore the content | |
233 | * of IRAM after resume. | |
234 | */ | |
235 | static bool tegra_lp1_iram_hook(void) | |
236 | { | |
304664ea | 237 | switch (tegra_get_chip_id()) { |
731a9274 JL |
238 | case TEGRA20: |
239 | if (IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC)) | |
240 | tegra20_lp1_iram_hook(); | |
241 | break; | |
e7a932b1 | 242 | case TEGRA30: |
e9f62449 | 243 | case TEGRA114: |
f0c4ac13 | 244 | case TEGRA124: |
e9f62449 | 245 | if (IS_ENABLED(CONFIG_ARCH_TEGRA_3x_SOC) || |
f0c4ac13 JL |
246 | IS_ENABLED(CONFIG_ARCH_TEGRA_114_SOC) || |
247 | IS_ENABLED(CONFIG_ARCH_TEGRA_124_SOC)) | |
e7a932b1 JL |
248 | tegra30_lp1_iram_hook(); |
249 | break; | |
250 | default: | |
251 | break; | |
252 | } | |
253 | ||
95872f42 JL |
254 | if (!tegra_lp1_iram.start_addr || !tegra_lp1_iram.end_addr) |
255 | return false; | |
256 | ||
257 | iram_save_size = tegra_lp1_iram.end_addr - tegra_lp1_iram.start_addr; | |
258 | iram_save_addr = kmalloc(iram_save_size, GFP_KERNEL); | |
259 | if (!iram_save_addr) | |
260 | return false; | |
261 | ||
262 | return true; | |
263 | } | |
264 | ||
265 | static bool tegra_sleep_core_init(void) | |
266 | { | |
304664ea | 267 | switch (tegra_get_chip_id()) { |
731a9274 JL |
268 | case TEGRA20: |
269 | if (IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC)) | |
270 | tegra20_sleep_core_init(); | |
271 | break; | |
e7a932b1 | 272 | case TEGRA30: |
e9f62449 | 273 | case TEGRA114: |
f0c4ac13 | 274 | case TEGRA124: |
e9f62449 | 275 | if (IS_ENABLED(CONFIG_ARCH_TEGRA_3x_SOC) || |
f0c4ac13 JL |
276 | IS_ENABLED(CONFIG_ARCH_TEGRA_114_SOC) || |
277 | IS_ENABLED(CONFIG_ARCH_TEGRA_124_SOC)) | |
e7a932b1 JL |
278 | tegra30_sleep_core_init(); |
279 | break; | |
280 | default: | |
281 | break; | |
282 | } | |
283 | ||
95872f42 JL |
284 | if (!tegra_sleep_core_finish) |
285 | return false; | |
286 | ||
287 | return true; | |
288 | } | |
289 | ||
290 | static void tegra_suspend_enter_lp1(void) | |
291 | { | |
95872f42 | 292 | /* copy the reset vector & SDRAM shutdown code into IRAM */ |
fddb770d | 293 | memcpy(iram_save_addr, IO_ADDRESS(TEGRA_IRAM_LPx_RESUME_AREA), |
95872f42 | 294 | iram_save_size); |
fddb770d SW |
295 | memcpy(IO_ADDRESS(TEGRA_IRAM_LPx_RESUME_AREA), |
296 | tegra_lp1_iram.start_addr, iram_save_size); | |
95872f42 JL |
297 | |
298 | *((u32 *)tegra_cpu_lp1_mask) = 1; | |
299 | } | |
300 | ||
301 | static void tegra_suspend_exit_lp1(void) | |
302 | { | |
95872f42 | 303 | /* restore IRAM */ |
fddb770d | 304 | memcpy(IO_ADDRESS(TEGRA_IRAM_LPx_RESUME_AREA), iram_save_addr, |
95872f42 JL |
305 | iram_save_size); |
306 | ||
307 | *(u32 *)tegra_cpu_lp1_mask = 0; | |
308 | } | |
309 | ||
c8c2e606 JL |
310 | static const char *lp_state[TEGRA_MAX_SUSPEND_MODE] = { |
311 | [TEGRA_SUSPEND_NONE] = "none", | |
312 | [TEGRA_SUSPEND_LP2] = "LP2", | |
313 | [TEGRA_SUSPEND_LP1] = "LP1", | |
314 | [TEGRA_SUSPEND_LP0] = "LP0", | |
315 | }; | |
316 | ||
8bd26e3a | 317 | static int tegra_suspend_enter(suspend_state_t state) |
c8c2e606 JL |
318 | { |
319 | enum tegra_suspend_mode mode = tegra_pmc_get_suspend_mode(); | |
320 | ||
321 | if (WARN_ON(mode < TEGRA_SUSPEND_NONE || | |
322 | mode >= TEGRA_MAX_SUSPEND_MODE)) | |
323 | return -EINVAL; | |
324 | ||
325 | pr_info("Entering suspend state %s\n", lp_state[mode]); | |
326 | ||
7232398a | 327 | tegra_pm_set(mode); |
c8c2e606 JL |
328 | |
329 | local_fiq_disable(); | |
330 | ||
331 | suspend_cpu_complex(); | |
332 | switch (mode) { | |
95872f42 JL |
333 | case TEGRA_SUSPEND_LP1: |
334 | tegra_suspend_enter_lp1(); | |
335 | break; | |
c8c2e606 | 336 | case TEGRA_SUSPEND_LP2: |
8f6a0b65 | 337 | tegra_set_cpu_in_lp2(); |
c8c2e606 JL |
338 | break; |
339 | default: | |
340 | break; | |
341 | } | |
342 | ||
95872f42 | 343 | cpu_suspend(PHYS_OFFSET - PAGE_OFFSET, tegra_sleep_func); |
c8c2e606 JL |
344 | |
345 | switch (mode) { | |
95872f42 JL |
346 | case TEGRA_SUSPEND_LP1: |
347 | tegra_suspend_exit_lp1(); | |
348 | break; | |
c8c2e606 | 349 | case TEGRA_SUSPEND_LP2: |
8f6a0b65 | 350 | tegra_clear_cpu_in_lp2(); |
c8c2e606 JL |
351 | break; |
352 | default: | |
353 | break; | |
354 | } | |
355 | restore_cpu_complex(); | |
356 | ||
357 | local_fiq_enable(); | |
358 | ||
359 | return 0; | |
360 | } | |
361 | ||
362 | static const struct platform_suspend_ops tegra_suspend_ops = { | |
363 | .valid = suspend_valid_only_mem, | |
364 | .enter = tegra_suspend_enter, | |
365 | }; | |
366 | ||
367 | void __init tegra_init_suspend(void) | |
368 | { | |
95872f42 JL |
369 | enum tegra_suspend_mode mode = tegra_pmc_get_suspend_mode(); |
370 | ||
371 | if (mode == TEGRA_SUSPEND_NONE) | |
c8c2e606 JL |
372 | return; |
373 | ||
bf91add4 | 374 | tegra_tear_down_cpu_init(); |
c8c2e606 | 375 | |
95872f42 JL |
376 | if (mode >= TEGRA_SUSPEND_LP1) { |
377 | if (!tegra_lp1_iram_hook() || !tegra_sleep_core_init()) { | |
378 | pr_err("%s: unable to allocate memory for SDRAM" | |
379 | "self-refresh -- LP0/LP1 unavailable\n", | |
380 | __func__); | |
381 | tegra_pmc_set_suspend_mode(TEGRA_SUSPEND_LP2); | |
382 | mode = TEGRA_SUSPEND_LP2; | |
383 | } | |
384 | } | |
385 | ||
386 | /* set up sleep function for cpu_suspend */ | |
387 | switch (mode) { | |
388 | case TEGRA_SUSPEND_LP1: | |
389 | tegra_sleep_func = tegra_sleep_core; | |
390 | break; | |
391 | case TEGRA_SUSPEND_LP2: | |
392 | tegra_sleep_func = tegra_sleep_cpu; | |
393 | break; | |
394 | default: | |
395 | break; | |
396 | } | |
397 | ||
c8c2e606 JL |
398 | suspend_set_ops(&tegra_suspend_ops); |
399 | } | |
d457ef35 | 400 | #endif |