]>
Commit | Line | Data |
---|---|---|
a6557eb7 MD |
1 | /* |
2 | * R-Car SYSC Power management support | |
3 | * | |
4 | * Copyright (C) 2014 Magnus Damm | |
afa6f53d | 5 | * Copyright (C) 2015-2017 Glider bvba |
a6557eb7 MD |
6 | * |
7 | * This file is subject to the terms and conditions of the GNU General Public | |
8 | * License. See the file "COPYING" in the main directory of this archive | |
9 | * for more details. | |
10 | */ | |
11 | ||
1c8c77f5 | 12 | #include <linux/clk/renesas.h> |
a6557eb7 MD |
13 | #include <linux/delay.h> |
14 | #include <linux/err.h> | |
15 | #include <linux/mm.h> | |
dcc09fd1 GU |
16 | #include <linux/of_address.h> |
17 | #include <linux/pm_domain.h> | |
18 | #include <linux/slab.h> | |
a6557eb7 | 19 | #include <linux/spinlock.h> |
2584cf83 | 20 | #include <linux/io.h> |
be32bcbb | 21 | #include <linux/soc/renesas/rcar-sysc.h> |
a6557eb7 | 22 | |
dcc09fd1 GU |
23 | #include "rcar-sysc.h" |
24 | ||
577d104d GU |
25 | /* SYSC Common */ |
26 | #define SYSCSR 0x00 /* SYSC Status Register */ | |
27 | #define SYSCISR 0x04 /* Interrupt Status Register */ | |
28 | #define SYSCISCR 0x08 /* Interrupt Status Clear Register */ | |
29 | #define SYSCIER 0x0c /* Interrupt Enable Register */ | |
30 | #define SYSCIMR 0x10 /* Interrupt Mask Register */ | |
a6557eb7 | 31 | |
577d104d GU |
32 | /* SYSC Status Register */ |
33 | #define SYSCSR_PONENB 1 /* Ready for power resume requests */ | |
34 | #define SYSCSR_POFFENB 0 /* Ready for power shutoff requests */ | |
a6557eb7 | 35 | |
577d104d GU |
36 | /* |
37 | * Power Control Register Offsets inside the register block for each domain | |
38 | * Note: The "CR" registers for ARM cores exist on H1 only | |
dcc09fd1 GU |
39 | * Use WFI to power off, CPG/APMU to resume ARM cores on R-Car Gen2 |
40 | * Use PSCI on R-Car Gen3 | |
577d104d GU |
41 | */ |
42 | #define PWRSR_OFFS 0x00 /* Power Status Register */ | |
43 | #define PWROFFCR_OFFS 0x04 /* Power Shutoff Control Register */ | |
44 | #define PWROFFSR_OFFS 0x08 /* Power Shutoff Status Register */ | |
45 | #define PWRONCR_OFFS 0x0c /* Power Resume Control Register */ | |
46 | #define PWRONSR_OFFS 0x10 /* Power Resume Status Register */ | |
47 | #define PWRER_OFFS 0x14 /* Power Shutoff/Resume Error */ | |
48 | ||
49 | ||
50 | #define SYSCSR_RETRIES 100 | |
51 | #define SYSCSR_DELAY_US 1 | |
a6557eb7 | 52 | |
2f575fcf GU |
53 | #define PWRER_RETRIES 100 |
54 | #define PWRER_DELAY_US 1 | |
55 | ||
577d104d GU |
56 | #define SYSCISR_RETRIES 1000 |
57 | #define SYSCISR_DELAY_US 1 | |
a6557eb7 | 58 | |
dcc09fd1 GU |
59 | #define RCAR_PD_ALWAYS_ON 32 /* Always-on power area */ |
60 | ||
c4ca5d80 | 61 | static void __iomem *rcar_sysc_base; |
a6557eb7 MD |
62 | static DEFINE_SPINLOCK(rcar_sysc_lock); /* SMP CPUs + I/O devices */ |
63 | ||
bcb82437 | 64 | static int rcar_sysc_pwr_on_off(const struct rcar_sysc_ch *sysc_ch, bool on) |
a6557eb7 | 65 | { |
bcb82437 | 66 | unsigned int sr_bit, reg_offs; |
a6557eb7 MD |
67 | int k; |
68 | ||
bcb82437 GU |
69 | if (on) { |
70 | sr_bit = SYSCSR_PONENB; | |
71 | reg_offs = PWRONCR_OFFS; | |
72 | } else { | |
73 | sr_bit = SYSCSR_POFFENB; | |
74 | reg_offs = PWROFFCR_OFFS; | |
75 | } | |
76 | ||
577d104d | 77 | /* Wait until SYSC is ready to accept a power request */ |
a6557eb7 | 78 | for (k = 0; k < SYSCSR_RETRIES; k++) { |
21437c53 | 79 | if (ioread32(rcar_sysc_base + SYSCSR) & BIT(sr_bit)) |
a6557eb7 MD |
80 | break; |
81 | udelay(SYSCSR_DELAY_US); | |
82 | } | |
83 | ||
84 | if (k == SYSCSR_RETRIES) | |
85 | return -EAGAIN; | |
86 | ||
577d104d | 87 | /* Submit power shutoff or power resume request */ |
21437c53 | 88 | iowrite32(BIT(sysc_ch->chan_bit), |
a6557eb7 MD |
89 | rcar_sysc_base + sysc_ch->chan_offs + reg_offs); |
90 | ||
91 | return 0; | |
92 | } | |
93 | ||
bcb82437 | 94 | static int rcar_sysc_power(const struct rcar_sysc_ch *sysc_ch, bool on) |
a6557eb7 | 95 | { |
21437c53 GU |
96 | unsigned int isr_mask = BIT(sysc_ch->isr_bit); |
97 | unsigned int chan_mask = BIT(sysc_ch->chan_bit); | |
a6557eb7 MD |
98 | unsigned int status; |
99 | unsigned long flags; | |
100 | int ret = 0; | |
101 | int k; | |
102 | ||
103 | spin_lock_irqsave(&rcar_sysc_lock, flags); | |
104 | ||
105 | iowrite32(isr_mask, rcar_sysc_base + SYSCISCR); | |
106 | ||
577d104d | 107 | /* Submit power shutoff or resume request until it was accepted */ |
2f575fcf | 108 | for (k = 0; k < PWRER_RETRIES; k++) { |
bcb82437 | 109 | ret = rcar_sysc_pwr_on_off(sysc_ch, on); |
a6557eb7 MD |
110 | if (ret) |
111 | goto out; | |
112 | ||
113 | status = ioread32(rcar_sysc_base + | |
114 | sysc_ch->chan_offs + PWRER_OFFS); | |
2f575fcf GU |
115 | if (!(status & chan_mask)) |
116 | break; | |
117 | ||
118 | udelay(PWRER_DELAY_US); | |
119 | } | |
120 | ||
121 | if (k == PWRER_RETRIES) { | |
122 | ret = -EIO; | |
123 | goto out; | |
124 | } | |
a6557eb7 | 125 | |
577d104d | 126 | /* Wait until the power shutoff or resume request has completed * */ |
a6557eb7 MD |
127 | for (k = 0; k < SYSCISR_RETRIES; k++) { |
128 | if (ioread32(rcar_sysc_base + SYSCISR) & isr_mask) | |
129 | break; | |
130 | udelay(SYSCISR_DELAY_US); | |
131 | } | |
132 | ||
133 | if (k == SYSCISR_RETRIES) | |
134 | ret = -EIO; | |
135 | ||
136 | iowrite32(isr_mask, rcar_sysc_base + SYSCISCR); | |
137 | ||
138 | out: | |
139 | spin_unlock_irqrestore(&rcar_sysc_lock, flags); | |
140 | ||
68667ceb | 141 | pr_debug("sysc power %s domain %d: %08x -> %d\n", on ? "on" : "off", |
a6557eb7 MD |
142 | sysc_ch->isr_bit, ioread32(rcar_sysc_base + SYSCISR), ret); |
143 | return ret; | |
144 | } | |
145 | ||
624deb39 | 146 | int rcar_sysc_power_down(const struct rcar_sysc_ch *sysc_ch) |
a6557eb7 | 147 | { |
bcb82437 | 148 | return rcar_sysc_power(sysc_ch, false); |
a6557eb7 MD |
149 | } |
150 | ||
624deb39 | 151 | int rcar_sysc_power_up(const struct rcar_sysc_ch *sysc_ch) |
a6557eb7 | 152 | { |
bcb82437 | 153 | return rcar_sysc_power(sysc_ch, true); |
a6557eb7 MD |
154 | } |
155 | ||
2f024cef | 156 | static bool rcar_sysc_power_is_off(const struct rcar_sysc_ch *sysc_ch) |
a6557eb7 MD |
157 | { |
158 | unsigned int st; | |
159 | ||
160 | st = ioread32(rcar_sysc_base + sysc_ch->chan_offs + PWRSR_OFFS); | |
21437c53 | 161 | if (st & BIT(sysc_ch->chan_bit)) |
a6557eb7 MD |
162 | return true; |
163 | ||
164 | return false; | |
165 | } | |
166 | ||
dcc09fd1 GU |
167 | struct rcar_sysc_pd { |
168 | struct generic_pm_domain genpd; | |
169 | struct rcar_sysc_ch ch; | |
170 | unsigned int flags; | |
171 | char name[0]; | |
172 | }; | |
173 | ||
174 | static inline struct rcar_sysc_pd *to_rcar_pd(struct generic_pm_domain *d) | |
175 | { | |
176 | return container_of(d, struct rcar_sysc_pd, genpd); | |
177 | } | |
178 | ||
179 | static int rcar_sysc_pd_power_off(struct generic_pm_domain *genpd) | |
180 | { | |
181 | struct rcar_sysc_pd *pd = to_rcar_pd(genpd); | |
182 | ||
183 | pr_debug("%s: %s\n", __func__, genpd->name); | |
dcc09fd1 GU |
184 | return rcar_sysc_power_down(&pd->ch); |
185 | } | |
186 | ||
187 | static int rcar_sysc_pd_power_on(struct generic_pm_domain *genpd) | |
188 | { | |
189 | struct rcar_sysc_pd *pd = to_rcar_pd(genpd); | |
190 | ||
191 | pr_debug("%s: %s\n", __func__, genpd->name); | |
dcc09fd1 GU |
192 | return rcar_sysc_power_up(&pd->ch); |
193 | } | |
194 | ||
1c8c77f5 GU |
195 | static bool has_cpg_mstp; |
196 | ||
dcc09fd1 GU |
197 | static void __init rcar_sysc_pd_setup(struct rcar_sysc_pd *pd) |
198 | { | |
199 | struct generic_pm_domain *genpd = &pd->genpd; | |
200 | const char *name = pd->genpd.name; | |
201 | struct dev_power_governor *gov = &simple_qos_governor; | |
202 | ||
203 | if (pd->flags & PD_CPU) { | |
204 | /* | |
205 | * This domain contains a CPU core and therefore it should | |
206 | * only be turned off if the CPU is not in use. | |
207 | */ | |
208 | pr_debug("PM domain %s contains %s\n", name, "CPU"); | |
980532a5 | 209 | genpd->flags |= GENPD_FLAG_ALWAYS_ON; |
dcc09fd1 GU |
210 | } else if (pd->flags & PD_SCU) { |
211 | /* | |
212 | * This domain contains an SCU and cache-controller, and | |
213 | * therefore it should only be turned off if the CPU cores are | |
214 | * not in use. | |
215 | */ | |
216 | pr_debug("PM domain %s contains %s\n", name, "SCU"); | |
980532a5 | 217 | genpd->flags |= GENPD_FLAG_ALWAYS_ON; |
dcc09fd1 GU |
218 | } else if (pd->flags & PD_NO_CR) { |
219 | /* | |
220 | * This domain cannot be turned off. | |
221 | */ | |
980532a5 | 222 | genpd->flags |= GENPD_FLAG_ALWAYS_ON; |
dcc09fd1 GU |
223 | } |
224 | ||
1c8c77f5 GU |
225 | if (!(pd->flags & (PD_CPU | PD_SCU))) { |
226 | /* Enable Clock Domain for I/O devices */ | |
980532a5 | 227 | genpd->flags |= GENPD_FLAG_PM_CLK; |
1c8c77f5 GU |
228 | if (has_cpg_mstp) { |
229 | genpd->attach_dev = cpg_mstp_attach_dev; | |
230 | genpd->detach_dev = cpg_mstp_detach_dev; | |
231 | } else { | |
232 | genpd->attach_dev = cpg_mssr_attach_dev; | |
233 | genpd->detach_dev = cpg_mssr_detach_dev; | |
234 | } | |
235 | } | |
236 | ||
dcc09fd1 GU |
237 | genpd->power_off = rcar_sysc_pd_power_off; |
238 | genpd->power_on = rcar_sysc_pd_power_on; | |
239 | ||
240 | if (pd->flags & (PD_CPU | PD_NO_CR)) { | |
241 | /* Skip CPUs (handled by SMP code) and areas without control */ | |
242 | pr_debug("%s: Not touching %s\n", __func__, genpd->name); | |
243 | goto finalize; | |
244 | } | |
245 | ||
246 | if (!rcar_sysc_power_is_off(&pd->ch)) { | |
247 | pr_debug("%s: %s is already powered\n", __func__, genpd->name); | |
248 | goto finalize; | |
249 | } | |
250 | ||
251 | rcar_sysc_power_up(&pd->ch); | |
252 | ||
253 | finalize: | |
254 | pm_genpd_init(genpd, gov, false); | |
255 | } | |
256 | ||
257 | static const struct of_device_id rcar_sysc_matches[] = { | |
8be381a1 | 258 | #ifdef CONFIG_SYSC_R8A7743 |
603311ba SS |
259 | { .compatible = "renesas,r8a7743-sysc", .data = &r8a7743_sysc_info }, |
260 | #endif | |
8be381a1 | 261 | #ifdef CONFIG_SYSC_R8A7745 |
141723e0 SS |
262 | { .compatible = "renesas,r8a7745-sysc", .data = &r8a7745_sysc_info }, |
263 | #endif | |
8be381a1 | 264 | #ifdef CONFIG_SYSC_R8A7779 |
9b83ea17 | 265 | { .compatible = "renesas,r8a7779-sysc", .data = &r8a7779_sysc_info }, |
ad7c9dbc | 266 | #endif |
8be381a1 | 267 | #ifdef CONFIG_SYSC_R8A7790 |
ad7c9dbc | 268 | { .compatible = "renesas,r8a7790-sysc", .data = &r8a7790_sysc_info }, |
c5fbb3c0 | 269 | #endif |
8be381a1 | 270 | #ifdef CONFIG_SYSC_R8A7791 |
c5fbb3c0 | 271 | { .compatible = "renesas,r8a7791-sysc", .data = &r8a7791_sysc_info }, |
a247eb93 GU |
272 | /* R-Car M2-N is identical to R-Car M2-W w.r.t. power domains. */ |
273 | { .compatible = "renesas,r8a7793-sysc", .data = &r8a7791_sysc_info }, | |
9af1dbcc | 274 | #endif |
8be381a1 GU |
275 | #ifdef CONFIG_SYSC_R8A7792 |
276 | { .compatible = "renesas,r8a7792-sysc", .data = &r8a7792_sysc_info }, | |
277 | #endif | |
278 | #ifdef CONFIG_SYSC_R8A7794 | |
9af1dbcc | 279 | { .compatible = "renesas,r8a7794-sysc", .data = &r8a7794_sysc_info }, |
23f1e2ec | 280 | #endif |
8be381a1 | 281 | #ifdef CONFIG_SYSC_R8A7795 |
23f1e2ec | 282 | { .compatible = "renesas,r8a7795-sysc", .data = &r8a7795_sysc_info }, |
e0c98b91 | 283 | #endif |
8be381a1 | 284 | #ifdef CONFIG_SYSC_R8A7796 |
e0c98b91 | 285 | { .compatible = "renesas,r8a7796-sysc", .data = &r8a7796_sysc_info }, |
9b83ea17 | 286 | #endif |
dcc09fd1 GU |
287 | { /* sentinel */ } |
288 | }; | |
289 | ||
290 | struct rcar_pm_domains { | |
291 | struct genpd_onecell_data onecell_data; | |
292 | struct generic_pm_domain *domains[RCAR_PD_ALWAYS_ON + 1]; | |
293 | }; | |
294 | ||
295 | static int __init rcar_sysc_pd_init(void) | |
296 | { | |
297 | const struct rcar_sysc_info *info; | |
298 | const struct of_device_id *match; | |
299 | struct rcar_pm_domains *domains; | |
300 | struct device_node *np; | |
301 | u32 syscier, syscimr; | |
302 | void __iomem *base; | |
303 | unsigned int i; | |
304 | int error; | |
305 | ||
b1e52287 GU |
306 | if (rcar_sysc_base) |
307 | return 0; | |
308 | ||
dcc09fd1 GU |
309 | np = of_find_matching_node_and_match(NULL, rcar_sysc_matches, &match); |
310 | if (!np) | |
311 | return -ENODEV; | |
312 | ||
313 | info = match->data; | |
314 | ||
afa6f53d GU |
315 | if (info->init) { |
316 | error = info->init(); | |
317 | if (error) | |
318 | return error; | |
319 | } | |
320 | ||
1c8c77f5 GU |
321 | has_cpg_mstp = of_find_compatible_node(NULL, NULL, |
322 | "renesas,cpg-mstp-clocks"); | |
323 | ||
dcc09fd1 GU |
324 | base = of_iomap(np, 0); |
325 | if (!base) { | |
326 | pr_warn("%s: Cannot map regs\n", np->full_name); | |
327 | error = -ENOMEM; | |
328 | goto out_put; | |
329 | } | |
330 | ||
331 | rcar_sysc_base = base; | |
332 | ||
333 | domains = kzalloc(sizeof(*domains), GFP_KERNEL); | |
334 | if (!domains) { | |
335 | error = -ENOMEM; | |
336 | goto out_put; | |
337 | } | |
338 | ||
339 | domains->onecell_data.domains = domains->domains; | |
340 | domains->onecell_data.num_domains = ARRAY_SIZE(domains->domains); | |
341 | ||
342 | for (i = 0, syscier = 0; i < info->num_areas; i++) | |
343 | syscier |= BIT(info->areas[i].isr_bit); | |
344 | ||
345 | /* | |
346 | * Mask all interrupt sources to prevent the CPU from receiving them. | |
347 | * Make sure not to clear reserved bits that were set before. | |
348 | */ | |
349 | syscimr = ioread32(base + SYSCIMR); | |
350 | syscimr |= syscier; | |
351 | pr_debug("%s: syscimr = 0x%08x\n", np->full_name, syscimr); | |
352 | iowrite32(syscimr, base + SYSCIMR); | |
353 | ||
354 | /* | |
355 | * SYSC needs all interrupt sources enabled to control power. | |
356 | */ | |
357 | pr_debug("%s: syscier = 0x%08x\n", np->full_name, syscier); | |
358 | iowrite32(syscier, base + SYSCIER); | |
359 | ||
360 | for (i = 0; i < info->num_areas; i++) { | |
361 | const struct rcar_sysc_area *area = &info->areas[i]; | |
362 | struct rcar_sysc_pd *pd; | |
363 | ||
afa6f53d GU |
364 | if (!area->name) { |
365 | /* Skip NULLified area */ | |
366 | continue; | |
367 | } | |
368 | ||
dcc09fd1 GU |
369 | pd = kzalloc(sizeof(*pd) + strlen(area->name) + 1, GFP_KERNEL); |
370 | if (!pd) { | |
371 | error = -ENOMEM; | |
372 | goto out_put; | |
373 | } | |
374 | ||
375 | strcpy(pd->name, area->name); | |
376 | pd->genpd.name = pd->name; | |
377 | pd->ch.chan_offs = area->chan_offs; | |
378 | pd->ch.chan_bit = area->chan_bit; | |
379 | pd->ch.isr_bit = area->isr_bit; | |
380 | pd->flags = area->flags; | |
381 | ||
382 | rcar_sysc_pd_setup(pd); | |
383 | if (area->parent >= 0) | |
384 | pm_genpd_add_subdomain(domains->domains[area->parent], | |
385 | &pd->genpd); | |
386 | ||
387 | domains->domains[area->isr_bit] = &pd->genpd; | |
388 | } | |
389 | ||
1023578e | 390 | error = of_genpd_add_provider_onecell(np, &domains->onecell_data); |
dcc09fd1 GU |
391 | |
392 | out_put: | |
393 | of_node_put(np); | |
394 | return error; | |
395 | } | |
396 | early_initcall(rcar_sysc_pd_init); | |
b1e52287 | 397 | |
afa6f53d GU |
398 | void __init rcar_sysc_nullify(struct rcar_sysc_area *areas, |
399 | unsigned int num_areas, u8 id) | |
400 | { | |
401 | unsigned int i; | |
402 | ||
403 | for (i = 0; i < num_areas; i++) | |
404 | if (areas[i].isr_bit == id) { | |
405 | areas[i].name = NULL; | |
406 | return; | |
407 | } | |
408 | } | |
409 | ||
05323998 | 410 | void __init rcar_sysc_init(phys_addr_t base, u32 syscier) |
b1e52287 | 411 | { |
ced42730 GU |
412 | u32 syscimr; |
413 | ||
05323998 GU |
414 | if (!rcar_sysc_pd_init()) |
415 | return; | |
b1e52287 | 416 | |
05323998 GU |
417 | rcar_sysc_base = ioremap_nocache(base, PAGE_SIZE); |
418 | ||
ced42730 GU |
419 | /* |
420 | * Mask all interrupt sources to prevent the CPU from receiving them. | |
421 | * Make sure not to clear reserved bits that were set before. | |
422 | */ | |
423 | syscimr = ioread32(rcar_sysc_base + SYSCIMR); | |
424 | syscimr |= syscier; | |
425 | pr_debug("%s: syscimr = 0x%08x\n", __func__, syscimr); | |
426 | iowrite32(syscimr, rcar_sysc_base + SYSCIMR); | |
427 | ||
428 | /* | |
429 | * SYSC needs all interrupt sources enabled to control power. | |
430 | */ | |
431 | pr_debug("%s: syscier = 0x%08x\n", __func__, syscier); | |
05323998 | 432 | iowrite32(syscier, rcar_sysc_base + SYSCIER); |
b1e52287 | 433 | } |