Commit | Line | Data |
---|---|---|
907d6deb | 1 | /* |
9d041268 | 2 | * arch/arm/mach-at91/pm.c |
907d6deb AV |
3 | * AT91 Power Management |
4 | * | |
5 | * Copyright (C) 2005 David Brownell | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License as published by | |
9 | * the Free Software Foundation; either version 2 of the License, or | |
10 | * (at your option) any later version. | |
11 | */ | |
12 | ||
95d9ffbe | 13 | #include <linux/suspend.h> |
907d6deb AV |
14 | #include <linux/sched.h> |
15 | #include <linux/proc_fs.h> | |
907d6deb AV |
16 | #include <linux/interrupt.h> |
17 | #include <linux/sysfs.h> | |
18 | #include <linux/module.h> | |
19 | #include <linux/platform_device.h> | |
20 | ||
21 | #include <asm/io.h> | |
22 | #include <asm/irq.h> | |
23 | #include <asm/atomic.h> | |
24 | #include <asm/mach/time.h> | |
25 | #include <asm/mach/irq.h> | |
26 | #include <asm/mach-types.h> | |
27 | ||
55d8baee AV |
28 | #include <asm/arch/at91_pmc.h> |
29 | #include <asm/arch/at91rm9200_mc.h> | |
907d6deb | 30 | #include <asm/arch/gpio.h> |
d481f864 | 31 | #include <asm/arch/cpu.h> |
907d6deb AV |
32 | |
33 | #include "generic.h" | |
34 | ||
35 | ||
36 | static int at91_pm_valid_state(suspend_state_t state) | |
37 | { | |
38 | switch (state) { | |
39 | case PM_SUSPEND_ON: | |
40 | case PM_SUSPEND_STANDBY: | |
41 | case PM_SUSPEND_MEM: | |
42 | return 1; | |
43 | ||
44 | default: | |
45 | return 0; | |
46 | } | |
47 | } | |
48 | ||
49 | ||
50 | static suspend_state_t target_state; | |
51 | ||
52 | /* | |
53 | * Called after processes are frozen, but before we shutdown devices. | |
54 | */ | |
2391dae3 | 55 | static int at91_pm_set_target(suspend_state_t state) |
907d6deb AV |
56 | { |
57 | target_state = state; | |
58 | return 0; | |
59 | } | |
60 | ||
61 | /* | |
62 | * Verify that all the clocks are correct before entering | |
63 | * slow-clock mode. | |
64 | */ | |
65 | static int at91_pm_verify_clocks(void) | |
66 | { | |
67 | unsigned long scsr; | |
68 | int i; | |
69 | ||
70 | scsr = at91_sys_read(AT91_PMC_SCSR); | |
71 | ||
72 | /* USB must not be using PLLB */ | |
d481f864 AV |
73 | if (cpu_is_at91rm9200()) { |
74 | if ((scsr & (AT91RM9200_PMC_UHP | AT91RM9200_PMC_UDP)) != 0) { | |
75 | pr_debug("AT91: PM - Suspend-to-RAM with USB still active\n"); | |
76 | return 0; | |
77 | } | |
b6b27ae5 AV |
78 | } else if (cpu_is_at91sam9260() || cpu_is_at91sam9261() || cpu_is_at91sam9263()) { |
79 | if ((scsr & (AT91SAM926x_PMC_UHP | AT91SAM926x_PMC_UDP)) != 0) { | |
80 | pr_debug("AT91: PM - Suspend-to-RAM with USB still active\n"); | |
81 | return 0; | |
82 | } | |
2b3b3516 AV |
83 | } else if (cpu_is_at91cap9()) { |
84 | if ((scsr & AT91CAP9_PMC_UHP) != 0) { | |
85 | pr_debug("AT91: PM - Suspend-to-RAM with USB still active\n"); | |
86 | return 0; | |
87 | } | |
907d6deb AV |
88 | } |
89 | ||
90 | #ifdef CONFIG_AT91_PROGRAMMABLE_CLOCKS | |
91 | /* PCK0..PCK3 must be disabled, or configured to use clk32k */ | |
92 | for (i = 0; i < 4; i++) { | |
93 | u32 css; | |
94 | ||
95 | if ((scsr & (AT91_PMC_PCK0 << i)) == 0) | |
96 | continue; | |
97 | ||
98 | css = at91_sys_read(AT91_PMC_PCKR(i)) & AT91_PMC_CSS; | |
99 | if (css != AT91_PMC_CSS_SLOW) { | |
100 | pr_debug("AT91: PM - Suspend-to-RAM with PCK%d src %d\n", i, css); | |
101 | return 0; | |
102 | } | |
103 | } | |
104 | #endif | |
105 | ||
106 | return 1; | |
107 | } | |
108 | ||
109 | /* | |
110 | * Call this from platform driver suspend() to see how deeply to suspend. | |
111 | * For example, some controllers (like OHCI) need one of the PLL clocks | |
112 | * in order to act as a wakeup source, and those are not available when | |
113 | * going into slow clock mode. | |
114 | * | |
115 | * REVISIT: generalize as clk_will_be_available(clk)? Other platforms have | |
116 | * the very same problem (but not using at91 main_clk), and it'd be better | |
117 | * to add one generic API rather than lots of platform-specific ones. | |
118 | */ | |
119 | int at91_suspend_entering_slow_clock(void) | |
120 | { | |
121 | return (target_state == PM_SUSPEND_MEM); | |
122 | } | |
123 | EXPORT_SYMBOL(at91_suspend_entering_slow_clock); | |
124 | ||
125 | ||
126 | static void (*slow_clock)(void); | |
127 | ||
128 | ||
907d6deb AV |
129 | static int at91_pm_enter(suspend_state_t state) |
130 | { | |
131 | at91_gpio_suspend(); | |
132 | at91_irq_suspend(); | |
133 | ||
134 | pr_debug("AT91: PM - wake mask %08x, pm state %d\n", | |
135 | /* remember all the always-wake irqs */ | |
136 | (at91_sys_read(AT91_PMC_PCSR) | |
137 | | (1 << AT91_ID_FIQ) | |
138 | | (1 << AT91_ID_SYS) | |
1f4fd0a0 | 139 | | (at91_extern_irq)) |
907d6deb AV |
140 | & at91_sys_read(AT91_AIC_IMR), |
141 | state); | |
142 | ||
143 | switch (state) { | |
144 | /* | |
145 | * Suspend-to-RAM is like STANDBY plus slow clock mode, so | |
146 | * drivers must suspend more deeply: only the master clock | |
147 | * controller may be using the main oscillator. | |
148 | */ | |
149 | case PM_SUSPEND_MEM: | |
150 | /* | |
151 | * Ensure that clocks are in a valid state. | |
152 | */ | |
153 | if (!at91_pm_verify_clocks()) | |
154 | goto error; | |
155 | ||
156 | /* | |
157 | * Enter slow clock mode by switching over to clk32k and | |
158 | * turning off the main oscillator; reverse on wakeup. | |
159 | */ | |
160 | if (slow_clock) { | |
161 | slow_clock(); | |
162 | break; | |
163 | } else { | |
164 | /* DEVELOPMENT ONLY */ | |
165 | pr_info("AT91: PM - no slow clock mode yet ...\n"); | |
166 | /* FALLTHROUGH leaving master clock alone */ | |
167 | } | |
168 | ||
169 | /* | |
170 | * STANDBY mode has *all* drivers suspended; ignores irqs not | |
171 | * marked as 'wakeup' event sources; and reduces DRAM power. | |
172 | * But otherwise it's identical to PM_SUSPEND_ON: cpu idle, and | |
173 | * nothing fancy done with main or cpu clocks. | |
174 | */ | |
175 | case PM_SUSPEND_STANDBY: | |
176 | /* | |
177 | * NOTE: the Wait-for-Interrupt instruction needs to be | |
178 | * in icache so the SDRAM stays in self-refresh mode until | |
179 | * the wakeup IRQ occurs. | |
180 | */ | |
181 | asm("b 1f; .align 5; 1:"); | |
182 | asm("mcr p15, 0, r0, c7, c10, 4"); /* drain write buffer */ | |
183 | at91_sys_write(AT91_SDRAMC_SRR, 1); /* self-refresh mode */ | |
184 | /* fall though to next state */ | |
185 | ||
186 | case PM_SUSPEND_ON: | |
187 | asm("mcr p15, 0, r0, c7, c0, 4"); /* wait for interrupt */ | |
188 | break; | |
189 | ||
190 | default: | |
191 | pr_debug("AT91: PM - bogus suspend state %d\n", state); | |
192 | goto error; | |
193 | } | |
194 | ||
195 | pr_debug("AT91: PM - wakeup %08x\n", | |
196 | at91_sys_read(AT91_AIC_IPR) & at91_sys_read(AT91_AIC_IMR)); | |
197 | ||
198 | error: | |
199 | target_state = PM_SUSPEND_ON; | |
200 | at91_irq_resume(); | |
201 | at91_gpio_resume(); | |
202 | return 0; | |
203 | } | |
204 | ||
205 | ||
26398a70 | 206 | static struct platform_suspend_ops at91_pm_ops ={ |
907d6deb | 207 | .valid = at91_pm_valid_state, |
2391dae3 | 208 | .set_target = at91_pm_set_target, |
907d6deb AV |
209 | .enter = at91_pm_enter, |
210 | }; | |
211 | ||
212 | static int __init at91_pm_init(void) | |
213 | { | |
214 | printk("AT91: Power Management\n"); | |
215 | ||
216 | #ifdef CONFIG_AT91_PM_SLOW_CLOCK | |
217 | /* REVISIT allocations of SRAM should be dynamically managed. | |
218 | * FIQ handlers and other components will want SRAM/TCM too... | |
219 | */ | |
220 | slow_clock = (void *) (AT91_VA_BASE_SRAM + (3 * SZ_4K)); | |
221 | memcpy(slow_clock, at91rm9200_slow_clock, at91rm9200_slow_clock_sz); | |
222 | #endif | |
223 | ||
224 | /* Disable SDRAM low-power mode. Cannot be used with self-refresh. */ | |
225 | at91_sys_write(AT91_SDRAMC_LPR, 0); | |
226 | ||
26398a70 | 227 | suspend_set_ops(&at91_pm_ops); |
907d6deb AV |
228 | |
229 | return 0; | |
230 | } | |
231 | arch_initcall(at91_pm_init); |