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