1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // |
3 | // Copyright 2008 Openmoko, Inc. |
4 | // Copyright 2008 Simtec Electronics |
5 | // Ben Dooks <ben@simtec.co.uk> |
6 | // http://armlinux.simtec.co.uk/ |
7 | // |
8 | // S3C64XX CPU PM support. |
9 | |
10 | #include <linux/init.h> |
11 | #include <linux/suspend.h> |
12 | #include <linux/serial_core.h> |
13 | #include <linux/io.h> |
14 | #include <linux/gpio.h> |
15 | #include <linux/pm_domain.h> |
16 | |
17 | #include "map.h" |
18 | #include "irqs.h" |
19 | |
20 | #include "cpu.h" |
21 | #include "devs.h" |
22 | #include "pm.h" |
23 | #include "wakeup-mask.h" |
24 | |
25 | #include "regs-gpio.h" |
26 | #include "regs-clock.h" |
27 | #include "gpio-samsung.h" |
28 | |
29 | #include "regs-gpio-memport-s3c64xx.h" |
30 | #include "regs-modem-s3c64xx.h" |
31 | #include "regs-sys-s3c64xx.h" |
32 | #include "regs-syscon-power-s3c64xx.h" |
33 | |
34 | struct s3c64xx_pm_domain { |
35 | char *const name; |
36 | u32 ena; |
37 | u32 pwr_stat; |
38 | struct generic_pm_domain pd; |
39 | }; |
40 | |
41 | static int s3c64xx_pd_off(struct generic_pm_domain *domain) |
42 | { |
43 | struct s3c64xx_pm_domain *pd; |
44 | u32 val; |
45 | |
46 | pd = container_of(domain, struct s3c64xx_pm_domain, pd); |
47 | |
48 | val = __raw_readl(S3C64XX_NORMAL_CFG); |
49 | val &= ~(pd->ena); |
50 | __raw_writel(val, S3C64XX_NORMAL_CFG); |
51 | |
52 | return 0; |
53 | } |
54 | |
55 | static int s3c64xx_pd_on(struct generic_pm_domain *domain) |
56 | { |
57 | struct s3c64xx_pm_domain *pd; |
58 | u32 val; |
59 | long retry = 1000000L; |
60 | |
61 | pd = container_of(domain, struct s3c64xx_pm_domain, pd); |
62 | |
63 | val = __raw_readl(S3C64XX_NORMAL_CFG); |
64 | val |= pd->ena; |
65 | __raw_writel(val, S3C64XX_NORMAL_CFG); |
66 | |
67 | /* Not all domains provide power status readback */ |
68 | if (pd->pwr_stat) { |
69 | do { |
70 | cpu_relax(); |
71 | if (__raw_readl(S3C64XX_BLK_PWR_STAT) & pd->pwr_stat) |
72 | break; |
73 | } while (retry--); |
74 | |
75 | if (!retry) { |
76 | pr_err("Failed to start domain %s\n" , pd->name); |
77 | return -EBUSY; |
78 | } |
79 | } |
80 | |
81 | return 0; |
82 | } |
83 | |
84 | static struct s3c64xx_pm_domain s3c64xx_pm_irom = { |
85 | .name = "IROM" , |
86 | .ena = S3C64XX_NORMALCFG_IROM_ON, |
87 | .pd = { |
88 | .power_off = s3c64xx_pd_off, |
89 | .power_on = s3c64xx_pd_on, |
90 | }, |
91 | }; |
92 | |
93 | static struct s3c64xx_pm_domain s3c64xx_pm_etm = { |
94 | .name = "ETM" , |
95 | .ena = S3C64XX_NORMALCFG_DOMAIN_ETM_ON, |
96 | .pwr_stat = S3C64XX_BLKPWRSTAT_ETM, |
97 | .pd = { |
98 | .power_off = s3c64xx_pd_off, |
99 | .power_on = s3c64xx_pd_on, |
100 | }, |
101 | }; |
102 | |
103 | static struct s3c64xx_pm_domain s3c64xx_pm_s = { |
104 | .name = "S" , |
105 | .ena = S3C64XX_NORMALCFG_DOMAIN_S_ON, |
106 | .pwr_stat = S3C64XX_BLKPWRSTAT_S, |
107 | .pd = { |
108 | .power_off = s3c64xx_pd_off, |
109 | .power_on = s3c64xx_pd_on, |
110 | }, |
111 | }; |
112 | |
113 | static struct s3c64xx_pm_domain s3c64xx_pm_f = { |
114 | .name = "F" , |
115 | .ena = S3C64XX_NORMALCFG_DOMAIN_F_ON, |
116 | .pwr_stat = S3C64XX_BLKPWRSTAT_F, |
117 | .pd = { |
118 | .power_off = s3c64xx_pd_off, |
119 | .power_on = s3c64xx_pd_on, |
120 | }, |
121 | }; |
122 | |
123 | static struct s3c64xx_pm_domain s3c64xx_pm_p = { |
124 | .name = "P" , |
125 | .ena = S3C64XX_NORMALCFG_DOMAIN_P_ON, |
126 | .pwr_stat = S3C64XX_BLKPWRSTAT_P, |
127 | .pd = { |
128 | .power_off = s3c64xx_pd_off, |
129 | .power_on = s3c64xx_pd_on, |
130 | }, |
131 | }; |
132 | |
133 | static struct s3c64xx_pm_domain s3c64xx_pm_i = { |
134 | .name = "I" , |
135 | .ena = S3C64XX_NORMALCFG_DOMAIN_I_ON, |
136 | .pwr_stat = S3C64XX_BLKPWRSTAT_I, |
137 | .pd = { |
138 | .power_off = s3c64xx_pd_off, |
139 | .power_on = s3c64xx_pd_on, |
140 | }, |
141 | }; |
142 | |
143 | static struct s3c64xx_pm_domain s3c64xx_pm_g = { |
144 | .name = "G" , |
145 | .ena = S3C64XX_NORMALCFG_DOMAIN_G_ON, |
146 | .pd = { |
147 | .power_off = s3c64xx_pd_off, |
148 | .power_on = s3c64xx_pd_on, |
149 | }, |
150 | }; |
151 | |
152 | static struct s3c64xx_pm_domain s3c64xx_pm_v = { |
153 | .name = "V" , |
154 | .ena = S3C64XX_NORMALCFG_DOMAIN_V_ON, |
155 | .pwr_stat = S3C64XX_BLKPWRSTAT_V, |
156 | .pd = { |
157 | .power_off = s3c64xx_pd_off, |
158 | .power_on = s3c64xx_pd_on, |
159 | }, |
160 | }; |
161 | |
162 | static struct s3c64xx_pm_domain *s3c64xx_always_on_pm_domains[] = { |
163 | &s3c64xx_pm_irom, |
164 | }; |
165 | |
166 | static struct s3c64xx_pm_domain *s3c64xx_pm_domains[] = { |
167 | &s3c64xx_pm_etm, |
168 | &s3c64xx_pm_g, |
169 | &s3c64xx_pm_v, |
170 | &s3c64xx_pm_i, |
171 | &s3c64xx_pm_p, |
172 | &s3c64xx_pm_s, |
173 | &s3c64xx_pm_f, |
174 | }; |
175 | |
176 | #ifdef CONFIG_PM_SLEEP |
177 | static struct sleep_save core_save[] = { |
178 | SAVE_ITEM(S3C64XX_MEM0DRVCON), |
179 | SAVE_ITEM(S3C64XX_MEM1DRVCON), |
180 | }; |
181 | |
182 | static struct sleep_save misc_save[] = { |
183 | SAVE_ITEM(S3C64XX_AHB_CON0), |
184 | SAVE_ITEM(S3C64XX_AHB_CON1), |
185 | SAVE_ITEM(S3C64XX_AHB_CON2), |
186 | |
187 | SAVE_ITEM(S3C64XX_SPCON), |
188 | |
189 | SAVE_ITEM(S3C64XX_MEM0CONSTOP), |
190 | SAVE_ITEM(S3C64XX_MEM1CONSTOP), |
191 | SAVE_ITEM(S3C64XX_MEM0CONSLP0), |
192 | SAVE_ITEM(S3C64XX_MEM0CONSLP1), |
193 | SAVE_ITEM(S3C64XX_MEM1CONSLP), |
194 | |
195 | SAVE_ITEM(S3C64XX_SDMA_SEL), |
196 | SAVE_ITEM(S3C64XX_MODEM_MIFPCON), |
197 | |
198 | SAVE_ITEM(S3C64XX_NORMAL_CFG), |
199 | }; |
200 | |
201 | void s3c_pm_configure_extint(void) |
202 | { |
203 | __raw_writel(val: s3c_irqwake_eintmask, S3C64XX_EINT_MASK); |
204 | } |
205 | |
206 | void s3c_pm_restore_core(void) |
207 | { |
208 | __raw_writel(val: 0, S3C64XX_EINT_MASK); |
209 | |
210 | s3c_pm_do_restore_core(ptr: core_save, ARRAY_SIZE(core_save)); |
211 | s3c_pm_do_restore(ptr: misc_save, ARRAY_SIZE(misc_save)); |
212 | } |
213 | |
214 | void s3c_pm_save_core(void) |
215 | { |
216 | s3c_pm_do_save(ptr: misc_save, ARRAY_SIZE(misc_save)); |
217 | s3c_pm_do_save(ptr: core_save, ARRAY_SIZE(core_save)); |
218 | } |
219 | #endif |
220 | |
221 | /* since both s3c6400 and s3c6410 share the same sleep pm calls, we |
222 | * put the per-cpu code in here until any new cpu comes along and changes |
223 | * this. |
224 | */ |
225 | |
226 | static int s3c64xx_cpu_suspend(unsigned long arg) |
227 | { |
228 | unsigned long tmp; |
229 | |
230 | /* set our standby method to sleep */ |
231 | |
232 | tmp = __raw_readl(S3C64XX_PWR_CFG); |
233 | tmp &= ~S3C64XX_PWRCFG_CFG_WFI_MASK; |
234 | tmp |= S3C64XX_PWRCFG_CFG_WFI_SLEEP; |
235 | __raw_writel(val: tmp, S3C64XX_PWR_CFG); |
236 | |
237 | /* clear any old wakeup */ |
238 | |
239 | __raw_writel(__raw_readl(S3C64XX_WAKEUP_STAT), |
240 | S3C64XX_WAKEUP_STAT); |
241 | |
242 | /* issue the standby signal into the pm unit. Note, we |
243 | * issue a write-buffer drain just in case */ |
244 | |
245 | tmp = 0; |
246 | |
247 | asm("b 1f\n\t" |
248 | ".align 5\n\t" |
249 | "1:\n\t" |
250 | "mcr p15, 0, %0, c7, c10, 5\n\t" |
251 | "mcr p15, 0, %0, c7, c10, 4\n\t" |
252 | "mcr p15, 0, %0, c7, c0, 4" :: "r" (tmp)); |
253 | |
254 | /* we should never get past here */ |
255 | |
256 | pr_info("Failed to suspend the system\n" ); |
257 | return 1; /* Aborting suspend */ |
258 | } |
259 | |
260 | /* mapping of interrupts to parts of the wakeup mask */ |
261 | static const struct samsung_wakeup_mask wake_irqs[] = { |
262 | { .irq = IRQ_RTC_ALARM, .bit = S3C64XX_PWRCFG_RTC_ALARM_DISABLE, }, |
263 | { .irq = IRQ_RTC_TIC, .bit = S3C64XX_PWRCFG_RTC_TICK_DISABLE, }, |
264 | { .irq = IRQ_PENDN, .bit = S3C64XX_PWRCFG_TS_DISABLE, }, |
265 | { .irq = IRQ_HSMMC0, .bit = S3C64XX_PWRCFG_MMC0_DISABLE, }, |
266 | { .irq = IRQ_HSMMC1, .bit = S3C64XX_PWRCFG_MMC1_DISABLE, }, |
267 | { .irq = IRQ_HSMMC2, .bit = S3C64XX_PWRCFG_MMC2_DISABLE, }, |
268 | { .irq = NO_WAKEUP_IRQ, .bit = S3C64XX_PWRCFG_BATF_DISABLE}, |
269 | { .irq = NO_WAKEUP_IRQ, .bit = S3C64XX_PWRCFG_MSM_DISABLE }, |
270 | { .irq = NO_WAKEUP_IRQ, .bit = S3C64XX_PWRCFG_HSI_DISABLE }, |
271 | { .irq = NO_WAKEUP_IRQ, .bit = S3C64XX_PWRCFG_MSM_DISABLE }, |
272 | }; |
273 | |
274 | static void s3c64xx_pm_prepare(void) |
275 | { |
276 | samsung_sync_wakemask(S3C64XX_PWR_CFG, |
277 | masks: wake_irqs, ARRAY_SIZE(wake_irqs)); |
278 | |
279 | /* store address of resume. */ |
280 | __raw_writel(__pa_symbol(s3c_cpu_resume), S3C64XX_INFORM0); |
281 | |
282 | /* ensure previous wakeup state is cleared before sleeping */ |
283 | __raw_writel(__raw_readl(S3C64XX_WAKEUP_STAT), S3C64XX_WAKEUP_STAT); |
284 | } |
285 | |
286 | int __init s3c64xx_pm_init(void) |
287 | { |
288 | int i; |
289 | |
290 | s3c_pm_init(); |
291 | |
292 | for (i = 0; i < ARRAY_SIZE(s3c64xx_always_on_pm_domains); i++) |
293 | pm_genpd_init(genpd: &s3c64xx_always_on_pm_domains[i]->pd, |
294 | gov: &pm_domain_always_on_gov, is_off: false); |
295 | |
296 | for (i = 0; i < ARRAY_SIZE(s3c64xx_pm_domains); i++) |
297 | pm_genpd_init(genpd: &s3c64xx_pm_domains[i]->pd, NULL, is_off: false); |
298 | |
299 | #ifdef CONFIG_S3C_DEV_FB |
300 | if (dev_get_platdata(&s3c_device_fb.dev)) |
301 | pm_genpd_add_device(&s3c64xx_pm_f.pd, &s3c_device_fb.dev); |
302 | #endif |
303 | |
304 | return 0; |
305 | } |
306 | |
307 | static __init int s3c64xx_pm_initcall(void) |
308 | { |
309 | if (!soc_is_s3c64xx()) |
310 | return 0; |
311 | |
312 | pm_cpu_prep = s3c64xx_pm_prepare; |
313 | pm_cpu_sleep = s3c64xx_cpu_suspend; |
314 | |
315 | return 0; |
316 | } |
317 | arch_initcall(s3c64xx_pm_initcall); |
318 | |