diff options
author | Geert Uytterhoeven <geert+renesas@glider.be> | 2016-04-20 15:02:38 +0300 |
---|---|---|
committer | Simon Horman <horms+renesas@verge.net.au> | 2016-04-22 10:30:37 +0300 |
commit | dcc09fd143bb97c2e83e443f7343c07aa0a9a6c0 (patch) | |
tree | e98508aebce84779290f9ff18d17d3ad9ad5f89e /drivers/soc/renesas/rcar-sysc.c | |
parent | 68667cebfc0d27d2153d7a6b489f3231b533d9bc (diff) | |
download | linux-dcc09fd143bb97c2e83e443f7343c07aa0a9a6c0.tar.xz |
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>
Diffstat (limited to 'drivers/soc/renesas/rcar-sysc.c')
-rw-r--r-- | drivers/soc/renesas/rcar-sysc.c | 202 |
1 files changed, 201 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 @@ * R-Car SYSC Power management support * * Copyright (C) 2014 Magnus Damm + * Copyright (C) 2015-2016 Glider bvba * * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive @@ -11,10 +12,15 @@ #include <linux/delay.h> #include <linux/err.h> #include <linux/mm.h> +#include <linux/of_address.h> +#include <linux/pm_domain.h> +#include <linux/slab.h> #include <linux/spinlock.h> #include <linux/io.h> #include <linux/soc/renesas/rcar-sysc.h> +#include "rcar-sysc.h" + /* SYSC Common */ #define SYSCSR 0x00 /* SYSC Status Register */ #define SYSCISR 0x04 /* Interrupt Status Register */ @@ -29,7 +35,8 @@ /* * Power Control Register Offsets inside the register block for each domain * Note: The "CR" registers for ARM cores exist on H1 only - * Use WFI to power off, CPG/APMU to resume ARM cores on R-Car Gen2 + * Use WFI to power off, CPG/APMU to resume ARM cores on R-Car Gen2 + * Use PSCI on R-Car Gen3 */ #define PWRSR_OFFS 0x00 /* Power Status Register */ #define PWROFFCR_OFFS 0x04 /* Power Shutoff Control Register */ @@ -48,6 +55,8 @@ #define SYSCISR_RETRIES 1000 #define SYSCISR_DELAY_US 1 +#define RCAR_PD_ALWAYS_ON 32 /* Always-on power area */ + static void __iomem *rcar_sysc_base; static DEFINE_SPINLOCK(rcar_sysc_lock); /* SMP CPUs + I/O devices */ @@ -162,3 +171,194 @@ void __iomem *rcar_sysc_init(phys_addr_t base) return rcar_sysc_base; } + +struct rcar_sysc_pd { + struct generic_pm_domain genpd; + struct rcar_sysc_ch ch; + unsigned int flags; + char name[0]; +}; + +static inline struct rcar_sysc_pd *to_rcar_pd(struct generic_pm_domain *d) +{ + return container_of(d, struct rcar_sysc_pd, genpd); +} + +static int rcar_sysc_pd_power_off(struct generic_pm_domain *genpd) +{ + struct rcar_sysc_pd *pd = to_rcar_pd(genpd); + + pr_debug("%s: %s\n", __func__, genpd->name); + + if (pd->flags & PD_NO_CR) { + pr_debug("%s: Cannot control %s\n", __func__, genpd->name); + return -EBUSY; + } + + if (pd->flags & PD_BUSY) { + pr_debug("%s: %s busy\n", __func__, genpd->name); + return -EBUSY; + } + + return rcar_sysc_power_down(&pd->ch); +} + +static int rcar_sysc_pd_power_on(struct generic_pm_domain *genpd) +{ + struct rcar_sysc_pd *pd = to_rcar_pd(genpd); + + pr_debug("%s: %s\n", __func__, genpd->name); + + if (pd->flags & PD_NO_CR) { + pr_debug("%s: Cannot control %s\n", __func__, genpd->name); + return 0; + } + + return rcar_sysc_power_up(&pd->ch); +} + +static void __init rcar_sysc_pd_setup(struct rcar_sysc_pd *pd) +{ + struct generic_pm_domain *genpd = &pd->genpd; + const char *name = pd->genpd.name; + struct dev_power_governor *gov = &simple_qos_governor; + + if (pd->flags & PD_CPU) { + /* + * This domain contains a CPU core and therefore it should + * only be turned off if the CPU is not in use. + */ + pr_debug("PM domain %s contains %s\n", name, "CPU"); + pd->flags |= PD_BUSY; + gov = &pm_domain_always_on_gov; + } else if (pd->flags & PD_SCU) { + /* + * This domain contains an SCU and cache-controller, and + * therefore it should only be turned off if the CPU cores are + * not in use. + */ + pr_debug("PM domain %s contains %s\n", name, "SCU"); + pd->flags |= PD_BUSY; + gov = &pm_domain_always_on_gov; + } else if (pd->flags & PD_NO_CR) { + /* + * This domain cannot be turned off. + */ + pd->flags |= PD_BUSY; + gov = &pm_domain_always_on_gov; + } + + genpd->power_off = rcar_sysc_pd_power_off; + genpd->power_on = rcar_sysc_pd_power_on; + + if (pd->flags & (PD_CPU | PD_NO_CR)) { + /* Skip CPUs (handled by SMP code) and areas without control */ + pr_debug("%s: Not touching %s\n", __func__, genpd->name); + goto finalize; + } + + if (!rcar_sysc_power_is_off(&pd->ch)) { + pr_debug("%s: %s is already powered\n", __func__, genpd->name); + goto finalize; + } + + rcar_sysc_power_up(&pd->ch); + +finalize: + pm_genpd_init(genpd, gov, false); +} + +static const struct of_device_id rcar_sysc_matches[] = { + { /* sentinel */ } +}; + +struct rcar_pm_domains { + struct genpd_onecell_data onecell_data; + struct generic_pm_domain *domains[RCAR_PD_ALWAYS_ON + 1]; +}; + +static int __init rcar_sysc_pd_init(void) +{ + const struct rcar_sysc_info *info; + const struct of_device_id *match; + struct rcar_pm_domains *domains; + struct device_node *np; + u32 syscier, syscimr; + void __iomem *base; + unsigned int i; + int error; + + np = of_find_matching_node_and_match(NULL, rcar_sysc_matches, &match); + if (!np) + return -ENODEV; + + info = match->data; + + base = of_iomap(np, 0); + if (!base) { + pr_warn("%s: Cannot map regs\n", np->full_name); + error = -ENOMEM; + goto out_put; + } + + rcar_sysc_base = base; + + domains = kzalloc(sizeof(*domains), GFP_KERNEL); + if (!domains) { + error = -ENOMEM; + goto out_put; + } + + domains->onecell_data.domains = domains->domains; + domains->onecell_data.num_domains = ARRAY_SIZE(domains->domains); + + for (i = 0, syscier = 0; i < info->num_areas; i++) + syscier |= BIT(info->areas[i].isr_bit); + + /* + * Mask all interrupt sources to prevent the CPU from receiving them. + * Make sure not to clear reserved bits that were set before. + */ + syscimr = ioread32(base + SYSCIMR); + syscimr |= syscier; + pr_debug("%s: syscimr = 0x%08x\n", np->full_name, syscimr); + iowrite32(syscimr, base + SYSCIMR); + + /* + * SYSC needs all interrupt sources enabled to control power. + */ + pr_debug("%s: syscier = 0x%08x\n", np->full_name, syscier); + iowrite32(syscier, base + SYSCIER); + + for (i = 0; i < info->num_areas; i++) { + const struct rcar_sysc_area *area = &info->areas[i]; + struct rcar_sysc_pd *pd; + + pd = kzalloc(sizeof(*pd) + strlen(area->name) + 1, GFP_KERNEL); + if (!pd) { + error = -ENOMEM; + goto out_put; + } + + strcpy(pd->name, area->name); + pd->genpd.name = pd->name; + pd->ch.chan_offs = area->chan_offs; + pd->ch.chan_bit = area->chan_bit; + pd->ch.isr_bit = area->isr_bit; + pd->flags = area->flags; + + rcar_sysc_pd_setup(pd); + if (area->parent >= 0) + pm_genpd_add_subdomain(domains->domains[area->parent], + &pd->genpd); + + domains->domains[area->isr_bit] = &pd->genpd; + } + + of_genpd_add_provider_onecell(np, &domains->onecell_data); + +out_put: + of_node_put(np); + return error; +} +early_initcall(rcar_sysc_pd_init); |