[PATCH/RFC v2 04/11] soc: renesas: rcar: Add DT support for SYSC PM domains
Laurent Pinchart
laurent.pinchart at ideasonboard.com
Mon Feb 15 14:51:45 PST 2016
Hi Geert,
Thank you for the patch.
On Monday 15 February 2016 22:16:53 Geert Uytterhoeven wrote:
> Populate the SYSC PM domains from DT.
>
> Special cases, like PM domains containing CPU cores or SCUs, are
> handled by scanning the DT topology.
>
> The SYSCIER register value is derived from the PM domains found in DT,
> which will allow to get rid of the hardcoded values in pm-rcar-gen2.c.
> However, this means we have to scan for PM domains even if CONFIG_PM=n.
>
> FIXME:
> - This needs better integration with the PM code in pm-rcar-gen2, the
> SMP code in smp-r8a7790, and Magnus' DT APMU series.
Have you given this some thoughts already ? Unfortunately smp_prepare_cpus()
is called before any initcall :-/ How do the other platforms handle this ?
> Signed-off-by: Geert Uytterhoeven <geert+renesas at glider.be>
> ---
> v2:
> - Add missing definitions for SYSC_PWR_CA15_CPU and SYSC_PWR_CA7_CPU,
> - Add R-Car H3 (r8a7795) support,
> - Drop tests for CONFIG_ARCH_SHMOBILE_LEGACY,
> - Add missing break statements in rcar_sysc_pwr_on_off(),
> - Add missing calls to of_node_put() in error paths,
> - Fix build if CONFIG_PM=n,
> - Update compatible values,
> - Update copyright.
> ---
> drivers/soc/renesas/pm-rcar.c | 327 +++++++++++++++++++++++++++++++++++++++
> 1 file changed, 327 insertions(+)
>
> diff --git a/drivers/soc/renesas/pm-rcar.c b/drivers/soc/renesas/pm-rcar.c
> index cc684e9cc8db5d1c..c0540934126e58eb 100644
> --- a/drivers/soc/renesas/pm-rcar.c
> +++ b/drivers/soc/renesas/pm-rcar.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,6 +12,9 @@
> #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/pm-rcar.h>
> @@ -38,6 +42,18 @@
> #define PWRONSR_OFFS 0x10 /* Power Resume Status Register */
> #define PWRER_OFFS 0x14 /* Power Shutoff/Resume Error */
>
> +/*
> + * SYSC Power Control Register Base Addresses (R-Car Gen2)
> + */
> +#define SYSC_PWR_CA15_CPU 0x40 /* CA15 cores (incl. L1C) (H2/M2/V2H) */
> +#define SYSC_PWR_CA7_CPU 0x1c0 /* CA7 cores (incl. L1C) (H2/E2) */
> +
> +/*
> + * SYSC Power Control Register Base Addresses (R-Car Gen3)
> + */
> +#define SYSC_PWR_CA57_CPU 0x80 /* CA57 cores (incl. L1C) (H3) */
> +#define SYSC_PWR_CA53_CPU 0x200 /* CA53 cores (incl. L1C) (H3) */
> +
>
> #define SYSCSR_RETRIES 100
> #define SYSCSR_DELAY_US 1
> @@ -51,11 +67,40 @@
> static void __iomem *rcar_sysc_base;
> static DEFINE_SPINLOCK(rcar_sysc_lock); /* SMP CPUs + I/O devices */
>
> +static unsigned int rcar_gen;
> +
> static int rcar_sysc_pwr_on_off(const struct rcar_sysc_ch *sysc_ch, bool
> on)
> {
> unsigned int sr_bit, reg_offs;
> int k;
>
> + /*
> + * Only R-Car H1 can control power to CPUs
> + * Use WFI to power off, CPG/APMU to resume ARM cores on later R-Car
> + * Generations
> + */
> + switch (rcar_gen) {
> + case 2:
> + /* FIXME Check rcar_pm_domain.cpu instead? */
> + switch (sysc_ch->chan_offs) {
> + case SYSC_PWR_CA15_CPU:
> + case SYSC_PWR_CA7_CPU:
> + pr_err("%s: Cannot control power to CPU\n", __func__);
> + return -EINVAL;
> + }
> + break;
> +
> + case 3:
> + /* FIXME Check rcar_pm_domain.cpu instead? */
> + switch (sysc_ch->chan_offs) {
> + case SYSC_PWR_CA57_CPU:
> + case SYSC_PWR_CA53_CPU:
> + pr_err("%s: Cannot control power to CPU\n", __func__);
> + return -EINVAL;
> + }
> + break;
> + }
> +
> if (on) {
> sr_bit = SYSCSR_PONENB;
> reg_offs = PWRONCR_OFFS;
> @@ -162,3 +207,285 @@ void __iomem *rcar_sysc_init(phys_addr_t base)
>
> return rcar_sysc_base;
> }
> +
> +#ifdef CONFIG_PM_GENERIC_DOMAINS
> +struct rcar_pm_domain {
> + struct generic_pm_domain genpd;
> + struct dev_power_governor *gov;
> + struct rcar_sysc_ch ch;
> + unsigned busy:1; /* Set if always -EBUSY */
> + unsigned cpu:1; /* Set if domain contains CPU */
> + char name[0];
> +};
> +
> +static inline struct rcar_pm_domain *to_rcar_pd(struct generic_pm_domain
> *d)
> +{
> + return container_of(d, struct rcar_pm_domain, genpd);
> +}
> +
> +static bool rcar_pd_active_wakeup(struct device *dev)
> +{
> + return true;
> +}
> +
> +static int rcar_pd_power_down(struct generic_pm_domain *genpd)
> +{
> + struct rcar_pm_domain *rcar_pd = to_rcar_pd(genpd);
> +
> + pr_debug("%s: %s\n", __func__, genpd->name);
> +
> + if (rcar_pd->busy) {
> + pr_debug("%s: %s busy\n", __func__, genpd->name);
> + return -EBUSY;
> + }
> +
> + return rcar_sysc_power_down(&rcar_pd->ch);
> +}
> +
> +static int rcar_pd_power_up(struct generic_pm_domain *genpd)
> +{
> + pr_debug("%s: %s\n", __func__, genpd->name);
> + return rcar_sysc_power_up(&to_rcar_pd(genpd)->ch);
> +}
> +
> +static void rcar_init_pm_domain(struct rcar_pm_domain *rcar_pd)
> +{
> + struct generic_pm_domain *genpd = &rcar_pd->genpd;
> + struct dev_power_governor *gov = rcar_pd->gov;
> +
> + pm_genpd_init(genpd, gov ? : &simple_qos_governor, false);
> + genpd->dev_ops.active_wakeup = rcar_pd_active_wakeup;
> + genpd->power_off = rcar_pd_power_down;
> + genpd->power_on = rcar_pd_power_up;
> +
> + if (rcar_sysc_power_is_off(&rcar_pd->ch))
> + rcar_sysc_power_up(&rcar_pd->ch);
> +}
> +
> +enum pd_types {
> + PD_NORMAL,
> + PD_CPU,
> + PD_SCU,
> +};
> +
> +#define MAX_NUM_SPECIAL_PDS 16
> +
> +static struct special_pd {
> + struct device_node *pd;
> + enum pd_types type;
> +} special_pds[MAX_NUM_SPECIAL_PDS] __initdata;
> +
> +static unsigned int num_special_pds __initdata;
> +
> +static void __init add_special_pd(struct device_node *np, enum pd_types
> type)
> +{
> + unsigned int i;
> + struct device_node *pd;
> +
> + pd = of_parse_phandle(np, "power-domains", 0);
> + if (!pd)
> + return;
> +
> + for (i = 0; i < num_special_pds; i++)
> + if (pd == special_pds[i].pd && type == special_pds[i].type) {
> + of_node_put(pd);
> + return;
> + }
> +
> + if (num_special_pds == ARRAY_SIZE(special_pds)) {
> + pr_warn("Too many special PM domains\n");
> + of_node_put(pd);
> + return;
> + }
> +
> + pr_debug("Special PM domain %s type %d for %s\n", pd->name, type,
> + np->full_name);
> +
> + special_pds[num_special_pds].pd = pd;
> + special_pds[num_special_pds].type = type;
> + num_special_pds++;
> +}
> +
> +static void __init get_special_pds(void)
> +{
> + struct device_node *cpu, *scu;
> +
> + /* PM domains containing CPUs */
> + for_each_node_by_type(cpu, "cpu") {
> + add_special_pd(cpu, PD_CPU);
> +
> + /* SCU, represented by an L2 node */
> + scu = of_parse_phandle(cpu, "next-level-cache", 0);
> + if (scu) {
> + add_special_pd(scu, PD_SCU);
> + of_node_put(scu);
> + }
> + }
> +}
> +
> +static void __init put_special_pds(void)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < num_special_pds; i++)
> + of_node_put(special_pds[i].pd);
> +}
> +
> +static enum pd_types __init pd_type(const struct device_node *pd)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < num_special_pds; i++)
> + if (pd == special_pds[i].pd)
> + return special_pds[i].type;
> +
> + return PD_NORMAL;
> +}
> +
> +static void __init rcar_setup_pm_domain(struct device_node *np,
> + struct rcar_pm_domain *pd)
> +{
> + const char *name = pd->genpd.name;
> +
> + switch (pd_type(np)) {
> + case 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 CPU\n", name);
> + pd->gov = &pm_domain_always_on_gov;
> + pd->busy = true;
> + pd->cpu = true;
> + break;
> +
> + case 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 SCU\n", name);
> + pd->gov = &pm_domain_always_on_gov;
> + pd->busy = true;
> + break;
> +
> + case PD_NORMAL:
> + break;
> + }
> +
> + rcar_init_pm_domain(pd);
> +}
> +
> +static int __init rcar_add_pm_domains(struct device_node *parent,
> + struct generic_pm_domain *genpd_parent,
> + u32 *syscier)
> +{
> + struct device_node *np;
> +
> + for_each_child_of_node(parent, np) {
> + struct rcar_pm_domain *pd;
> + u32 reg[2];
> + int n;
> +
> + if (of_property_read_u32_array(np, "reg", reg,
> + ARRAY_SIZE(reg))) {
> + of_node_put(np);
> + return -EINVAL;
> + }
> +
> + *syscier |= BIT(reg[0]);
> +
> + if (!IS_ENABLED(CONFIG_PM)) {
> + /* Just continue parsing "reg" to update *syscier */
> + rcar_add_pm_domains(np, NULL, syscier);
> + continue;
> + }
> +
> + n = snprintf(NULL, 0, "%s@%u", np->name, reg[0]) + 1;
> +
> + pd = kzalloc(sizeof(*pd) + n, GFP_KERNEL);
> + if (!pd) {
> + of_node_put(np);
> + return -ENOMEM;
> + }
> +
> + snprintf(pd->name, n, "%s@%u", np->name, reg[0]);
> + pd->genpd.name = pd->name;
> + pd->ch.chan_offs = reg[1] & ~31;
> + pd->ch.chan_bit = reg[1] & 31;
> + pd->ch.isr_bit = reg[0];
> +
> + rcar_setup_pm_domain(np, pd);
> + if (genpd_parent)
> + pm_genpd_add_subdomain(genpd_parent, &pd->genpd);
> + of_genpd_add_provider_simple(np, &pd->genpd);
> +
> + rcar_add_pm_domains(np, &pd->genpd, syscier);
> + }
> + return 0;
> +}
> +
> +static const struct of_device_id rcar_sysc_matches[] = {
> + { .compatible = "renesas,r8a7779-sysc", .data = (void *)1 },
> + { .compatible = "renesas,rcar-gen2-sysc", .data = (void *)2 },
> + { .compatible = "renesas,rcar-gen3-sysc", .data = (void *)3 },
How about RCAR_GEN1, RCAR_GEN2 and RCAR_GEN3 macros ?
> + { /* sentinel */ }
> +};
> +
> +static int __init rcar_init_pm_domains(void)
> +{
> + const struct of_device_id *match;
> + struct device_node *np, *pmd;
> + bool scanned = false;
> + void __iomem *base;
> + int ret = 0;
> +
> + for_each_matching_node_and_match(np, rcar_sysc_matches, &match) {
> + u32 syscier = 0;
> +
> + rcar_gen = (uintptr_t)match->data;
> +
> + base = of_iomap(np, 0);
> + if (!base) {
> + pr_warn("%s cannot map reg 0\n", np->full_name);
> + continue;
> + }
> +
> + rcar_sysc_base = base; // FIXME conflicts with rcar_sysc_init()
> +
> + pmd = of_get_child_by_name(np, "pm-domains");
> + if (!pmd) {
> + pr_warn("%s lacks pm-domains node\n", np->full_name);
Shouldn't you call iounmap() here ?
> + continue;
> + }
> +
> + if (!scanned) {
> + /* Find PM domains containing special blocks */
> + get_special_pds();
> + scanned = true;
> + }
> +
> + ret = rcar_add_pm_domains(pmd, NULL, &syscier);
> + of_node_put(pmd);
> + if (ret) {
> + of_node_put(np);
> + break;
> + }
> +
> + /*
> + * Enable all interrupt sources, but do not use interrupt
> + * handler
> + */
> + pr_debug("%s: syscier = 0x%08x\n", np->full_name, syscier);
> + iowrite32(syscier, rcar_sysc_base + SYSCIER);
> + iowrite32(0, rcar_sysc_base + SYSCIMR);
Shouldn't the SYSCIMR bits be set to 1 to mask interrupts ?
> + }
> +
> + put_special_pds();
> +
> + return ret;
> +}
> +
> +core_initcall(rcar_init_pm_domains);
> +#endif /* PM_GENERIC_DOMAINS */
--
Regards,
Laurent Pinchart
More information about the linux-arm-kernel
mailing list