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/suspend.c | |
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/suspend.c')
-rw-r--r-- | arch/powerpc/platforms/83xx/suspend.c | 388 |
1 files changed, 388 insertions, 0 deletions
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); | ||