162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * rmobile power management support
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2012  Renesas Solutions Corp.
662306a36Sopenharmony_ci * Copyright (C) 2012  Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
762306a36Sopenharmony_ci * Copyright (C) 2014  Glider bvba
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * based on pm-sh7372.c
1062306a36Sopenharmony_ci *  Copyright (C) 2011 Magnus Damm
1162306a36Sopenharmony_ci */
1262306a36Sopenharmony_ci#include <linux/clk/renesas.h>
1362306a36Sopenharmony_ci#include <linux/console.h>
1462306a36Sopenharmony_ci#include <linux/delay.h>
1562306a36Sopenharmony_ci#include <linux/io.h>
1662306a36Sopenharmony_ci#include <linux/iopoll.h>
1762306a36Sopenharmony_ci#include <linux/of.h>
1862306a36Sopenharmony_ci#include <linux/of_address.h>
1962306a36Sopenharmony_ci#include <linux/pm.h>
2062306a36Sopenharmony_ci#include <linux/pm_clock.h>
2162306a36Sopenharmony_ci#include <linux/pm_domain.h>
2262306a36Sopenharmony_ci#include <linux/slab.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci/* SYSC */
2562306a36Sopenharmony_ci#define SPDCR		0x08	/* SYS Power Down Control Register */
2662306a36Sopenharmony_ci#define SWUCR		0x14	/* SYS Wakeup Control Register */
2762306a36Sopenharmony_ci#define PSTR		0x80	/* Power Status Register */
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#define PSTR_RETRIES	100
3062306a36Sopenharmony_ci#define PSTR_DELAY_US	10
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistruct rmobile_pm_domain {
3362306a36Sopenharmony_ci	struct generic_pm_domain genpd;
3462306a36Sopenharmony_ci	struct dev_power_governor *gov;
3562306a36Sopenharmony_ci	int (*suspend)(void);
3662306a36Sopenharmony_ci	void __iomem *base;
3762306a36Sopenharmony_ci	unsigned int bit_shift;
3862306a36Sopenharmony_ci};
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistatic inline
4162306a36Sopenharmony_cistruct rmobile_pm_domain *to_rmobile_pd(struct generic_pm_domain *d)
4262306a36Sopenharmony_ci{
4362306a36Sopenharmony_ci	return container_of(d, struct rmobile_pm_domain, genpd);
4462306a36Sopenharmony_ci}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cistatic int rmobile_pd_power_down(struct generic_pm_domain *genpd)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	struct rmobile_pm_domain *rmobile_pd = to_rmobile_pd(genpd);
4962306a36Sopenharmony_ci	unsigned int mask = BIT(rmobile_pd->bit_shift);
5062306a36Sopenharmony_ci	u32 val;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	if (rmobile_pd->suspend) {
5362306a36Sopenharmony_ci		int ret = rmobile_pd->suspend();
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci		if (ret)
5662306a36Sopenharmony_ci			return ret;
5762306a36Sopenharmony_ci	}
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	if (readl(rmobile_pd->base + PSTR) & mask) {
6062306a36Sopenharmony_ci		writel(mask, rmobile_pd->base + SPDCR);
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci		readl_poll_timeout_atomic(rmobile_pd->base + SPDCR, val,
6362306a36Sopenharmony_ci					  !(val & mask), 0, PSTR_RETRIES);
6462306a36Sopenharmony_ci	}
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	pr_debug("%s: Power off, 0x%08x -> PSTR = 0x%08x\n", genpd->name, mask,
6762306a36Sopenharmony_ci		 readl(rmobile_pd->base + PSTR));
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	return 0;
7062306a36Sopenharmony_ci}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cistatic int __rmobile_pd_power_up(struct rmobile_pm_domain *rmobile_pd)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	unsigned int val, mask = BIT(rmobile_pd->bit_shift);
7562306a36Sopenharmony_ci	int ret = 0;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	if (readl(rmobile_pd->base + PSTR) & mask)
7862306a36Sopenharmony_ci		return ret;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	writel(mask, rmobile_pd->base + SWUCR);
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	ret = readl_poll_timeout_atomic(rmobile_pd->base + SWUCR, val,
8362306a36Sopenharmony_ci					(val & mask), PSTR_DELAY_US,
8462306a36Sopenharmony_ci					PSTR_RETRIES * PSTR_DELAY_US);
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	pr_debug("%s: Power on, 0x%08x -> PSTR = 0x%08x\n",
8762306a36Sopenharmony_ci		 rmobile_pd->genpd.name, mask,
8862306a36Sopenharmony_ci		 readl(rmobile_pd->base + PSTR));
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	return ret;
9162306a36Sopenharmony_ci}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cistatic int rmobile_pd_power_up(struct generic_pm_domain *genpd)
9462306a36Sopenharmony_ci{
9562306a36Sopenharmony_ci	return __rmobile_pd_power_up(to_rmobile_pd(genpd));
9662306a36Sopenharmony_ci}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_cistatic void rmobile_init_pm_domain(struct rmobile_pm_domain *rmobile_pd)
9962306a36Sopenharmony_ci{
10062306a36Sopenharmony_ci	struct generic_pm_domain *genpd = &rmobile_pd->genpd;
10162306a36Sopenharmony_ci	struct dev_power_governor *gov = rmobile_pd->gov;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	genpd->flags |= GENPD_FLAG_PM_CLK | GENPD_FLAG_ACTIVE_WAKEUP;
10462306a36Sopenharmony_ci	genpd->attach_dev = cpg_mstp_attach_dev;
10562306a36Sopenharmony_ci	genpd->detach_dev = cpg_mstp_detach_dev;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	if (!(genpd->flags & GENPD_FLAG_ALWAYS_ON)) {
10862306a36Sopenharmony_ci		genpd->power_off = rmobile_pd_power_down;
10962306a36Sopenharmony_ci		genpd->power_on = rmobile_pd_power_up;
11062306a36Sopenharmony_ci		__rmobile_pd_power_up(rmobile_pd);
11162306a36Sopenharmony_ci	}
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	pm_genpd_init(genpd, gov ? : &simple_qos_governor, false);
11462306a36Sopenharmony_ci}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic int rmobile_pd_suspend_console(void)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	/*
11962306a36Sopenharmony_ci	 * Serial consoles make use of SCIF hardware located in this domain,
12062306a36Sopenharmony_ci	 * hence keep the power domain on if "no_console_suspend" is set.
12162306a36Sopenharmony_ci	 */
12262306a36Sopenharmony_ci	return console_suspend_enabled ? 0 : -EBUSY;
12362306a36Sopenharmony_ci}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_cienum pd_types {
12662306a36Sopenharmony_ci	PD_NORMAL,
12762306a36Sopenharmony_ci	PD_CPU,
12862306a36Sopenharmony_ci	PD_CONSOLE,
12962306a36Sopenharmony_ci	PD_DEBUG,
13062306a36Sopenharmony_ci	PD_MEMCTL,
13162306a36Sopenharmony_ci};
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci#define MAX_NUM_SPECIAL_PDS	16
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_cistatic struct special_pd {
13662306a36Sopenharmony_ci	struct device_node *pd;
13762306a36Sopenharmony_ci	enum pd_types type;
13862306a36Sopenharmony_ci} special_pds[MAX_NUM_SPECIAL_PDS] __initdata;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_cistatic unsigned int num_special_pds __initdata;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_cistatic const struct of_device_id special_ids[] __initconst = {
14362306a36Sopenharmony_ci	{ .compatible = "arm,coresight-etm3x", .data = (void *)PD_DEBUG },
14462306a36Sopenharmony_ci	{ .compatible = "renesas,dbsc-r8a73a4", .data = (void *)PD_MEMCTL, },
14562306a36Sopenharmony_ci	{ .compatible = "renesas,dbsc3-r8a7740", .data = (void *)PD_MEMCTL, },
14662306a36Sopenharmony_ci	{ .compatible = "renesas,sbsc-sh73a0", .data = (void *)PD_MEMCTL, },
14762306a36Sopenharmony_ci	{ /* sentinel */ },
14862306a36Sopenharmony_ci};
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_cistatic void __init add_special_pd(struct device_node *np, enum pd_types type)
15162306a36Sopenharmony_ci{
15262306a36Sopenharmony_ci	unsigned int i;
15362306a36Sopenharmony_ci	struct device_node *pd;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	pd = of_parse_phandle(np, "power-domains", 0);
15662306a36Sopenharmony_ci	if (!pd)
15762306a36Sopenharmony_ci		return;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	for (i = 0; i < num_special_pds; i++)
16062306a36Sopenharmony_ci		if (pd == special_pds[i].pd && type == special_pds[i].type) {
16162306a36Sopenharmony_ci			of_node_put(pd);
16262306a36Sopenharmony_ci			return;
16362306a36Sopenharmony_ci		}
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	if (num_special_pds == ARRAY_SIZE(special_pds)) {
16662306a36Sopenharmony_ci		pr_warn("Too many special PM domains\n");
16762306a36Sopenharmony_ci		of_node_put(pd);
16862306a36Sopenharmony_ci		return;
16962306a36Sopenharmony_ci	}
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	pr_debug("Special PM domain %pOFn type %d for %pOF\n", pd, type, np);
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	special_pds[num_special_pds].pd = pd;
17462306a36Sopenharmony_ci	special_pds[num_special_pds].type = type;
17562306a36Sopenharmony_ci	num_special_pds++;
17662306a36Sopenharmony_ci}
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_cistatic void __init get_special_pds(void)
17962306a36Sopenharmony_ci{
18062306a36Sopenharmony_ci	struct device_node *np;
18162306a36Sopenharmony_ci	const struct of_device_id *id;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	/* PM domains containing CPUs */
18462306a36Sopenharmony_ci	for_each_of_cpu_node(np)
18562306a36Sopenharmony_ci		add_special_pd(np, PD_CPU);
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	/* PM domain containing console */
18862306a36Sopenharmony_ci	if (of_stdout)
18962306a36Sopenharmony_ci		add_special_pd(of_stdout, PD_CONSOLE);
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	/* PM domains containing other special devices */
19262306a36Sopenharmony_ci	for_each_matching_node_and_match(np, special_ids, &id)
19362306a36Sopenharmony_ci		add_special_pd(np, (enum pd_types)id->data);
19462306a36Sopenharmony_ci}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_cistatic void __init put_special_pds(void)
19762306a36Sopenharmony_ci{
19862306a36Sopenharmony_ci	unsigned int i;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	for (i = 0; i < num_special_pds; i++)
20162306a36Sopenharmony_ci		of_node_put(special_pds[i].pd);
20262306a36Sopenharmony_ci}
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_cistatic enum pd_types __init pd_type(const struct device_node *pd)
20562306a36Sopenharmony_ci{
20662306a36Sopenharmony_ci	unsigned int i;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	for (i = 0; i < num_special_pds; i++)
20962306a36Sopenharmony_ci		if (pd == special_pds[i].pd)
21062306a36Sopenharmony_ci			return special_pds[i].type;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	return PD_NORMAL;
21362306a36Sopenharmony_ci}
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_cistatic void __init rmobile_setup_pm_domain(struct device_node *np,
21662306a36Sopenharmony_ci					   struct rmobile_pm_domain *pd)
21762306a36Sopenharmony_ci{
21862306a36Sopenharmony_ci	const char *name = pd->genpd.name;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	switch (pd_type(np)) {
22162306a36Sopenharmony_ci	case PD_CPU:
22262306a36Sopenharmony_ci		/*
22362306a36Sopenharmony_ci		 * This domain contains the CPU core and therefore it should
22462306a36Sopenharmony_ci		 * only be turned off if the CPU is not in use.
22562306a36Sopenharmony_ci		 */
22662306a36Sopenharmony_ci		pr_debug("PM domain %s contains CPU\n", name);
22762306a36Sopenharmony_ci		pd->genpd.flags |= GENPD_FLAG_ALWAYS_ON;
22862306a36Sopenharmony_ci		break;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	case PD_CONSOLE:
23162306a36Sopenharmony_ci		pr_debug("PM domain %s contains serial console\n", name);
23262306a36Sopenharmony_ci		pd->gov = &pm_domain_always_on_gov;
23362306a36Sopenharmony_ci		pd->suspend = rmobile_pd_suspend_console;
23462306a36Sopenharmony_ci		break;
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	case PD_DEBUG:
23762306a36Sopenharmony_ci		/*
23862306a36Sopenharmony_ci		 * This domain contains the Coresight-ETM hardware block and
23962306a36Sopenharmony_ci		 * therefore it should only be turned off if the debug module
24062306a36Sopenharmony_ci		 * is not in use.
24162306a36Sopenharmony_ci		 */
24262306a36Sopenharmony_ci		pr_debug("PM domain %s contains Coresight-ETM\n", name);
24362306a36Sopenharmony_ci		pd->genpd.flags |= GENPD_FLAG_ALWAYS_ON;
24462306a36Sopenharmony_ci		break;
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	case PD_MEMCTL:
24762306a36Sopenharmony_ci		/*
24862306a36Sopenharmony_ci		 * This domain contains a memory-controller and therefore it
24962306a36Sopenharmony_ci		 * should only be turned off if memory is not in use.
25062306a36Sopenharmony_ci		 */
25162306a36Sopenharmony_ci		pr_debug("PM domain %s contains MEMCTL\n", name);
25262306a36Sopenharmony_ci		pd->genpd.flags |= GENPD_FLAG_ALWAYS_ON;
25362306a36Sopenharmony_ci		break;
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	case PD_NORMAL:
25662306a36Sopenharmony_ci		if (pd->bit_shift == ~0) {
25762306a36Sopenharmony_ci			/* Top-level always-on domain */
25862306a36Sopenharmony_ci			pr_debug("PM domain %s is always-on domain\n", name);
25962306a36Sopenharmony_ci			pd->genpd.flags |= GENPD_FLAG_ALWAYS_ON;
26062306a36Sopenharmony_ci		}
26162306a36Sopenharmony_ci		break;
26262306a36Sopenharmony_ci	}
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	rmobile_init_pm_domain(pd);
26562306a36Sopenharmony_ci}
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_cistatic int __init rmobile_add_pm_domains(void __iomem *base,
26862306a36Sopenharmony_ci					 struct device_node *parent,
26962306a36Sopenharmony_ci					 struct generic_pm_domain *genpd_parent)
27062306a36Sopenharmony_ci{
27162306a36Sopenharmony_ci	struct device_node *np;
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	for_each_child_of_node(parent, np) {
27462306a36Sopenharmony_ci		struct rmobile_pm_domain *pd;
27562306a36Sopenharmony_ci		u32 idx = ~0;
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci		if (of_property_read_u32(np, "reg", &idx)) {
27862306a36Sopenharmony_ci			/* always-on domain */
27962306a36Sopenharmony_ci		}
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci		pd = kzalloc(sizeof(*pd), GFP_KERNEL);
28262306a36Sopenharmony_ci		if (!pd) {
28362306a36Sopenharmony_ci			of_node_put(np);
28462306a36Sopenharmony_ci			return -ENOMEM;
28562306a36Sopenharmony_ci		}
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci		pd->genpd.name = np->name;
28862306a36Sopenharmony_ci		pd->base = base;
28962306a36Sopenharmony_ci		pd->bit_shift = idx;
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci		rmobile_setup_pm_domain(np, pd);
29262306a36Sopenharmony_ci		if (genpd_parent)
29362306a36Sopenharmony_ci			pm_genpd_add_subdomain(genpd_parent, &pd->genpd);
29462306a36Sopenharmony_ci		of_genpd_add_provider_simple(np, &pd->genpd);
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci		rmobile_add_pm_domains(base, np, &pd->genpd);
29762306a36Sopenharmony_ci	}
29862306a36Sopenharmony_ci	return 0;
29962306a36Sopenharmony_ci}
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_cistatic int __init rmobile_init_pm_domains(void)
30262306a36Sopenharmony_ci{
30362306a36Sopenharmony_ci	struct device_node *np, *pmd;
30462306a36Sopenharmony_ci	bool scanned = false;
30562306a36Sopenharmony_ci	void __iomem *base;
30662306a36Sopenharmony_ci	int ret = 0;
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	for_each_compatible_node(np, NULL, "renesas,sysc-rmobile") {
30962306a36Sopenharmony_ci		base = of_iomap(np, 0);
31062306a36Sopenharmony_ci		if (!base) {
31162306a36Sopenharmony_ci			pr_warn("%pOF cannot map reg 0\n", np);
31262306a36Sopenharmony_ci			continue;
31362306a36Sopenharmony_ci		}
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci		pmd = of_get_child_by_name(np, "pm-domains");
31662306a36Sopenharmony_ci		if (!pmd) {
31762306a36Sopenharmony_ci			iounmap(base);
31862306a36Sopenharmony_ci			pr_warn("%pOF lacks pm-domains node\n", np);
31962306a36Sopenharmony_ci			continue;
32062306a36Sopenharmony_ci		}
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci		if (!scanned) {
32362306a36Sopenharmony_ci			/* Find PM domains containing special blocks */
32462306a36Sopenharmony_ci			get_special_pds();
32562306a36Sopenharmony_ci			scanned = true;
32662306a36Sopenharmony_ci		}
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci		ret = rmobile_add_pm_domains(base, pmd, NULL);
32962306a36Sopenharmony_ci		of_node_put(pmd);
33062306a36Sopenharmony_ci		if (ret) {
33162306a36Sopenharmony_ci			of_node_put(np);
33262306a36Sopenharmony_ci			break;
33362306a36Sopenharmony_ci		}
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci		fwnode_dev_initialized(of_fwnode_handle(np), true);
33662306a36Sopenharmony_ci	}
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ci	put_special_pds();
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci	return ret;
34162306a36Sopenharmony_ci}
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_cicore_initcall(rmobile_init_pm_domains);
344