]>
Commit | Line | Data |
---|---|---|
d49747bd SW |
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 | ||
d49747bd SW |
13 | #include <linux/pm.h> |
14 | #include <linux/types.h> | |
15 | #include <linux/ioport.h> | |
16 | #include <linux/interrupt.h> | |
17 | #include <linux/wait.h> | |
18 | #include <linux/kthread.h> | |
19 | #include <linux/freezer.h> | |
20 | #include <linux/suspend.h> | |
21 | #include <linux/fsl_devices.h> | |
26a2056e RH |
22 | #include <linux/of_address.h> |
23 | #include <linux/of_irq.h> | |
d49747bd | 24 | #include <linux/of_platform.h> |
66b15db6 | 25 | #include <linux/export.h> |
d49747bd SW |
26 | |
27 | #include <asm/reg.h> | |
28 | #include <asm/io.h> | |
29 | #include <asm/time.h> | |
30 | #include <asm/mpc6xx.h> | |
ae3a197e | 31 | #include <asm/switch_to.h> |
d49747bd SW |
32 | |
33 | #include <sysdev/fsl_soc.h> | |
34 | ||
35 | #define PMCCR1_NEXT_STATE 0x0C /* Next state for power management */ | |
36 | #define PMCCR1_NEXT_STATE_SHIFT 2 | |
37 | #define PMCCR1_CURR_STATE 0x03 /* Current state for power management*/ | |
87faaabb | 38 | #define IMMR_SYSCR_OFFSET 0x100 |
d49747bd SW |
39 | #define IMMR_RCW_OFFSET 0x900 |
40 | #define RCW_PCI_HOST 0x80000000 | |
41 | ||
42 | void mpc83xx_enter_deep_sleep(phys_addr_t immrbase); | |
43 | ||
44 | struct mpc83xx_pmc { | |
45 | u32 config; | |
46 | #define PMCCR_DLPEN 2 /* DDR SDRAM low power enable */ | |
47 | #define PMCCR_SLPEN 1 /* System low power enable */ | |
48 | ||
49 | u32 event; | |
50 | u32 mask; | |
51 | /* All but PMCI are deep-sleep only */ | |
52 | #define PMCER_GPIO 0x100 | |
53 | #define PMCER_PCI 0x080 | |
54 | #define PMCER_USB 0x040 | |
55 | #define PMCER_ETSEC1 0x020 | |
56 | #define PMCER_ETSEC2 0x010 | |
57 | #define PMCER_TIMER 0x008 | |
58 | #define PMCER_INT1 0x004 | |
59 | #define PMCER_INT2 0x002 | |
60 | #define PMCER_PMCI 0x001 | |
61 | #define PMCER_ALL 0x1FF | |
62 | ||
63 | /* deep-sleep only */ | |
64 | u32 config1; | |
65 | #define PMCCR1_USE_STATE 0x80000000 | |
66 | #define PMCCR1_PME_EN 0x00000080 | |
67 | #define PMCCR1_ASSERT_PME 0x00000040 | |
68 | #define PMCCR1_POWER_OFF 0x00000020 | |
69 | ||
70 | /* deep-sleep only */ | |
71 | u32 config2; | |
72 | }; | |
73 | ||
74 | struct mpc83xx_rcw { | |
75 | u32 rcwlr; | |
76 | u32 rcwhr; | |
77 | }; | |
78 | ||
79 | struct mpc83xx_clock { | |
80 | u32 spmr; | |
81 | u32 occr; | |
82 | u32 sccr; | |
83 | }; | |
84 | ||
87faaabb AV |
85 | struct mpc83xx_syscr { |
86 | __be32 sgprl; | |
87 | __be32 sgprh; | |
88 | __be32 spridr; | |
89 | __be32 :32; | |
90 | __be32 spcr; | |
91 | __be32 sicrl; | |
92 | __be32 sicrh; | |
93 | }; | |
94 | ||
95 | struct mpc83xx_saved { | |
96 | u32 sicrl; | |
97 | u32 sicrh; | |
98 | u32 sccr; | |
99 | }; | |
100 | ||
d49747bd SW |
101 | struct pmc_type { |
102 | int has_deep_sleep; | |
103 | }; | |
104 | ||
a454dc50 | 105 | static struct platform_device *pmc_dev; |
d49747bd SW |
106 | static int has_deep_sleep, deep_sleeping; |
107 | static int pmc_irq; | |
108 | static struct mpc83xx_pmc __iomem *pmc_regs; | |
109 | static struct mpc83xx_clock __iomem *clock_regs; | |
87faaabb AV |
110 | static struct mpc83xx_syscr __iomem *syscr_regs; |
111 | static struct mpc83xx_saved saved_regs; | |
d49747bd SW |
112 | static int is_pci_agent, wake_from_pci; |
113 | static phys_addr_t immrbase; | |
114 | static int pci_pm_state; | |
115 | static DECLARE_WAIT_QUEUE_HEAD(agent_wq); | |
116 | ||
117 | int fsl_deep_sleep(void) | |
118 | { | |
119 | return deep_sleeping; | |
120 | } | |
2e9d546e | 121 | EXPORT_SYMBOL(fsl_deep_sleep); |
d49747bd SW |
122 | |
123 | static int mpc83xx_change_state(void) | |
124 | { | |
125 | u32 curr_state; | |
126 | u32 reg_cfg1 = in_be32(&pmc_regs->config1); | |
127 | ||
128 | if (is_pci_agent) { | |
129 | pci_pm_state = (reg_cfg1 & PMCCR1_NEXT_STATE) >> | |
130 | PMCCR1_NEXT_STATE_SHIFT; | |
131 | curr_state = reg_cfg1 & PMCCR1_CURR_STATE; | |
132 | ||
133 | if (curr_state != pci_pm_state) { | |
134 | reg_cfg1 &= ~PMCCR1_CURR_STATE; | |
135 | reg_cfg1 |= pci_pm_state; | |
136 | out_be32(&pmc_regs->config1, reg_cfg1); | |
137 | ||
138 | wake_up(&agent_wq); | |
139 | return 1; | |
140 | } | |
141 | } | |
142 | ||
143 | return 0; | |
144 | } | |
145 | ||
146 | static irqreturn_t pmc_irq_handler(int irq, void *dev_id) | |
147 | { | |
148 | u32 event = in_be32(&pmc_regs->event); | |
149 | int ret = IRQ_NONE; | |
150 | ||
151 | if (mpc83xx_change_state()) | |
152 | ret = IRQ_HANDLED; | |
153 | ||
154 | if (event) { | |
155 | out_be32(&pmc_regs->event, event); | |
156 | ret = IRQ_HANDLED; | |
157 | } | |
158 | ||
159 | return ret; | |
160 | } | |
161 | ||
87faaabb AV |
162 | static void mpc83xx_suspend_restore_regs(void) |
163 | { | |
164 | out_be32(&syscr_regs->sicrl, saved_regs.sicrl); | |
165 | out_be32(&syscr_regs->sicrh, saved_regs.sicrh); | |
166 | out_be32(&clock_regs->sccr, saved_regs.sccr); | |
167 | } | |
168 | ||
169 | static void mpc83xx_suspend_save_regs(void) | |
170 | { | |
171 | saved_regs.sicrl = in_be32(&syscr_regs->sicrl); | |
172 | saved_regs.sicrh = in_be32(&syscr_regs->sicrh); | |
173 | saved_regs.sccr = in_be32(&clock_regs->sccr); | |
174 | } | |
175 | ||
d49747bd SW |
176 | static int mpc83xx_suspend_enter(suspend_state_t state) |
177 | { | |
178 | int ret = -EAGAIN; | |
179 | ||
180 | /* Don't go to sleep if there's a race where pci_pm_state changes | |
181 | * between the agent thread checking it and the PM code disabling | |
182 | * interrupts. | |
183 | */ | |
184 | if (wake_from_pci) { | |
185 | if (pci_pm_state != (deep_sleeping ? 3 : 2)) | |
186 | goto out; | |
187 | ||
188 | out_be32(&pmc_regs->config1, | |
189 | in_be32(&pmc_regs->config1) | PMCCR1_PME_EN); | |
190 | } | |
191 | ||
192 | /* Put the system into low-power mode and the RAM | |
193 | * into self-refresh mode once the core goes to | |
194 | * sleep. | |
195 | */ | |
196 | ||
197 | out_be32(&pmc_regs->config, PMCCR_SLPEN | PMCCR_DLPEN); | |
198 | ||
199 | /* If it has deep sleep (i.e. it's an 831x or compatible), | |
200 | * disable power to the core upon entering sleep mode. This will | |
201 | * require going through the boot firmware upon a wakeup event. | |
202 | */ | |
203 | ||
204 | if (deep_sleeping) { | |
87faaabb AV |
205 | mpc83xx_suspend_save_regs(); |
206 | ||
d49747bd SW |
207 | out_be32(&pmc_regs->mask, PMCER_ALL); |
208 | ||
209 | out_be32(&pmc_regs->config1, | |
210 | in_be32(&pmc_regs->config1) | PMCCR1_POWER_OFF); | |
211 | ||
212 | enable_kernel_fp(); | |
213 | ||
214 | mpc83xx_enter_deep_sleep(immrbase); | |
215 | ||
216 | out_be32(&pmc_regs->config1, | |
217 | in_be32(&pmc_regs->config1) & ~PMCCR1_POWER_OFF); | |
218 | ||
219 | out_be32(&pmc_regs->mask, PMCER_PMCI); | |
87faaabb AV |
220 | |
221 | mpc83xx_suspend_restore_regs(); | |
d49747bd SW |
222 | } else { |
223 | out_be32(&pmc_regs->mask, PMCER_PMCI); | |
224 | ||
225 | mpc6xx_enter_standby(); | |
226 | } | |
227 | ||
228 | ret = 0; | |
229 | ||
230 | out: | |
231 | out_be32(&pmc_regs->config1, | |
232 | in_be32(&pmc_regs->config1) & ~PMCCR1_PME_EN); | |
233 | ||
234 | return ret; | |
235 | } | |
236 | ||
f25c525c | 237 | static void mpc83xx_suspend_end(void) |
d49747bd SW |
238 | { |
239 | deep_sleeping = 0; | |
240 | } | |
241 | ||
242 | static int mpc83xx_suspend_valid(suspend_state_t state) | |
243 | { | |
244 | return state == PM_SUSPEND_STANDBY || state == PM_SUSPEND_MEM; | |
245 | } | |
246 | ||
247 | static int mpc83xx_suspend_begin(suspend_state_t state) | |
248 | { | |
249 | switch (state) { | |
250 | case PM_SUSPEND_STANDBY: | |
251 | deep_sleeping = 0; | |
252 | return 0; | |
253 | ||
254 | case PM_SUSPEND_MEM: | |
255 | if (has_deep_sleep) | |
256 | deep_sleeping = 1; | |
257 | ||
258 | return 0; | |
259 | ||
260 | default: | |
261 | return -EINVAL; | |
262 | } | |
263 | } | |
264 | ||
265 | static int agent_thread_fn(void *data) | |
266 | { | |
267 | while (1) { | |
268 | wait_event_interruptible(agent_wq, pci_pm_state >= 2); | |
269 | try_to_freeze(); | |
270 | ||
271 | if (signal_pending(current) || pci_pm_state < 2) | |
272 | continue; | |
273 | ||
274 | /* With a preemptible kernel (or SMP), this could race with | |
275 | * a userspace-driven suspend request. It's probably best | |
276 | * to avoid mixing the two with such a configuration (or | |
277 | * else fix it by adding a mutex to state_store that we can | |
278 | * synchronize with). | |
279 | */ | |
280 | ||
281 | wake_from_pci = 1; | |
282 | ||
283 | pm_suspend(pci_pm_state == 3 ? PM_SUSPEND_MEM : | |
284 | PM_SUSPEND_STANDBY); | |
285 | ||
286 | wake_from_pci = 0; | |
287 | } | |
288 | ||
289 | return 0; | |
290 | } | |
291 | ||
292 | static void mpc83xx_set_agent(void) | |
293 | { | |
294 | out_be32(&pmc_regs->config1, PMCCR1_USE_STATE); | |
295 | out_be32(&pmc_regs->mask, PMCER_PMCI); | |
296 | ||
297 | kthread_run(agent_thread_fn, NULL, "PCI power mgt"); | |
298 | } | |
299 | ||
300 | static int mpc83xx_is_pci_agent(void) | |
301 | { | |
302 | struct mpc83xx_rcw __iomem *rcw_regs; | |
303 | int ret; | |
304 | ||
305 | rcw_regs = ioremap(get_immrbase() + IMMR_RCW_OFFSET, | |
306 | sizeof(struct mpc83xx_rcw)); | |
307 | ||
308 | if (!rcw_regs) | |
309 | return -ENOMEM; | |
310 | ||
311 | ret = !(in_be32(&rcw_regs->rcwhr) & RCW_PCI_HOST); | |
312 | ||
313 | iounmap(rcw_regs); | |
314 | return ret; | |
315 | } | |
316 | ||
2f55ac07 | 317 | static const struct platform_suspend_ops mpc83xx_suspend_ops = { |
d49747bd SW |
318 | .valid = mpc83xx_suspend_valid, |
319 | .begin = mpc83xx_suspend_begin, | |
320 | .enter = mpc83xx_suspend_enter, | |
f25c525c | 321 | .end = mpc83xx_suspend_end, |
d49747bd SW |
322 | }; |
323 | ||
ce6d73c9 | 324 | static const struct of_device_id pmc_match[]; |
00006124 | 325 | static int pmc_probe(struct platform_device *ofdev) |
d49747bd | 326 | { |
b1608d69 | 327 | const struct of_device_id *match; |
61c7a080 | 328 | struct device_node *np = ofdev->dev.of_node; |
d49747bd | 329 | struct resource res; |
bfef61d0 | 330 | const struct pmc_type *type; |
d49747bd SW |
331 | int ret = 0; |
332 | ||
b1608d69 GL |
333 | match = of_match_device(pmc_match, &ofdev->dev); |
334 | if (!match) | |
00006124 GL |
335 | return -EINVAL; |
336 | ||
b1608d69 | 337 | type = match->data; |
00006124 | 338 | |
d49747bd SW |
339 | if (!of_device_is_available(np)) |
340 | return -ENODEV; | |
341 | ||
342 | has_deep_sleep = type->has_deep_sleep; | |
343 | immrbase = get_immrbase(); | |
344 | pmc_dev = ofdev; | |
345 | ||
346 | is_pci_agent = mpc83xx_is_pci_agent(); | |
347 | if (is_pci_agent < 0) | |
348 | return is_pci_agent; | |
349 | ||
350 | ret = of_address_to_resource(np, 0, &res); | |
351 | if (ret) | |
352 | return -ENODEV; | |
353 | ||
354 | pmc_irq = irq_of_parse_and_map(np, 0); | |
355 | if (pmc_irq != NO_IRQ) { | |
356 | ret = request_irq(pmc_irq, pmc_irq_handler, IRQF_SHARED, | |
357 | "pmc", ofdev); | |
358 | ||
359 | if (ret) | |
360 | return -EBUSY; | |
361 | } | |
362 | ||
363 | pmc_regs = ioremap(res.start, sizeof(struct mpc83xx_pmc)); | |
364 | ||
365 | if (!pmc_regs) { | |
366 | ret = -ENOMEM; | |
367 | goto out; | |
368 | } | |
369 | ||
370 | ret = of_address_to_resource(np, 1, &res); | |
371 | if (ret) { | |
372 | ret = -ENODEV; | |
373 | goto out_pmc; | |
374 | } | |
375 | ||
376 | clock_regs = ioremap(res.start, sizeof(struct mpc83xx_pmc)); | |
377 | ||
378 | if (!clock_regs) { | |
379 | ret = -ENOMEM; | |
380 | goto out_pmc; | |
381 | } | |
382 | ||
87faaabb AV |
383 | if (has_deep_sleep) { |
384 | syscr_regs = ioremap(immrbase + IMMR_SYSCR_OFFSET, | |
385 | sizeof(*syscr_regs)); | |
386 | if (!syscr_regs) { | |
387 | ret = -ENOMEM; | |
388 | goto out_syscr; | |
389 | } | |
390 | } | |
391 | ||
d49747bd SW |
392 | if (is_pci_agent) |
393 | mpc83xx_set_agent(); | |
394 | ||
395 | suspend_set_ops(&mpc83xx_suspend_ops); | |
396 | return 0; | |
397 | ||
87faaabb AV |
398 | out_syscr: |
399 | iounmap(clock_regs); | |
d49747bd SW |
400 | out_pmc: |
401 | iounmap(pmc_regs); | |
402 | out: | |
403 | if (pmc_irq != NO_IRQ) | |
404 | free_irq(pmc_irq, ofdev); | |
405 | ||
406 | return ret; | |
407 | } | |
408 | ||
a454dc50 | 409 | static int pmc_remove(struct platform_device *ofdev) |
d49747bd SW |
410 | { |
411 | return -EPERM; | |
412 | }; | |
413 | ||
414 | static struct pmc_type pmc_types[] = { | |
415 | { | |
416 | .has_deep_sleep = 1, | |
417 | }, | |
418 | { | |
419 | .has_deep_sleep = 0, | |
420 | } | |
421 | }; | |
422 | ||
ce6d73c9 | 423 | static const struct of_device_id pmc_match[] = { |
d49747bd SW |
424 | { |
425 | .compatible = "fsl,mpc8313-pmc", | |
426 | .data = &pmc_types[0], | |
427 | }, | |
428 | { | |
429 | .compatible = "fsl,mpc8349-pmc", | |
430 | .data = &pmc_types[1], | |
431 | }, | |
432 | {} | |
433 | }; | |
434 | ||
00006124 | 435 | static struct platform_driver pmc_driver = { |
4018294b GL |
436 | .driver = { |
437 | .name = "mpc83xx-pmc", | |
4018294b GL |
438 | .of_match_table = pmc_match, |
439 | }, | |
d49747bd SW |
440 | .probe = pmc_probe, |
441 | .remove = pmc_remove | |
442 | }; | |
443 | ||
444 | static int pmc_init(void) | |
445 | { | |
00006124 | 446 | return platform_driver_register(&pmc_driver); |
d49747bd SW |
447 | } |
448 | ||
449 | module_init(pmc_init); |