Commit | Line | Data |
---|---|---|
a6557eb7 MD |
1 | /* |
2 | * R-Car SYSC Power management support | |
3 | * | |
4 | * Copyright (C) 2014 Magnus Damm | |
dcc09fd1 | 5 | * Copyright (C) 2015-2016 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 | ||
167 | void __iomem *rcar_sysc_init(phys_addr_t base) | |
168 | { | |
169 | rcar_sysc_base = ioremap_nocache(base, PAGE_SIZE); | |
170 | if (!rcar_sysc_base) | |
171 | panic("unable to ioremap R-Car SYSC hardware block\n"); | |
172 | ||
173 | return rcar_sysc_base; | |
174 | } | |
dcc09fd1 GU |
175 | |
176 | struct rcar_sysc_pd { | |
177 | struct generic_pm_domain genpd; | |
178 | struct rcar_sysc_ch ch; | |
179 | unsigned int flags; | |
180 | char name[0]; | |
181 | }; | |
182 | ||
183 | static inline struct rcar_sysc_pd *to_rcar_pd(struct generic_pm_domain *d) | |
184 | { | |
185 | return container_of(d, struct rcar_sysc_pd, genpd); | |
186 | } | |
187 | ||
188 | static int rcar_sysc_pd_power_off(struct generic_pm_domain *genpd) | |
189 | { | |
190 | struct rcar_sysc_pd *pd = to_rcar_pd(genpd); | |
191 | ||
192 | pr_debug("%s: %s\n", __func__, genpd->name); | |
193 | ||
194 | if (pd->flags & PD_NO_CR) { | |
195 | pr_debug("%s: Cannot control %s\n", __func__, genpd->name); | |
196 | return -EBUSY; | |
197 | } | |
198 | ||
199 | if (pd->flags & PD_BUSY) { | |
200 | pr_debug("%s: %s busy\n", __func__, genpd->name); | |
201 | return -EBUSY; | |
202 | } | |
203 | ||
204 | return rcar_sysc_power_down(&pd->ch); | |
205 | } | |
206 | ||
207 | static int rcar_sysc_pd_power_on(struct generic_pm_domain *genpd) | |
208 | { | |
209 | struct rcar_sysc_pd *pd = to_rcar_pd(genpd); | |
210 | ||
211 | pr_debug("%s: %s\n", __func__, genpd->name); | |
212 | ||
213 | if (pd->flags & PD_NO_CR) { | |
214 | pr_debug("%s: Cannot control %s\n", __func__, genpd->name); | |
215 | return 0; | |
216 | } | |
217 | ||
218 | return rcar_sysc_power_up(&pd->ch); | |
219 | } | |
220 | ||
1c8c77f5 GU |
221 | static bool has_cpg_mstp; |
222 | ||
dcc09fd1 GU |
223 | static void __init rcar_sysc_pd_setup(struct rcar_sysc_pd *pd) |
224 | { | |
225 | struct generic_pm_domain *genpd = &pd->genpd; | |
226 | const char *name = pd->genpd.name; | |
227 | struct dev_power_governor *gov = &simple_qos_governor; | |
228 | ||
229 | if (pd->flags & PD_CPU) { | |
230 | /* | |
231 | * This domain contains a CPU core and therefore it should | |
232 | * only be turned off if the CPU is not in use. | |
233 | */ | |
234 | pr_debug("PM domain %s contains %s\n", name, "CPU"); | |
235 | pd->flags |= PD_BUSY; | |
236 | gov = &pm_domain_always_on_gov; | |
237 | } else if (pd->flags & PD_SCU) { | |
238 | /* | |
239 | * This domain contains an SCU and cache-controller, and | |
240 | * therefore it should only be turned off if the CPU cores are | |
241 | * not in use. | |
242 | */ | |
243 | pr_debug("PM domain %s contains %s\n", name, "SCU"); | |
244 | pd->flags |= PD_BUSY; | |
245 | gov = &pm_domain_always_on_gov; | |
246 | } else if (pd->flags & PD_NO_CR) { | |
247 | /* | |
248 | * This domain cannot be turned off. | |
249 | */ | |
250 | pd->flags |= PD_BUSY; | |
251 | gov = &pm_domain_always_on_gov; | |
252 | } | |
253 | ||
1c8c77f5 GU |
254 | if (!(pd->flags & (PD_CPU | PD_SCU))) { |
255 | /* Enable Clock Domain for I/O devices */ | |
256 | genpd->flags = GENPD_FLAG_PM_CLK; | |
257 | if (has_cpg_mstp) { | |
258 | genpd->attach_dev = cpg_mstp_attach_dev; | |
259 | genpd->detach_dev = cpg_mstp_detach_dev; | |
260 | } else { | |
261 | genpd->attach_dev = cpg_mssr_attach_dev; | |
262 | genpd->detach_dev = cpg_mssr_detach_dev; | |
263 | } | |
264 | } | |
265 | ||
dcc09fd1 GU |
266 | genpd->power_off = rcar_sysc_pd_power_off; |
267 | genpd->power_on = rcar_sysc_pd_power_on; | |
268 | ||
269 | if (pd->flags & (PD_CPU | PD_NO_CR)) { | |
270 | /* Skip CPUs (handled by SMP code) and areas without control */ | |
271 | pr_debug("%s: Not touching %s\n", __func__, genpd->name); | |
272 | goto finalize; | |
273 | } | |
274 | ||
275 | if (!rcar_sysc_power_is_off(&pd->ch)) { | |
276 | pr_debug("%s: %s is already powered\n", __func__, genpd->name); | |
277 | goto finalize; | |
278 | } | |
279 | ||
280 | rcar_sysc_power_up(&pd->ch); | |
281 | ||
282 | finalize: | |
283 | pm_genpd_init(genpd, gov, false); | |
284 | } | |
285 | ||
286 | static const struct of_device_id rcar_sysc_matches[] = { | |
9b83ea17 GU |
287 | #ifdef CONFIG_ARCH_R8A7779 |
288 | { .compatible = "renesas,r8a7779-sysc", .data = &r8a7779_sysc_info }, | |
ad7c9dbc GU |
289 | #endif |
290 | #ifdef CONFIG_ARCH_R8A7790 | |
291 | { .compatible = "renesas,r8a7790-sysc", .data = &r8a7790_sysc_info }, | |
c5fbb3c0 GU |
292 | #endif |
293 | #ifdef CONFIG_ARCH_R8A7791 | |
294 | { .compatible = "renesas,r8a7791-sysc", .data = &r8a7791_sysc_info }, | |
a247eb93 GU |
295 | #endif |
296 | #ifdef CONFIG_ARCH_R8A7793 | |
297 | /* R-Car M2-N is identical to R-Car M2-W w.r.t. power domains. */ | |
298 | { .compatible = "renesas,r8a7793-sysc", .data = &r8a7791_sysc_info }, | |
9af1dbcc GU |
299 | #endif |
300 | #ifdef CONFIG_ARCH_R8A7794 | |
301 | { .compatible = "renesas,r8a7794-sysc", .data = &r8a7794_sysc_info }, | |
23f1e2ec GU |
302 | #endif |
303 | #ifdef CONFIG_ARCH_R8A7795 | |
304 | { .compatible = "renesas,r8a7795-sysc", .data = &r8a7795_sysc_info }, | |
9b83ea17 | 305 | #endif |
dcc09fd1 GU |
306 | { /* sentinel */ } |
307 | }; | |
308 | ||
309 | struct rcar_pm_domains { | |
310 | struct genpd_onecell_data onecell_data; | |
311 | struct generic_pm_domain *domains[RCAR_PD_ALWAYS_ON + 1]; | |
312 | }; | |
313 | ||
314 | static int __init rcar_sysc_pd_init(void) | |
315 | { | |
316 | const struct rcar_sysc_info *info; | |
317 | const struct of_device_id *match; | |
318 | struct rcar_pm_domains *domains; | |
319 | struct device_node *np; | |
320 | u32 syscier, syscimr; | |
321 | void __iomem *base; | |
322 | unsigned int i; | |
323 | int error; | |
324 | ||
325 | np = of_find_matching_node_and_match(NULL, rcar_sysc_matches, &match); | |
326 | if (!np) | |
327 | return -ENODEV; | |
328 | ||
329 | info = match->data; | |
330 | ||
1c8c77f5 GU |
331 | has_cpg_mstp = of_find_compatible_node(NULL, NULL, |
332 | "renesas,cpg-mstp-clocks"); | |
333 | ||
dcc09fd1 GU |
334 | base = of_iomap(np, 0); |
335 | if (!base) { | |
336 | pr_warn("%s: Cannot map regs\n", np->full_name); | |
337 | error = -ENOMEM; | |
338 | goto out_put; | |
339 | } | |
340 | ||
341 | rcar_sysc_base = base; | |
342 | ||
343 | domains = kzalloc(sizeof(*domains), GFP_KERNEL); | |
344 | if (!domains) { | |
345 | error = -ENOMEM; | |
346 | goto out_put; | |
347 | } | |
348 | ||
349 | domains->onecell_data.domains = domains->domains; | |
350 | domains->onecell_data.num_domains = ARRAY_SIZE(domains->domains); | |
351 | ||
352 | for (i = 0, syscier = 0; i < info->num_areas; i++) | |
353 | syscier |= BIT(info->areas[i].isr_bit); | |
354 | ||
355 | /* | |
356 | * Mask all interrupt sources to prevent the CPU from receiving them. | |
357 | * Make sure not to clear reserved bits that were set before. | |
358 | */ | |
359 | syscimr = ioread32(base + SYSCIMR); | |
360 | syscimr |= syscier; | |
361 | pr_debug("%s: syscimr = 0x%08x\n", np->full_name, syscimr); | |
362 | iowrite32(syscimr, base + SYSCIMR); | |
363 | ||
364 | /* | |
365 | * SYSC needs all interrupt sources enabled to control power. | |
366 | */ | |
367 | pr_debug("%s: syscier = 0x%08x\n", np->full_name, syscier); | |
368 | iowrite32(syscier, base + SYSCIER); | |
369 | ||
370 | for (i = 0; i < info->num_areas; i++) { | |
371 | const struct rcar_sysc_area *area = &info->areas[i]; | |
372 | struct rcar_sysc_pd *pd; | |
373 | ||
374 | pd = kzalloc(sizeof(*pd) + strlen(area->name) + 1, GFP_KERNEL); | |
375 | if (!pd) { | |
376 | error = -ENOMEM; | |
377 | goto out_put; | |
378 | } | |
379 | ||
380 | strcpy(pd->name, area->name); | |
381 | pd->genpd.name = pd->name; | |
382 | pd->ch.chan_offs = area->chan_offs; | |
383 | pd->ch.chan_bit = area->chan_bit; | |
384 | pd->ch.isr_bit = area->isr_bit; | |
385 | pd->flags = area->flags; | |
386 | ||
387 | rcar_sysc_pd_setup(pd); | |
388 | if (area->parent >= 0) | |
389 | pm_genpd_add_subdomain(domains->domains[area->parent], | |
390 | &pd->genpd); | |
391 | ||
392 | domains->domains[area->isr_bit] = &pd->genpd; | |
393 | } | |
394 | ||
395 | of_genpd_add_provider_onecell(np, &domains->onecell_data); | |
396 | ||
397 | out_put: | |
398 | of_node_put(np); | |
399 | return error; | |
400 | } | |
401 | early_initcall(rcar_sysc_pd_init); |