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 | } |