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