aboutsummaryrefslogtreecommitdiffstats
path: root/arch/powerpc/platforms/83xx/suspend.c
diff options
context:
space:
mode:
authorScott Wood <scottwood@freescale.com>2007-10-09 13:37:13 -0400
committerKumar Gala <galak@kernel.crashing.org>2008-07-16 18:57:30 -0400
commitd49747bdfb2ddebea24d1580da55b79d093d48a9 (patch)
treecb2ae6ea03bab0ff7901c9997ef50131bba6b511 /arch/powerpc/platforms/83xx/suspend.c
parent7e72063c9aaeb618815589cd4d57f26186e6fcad (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.c388
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 00000000000..08e65fc8b98
--- /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
38void mpc83xx_enter_deep_sleep(phys_addr_t immrbase);
39
40struct 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
70struct mpc83xx_rcw {
71 u32 rcwlr;
72 u32 rcwhr;
73};
74
75struct mpc83xx_clock {
76 u32 spmr;
77 u32 occr;
78 u32 sccr;
79};
80
81struct pmc_type {
82 int has_deep_sleep;
83};
84
85static struct of_device *pmc_dev;
86static int has_deep_sleep, deep_sleeping;
87static int pmc_irq;
88static struct mpc83xx_pmc __iomem *pmc_regs;
89static struct mpc83xx_clock __iomem *clock_regs;
90static int is_pci_agent, wake_from_pci;
91static phys_addr_t immrbase;
92static int pci_pm_state;
93static DECLARE_WAIT_QUEUE_HEAD(agent_wq);
94
95int fsl_deep_sleep(void)
96{
97 return deep_sleeping;
98}
99
100static 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
123static 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
139static 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
189out:
190 out_be32(&pmc_regs->config1,
191 in_be32(&pmc_regs->config1) & ~PMCCR1_PME_EN);
192
193 return ret;
194}
195
196static void mpc83xx_suspend_finish(void)
197{
198 deep_sleeping = 0;
199}
200
201static int mpc83xx_suspend_valid(suspend_state_t state)
202{
203 return state == PM_SUSPEND_STANDBY || state == PM_SUSPEND_MEM;
204}
205
206static 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
224static 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
251static 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
259static 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
276static 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
283static 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
341out_pmc:
342 iounmap(pmc_regs);
343out:
344 if (pmc_irq != NO_IRQ)
345 free_irq(pmc_irq, ofdev);
346
347 return ret;
348}
349
350static int pmc_remove(struct of_device *ofdev)
351{
352 return -EPERM;
353};
354
355static struct pmc_type pmc_types[] = {
356 {
357 .has_deep_sleep = 1,
358 },
359 {
360 .has_deep_sleep = 0,
361 }
362};
363
364static 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
376static 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
383static int pmc_init(void)
384{
385 return of_register_platform_driver(&pmc_driver);
386}
387
388module_init(pmc_init);