]>
Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
d17799f9 | 2 | /* |
3 | * RCPM(Run Control/Power Management) support | |
4 | * | |
5 | * Copyright 2012-2015 Freescale Semiconductor Inc. | |
6 | * | |
7 | * Author: Chenhui Zhao <chenhui.zhao@freescale.com> | |
d17799f9 | 8 | */ |
9 | ||
10 | #define pr_fmt(fmt) "%s: " fmt, __func__ | |
11 | ||
12 | #include <linux/types.h> | |
13 | #include <linux/errno.h> | |
14 | #include <linux/of_address.h> | |
15 | #include <linux/export.h> | |
16 | ||
17 | #include <asm/io.h> | |
18 | #include <linux/fsl/guts.h> | |
19 | #include <asm/cputhreads.h> | |
20 | #include <asm/fsl_pm.h> | |
b081251e | 21 | #include <asm/smp.h> |
d17799f9 | 22 | |
23 | static struct ccsr_rcpm_v1 __iomem *rcpm_v1_regs; | |
24 | static struct ccsr_rcpm_v2 __iomem *rcpm_v2_regs; | |
25 | static unsigned int fsl_supported_pm_modes; | |
26 | ||
27 | static void rcpm_v1_irq_mask(int cpu) | |
28 | { | |
29 | int hw_cpu = get_hard_smp_processor_id(cpu); | |
30 | unsigned int mask = 1 << hw_cpu; | |
31 | ||
32 | setbits32(&rcpm_v1_regs->cpmimr, mask); | |
33 | setbits32(&rcpm_v1_regs->cpmcimr, mask); | |
34 | setbits32(&rcpm_v1_regs->cpmmcmr, mask); | |
35 | setbits32(&rcpm_v1_regs->cpmnmimr, mask); | |
36 | } | |
37 | ||
38 | static void rcpm_v2_irq_mask(int cpu) | |
39 | { | |
40 | int hw_cpu = get_hard_smp_processor_id(cpu); | |
41 | unsigned int mask = 1 << hw_cpu; | |
42 | ||
43 | setbits32(&rcpm_v2_regs->tpmimr0, mask); | |
44 | setbits32(&rcpm_v2_regs->tpmcimr0, mask); | |
45 | setbits32(&rcpm_v2_regs->tpmmcmr0, mask); | |
46 | setbits32(&rcpm_v2_regs->tpmnmimr0, mask); | |
47 | } | |
48 | ||
49 | static void rcpm_v1_irq_unmask(int cpu) | |
50 | { | |
51 | int hw_cpu = get_hard_smp_processor_id(cpu); | |
52 | unsigned int mask = 1 << hw_cpu; | |
53 | ||
54 | clrbits32(&rcpm_v1_regs->cpmimr, mask); | |
55 | clrbits32(&rcpm_v1_regs->cpmcimr, mask); | |
56 | clrbits32(&rcpm_v1_regs->cpmmcmr, mask); | |
57 | clrbits32(&rcpm_v1_regs->cpmnmimr, mask); | |
58 | } | |
59 | ||
60 | static void rcpm_v2_irq_unmask(int cpu) | |
61 | { | |
62 | int hw_cpu = get_hard_smp_processor_id(cpu); | |
63 | unsigned int mask = 1 << hw_cpu; | |
64 | ||
65 | clrbits32(&rcpm_v2_regs->tpmimr0, mask); | |
66 | clrbits32(&rcpm_v2_regs->tpmcimr0, mask); | |
67 | clrbits32(&rcpm_v2_regs->tpmmcmr0, mask); | |
68 | clrbits32(&rcpm_v2_regs->tpmnmimr0, mask); | |
69 | } | |
70 | ||
71 | static void rcpm_v1_set_ip_power(bool enable, u32 mask) | |
72 | { | |
73 | if (enable) | |
74 | setbits32(&rcpm_v1_regs->ippdexpcr, mask); | |
75 | else | |
76 | clrbits32(&rcpm_v1_regs->ippdexpcr, mask); | |
77 | } | |
78 | ||
79 | static void rcpm_v2_set_ip_power(bool enable, u32 mask) | |
80 | { | |
81 | if (enable) | |
82 | setbits32(&rcpm_v2_regs->ippdexpcr[0], mask); | |
83 | else | |
84 | clrbits32(&rcpm_v2_regs->ippdexpcr[0], mask); | |
85 | } | |
86 | ||
87 | static void rcpm_v1_cpu_enter_state(int cpu, int state) | |
88 | { | |
89 | int hw_cpu = get_hard_smp_processor_id(cpu); | |
90 | unsigned int mask = 1 << hw_cpu; | |
91 | ||
92 | switch (state) { | |
93 | case E500_PM_PH10: | |
94 | setbits32(&rcpm_v1_regs->cdozcr, mask); | |
95 | break; | |
96 | case E500_PM_PH15: | |
97 | setbits32(&rcpm_v1_regs->cnapcr, mask); | |
98 | break; | |
99 | default: | |
100 | pr_warn("Unknown cpu PM state (%d)\n", state); | |
101 | break; | |
102 | } | |
103 | } | |
104 | ||
105 | static void rcpm_v2_cpu_enter_state(int cpu, int state) | |
106 | { | |
107 | int hw_cpu = get_hard_smp_processor_id(cpu); | |
108 | u32 mask = 1 << cpu_core_index_of_thread(cpu); | |
109 | ||
110 | switch (state) { | |
111 | case E500_PM_PH10: | |
112 | /* one bit corresponds to one thread for PH10 of 6500 */ | |
113 | setbits32(&rcpm_v2_regs->tph10setr0, 1 << hw_cpu); | |
114 | break; | |
115 | case E500_PM_PH15: | |
116 | setbits32(&rcpm_v2_regs->pcph15setr, mask); | |
117 | break; | |
118 | case E500_PM_PH20: | |
119 | setbits32(&rcpm_v2_regs->pcph20setr, mask); | |
120 | break; | |
121 | case E500_PM_PH30: | |
122 | setbits32(&rcpm_v2_regs->pcph30setr, mask); | |
123 | break; | |
124 | default: | |
125 | pr_warn("Unknown cpu PM state (%d)\n", state); | |
126 | } | |
127 | } | |
128 | ||
129 | static void rcpm_v1_cpu_die(int cpu) | |
130 | { | |
131 | rcpm_v1_cpu_enter_state(cpu, E500_PM_PH15); | |
132 | } | |
133 | ||
134 | #ifdef CONFIG_PPC64 | |
135 | static void qoriq_disable_thread(int cpu) | |
136 | { | |
137 | int thread = cpu_thread_in_core(cpu); | |
138 | ||
139 | book3e_stop_thread(thread); | |
140 | } | |
141 | #endif | |
142 | ||
143 | static void rcpm_v2_cpu_die(int cpu) | |
144 | { | |
145 | #ifdef CONFIG_PPC64 | |
146 | int primary; | |
147 | ||
148 | if (threads_per_core == 2) { | |
149 | primary = cpu_first_thread_sibling(cpu); | |
150 | if (cpu_is_offline(primary) && cpu_is_offline(primary + 1)) { | |
151 | /* if both threads are offline, put the cpu in PH20 */ | |
152 | rcpm_v2_cpu_enter_state(cpu, E500_PM_PH20); | |
153 | } else { | |
154 | /* if only one thread is offline, disable the thread */ | |
155 | qoriq_disable_thread(cpu); | |
156 | } | |
157 | } | |
158 | #endif | |
159 | ||
160 | if (threads_per_core == 1) | |
161 | rcpm_v2_cpu_enter_state(cpu, E500_PM_PH20); | |
162 | } | |
163 | ||
164 | static void rcpm_v1_cpu_exit_state(int cpu, int state) | |
165 | { | |
166 | int hw_cpu = get_hard_smp_processor_id(cpu); | |
167 | unsigned int mask = 1 << hw_cpu; | |
168 | ||
169 | switch (state) { | |
170 | case E500_PM_PH10: | |
171 | clrbits32(&rcpm_v1_regs->cdozcr, mask); | |
172 | break; | |
173 | case E500_PM_PH15: | |
174 | clrbits32(&rcpm_v1_regs->cnapcr, mask); | |
175 | break; | |
176 | default: | |
177 | pr_warn("Unknown cpu PM state (%d)\n", state); | |
178 | break; | |
179 | } | |
180 | } | |
181 | ||
182 | static void rcpm_v1_cpu_up_prepare(int cpu) | |
183 | { | |
184 | rcpm_v1_cpu_exit_state(cpu, E500_PM_PH15); | |
185 | rcpm_v1_irq_unmask(cpu); | |
186 | } | |
187 | ||
188 | static void rcpm_v2_cpu_exit_state(int cpu, int state) | |
189 | { | |
190 | int hw_cpu = get_hard_smp_processor_id(cpu); | |
191 | u32 mask = 1 << cpu_core_index_of_thread(cpu); | |
192 | ||
193 | switch (state) { | |
194 | case E500_PM_PH10: | |
195 | setbits32(&rcpm_v2_regs->tph10clrr0, 1 << hw_cpu); | |
196 | break; | |
197 | case E500_PM_PH15: | |
198 | setbits32(&rcpm_v2_regs->pcph15clrr, mask); | |
199 | break; | |
200 | case E500_PM_PH20: | |
201 | setbits32(&rcpm_v2_regs->pcph20clrr, mask); | |
202 | break; | |
203 | case E500_PM_PH30: | |
204 | setbits32(&rcpm_v2_regs->pcph30clrr, mask); | |
205 | break; | |
206 | default: | |
207 | pr_warn("Unknown cpu PM state (%d)\n", state); | |
208 | } | |
209 | } | |
210 | ||
211 | static void rcpm_v2_cpu_up_prepare(int cpu) | |
212 | { | |
213 | rcpm_v2_cpu_exit_state(cpu, E500_PM_PH20); | |
214 | rcpm_v2_irq_unmask(cpu); | |
215 | } | |
216 | ||
217 | static int rcpm_v1_plat_enter_state(int state) | |
218 | { | |
219 | u32 *pmcsr_reg = &rcpm_v1_regs->powmgtcsr; | |
220 | int ret = 0; | |
221 | int result; | |
222 | ||
223 | switch (state) { | |
224 | case PLAT_PM_SLEEP: | |
225 | setbits32(pmcsr_reg, RCPM_POWMGTCSR_SLP); | |
226 | ||
227 | /* Upon resume, wait for RCPM_POWMGTCSR_SLP bit to be clear. */ | |
228 | result = spin_event_timeout( | |
229 | !(in_be32(pmcsr_reg) & RCPM_POWMGTCSR_SLP), 10000, 10); | |
230 | if (!result) { | |
231 | pr_err("timeout waiting for SLP bit to be cleared\n"); | |
232 | ret = -ETIMEDOUT; | |
233 | } | |
234 | break; | |
235 | default: | |
236 | pr_warn("Unknown platform PM state (%d)", state); | |
237 | ret = -EINVAL; | |
238 | } | |
239 | ||
240 | return ret; | |
241 | } | |
242 | ||
243 | static int rcpm_v2_plat_enter_state(int state) | |
244 | { | |
245 | u32 *pmcsr_reg = &rcpm_v2_regs->powmgtcsr; | |
246 | int ret = 0; | |
247 | int result; | |
248 | ||
249 | switch (state) { | |
250 | case PLAT_PM_LPM20: | |
251 | /* clear previous LPM20 status */ | |
252 | setbits32(pmcsr_reg, RCPM_POWMGTCSR_P_LPM20_ST); | |
253 | /* enter LPM20 status */ | |
254 | setbits32(pmcsr_reg, RCPM_POWMGTCSR_LPM20_RQ); | |
255 | ||
256 | /* At this point, the device is in LPM20 status. */ | |
257 | ||
258 | /* resume ... */ | |
259 | result = spin_event_timeout( | |
260 | !(in_be32(pmcsr_reg) & RCPM_POWMGTCSR_LPM20_ST), 10000, 10); | |
261 | if (!result) { | |
262 | pr_err("timeout waiting for LPM20 bit to be cleared\n"); | |
263 | ret = -ETIMEDOUT; | |
264 | } | |
265 | break; | |
266 | default: | |
267 | pr_warn("Unknown platform PM state (%d)\n", state); | |
268 | ret = -EINVAL; | |
269 | } | |
270 | ||
271 | return ret; | |
272 | } | |
273 | ||
274 | static int rcpm_v1_plat_enter_sleep(void) | |
275 | { | |
276 | return rcpm_v1_plat_enter_state(PLAT_PM_SLEEP); | |
277 | } | |
278 | ||
279 | static int rcpm_v2_plat_enter_sleep(void) | |
280 | { | |
281 | return rcpm_v2_plat_enter_state(PLAT_PM_LPM20); | |
282 | } | |
283 | ||
284 | static void rcpm_common_freeze_time_base(u32 *tben_reg, int freeze) | |
285 | { | |
286 | static u32 mask; | |
287 | ||
288 | if (freeze) { | |
289 | mask = in_be32(tben_reg); | |
290 | clrbits32(tben_reg, mask); | |
291 | } else { | |
292 | setbits32(tben_reg, mask); | |
293 | } | |
294 | ||
295 | /* read back to push the previous write */ | |
296 | in_be32(tben_reg); | |
297 | } | |
298 | ||
299 | static void rcpm_v1_freeze_time_base(bool freeze) | |
300 | { | |
301 | rcpm_common_freeze_time_base(&rcpm_v1_regs->ctbenr, freeze); | |
302 | } | |
303 | ||
304 | static void rcpm_v2_freeze_time_base(bool freeze) | |
305 | { | |
306 | rcpm_common_freeze_time_base(&rcpm_v2_regs->pctbenr, freeze); | |
307 | } | |
308 | ||
309 | static unsigned int rcpm_get_pm_modes(void) | |
310 | { | |
311 | return fsl_supported_pm_modes; | |
312 | } | |
313 | ||
314 | static const struct fsl_pm_ops qoriq_rcpm_v1_ops = { | |
315 | .irq_mask = rcpm_v1_irq_mask, | |
316 | .irq_unmask = rcpm_v1_irq_unmask, | |
317 | .cpu_enter_state = rcpm_v1_cpu_enter_state, | |
318 | .cpu_exit_state = rcpm_v1_cpu_exit_state, | |
319 | .cpu_up_prepare = rcpm_v1_cpu_up_prepare, | |
320 | .cpu_die = rcpm_v1_cpu_die, | |
321 | .plat_enter_sleep = rcpm_v1_plat_enter_sleep, | |
322 | .set_ip_power = rcpm_v1_set_ip_power, | |
323 | .freeze_time_base = rcpm_v1_freeze_time_base, | |
324 | .get_pm_modes = rcpm_get_pm_modes, | |
325 | }; | |
326 | ||
327 | static const struct fsl_pm_ops qoriq_rcpm_v2_ops = { | |
328 | .irq_mask = rcpm_v2_irq_mask, | |
329 | .irq_unmask = rcpm_v2_irq_unmask, | |
330 | .cpu_enter_state = rcpm_v2_cpu_enter_state, | |
331 | .cpu_exit_state = rcpm_v2_cpu_exit_state, | |
332 | .cpu_up_prepare = rcpm_v2_cpu_up_prepare, | |
333 | .cpu_die = rcpm_v2_cpu_die, | |
334 | .plat_enter_sleep = rcpm_v2_plat_enter_sleep, | |
335 | .set_ip_power = rcpm_v2_set_ip_power, | |
336 | .freeze_time_base = rcpm_v2_freeze_time_base, | |
337 | .get_pm_modes = rcpm_get_pm_modes, | |
338 | }; | |
339 | ||
340 | static const struct of_device_id rcpm_matches[] = { | |
341 | { | |
342 | .compatible = "fsl,qoriq-rcpm-1.0", | |
343 | .data = &qoriq_rcpm_v1_ops, | |
344 | }, | |
345 | { | |
346 | .compatible = "fsl,qoriq-rcpm-2.0", | |
347 | .data = &qoriq_rcpm_v2_ops, | |
348 | }, | |
349 | { | |
350 | .compatible = "fsl,qoriq-rcpm-2.1", | |
351 | .data = &qoriq_rcpm_v2_ops, | |
352 | }, | |
353 | {}, | |
354 | }; | |
355 | ||
356 | int __init fsl_rcpm_init(void) | |
357 | { | |
358 | struct device_node *np; | |
359 | const struct of_device_id *match; | |
360 | void __iomem *base; | |
361 | ||
362 | np = of_find_matching_node_and_match(NULL, rcpm_matches, &match); | |
363 | if (!np) | |
364 | return 0; | |
365 | ||
366 | base = of_iomap(np, 0); | |
367 | of_node_put(np); | |
368 | if (!base) { | |
369 | pr_err("of_iomap() error.\n"); | |
370 | return -ENOMEM; | |
371 | } | |
372 | ||
373 | rcpm_v1_regs = base; | |
374 | rcpm_v2_regs = base; | |
375 | ||
376 | /* support sleep by default */ | |
377 | fsl_supported_pm_modes = FSL_PM_SLEEP; | |
378 | ||
379 | qoriq_pm_ops = match->data; | |
380 | ||
381 | return 0; | |
382 | } |