diff options
author | Scott Wood <scottwood@freescale.com> | 2007-10-09 13:37:13 -0400 |
---|---|---|
committer | Kumar Gala <galak@kernel.crashing.org> | 2008-07-16 18:57:30 -0400 |
commit | d49747bdfb2ddebea24d1580da55b79d093d48a9 (patch) | |
tree | cb2ae6ea03bab0ff7901c9997ef50131bba6b511 /arch/powerpc/platforms/83xx | |
parent | 7e72063c9aaeb618815589cd4d57f26186e6fcad (diff) |
powerpc/mpc83xx: Power Management support
Basic PM support for 83xx. Standby is implemented as sleep.
Suspend-to-RAM is implemented as "deep sleep" (with the processor
turned off) on 831x.
Signed-off-by: Scott Wood <scottwood@freescale.com>
Signed-off-by: Kumar Gala <galak@kernel.crashing.org>
Diffstat (limited to 'arch/powerpc/platforms/83xx')
-rw-r--r-- | arch/powerpc/platforms/83xx/Makefile | 1 | ||||
-rw-r--r-- | arch/powerpc/platforms/83xx/suspend-asm.S | 533 | ||||
-rw-r--r-- | arch/powerpc/platforms/83xx/suspend.c | 388 |
3 files changed, 922 insertions, 0 deletions
diff --git a/arch/powerpc/platforms/83xx/Makefile b/arch/powerpc/platforms/83xx/Makefile index f331fd7dd836..32c7ad13911c 100644 --- a/arch/powerpc/platforms/83xx/Makefile +++ b/arch/powerpc/platforms/83xx/Makefile | |||
@@ -3,6 +3,7 @@ | |||
3 | # | 3 | # |
4 | obj-y := misc.o usb.o | 4 | obj-y := misc.o usb.o |
5 | obj-$(CONFIG_PCI) += pci.o | 5 | obj-$(CONFIG_PCI) += pci.o |
6 | obj-$(CONFIG_SUSPEND) += suspend.o suspend-asm.o | ||
6 | obj-$(CONFIG_MPC831x_RDB) += mpc831x_rdb.o | 7 | obj-$(CONFIG_MPC831x_RDB) += mpc831x_rdb.o |
7 | obj-$(CONFIG_MPC832x_RDB) += mpc832x_rdb.o | 8 | obj-$(CONFIG_MPC832x_RDB) += mpc832x_rdb.o |
8 | obj-$(CONFIG_MPC834x_MDS) += mpc834x_mds.o | 9 | obj-$(CONFIG_MPC834x_MDS) += mpc834x_mds.o |
diff --git a/arch/powerpc/platforms/83xx/suspend-asm.S b/arch/powerpc/platforms/83xx/suspend-asm.S new file mode 100644 index 000000000000..1930543c98d3 --- /dev/null +++ b/arch/powerpc/platforms/83xx/suspend-asm.S | |||
@@ -0,0 +1,533 @@ | |||
1 | /* | ||
2 | * Enter and leave deep sleep state on MPC83xx | ||
3 | * | ||
4 | * Copyright (c) 2006-2008 Freescale Semiconductor, Inc. | ||
5 | * Author: Scott Wood <scottwood@freescale.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify it | ||
8 | * under the terms of the GNU General Public License version 2 as published | ||
9 | * by the Free Software Foundation. | ||
10 | */ | ||
11 | |||
12 | #include <asm/page.h> | ||
13 | #include <asm/ppc_asm.h> | ||
14 | #include <asm/reg.h> | ||
15 | #include <asm/asm-offsets.h> | ||
16 | |||
17 | #define SS_MEMSAVE 0x00 /* First 8 bytes of RAM */ | ||
18 | #define SS_HID 0x08 /* 3 HIDs */ | ||
19 | #define SS_IABR 0x14 /* 2 IABRs */ | ||
20 | #define SS_IBCR 0x1c | ||
21 | #define SS_DABR 0x20 /* 2 DABRs */ | ||
22 | #define SS_DBCR 0x28 | ||
23 | #define SS_SP 0x2c | ||
24 | #define SS_SR 0x30 /* 16 segment registers */ | ||
25 | #define SS_R2 0x70 | ||
26 | #define SS_MSR 0x74 | ||
27 | #define SS_SDR1 0x78 | ||
28 | #define SS_LR 0x7c | ||
29 | #define SS_SPRG 0x80 /* 4 SPRGs */ | ||
30 | #define SS_DBAT 0x90 /* 8 DBATs */ | ||
31 | #define SS_IBAT 0xd0 /* 8 IBATs */ | ||
32 | #define SS_TB 0x110 | ||
33 | #define SS_CR 0x118 | ||
34 | #define SS_GPREG 0x11c /* r12-r31 */ | ||
35 | #define STATE_SAVE_SIZE 0x16c | ||
36 | |||
37 | .section .data | ||
38 | .align 5 | ||
39 | |||
40 | mpc83xx_sleep_save_area: | ||
41 | .space STATE_SAVE_SIZE | ||
42 | immrbase: | ||
43 | .long 0 | ||
44 | |||
45 | .section .text | ||
46 | .align 5 | ||
47 | |||
48 | /* r3 = physical address of IMMR */ | ||
49 | _GLOBAL(mpc83xx_enter_deep_sleep) | ||
50 | lis r4, immrbase@ha | ||
51 | stw r3, immrbase@l(r4) | ||
52 | |||
53 | /* The first 2 words of memory are used to communicate with the | ||
54 | * bootloader, to tell it how to resume. | ||
55 | * | ||
56 | * The first word is the magic number 0xf5153ae5, and the second | ||
57 | * is the pointer to mpc83xx_deep_resume. | ||
58 | * | ||
59 | * The original content of these two words is saved in SS_MEMSAVE. | ||
60 | */ | ||
61 | |||
62 | lis r3, mpc83xx_sleep_save_area@h | ||
63 | ori r3, r3, mpc83xx_sleep_save_area@l | ||
64 | |||
65 | lis r4, KERNELBASE@h | ||
66 | lwz r5, 0(r4) | ||
67 | lwz r6, 4(r4) | ||
68 | |||
69 | stw r5, SS_MEMSAVE+0(r3) | ||
70 | stw r6, SS_MEMSAVE+4(r3) | ||
71 | |||
72 | mfspr r5, SPRN_HID0 | ||
73 | mfspr r6, SPRN_HID1 | ||
74 | mfspr r7, SPRN_HID2 | ||
75 | |||
76 | stw r5, SS_HID+0(r3) | ||
77 | stw r6, SS_HID+4(r3) | ||
78 | stw r7, SS_HID+8(r3) | ||
79 | |||
80 | mfspr r4, SPRN_IABR | ||
81 | mfspr r5, SPRN_IABR2 | ||
82 | mfspr r6, SPRN_IBCR | ||
83 | mfspr r7, SPRN_DABR | ||
84 | mfspr r8, SPRN_DABR2 | ||
85 | mfspr r9, SPRN_DBCR | ||
86 | |||
87 | stw r4, SS_IABR+0(r3) | ||
88 | stw r5, SS_IABR+4(r3) | ||
89 | stw r6, SS_IBCR(r3) | ||
90 | stw r7, SS_DABR+0(r3) | ||
91 | stw r8, SS_DABR+4(r3) | ||
92 | stw r9, SS_DBCR(r3) | ||
93 | |||
94 | mfspr r4, SPRN_SPRG0 | ||
95 | mfspr r5, SPRN_SPRG1 | ||
96 | mfspr r6, SPRN_SPRG2 | ||
97 | mfspr r7, SPRN_SPRG3 | ||
98 | mfsdr1 r8 | ||
99 | |||
100 | stw r4, SS_SPRG+0(r3) | ||
101 | stw r5, SS_SPRG+4(r3) | ||
102 | stw r6, SS_SPRG+8(r3) | ||
103 | stw r7, SS_SPRG+12(r3) | ||
104 | stw r8, SS_SDR1(r3) | ||
105 | |||
106 | mfspr r4, SPRN_DBAT0U | ||
107 | mfspr r5, SPRN_DBAT0L | ||
108 | mfspr r6, SPRN_DBAT1U | ||
109 | mfspr r7, SPRN_DBAT1L | ||
110 | |||
111 | stw r4, SS_DBAT+0x00(r3) | ||
112 | stw r5, SS_DBAT+0x04(r3) | ||
113 | stw r6, SS_DBAT+0x08(r3) | ||
114 | stw r7, SS_DBAT+0x0c(r3) | ||
115 | |||
116 | mfspr r4, SPRN_DBAT2U | ||
117 | mfspr r5, SPRN_DBAT2L | ||
118 | mfspr r6, SPRN_DBAT3U | ||
119 | mfspr r7, SPRN_DBAT3L | ||
120 | |||
121 | stw r4, SS_DBAT+0x10(r3) | ||
122 | stw r5, SS_DBAT+0x14(r3) | ||
123 | stw r6, SS_DBAT+0x18(r3) | ||
124 | stw r7, SS_DBAT+0x1c(r3) | ||
125 | |||
126 | mfspr r4, SPRN_DBAT4U | ||
127 | mfspr r5, SPRN_DBAT4L | ||
128 | mfspr r6, SPRN_DBAT5U | ||
129 | mfspr r7, SPRN_DBAT5L | ||
130 | |||
131 | stw r4, SS_DBAT+0x20(r3) | ||
132 | stw r5, SS_DBAT+0x24(r3) | ||
133 | stw r6, SS_DBAT+0x28(r3) | ||
134 | stw r7, SS_DBAT+0x2c(r3) | ||
135 | |||
136 | mfspr r4, SPRN_DBAT6U | ||
137 | mfspr r5, SPRN_DBAT6L | ||
138 | mfspr r6, SPRN_DBAT7U | ||
139 | mfspr r7, SPRN_DBAT7L | ||
140 | |||
141 | stw r4, SS_DBAT+0x30(r3) | ||
142 | stw r5, SS_DBAT+0x34(r3) | ||
143 | stw r6, SS_DBAT+0x38(r3) | ||
144 | stw r7, SS_DBAT+0x3c(r3) | ||
145 | |||
146 | mfspr r4, SPRN_IBAT0U | ||
147 | mfspr r5, SPRN_IBAT0L | ||
148 | mfspr r6, SPRN_IBAT1U | ||
149 | mfspr r7, SPRN_IBAT1L | ||
150 | |||
151 | stw r4, SS_IBAT+0x00(r3) | ||
152 | stw r5, SS_IBAT+0x04(r3) | ||
153 | stw r6, SS_IBAT+0x08(r3) | ||
154 | stw r7, SS_IBAT+0x0c(r3) | ||
155 | |||
156 | mfspr r4, SPRN_IBAT2U | ||
157 | mfspr r5, SPRN_IBAT2L | ||
158 | mfspr r6, SPRN_IBAT3U | ||
159 | mfspr r7, SPRN_IBAT3L | ||
160 | |||
161 | stw r4, SS_IBAT+0x10(r3) | ||
162 | stw r5, SS_IBAT+0x14(r3) | ||
163 | stw r6, SS_IBAT+0x18(r3) | ||
164 | stw r7, SS_IBAT+0x1c(r3) | ||
165 | |||
166 | mfspr r4, SPRN_IBAT4U | ||
167 | mfspr r5, SPRN_IBAT4L | ||
168 | mfspr r6, SPRN_IBAT5U | ||
169 | mfspr r7, SPRN_IBAT5L | ||
170 | |||
171 | stw r4, SS_IBAT+0x20(r3) | ||
172 | stw r5, SS_IBAT+0x24(r3) | ||
173 | stw r6, SS_IBAT+0x28(r3) | ||
174 | stw r7, SS_IBAT+0x2c(r3) | ||
175 | |||
176 | mfspr r4, SPRN_IBAT6U | ||
177 | mfspr r5, SPRN_IBAT6L | ||
178 | mfspr r6, SPRN_IBAT7U | ||
179 | mfspr r7, SPRN_IBAT7L | ||
180 | |||
181 | stw r4, SS_IBAT+0x30(r3) | ||
182 | stw r5, SS_IBAT+0x34(r3) | ||
183 | stw r6, SS_IBAT+0x38(r3) | ||
184 | stw r7, SS_IBAT+0x3c(r3) | ||
185 | |||
186 | mfmsr r4 | ||
187 | mflr r5 | ||
188 | mfcr r6 | ||
189 | |||
190 | stw r4, SS_MSR(r3) | ||
191 | stw r5, SS_LR(r3) | ||
192 | stw r6, SS_CR(r3) | ||
193 | stw r1, SS_SP(r3) | ||
194 | stw r2, SS_R2(r3) | ||
195 | |||
196 | 1: mftbu r4 | ||
197 | mftb r5 | ||
198 | mftbu r6 | ||
199 | cmpw r4, r6 | ||
200 | bne 1b | ||
201 | |||
202 | stw r4, SS_TB+0(r3) | ||
203 | stw r5, SS_TB+4(r3) | ||
204 | |||
205 | stmw r12, SS_GPREG(r3) | ||
206 | |||
207 | li r4, 0 | ||
208 | addi r6, r3, SS_SR-4 | ||
209 | 1: mfsrin r5, r4 | ||
210 | stwu r5, 4(r6) | ||
211 | addis r4, r4, 0x1000 | ||
212 | cmpwi r4, 0 | ||
213 | bne 1b | ||
214 | |||
215 | /* Disable machine checks and critical exceptions */ | ||
216 | mfmsr r4 | ||
217 | rlwinm r4, r4, 0, ~MSR_CE | ||
218 | rlwinm r4, r4, 0, ~MSR_ME | ||
219 | mtmsr r4 | ||
220 | isync | ||
221 | |||
222 | #define TMP_VIRT_IMMR 0xf0000000 | ||
223 | #define DEFAULT_IMMR_VALUE 0xff400000 | ||
224 | #define IMMRBAR_BASE 0x0000 | ||
225 | |||
226 | lis r4, immrbase@ha | ||
227 | lwz r4, immrbase@l(r4) | ||
228 | |||
229 | /* Use DBAT0 to address the current IMMR space */ | ||
230 | |||
231 | ori r4, r4, 0x002a | ||
232 | mtspr SPRN_DBAT0L, r4 | ||
233 | lis r8, TMP_VIRT_IMMR@h | ||
234 | ori r4, r8, 0x001e /* 1 MByte accessable from Kernel Space only */ | ||
235 | mtspr SPRN_DBAT0U, r4 | ||
236 | isync | ||
237 | |||
238 | /* Use DBAT1 to address the original IMMR space */ | ||
239 | |||
240 | lis r4, DEFAULT_IMMR_VALUE@h | ||
241 | ori r4, r4, 0x002a | ||
242 | mtspr SPRN_DBAT1L, r4 | ||
243 | lis r9, (TMP_VIRT_IMMR + 0x01000000)@h | ||
244 | ori r4, r9, 0x001e /* 1 MByte accessable from Kernel Space only */ | ||
245 | mtspr SPRN_DBAT1U, r4 | ||
246 | isync | ||
247 | |||
248 | /* Use DBAT2 to address the beginning of RAM. This isn't done | ||
249 | * using the normal virtual mapping, because with page debugging | ||
250 | * enabled it will be read-only. | ||
251 | */ | ||
252 | |||
253 | li r4, 0x0002 | ||
254 | mtspr SPRN_DBAT2L, r4 | ||
255 | lis r4, KERNELBASE@h | ||
256 | ori r4, r4, 0x001e /* 1 MByte accessable from Kernel Space only */ | ||
257 | mtspr SPRN_DBAT2U, r4 | ||
258 | isync | ||
259 | |||
260 | /* Flush the cache with our BAT, as there will be TLB misses | ||
261 | * otherwise if page debugging is enabled, and these misses | ||
262 | * will disturb the PLRU algorithm. | ||
263 | */ | ||
264 | |||
265 | bl __flush_disable_L1 | ||
266 | |||
267 | /* Keep the i-cache enabled, so the hack below for low-boot | ||
268 | * flash will work. | ||
269 | */ | ||
270 | mfspr r3, SPRN_HID0 | ||
271 | ori r3, r3, HID0_ICE | ||
272 | mtspr SPRN_HID0, r3 | ||
273 | isync | ||
274 | |||
275 | lis r6, 0xf515 | ||
276 | ori r6, r6, 0x3ae5 | ||
277 | |||
278 | lis r7, mpc83xx_deep_resume@h | ||
279 | ori r7, r7, mpc83xx_deep_resume@l | ||
280 | tophys(r7, r7) | ||
281 | |||
282 | lis r5, KERNELBASE@h | ||
283 | stw r6, 0(r5) | ||
284 | stw r7, 4(r5) | ||
285 | |||
286 | /* Reset BARs */ | ||
287 | |||
288 | li r4, 0 | ||
289 | stw r4, 0x0024(r8) | ||
290 | stw r4, 0x002c(r8) | ||
291 | stw r4, 0x0034(r8) | ||
292 | stw r4, 0x003c(r8) | ||
293 | stw r4, 0x0064(r8) | ||
294 | stw r4, 0x006c(r8) | ||
295 | |||
296 | /* Rev 1 of the 8313 has problems with wakeup events that are | ||
297 | * pending during the transition to deep sleep state (such as if | ||
298 | * the PCI host sets the state to D3 and then D0 in rapid | ||
299 | * succession). This check shrinks the race window somewhat. | ||
300 | * | ||
301 | * See erratum PCI23, though the problem is not limited | ||
302 | * to PCI. | ||
303 | */ | ||
304 | |||
305 | lwz r3, 0x0b04(r8) | ||
306 | andi. r3, r3, 1 | ||
307 | bne- mpc83xx_deep_resume | ||
308 | |||
309 | /* Move IMMR back to the default location, following the | ||
310 | * procedure specified in the MPC8313 manual. | ||
311 | */ | ||
312 | lwz r4, IMMRBAR_BASE(r8) | ||
313 | isync | ||
314 | lis r4, DEFAULT_IMMR_VALUE@h | ||
315 | stw r4, IMMRBAR_BASE(r8) | ||
316 | lis r4, KERNELBASE@h | ||
317 | lwz r4, 0(r4) | ||
318 | isync | ||
319 | lwz r4, IMMRBAR_BASE(r9) | ||
320 | mr r8, r9 | ||
321 | isync | ||
322 | |||
323 | /* Check the Reset Configuration Word to see whether flash needs | ||
324 | * to be mapped at a low address or a high address. | ||
325 | */ | ||
326 | |||
327 | lwz r4, 0x0904(r8) | ||
328 | andis. r4, r4, 0x0400 | ||
329 | li r4, 0 | ||
330 | beq boot_low | ||
331 | lis r4, 0xff80 | ||
332 | boot_low: | ||
333 | stw r4, 0x0020(r8) | ||
334 | lis r7, 0x8000 | ||
335 | ori r7, r7, 0x0016 | ||
336 | |||
337 | mfspr r5, SPRN_HID0 | ||
338 | rlwinm r5, r5, 0, ~(HID0_DOZE | HID0_NAP) | ||
339 | oris r5, r5, HID0_SLEEP@h | ||
340 | mtspr SPRN_HID0, r5 | ||
341 | isync | ||
342 | |||
343 | mfmsr r5 | ||
344 | oris r5, r5, MSR_POW@h | ||
345 | |||
346 | /* Enable the flash mapping at the appropriate address. This | ||
347 | * mapping will override the RAM mapping if booting low, so there's | ||
348 | * no need to disable the latter. This must be done inside the same | ||
349 | * cache line as setting MSR_POW, so that no instruction fetches | ||
350 | * from RAM happen after the flash mapping is turned on. | ||
351 | */ | ||
352 | |||
353 | .align 5 | ||
354 | stw r7, 0x0024(r8) | ||
355 | sync | ||
356 | isync | ||
357 | mtmsr r5 | ||
358 | isync | ||
359 | 1: b 1b | ||
360 | |||
361 | mpc83xx_deep_resume: | ||
362 | lis r4, 1f@h | ||
363 | ori r4, r4, 1f@l | ||
364 | tophys(r4, r4) | ||
365 | mtsrr0 r4 | ||
366 | |||
367 | mfmsr r4 | ||
368 | rlwinm r4, r4, 0, ~(MSR_IR | MSR_DR) | ||
369 | mtsrr1 r4 | ||
370 | |||
371 | rfi | ||
372 | |||
373 | 1: tlbia | ||
374 | bl __inval_enable_L1 | ||
375 | |||
376 | lis r3, mpc83xx_sleep_save_area@h | ||
377 | ori r3, r3, mpc83xx_sleep_save_area@l | ||
378 | tophys(r3, r3) | ||
379 | |||
380 | lwz r5, SS_MEMSAVE+0(r3) | ||
381 | lwz r6, SS_MEMSAVE+4(r3) | ||
382 | |||
383 | stw r5, 0(0) | ||
384 | stw r6, 4(0) | ||
385 | |||
386 | lwz r5, SS_HID+0(r3) | ||
387 | lwz r6, SS_HID+4(r3) | ||
388 | lwz r7, SS_HID+8(r3) | ||
389 | |||
390 | mtspr SPRN_HID0, r5 | ||
391 | mtspr SPRN_HID1, r6 | ||
392 | mtspr SPRN_HID2, r7 | ||
393 | |||
394 | lwz r4, SS_IABR+0(r3) | ||
395 | lwz r5, SS_IABR+4(r3) | ||
396 | lwz r6, SS_IBCR(r3) | ||
397 | lwz r7, SS_DABR+0(r3) | ||
398 | lwz r8, SS_DABR+4(r3) | ||
399 | lwz r9, SS_DBCR(r3) | ||
400 | |||
401 | mtspr SPRN_IABR, r4 | ||
402 | mtspr SPRN_IABR2, r5 | ||
403 | mtspr SPRN_IBCR, r6 | ||
404 | mtspr SPRN_DABR, r7 | ||
405 | mtspr SPRN_DABR2, r8 | ||
406 | mtspr SPRN_DBCR, r9 | ||
407 | |||
408 | li r4, 0 | ||
409 | addi r6, r3, SS_SR-4 | ||
410 | 1: lwzu r5, 4(r6) | ||
411 | mtsrin r5, r4 | ||
412 | addis r4, r4, 0x1000 | ||
413 | cmpwi r4, 0 | ||
414 | bne 1b | ||
415 | |||
416 | lwz r4, SS_DBAT+0x00(r3) | ||
417 | lwz r5, SS_DBAT+0x04(r3) | ||
418 | lwz r6, SS_DBAT+0x08(r3) | ||
419 | lwz r7, SS_DBAT+0x0c(r3) | ||
420 | |||
421 | mtspr SPRN_DBAT0U, r4 | ||
422 | mtspr SPRN_DBAT0L, r5 | ||
423 | mtspr SPRN_DBAT1U, r6 | ||
424 | mtspr SPRN_DBAT1L, r7 | ||
425 | |||
426 | lwz r4, SS_DBAT+0x10(r3) | ||
427 | lwz r5, SS_DBAT+0x14(r3) | ||
428 | lwz r6, SS_DBAT+0x18(r3) | ||
429 | lwz r7, SS_DBAT+0x1c(r3) | ||
430 | |||
431 | mtspr SPRN_DBAT2U, r4 | ||
432 | mtspr SPRN_DBAT2L, r5 | ||
433 | mtspr SPRN_DBAT3U, r6 | ||
434 | mtspr SPRN_DBAT3L, r7 | ||
435 | |||
436 | lwz r4, SS_DBAT+0x20(r3) | ||
437 | lwz r5, SS_DBAT+0x24(r3) | ||
438 | lwz r6, SS_DBAT+0x28(r3) | ||
439 | lwz r7, SS_DBAT+0x2c(r3) | ||
440 | |||
441 | mtspr SPRN_DBAT4U, r4 | ||
442 | mtspr SPRN_DBAT4L, r5 | ||
443 | mtspr SPRN_DBAT5U, r6 | ||
444 | mtspr SPRN_DBAT5L, r7 | ||
445 | |||
446 | lwz r4, SS_DBAT+0x30(r3) | ||
447 | lwz r5, SS_DBAT+0x34(r3) | ||
448 | lwz r6, SS_DBAT+0x38(r3) | ||
449 | lwz r7, SS_DBAT+0x3c(r3) | ||
450 | |||
451 | mtspr SPRN_DBAT6U, r4 | ||
452 | mtspr SPRN_DBAT6L, r5 | ||
453 | mtspr SPRN_DBAT7U, r6 | ||
454 | mtspr SPRN_DBAT7L, r7 | ||
455 | |||
456 | lwz r4, SS_IBAT+0x00(r3) | ||
457 | lwz r5, SS_IBAT+0x04(r3) | ||
458 | lwz r6, SS_IBAT+0x08(r3) | ||
459 | lwz r7, SS_IBAT+0x0c(r3) | ||
460 | |||
461 | mtspr SPRN_IBAT0U, r4 | ||
462 | mtspr SPRN_IBAT0L, r5 | ||
463 | mtspr SPRN_IBAT1U, r6 | ||
464 | mtspr SPRN_IBAT1L, r7 | ||
465 | |||
466 | lwz r4, SS_IBAT+0x10(r3) | ||
467 | lwz r5, SS_IBAT+0x14(r3) | ||
468 | lwz r6, SS_IBAT+0x18(r3) | ||
469 | lwz r7, SS_IBAT+0x1c(r3) | ||
470 | |||
471 | mtspr SPRN_IBAT2U, r4 | ||
472 | mtspr SPRN_IBAT2L, r5 | ||
473 | mtspr SPRN_IBAT3U, r6 | ||
474 | mtspr SPRN_IBAT3L, r7 | ||
475 | |||
476 | lwz r4, SS_IBAT+0x20(r3) | ||
477 | lwz r5, SS_IBAT+0x24(r3) | ||
478 | lwz r6, SS_IBAT+0x28(r3) | ||
479 | lwz r7, SS_IBAT+0x2c(r3) | ||
480 | |||
481 | mtspr SPRN_IBAT4U, r4 | ||
482 | mtspr SPRN_IBAT4L, r5 | ||
483 | mtspr SPRN_IBAT5U, r6 | ||
484 | mtspr SPRN_IBAT5L, r7 | ||
485 | |||
486 | lwz r4, SS_IBAT+0x30(r3) | ||
487 | lwz r5, SS_IBAT+0x34(r3) | ||
488 | lwz r6, SS_IBAT+0x38(r3) | ||
489 | lwz r7, SS_IBAT+0x3c(r3) | ||
490 | |||
491 | mtspr SPRN_IBAT6U, r4 | ||
492 | mtspr SPRN_IBAT6L, r5 | ||
493 | mtspr SPRN_IBAT7U, r6 | ||
494 | mtspr SPRN_IBAT7L, r7 | ||
495 | |||
496 | lwz r4, SS_SPRG+0(r3) | ||
497 | lwz r5, SS_SPRG+4(r3) | ||
498 | lwz r6, SS_SPRG+8(r3) | ||
499 | lwz r7, SS_SPRG+12(r3) | ||
500 | lwz r8, SS_SDR1(r3) | ||
501 | |||
502 | mtspr SPRN_SPRG0, r4 | ||
503 | mtspr SPRN_SPRG1, r5 | ||
504 | mtspr SPRN_SPRG2, r6 | ||
505 | mtspr SPRN_SPRG3, r7 | ||
506 | mtsdr1 r8 | ||
507 | |||
508 | lwz r4, SS_MSR(r3) | ||
509 | lwz r5, SS_LR(r3) | ||
510 | lwz r6, SS_CR(r3) | ||
511 | lwz r1, SS_SP(r3) | ||
512 | lwz r2, SS_R2(r3) | ||
513 | |||
514 | mtsrr1 r4 | ||
515 | mtsrr0 r5 | ||
516 | mtcr r6 | ||
517 | |||
518 | li r4, 0 | ||
519 | mtspr SPRN_TBWL, r4 | ||
520 | |||
521 | lwz r4, SS_TB+0(r3) | ||
522 | lwz r5, SS_TB+4(r3) | ||
523 | |||
524 | mtspr SPRN_TBWU, r4 | ||
525 | mtspr SPRN_TBWL, r5 | ||
526 | |||
527 | lmw r12, SS_GPREG(r3) | ||
528 | |||
529 | /* Kick decrementer */ | ||
530 | li r0, 1 | ||
531 | mtdec r0 | ||
532 | |||
533 | rfi | ||
diff --git a/arch/powerpc/platforms/83xx/suspend.c b/arch/powerpc/platforms/83xx/suspend.c new file mode 100644 index 000000000000..08e65fc8b98c --- /dev/null +++ b/arch/powerpc/platforms/83xx/suspend.c | |||
@@ -0,0 +1,388 @@ | |||
1 | /* | ||
2 | * MPC83xx suspend support | ||
3 | * | ||
4 | * Author: Scott Wood <scottwood@freescale.com> | ||
5 | * | ||
6 | * Copyright (c) 2006-2007 Freescale Semiconductor, Inc. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify it | ||
9 | * under the terms of the GNU General Public License version 2 as published | ||
10 | * by the Free Software Foundation. | ||
11 | */ | ||
12 | |||
13 | #include <linux/init.h> | ||
14 | #include <linux/pm.h> | ||
15 | #include <linux/types.h> | ||
16 | #include <linux/ioport.h> | ||
17 | #include <linux/interrupt.h> | ||
18 | #include <linux/wait.h> | ||
19 | #include <linux/kthread.h> | ||
20 | #include <linux/freezer.h> | ||
21 | #include <linux/suspend.h> | ||
22 | #include <linux/fsl_devices.h> | ||
23 | #include <linux/of_platform.h> | ||
24 | |||
25 | #include <asm/reg.h> | ||
26 | #include <asm/io.h> | ||
27 | #include <asm/time.h> | ||
28 | #include <asm/mpc6xx.h> | ||
29 | |||
30 | #include <sysdev/fsl_soc.h> | ||
31 | |||
32 | #define PMCCR1_NEXT_STATE 0x0C /* Next state for power management */ | ||
33 | #define PMCCR1_NEXT_STATE_SHIFT 2 | ||
34 | #define PMCCR1_CURR_STATE 0x03 /* Current state for power management*/ | ||
35 | #define IMMR_RCW_OFFSET 0x900 | ||
36 | #define RCW_PCI_HOST 0x80000000 | ||
37 | |||
38 | void mpc83xx_enter_deep_sleep(phys_addr_t immrbase); | ||
39 | |||
40 | struct mpc83xx_pmc { | ||
41 | u32 config; | ||
42 | #define PMCCR_DLPEN 2 /* DDR SDRAM low power enable */ | ||
43 | #define PMCCR_SLPEN 1 /* System low power enable */ | ||
44 | |||
45 | u32 event; | ||
46 | u32 mask; | ||
47 | /* All but PMCI are deep-sleep only */ | ||
48 | #define PMCER_GPIO 0x100 | ||
49 | #define PMCER_PCI 0x080 | ||
50 | #define PMCER_USB 0x040 | ||
51 | #define PMCER_ETSEC1 0x020 | ||
52 | #define PMCER_ETSEC2 0x010 | ||
53 | #define PMCER_TIMER 0x008 | ||
54 | #define PMCER_INT1 0x004 | ||
55 | #define PMCER_INT2 0x002 | ||
56 | #define PMCER_PMCI 0x001 | ||
57 | #define PMCER_ALL 0x1FF | ||
58 | |||
59 | /* deep-sleep only */ | ||
60 | u32 config1; | ||
61 | #define PMCCR1_USE_STATE 0x80000000 | ||
62 | #define PMCCR1_PME_EN 0x00000080 | ||
63 | #define PMCCR1_ASSERT_PME 0x00000040 | ||
64 | #define PMCCR1_POWER_OFF 0x00000020 | ||
65 | |||
66 | /* deep-sleep only */ | ||
67 | u32 config2; | ||
68 | }; | ||
69 | |||
70 | struct mpc83xx_rcw { | ||
71 | u32 rcwlr; | ||
72 | u32 rcwhr; | ||
73 | }; | ||
74 | |||
75 | struct mpc83xx_clock { | ||
76 | u32 spmr; | ||
77 | u32 occr; | ||
78 | u32 sccr; | ||
79 | }; | ||
80 | |||
81 | struct pmc_type { | ||
82 | int has_deep_sleep; | ||
83 | }; | ||
84 | |||
85 | static struct of_device *pmc_dev; | ||
86 | static int has_deep_sleep, deep_sleeping; | ||
87 | static int pmc_irq; | ||
88 | static struct mpc83xx_pmc __iomem *pmc_regs; | ||
89 | static struct mpc83xx_clock __iomem *clock_regs; | ||
90 | static int is_pci_agent, wake_from_pci; | ||
91 | static phys_addr_t immrbase; | ||
92 | static int pci_pm_state; | ||
93 | static DECLARE_WAIT_QUEUE_HEAD(agent_wq); | ||
94 | |||
95 | int fsl_deep_sleep(void) | ||
96 | { | ||
97 | return deep_sleeping; | ||
98 | } | ||
99 | |||
100 | static int mpc83xx_change_state(void) | ||
101 | { | ||
102 | u32 curr_state; | ||
103 | u32 reg_cfg1 = in_be32(&pmc_regs->config1); | ||
104 | |||
105 | if (is_pci_agent) { | ||
106 | pci_pm_state = (reg_cfg1 & PMCCR1_NEXT_STATE) >> | ||
107 | PMCCR1_NEXT_STATE_SHIFT; | ||
108 | curr_state = reg_cfg1 & PMCCR1_CURR_STATE; | ||
109 | |||
110 | if (curr_state != pci_pm_state) { | ||
111 | reg_cfg1 &= ~PMCCR1_CURR_STATE; | ||
112 | reg_cfg1 |= pci_pm_state; | ||
113 | out_be32(&pmc_regs->config1, reg_cfg1); | ||
114 | |||
115 | wake_up(&agent_wq); | ||
116 | return 1; | ||
117 | } | ||
118 | } | ||
119 | |||
120 | return 0; | ||
121 | } | ||
122 | |||
123 | static irqreturn_t pmc_irq_handler(int irq, void *dev_id) | ||
124 | { | ||
125 | u32 event = in_be32(&pmc_regs->event); | ||
126 | int ret = IRQ_NONE; | ||
127 | |||
128 | if (mpc83xx_change_state()) | ||
129 | ret = IRQ_HANDLED; | ||
130 | |||
131 | if (event) { | ||
132 | out_be32(&pmc_regs->event, event); | ||
133 | ret = IRQ_HANDLED; | ||
134 | } | ||
135 | |||
136 | return ret; | ||
137 | } | ||
138 | |||
139 | static int mpc83xx_suspend_enter(suspend_state_t state) | ||
140 | { | ||
141 | int ret = -EAGAIN; | ||
142 | |||
143 | /* Don't go to sleep if there's a race where pci_pm_state changes | ||
144 | * between the agent thread checking it and the PM code disabling | ||
145 | * interrupts. | ||
146 | */ | ||
147 | if (wake_from_pci) { | ||
148 | if (pci_pm_state != (deep_sleeping ? 3 : 2)) | ||
149 | goto out; | ||
150 | |||
151 | out_be32(&pmc_regs->config1, | ||
152 | in_be32(&pmc_regs->config1) | PMCCR1_PME_EN); | ||
153 | } | ||
154 | |||
155 | /* Put the system into low-power mode and the RAM | ||
156 | * into self-refresh mode once the core goes to | ||
157 | * sleep. | ||
158 | */ | ||
159 | |||
160 | out_be32(&pmc_regs->config, PMCCR_SLPEN | PMCCR_DLPEN); | ||
161 | |||
162 | /* If it has deep sleep (i.e. it's an 831x or compatible), | ||
163 | * disable power to the core upon entering sleep mode. This will | ||
164 | * require going through the boot firmware upon a wakeup event. | ||
165 | */ | ||
166 | |||
167 | if (deep_sleeping) { | ||
168 | out_be32(&pmc_regs->mask, PMCER_ALL); | ||
169 | |||
170 | out_be32(&pmc_regs->config1, | ||
171 | in_be32(&pmc_regs->config1) | PMCCR1_POWER_OFF); | ||
172 | |||
173 | enable_kernel_fp(); | ||
174 | |||
175 | mpc83xx_enter_deep_sleep(immrbase); | ||
176 | |||
177 | out_be32(&pmc_regs->config1, | ||
178 | in_be32(&pmc_regs->config1) & ~PMCCR1_POWER_OFF); | ||
179 | |||
180 | out_be32(&pmc_regs->mask, PMCER_PMCI); | ||
181 | } else { | ||
182 | out_be32(&pmc_regs->mask, PMCER_PMCI); | ||
183 | |||
184 | mpc6xx_enter_standby(); | ||
185 | } | ||
186 | |||
187 | ret = 0; | ||
188 | |||
189 | out: | ||
190 | out_be32(&pmc_regs->config1, | ||
191 | in_be32(&pmc_regs->config1) & ~PMCCR1_PME_EN); | ||
192 | |||
193 | return ret; | ||
194 | } | ||
195 | |||
196 | static void mpc83xx_suspend_finish(void) | ||
197 | { | ||
198 | deep_sleeping = 0; | ||
199 | } | ||
200 | |||
201 | static int mpc83xx_suspend_valid(suspend_state_t state) | ||
202 | { | ||
203 | return state == PM_SUSPEND_STANDBY || state == PM_SUSPEND_MEM; | ||
204 | } | ||
205 | |||
206 | static int mpc83xx_suspend_begin(suspend_state_t state) | ||
207 | { | ||
208 | switch (state) { | ||
209 | case PM_SUSPEND_STANDBY: | ||
210 | deep_sleeping = 0; | ||
211 | return 0; | ||
212 | |||
213 | case PM_SUSPEND_MEM: | ||
214 | if (has_deep_sleep) | ||
215 | deep_sleeping = 1; | ||
216 | |||
217 | return 0; | ||
218 | |||
219 | default: | ||
220 | return -EINVAL; | ||
221 | } | ||
222 | } | ||
223 | |||
224 | static int agent_thread_fn(void *data) | ||
225 | { | ||
226 | while (1) { | ||
227 | wait_event_interruptible(agent_wq, pci_pm_state >= 2); | ||
228 | try_to_freeze(); | ||
229 | |||
230 | if (signal_pending(current) || pci_pm_state < 2) | ||
231 | continue; | ||
232 | |||
233 | /* With a preemptible kernel (or SMP), this could race with | ||
234 | * a userspace-driven suspend request. It's probably best | ||
235 | * to avoid mixing the two with such a configuration (or | ||
236 | * else fix it by adding a mutex to state_store that we can | ||
237 | * synchronize with). | ||
238 | */ | ||
239 | |||
240 | wake_from_pci = 1; | ||
241 | |||
242 | pm_suspend(pci_pm_state == 3 ? PM_SUSPEND_MEM : | ||
243 | PM_SUSPEND_STANDBY); | ||
244 | |||
245 | wake_from_pci = 0; | ||
246 | } | ||
247 | |||
248 | return 0; | ||
249 | } | ||
250 | |||
251 | static void mpc83xx_set_agent(void) | ||
252 | { | ||
253 | out_be32(&pmc_regs->config1, PMCCR1_USE_STATE); | ||
254 | out_be32(&pmc_regs->mask, PMCER_PMCI); | ||
255 | |||
256 | kthread_run(agent_thread_fn, NULL, "PCI power mgt"); | ||
257 | } | ||
258 | |||
259 | static int mpc83xx_is_pci_agent(void) | ||
260 | { | ||
261 | struct mpc83xx_rcw __iomem *rcw_regs; | ||
262 | int ret; | ||
263 | |||
264 | rcw_regs = ioremap(get_immrbase() + IMMR_RCW_OFFSET, | ||
265 | sizeof(struct mpc83xx_rcw)); | ||
266 | |||
267 | if (!rcw_regs) | ||
268 | return -ENOMEM; | ||
269 | |||
270 | ret = !(in_be32(&rcw_regs->rcwhr) & RCW_PCI_HOST); | ||
271 | |||
272 | iounmap(rcw_regs); | ||
273 | return ret; | ||
274 | } | ||
275 | |||
276 | static struct platform_suspend_ops mpc83xx_suspend_ops = { | ||
277 | .valid = mpc83xx_suspend_valid, | ||
278 | .begin = mpc83xx_suspend_begin, | ||
279 | .enter = mpc83xx_suspend_enter, | ||
280 | .finish = mpc83xx_suspend_finish, | ||
281 | }; | ||
282 | |||
283 | static int pmc_probe(struct of_device *ofdev, | ||
284 | const struct of_device_id *match) | ||
285 | { | ||
286 | struct device_node *np = ofdev->node; | ||
287 | struct resource res; | ||
288 | struct pmc_type *type = match->data; | ||
289 | int ret = 0; | ||
290 | |||
291 | if (!of_device_is_available(np)) | ||
292 | return -ENODEV; | ||
293 | |||
294 | has_deep_sleep = type->has_deep_sleep; | ||
295 | immrbase = get_immrbase(); | ||
296 | pmc_dev = ofdev; | ||
297 | |||
298 | is_pci_agent = mpc83xx_is_pci_agent(); | ||
299 | if (is_pci_agent < 0) | ||
300 | return is_pci_agent; | ||
301 | |||
302 | ret = of_address_to_resource(np, 0, &res); | ||
303 | if (ret) | ||
304 | return -ENODEV; | ||
305 | |||
306 | pmc_irq = irq_of_parse_and_map(np, 0); | ||
307 | if (pmc_irq != NO_IRQ) { | ||
308 | ret = request_irq(pmc_irq, pmc_irq_handler, IRQF_SHARED, | ||
309 | "pmc", ofdev); | ||
310 | |||
311 | if (ret) | ||
312 | return -EBUSY; | ||
313 | } | ||
314 | |||
315 | pmc_regs = ioremap(res.start, sizeof(struct mpc83xx_pmc)); | ||
316 | |||
317 | if (!pmc_regs) { | ||
318 | ret = -ENOMEM; | ||
319 | goto out; | ||
320 | } | ||
321 | |||
322 | ret = of_address_to_resource(np, 1, &res); | ||
323 | if (ret) { | ||
324 | ret = -ENODEV; | ||
325 | goto out_pmc; | ||
326 | } | ||
327 | |||
328 | clock_regs = ioremap(res.start, sizeof(struct mpc83xx_pmc)); | ||
329 | |||
330 | if (!clock_regs) { | ||
331 | ret = -ENOMEM; | ||
332 | goto out_pmc; | ||
333 | } | ||
334 | |||
335 | if (is_pci_agent) | ||
336 | mpc83xx_set_agent(); | ||
337 | |||
338 | suspend_set_ops(&mpc83xx_suspend_ops); | ||
339 | return 0; | ||
340 | |||
341 | out_pmc: | ||
342 | iounmap(pmc_regs); | ||
343 | out: | ||
344 | if (pmc_irq != NO_IRQ) | ||
345 | free_irq(pmc_irq, ofdev); | ||
346 | |||
347 | return ret; | ||
348 | } | ||
349 | |||
350 | static int pmc_remove(struct of_device *ofdev) | ||
351 | { | ||
352 | return -EPERM; | ||
353 | }; | ||
354 | |||
355 | static struct pmc_type pmc_types[] = { | ||
356 | { | ||
357 | .has_deep_sleep = 1, | ||
358 | }, | ||
359 | { | ||
360 | .has_deep_sleep = 0, | ||
361 | } | ||
362 | }; | ||
363 | |||
364 | static struct of_device_id pmc_match[] = { | ||
365 | { | ||
366 | .compatible = "fsl,mpc8313-pmc", | ||
367 | .data = &pmc_types[0], | ||
368 | }, | ||
369 | { | ||
370 | .compatible = "fsl,mpc8349-pmc", | ||
371 | .data = &pmc_types[1], | ||
372 | }, | ||
373 | {} | ||
374 | }; | ||
375 | |||
376 | static struct of_platform_driver pmc_driver = { | ||
377 | .name = "mpc83xx-pmc", | ||
378 | .match_table = pmc_match, | ||
379 | .probe = pmc_probe, | ||
380 | .remove = pmc_remove | ||
381 | }; | ||
382 | |||
383 | static int pmc_init(void) | ||
384 | { | ||
385 | return of_register_platform_driver(&pmc_driver); | ||
386 | } | ||
387 | |||
388 | module_init(pmc_init); | ||