diff options
author | Geert Uytterhoeven <geert+renesas@glider.be> | 2016-04-20 08:02:38 -0400 |
---|---|---|
committer | Simon Horman <horms+renesas@verge.net.au> | 2016-04-22 03:30:37 -0400 |
commit | dcc09fd143bb97c2e83e443f7343c07aa0a9a6c0 (patch) | |
tree | e98508aebce84779290f9ff18d17d3ad9ad5f89e | |
parent | 68667cebfc0d27d2153d7a6b489f3231b533d9bc (diff) |
soc: renesas: rcar-sysc: Add DT support for SYSC PM domains
Populate the SYSC PM domains from DT, based on the presence of a device
node for the System Controller. The actual power area hiearchy, and
features of specific areas are obtained from tables in the C code.
The SYSCIER and SYSCIMR register values are derived from the power areas
present, which will help to get rid of the hardcoded values in R-Car H1
and R-Car Gen2 platform code later.
Initialization is done from an early_initcall(), to make sure the PM
Domains are initialized before secondary CPU bringup.
Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Ulf Hansson <ulf.hansson@linaro.org>
Signed-off-by: Simon Horman <horms+renesas@verge.net.au>
-rw-r--r-- | drivers/soc/renesas/rcar-sysc.c | 202 | ||||
-rw-r--r-- | drivers/soc/renesas/rcar-sysc.h | 53 |
2 files changed, 254 insertions, 1 deletions
diff --git a/drivers/soc/renesas/rcar-sysc.c b/drivers/soc/renesas/rcar-sysc.c index 9ba5fd15c53b..95f2b59cbd76 100644 --- a/drivers/soc/renesas/rcar-sysc.c +++ b/drivers/soc/renesas/rcar-sysc.c | |||
@@ -2,6 +2,7 @@ | |||
2 | * R-Car SYSC Power management support | 2 | * R-Car SYSC Power management support |
3 | * | 3 | * |
4 | * Copyright (C) 2014 Magnus Damm | 4 | * Copyright (C) 2014 Magnus Damm |
5 | * Copyright (C) 2015-2016 Glider bvba | ||
5 | * | 6 | * |
6 | * This file is subject to the terms and conditions of the GNU General Public | 7 | * This file is subject to the terms and conditions of the GNU General Public |
7 | * License. See the file "COPYING" in the main directory of this archive | 8 | * License. See the file "COPYING" in the main directory of this archive |
@@ -11,10 +12,15 @@ | |||
11 | #include <linux/delay.h> | 12 | #include <linux/delay.h> |
12 | #include <linux/err.h> | 13 | #include <linux/err.h> |
13 | #include <linux/mm.h> | 14 | #include <linux/mm.h> |
15 | #include <linux/of_address.h> | ||
16 | #include <linux/pm_domain.h> | ||
17 | #include <linux/slab.h> | ||
14 | #include <linux/spinlock.h> | 18 | #include <linux/spinlock.h> |
15 | #include <linux/io.h> | 19 | #include <linux/io.h> |
16 | #include <linux/soc/renesas/rcar-sysc.h> | 20 | #include <linux/soc/renesas/rcar-sysc.h> |
17 | 21 | ||
22 | #include "rcar-sysc.h" | ||
23 | |||
18 | /* SYSC Common */ | 24 | /* SYSC Common */ |
19 | #define SYSCSR 0x00 /* SYSC Status Register */ | 25 | #define SYSCSR 0x00 /* SYSC Status Register */ |
20 | #define SYSCISR 0x04 /* Interrupt Status Register */ | 26 | #define SYSCISR 0x04 /* Interrupt Status Register */ |
@@ -29,7 +35,8 @@ | |||
29 | /* | 35 | /* |
30 | * Power Control Register Offsets inside the register block for each domain | 36 | * Power Control Register Offsets inside the register block for each domain |
31 | * Note: The "CR" registers for ARM cores exist on H1 only | 37 | * Note: The "CR" registers for ARM cores exist on H1 only |
32 | * Use WFI to power off, CPG/APMU to resume ARM cores on R-Car Gen2 | 38 | * Use WFI to power off, CPG/APMU to resume ARM cores on R-Car Gen2 |
39 | * Use PSCI on R-Car Gen3 | ||
33 | */ | 40 | */ |
34 | #define PWRSR_OFFS 0x00 /* Power Status Register */ | 41 | #define PWRSR_OFFS 0x00 /* Power Status Register */ |
35 | #define PWROFFCR_OFFS 0x04 /* Power Shutoff Control Register */ | 42 | #define PWROFFCR_OFFS 0x04 /* Power Shutoff Control Register */ |
@@ -48,6 +55,8 @@ | |||
48 | #define SYSCISR_RETRIES 1000 | 55 | #define SYSCISR_RETRIES 1000 |
49 | #define SYSCISR_DELAY_US 1 | 56 | #define SYSCISR_DELAY_US 1 |
50 | 57 | ||
58 | #define RCAR_PD_ALWAYS_ON 32 /* Always-on power area */ | ||
59 | |||
51 | static void __iomem *rcar_sysc_base; | 60 | static void __iomem *rcar_sysc_base; |
52 | static DEFINE_SPINLOCK(rcar_sysc_lock); /* SMP CPUs + I/O devices */ | 61 | static DEFINE_SPINLOCK(rcar_sysc_lock); /* SMP CPUs + I/O devices */ |
53 | 62 | ||
@@ -162,3 +171,194 @@ void __iomem *rcar_sysc_init(phys_addr_t base) | |||
162 | 171 | ||
163 | return rcar_sysc_base; | 172 | return rcar_sysc_base; |
164 | } | 173 | } |
174 | |||
175 | struct rcar_sysc_pd { | ||
176 | struct generic_pm_domain genpd; | ||
177 | struct rcar_sysc_ch ch; | ||
178 | unsigned int flags; | ||
179 | char name[0]; | ||
180 | }; | ||
181 | |||
182 | static inline struct rcar_sysc_pd *to_rcar_pd(struct generic_pm_domain *d) | ||
183 | { | ||
184 | return container_of(d, struct rcar_sysc_pd, genpd); | ||
185 | } | ||
186 | |||
187 | static int rcar_sysc_pd_power_off(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); | ||
192 | |||
193 | if (pd->flags & PD_NO_CR) { | ||
194 | pr_debug("%s: Cannot control %s\n", __func__, genpd->name); | ||
195 | return -EBUSY; | ||
196 | } | ||
197 | |||
198 | if (pd->flags & PD_BUSY) { | ||
199 | pr_debug("%s: %s busy\n", __func__, genpd->name); | ||
200 | return -EBUSY; | ||
201 | } | ||
202 | |||
203 | return rcar_sysc_power_down(&pd->ch); | ||
204 | } | ||
205 | |||
206 | static int rcar_sysc_pd_power_on(struct generic_pm_domain *genpd) | ||
207 | { | ||
208 | struct rcar_sysc_pd *pd = to_rcar_pd(genpd); | ||
209 | |||
210 | pr_debug("%s: %s\n", __func__, genpd->name); | ||
211 | |||
212 | if (pd->flags & PD_NO_CR) { | ||
213 | pr_debug("%s: Cannot control %s\n", __func__, genpd->name); | ||
214 | return 0; | ||
215 | } | ||
216 | |||
217 | return rcar_sysc_power_up(&pd->ch); | ||
218 | } | ||
219 | |||
220 | static void __init rcar_sysc_pd_setup(struct rcar_sysc_pd *pd) | ||
221 | { | ||
222 | struct generic_pm_domain *genpd = &pd->genpd; | ||
223 | const char *name = pd->genpd.name; | ||
224 | struct dev_power_governor *gov = &simple_qos_governor; | ||
225 | |||
226 | if (pd->flags & PD_CPU) { | ||
227 | /* | ||
228 | * This domain contains a CPU core and therefore it should | ||
229 | * only be turned off if the CPU is not in use. | ||
230 | */ | ||
231 | pr_debug("PM domain %s contains %s\n", name, "CPU"); | ||
232 | pd->flags |= PD_BUSY; | ||
233 | gov = &pm_domain_always_on_gov; | ||
234 | } else if (pd->flags & PD_SCU) { | ||
235 | /* | ||
236 | * This domain contains an SCU and cache-controller, and | ||
237 | * therefore it should only be turned off if the CPU cores are | ||
238 | * not in use. | ||
239 | */ | ||
240 | pr_debug("PM domain %s contains %s\n", name, "SCU"); | ||
241 | pd->flags |= PD_BUSY; | ||
242 | gov = &pm_domain_always_on_gov; | ||
243 | } else if (pd->flags & PD_NO_CR) { | ||
244 | /* | ||
245 | * This domain cannot be turned off. | ||
246 | */ | ||
247 | pd->flags |= PD_BUSY; | ||
248 | gov = &pm_domain_always_on_gov; | ||
249 | } | ||
250 | |||
251 | genpd->power_off = rcar_sysc_pd_power_off; | ||
252 | genpd->power_on = rcar_sysc_pd_power_on; | ||
253 | |||
254 | if (pd->flags & (PD_CPU | PD_NO_CR)) { | ||
255 | /* Skip CPUs (handled by SMP code) and areas without control */ | ||
256 | pr_debug("%s: Not touching %s\n", __func__, genpd->name); | ||
257 | goto finalize; | ||
258 | } | ||
259 | |||
260 | if (!rcar_sysc_power_is_off(&pd->ch)) { | ||
261 | pr_debug("%s: %s is already powered\n", __func__, genpd->name); | ||
262 | goto finalize; | ||
263 | } | ||
264 | |||
265 | rcar_sysc_power_up(&pd->ch); | ||
266 | |||
267 | finalize: | ||
268 | pm_genpd_init(genpd, gov, false); | ||
269 | } | ||
270 | |||
271 | static const struct of_device_id rcar_sysc_matches[] = { | ||
272 | { /* sentinel */ } | ||
273 | }; | ||
274 | |||
275 | struct rcar_pm_domains { | ||
276 | struct genpd_onecell_data onecell_data; | ||
277 | struct generic_pm_domain *domains[RCAR_PD_ALWAYS_ON + 1]; | ||
278 | }; | ||
279 | |||
280 | static int __init rcar_sysc_pd_init(void) | ||
281 | { | ||
282 | const struct rcar_sysc_info *info; | ||
283 | const struct of_device_id *match; | ||
284 | struct rcar_pm_domains *domains; | ||
285 | struct device_node *np; | ||
286 | u32 syscier, syscimr; | ||
287 | void __iomem *base; | ||
288 | unsigned int i; | ||
289 | int error; | ||
290 | |||
291 | np = of_find_matching_node_and_match(NULL, rcar_sysc_matches, &match); | ||
292 | if (!np) | ||
293 | return -ENODEV; | ||
294 | |||
295 | info = match->data; | ||
296 | |||
297 | base = of_iomap(np, 0); | ||
298 | if (!base) { | ||
299 | pr_warn("%s: Cannot map regs\n", np->full_name); | ||
300 | error = -ENOMEM; | ||
301 | goto out_put; | ||
302 | } | ||
303 | |||
304 | rcar_sysc_base = base; | ||
305 | |||
306 | domains = kzalloc(sizeof(*domains), GFP_KERNEL); | ||
307 | if (!domains) { | ||
308 | error = -ENOMEM; | ||
309 | goto out_put; | ||
310 | } | ||
311 | |||
312 | domains->onecell_data.domains = domains->domains; | ||
313 | domains->onecell_data.num_domains = ARRAY_SIZE(domains->domains); | ||
314 | |||
315 | for (i = 0, syscier = 0; i < info->num_areas; i++) | ||
316 | syscier |= BIT(info->areas[i].isr_bit); | ||
317 | |||
318 | /* | ||
319 | * Mask all interrupt sources to prevent the CPU from receiving them. | ||
320 | * Make sure not to clear reserved bits that were set before. | ||
321 | */ | ||
322 | syscimr = ioread32(base + SYSCIMR); | ||
323 | syscimr |= syscier; | ||
324 | pr_debug("%s: syscimr = 0x%08x\n", np->full_name, syscimr); | ||
325 | iowrite32(syscimr, base + SYSCIMR); | ||
326 | |||
327 | /* | ||
328 | * SYSC needs all interrupt sources enabled to control power. | ||
329 | */ | ||
330 | pr_debug("%s: syscier = 0x%08x\n", np->full_name, syscier); | ||
331 | iowrite32(syscier, base + SYSCIER); | ||
332 | |||
333 | for (i = 0; i < info->num_areas; i++) { | ||
334 | const struct rcar_sysc_area *area = &info->areas[i]; | ||
335 | struct rcar_sysc_pd *pd; | ||
336 | |||
337 | pd = kzalloc(sizeof(*pd) + strlen(area->name) + 1, GFP_KERNEL); | ||
338 | if (!pd) { | ||
339 | error = -ENOMEM; | ||
340 | goto out_put; | ||
341 | } | ||
342 | |||
343 | strcpy(pd->name, area->name); | ||
344 | pd->genpd.name = pd->name; | ||
345 | pd->ch.chan_offs = area->chan_offs; | ||
346 | pd->ch.chan_bit = area->chan_bit; | ||
347 | pd->ch.isr_bit = area->isr_bit; | ||
348 | pd->flags = area->flags; | ||
349 | |||
350 | rcar_sysc_pd_setup(pd); | ||
351 | if (area->parent >= 0) | ||
352 | pm_genpd_add_subdomain(domains->domains[area->parent], | ||
353 | &pd->genpd); | ||
354 | |||
355 | domains->domains[area->isr_bit] = &pd->genpd; | ||
356 | } | ||
357 | |||
358 | of_genpd_add_provider_onecell(np, &domains->onecell_data); | ||
359 | |||
360 | out_put: | ||
361 | of_node_put(np); | ||
362 | return error; | ||
363 | } | ||
364 | early_initcall(rcar_sysc_pd_init); | ||
diff --git a/drivers/soc/renesas/rcar-sysc.h b/drivers/soc/renesas/rcar-sysc.h new file mode 100644 index 000000000000..7bb48b4f7334 --- /dev/null +++ b/drivers/soc/renesas/rcar-sysc.h | |||
@@ -0,0 +1,53 @@ | |||
1 | /* | ||
2 | * Renesas R-Car System Controller | ||
3 | * | ||
4 | * Copyright (C) 2016 Glider bvba | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; version 2 of the License. | ||
9 | */ | ||
10 | #ifndef __SOC_RENESAS_RCAR_SYSC_H__ | ||
11 | #define __SOC_RENESAS_RCAR_SYSC_H__ | ||
12 | |||
13 | #include <linux/types.h> | ||
14 | |||
15 | |||
16 | /* | ||
17 | * Power Domain flags | ||
18 | */ | ||
19 | #define PD_CPU BIT(0) /* Area contains main CPU core */ | ||
20 | #define PD_SCU BIT(1) /* Area contains SCU and L2 cache */ | ||
21 | #define PD_NO_CR BIT(2) /* Area lacks PWR{ON,OFF}CR registers */ | ||
22 | |||
23 | #define PD_BUSY BIT(3) /* Busy, for internal use only */ | ||
24 | |||
25 | #define PD_CPU_CR PD_CPU /* CPU area has CR (R-Car H1) */ | ||
26 | #define PD_CPU_NOCR PD_CPU | PD_NO_CR /* CPU area lacks CR (R-Car Gen2/3) */ | ||
27 | #define PD_ALWAYS_ON PD_NO_CR /* Always-on area */ | ||
28 | |||
29 | |||
30 | /* | ||
31 | * Description of a Power Area | ||
32 | */ | ||
33 | |||
34 | struct rcar_sysc_area { | ||
35 | const char *name; | ||
36 | u16 chan_offs; /* Offset of PWRSR register for this area */ | ||
37 | u8 chan_bit; /* Bit in PWR* (except for PWRUP in PWRSR) */ | ||
38 | u8 isr_bit; /* Bit in SYSCI*R */ | ||
39 | int parent; /* -1 if none */ | ||
40 | unsigned int flags; /* See PD_* */ | ||
41 | }; | ||
42 | |||
43 | |||
44 | /* | ||
45 | * SoC-specific Power Area Description | ||
46 | */ | ||
47 | |||
48 | struct rcar_sysc_info { | ||
49 | const struct rcar_sysc_area *areas; | ||
50 | unsigned int num_areas; | ||
51 | }; | ||
52 | |||
53 | #endif /* __SOC_RENESAS_RCAR_SYSC_H__ */ | ||