18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci//
38c2ecf20Sopenharmony_ci// Exynos Generic power domain support.
48c2ecf20Sopenharmony_ci//
58c2ecf20Sopenharmony_ci// Copyright (c) 2012 Samsung Electronics Co., Ltd.
68c2ecf20Sopenharmony_ci//		http://www.samsung.com
78c2ecf20Sopenharmony_ci//
88c2ecf20Sopenharmony_ci// Implementation of Exynos specific power domain control which is used in
98c2ecf20Sopenharmony_ci// conjunction with runtime-pm. Support for both device-tree and non-device-tree
108c2ecf20Sopenharmony_ci// based power domain support is included.
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#include <linux/io.h>
138c2ecf20Sopenharmony_ci#include <linux/err.h>
148c2ecf20Sopenharmony_ci#include <linux/slab.h>
158c2ecf20Sopenharmony_ci#include <linux/pm_domain.h>
168c2ecf20Sopenharmony_ci#include <linux/delay.h>
178c2ecf20Sopenharmony_ci#include <linux/of_address.h>
188c2ecf20Sopenharmony_ci#include <linux/of_platform.h>
198c2ecf20Sopenharmony_ci#include <linux/sched.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_cistruct exynos_pm_domain_config {
228c2ecf20Sopenharmony_ci	/* Value for LOCAL_PWR_CFG and STATUS fields for each domain */
238c2ecf20Sopenharmony_ci	u32 local_pwr_cfg;
248c2ecf20Sopenharmony_ci};
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci/*
278c2ecf20Sopenharmony_ci * Exynos specific wrapper around the generic power domain
288c2ecf20Sopenharmony_ci */
298c2ecf20Sopenharmony_cistruct exynos_pm_domain {
308c2ecf20Sopenharmony_ci	void __iomem *base;
318c2ecf20Sopenharmony_ci	bool is_off;
328c2ecf20Sopenharmony_ci	struct generic_pm_domain pd;
338c2ecf20Sopenharmony_ci	u32 local_pwr_cfg;
348c2ecf20Sopenharmony_ci};
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_cistatic int exynos_pd_power(struct generic_pm_domain *domain, bool power_on)
378c2ecf20Sopenharmony_ci{
388c2ecf20Sopenharmony_ci	struct exynos_pm_domain *pd;
398c2ecf20Sopenharmony_ci	void __iomem *base;
408c2ecf20Sopenharmony_ci	u32 timeout, pwr;
418c2ecf20Sopenharmony_ci	char *op;
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci	pd = container_of(domain, struct exynos_pm_domain, pd);
448c2ecf20Sopenharmony_ci	base = pd->base;
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	pwr = power_on ? pd->local_pwr_cfg : 0;
478c2ecf20Sopenharmony_ci	writel_relaxed(pwr, base);
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci	/* Wait max 1ms */
508c2ecf20Sopenharmony_ci	timeout = 10;
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	while ((readl_relaxed(base + 0x4) & pd->local_pwr_cfg) != pwr) {
538c2ecf20Sopenharmony_ci		if (!timeout) {
548c2ecf20Sopenharmony_ci			op = (power_on) ? "enable" : "disable";
558c2ecf20Sopenharmony_ci			pr_err("Power domain %s %s failed\n", domain->name, op);
568c2ecf20Sopenharmony_ci			return -ETIMEDOUT;
578c2ecf20Sopenharmony_ci		}
588c2ecf20Sopenharmony_ci		timeout--;
598c2ecf20Sopenharmony_ci		cpu_relax();
608c2ecf20Sopenharmony_ci		usleep_range(80, 100);
618c2ecf20Sopenharmony_ci	}
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	return 0;
648c2ecf20Sopenharmony_ci}
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_cistatic int exynos_pd_power_on(struct generic_pm_domain *domain)
678c2ecf20Sopenharmony_ci{
688c2ecf20Sopenharmony_ci	return exynos_pd_power(domain, true);
698c2ecf20Sopenharmony_ci}
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_cistatic int exynos_pd_power_off(struct generic_pm_domain *domain)
728c2ecf20Sopenharmony_ci{
738c2ecf20Sopenharmony_ci	return exynos_pd_power(domain, false);
748c2ecf20Sopenharmony_ci}
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_cistatic const struct exynos_pm_domain_config exynos4210_cfg __initconst = {
778c2ecf20Sopenharmony_ci	.local_pwr_cfg		= 0x7,
788c2ecf20Sopenharmony_ci};
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_cistatic const struct exynos_pm_domain_config exynos5433_cfg __initconst = {
818c2ecf20Sopenharmony_ci	.local_pwr_cfg		= 0xf,
828c2ecf20Sopenharmony_ci};
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_cistatic const struct of_device_id exynos_pm_domain_of_match[] __initconst = {
858c2ecf20Sopenharmony_ci	{
868c2ecf20Sopenharmony_ci		.compatible = "samsung,exynos4210-pd",
878c2ecf20Sopenharmony_ci		.data = &exynos4210_cfg,
888c2ecf20Sopenharmony_ci	}, {
898c2ecf20Sopenharmony_ci		.compatible = "samsung,exynos5433-pd",
908c2ecf20Sopenharmony_ci		.data = &exynos5433_cfg,
918c2ecf20Sopenharmony_ci	},
928c2ecf20Sopenharmony_ci	{ },
938c2ecf20Sopenharmony_ci};
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_cistatic __init const char *exynos_get_domain_name(struct device_node *node)
968c2ecf20Sopenharmony_ci{
978c2ecf20Sopenharmony_ci	const char *name;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	if (of_property_read_string(node, "label", &name) < 0)
1008c2ecf20Sopenharmony_ci		name = kbasename(node->full_name);
1018c2ecf20Sopenharmony_ci	return kstrdup_const(name, GFP_KERNEL);
1028c2ecf20Sopenharmony_ci}
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_cistatic __init int exynos4_pm_init_power_domain(void)
1058c2ecf20Sopenharmony_ci{
1068c2ecf20Sopenharmony_ci	struct device_node *np;
1078c2ecf20Sopenharmony_ci	const struct of_device_id *match;
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	for_each_matching_node_and_match(np, exynos_pm_domain_of_match, &match) {
1108c2ecf20Sopenharmony_ci		const struct exynos_pm_domain_config *pm_domain_cfg;
1118c2ecf20Sopenharmony_ci		struct exynos_pm_domain *pd;
1128c2ecf20Sopenharmony_ci		int on;
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci		pm_domain_cfg = match->data;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci		pd = kzalloc(sizeof(*pd), GFP_KERNEL);
1178c2ecf20Sopenharmony_ci		if (!pd) {
1188c2ecf20Sopenharmony_ci			of_node_put(np);
1198c2ecf20Sopenharmony_ci			return -ENOMEM;
1208c2ecf20Sopenharmony_ci		}
1218c2ecf20Sopenharmony_ci		pd->pd.name = exynos_get_domain_name(np);
1228c2ecf20Sopenharmony_ci		if (!pd->pd.name) {
1238c2ecf20Sopenharmony_ci			kfree(pd);
1248c2ecf20Sopenharmony_ci			of_node_put(np);
1258c2ecf20Sopenharmony_ci			return -ENOMEM;
1268c2ecf20Sopenharmony_ci		}
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci		pd->base = of_iomap(np, 0);
1298c2ecf20Sopenharmony_ci		if (!pd->base) {
1308c2ecf20Sopenharmony_ci			pr_warn("%s: failed to map memory\n", __func__);
1318c2ecf20Sopenharmony_ci			kfree_const(pd->pd.name);
1328c2ecf20Sopenharmony_ci			kfree(pd);
1338c2ecf20Sopenharmony_ci			continue;
1348c2ecf20Sopenharmony_ci		}
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci		pd->pd.power_off = exynos_pd_power_off;
1378c2ecf20Sopenharmony_ci		pd->pd.power_on = exynos_pd_power_on;
1388c2ecf20Sopenharmony_ci		pd->local_pwr_cfg = pm_domain_cfg->local_pwr_cfg;
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci		on = readl_relaxed(pd->base + 0x4) & pd->local_pwr_cfg;
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci		pm_genpd_init(&pd->pd, NULL, !on);
1438c2ecf20Sopenharmony_ci		of_genpd_add_provider_simple(np, &pd->pd);
1448c2ecf20Sopenharmony_ci	}
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	/* Assign the child power domains to their parents */
1478c2ecf20Sopenharmony_ci	for_each_matching_node(np, exynos_pm_domain_of_match) {
1488c2ecf20Sopenharmony_ci		struct of_phandle_args child, parent;
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci		child.np = np;
1518c2ecf20Sopenharmony_ci		child.args_count = 0;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci		if (of_parse_phandle_with_args(np, "power-domains",
1548c2ecf20Sopenharmony_ci					       "#power-domain-cells", 0,
1558c2ecf20Sopenharmony_ci					       &parent) != 0)
1568c2ecf20Sopenharmony_ci			continue;
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci		if (of_genpd_add_subdomain(&parent, &child))
1598c2ecf20Sopenharmony_ci			pr_warn("%pOF failed to add subdomain: %pOF\n",
1608c2ecf20Sopenharmony_ci				parent.np, child.np);
1618c2ecf20Sopenharmony_ci		else
1628c2ecf20Sopenharmony_ci			pr_info("%pOF has as child subdomain: %pOF.\n",
1638c2ecf20Sopenharmony_ci				parent.np, child.np);
1648c2ecf20Sopenharmony_ci	}
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	return 0;
1678c2ecf20Sopenharmony_ci}
1688c2ecf20Sopenharmony_cicore_initcall(exynos4_pm_init_power_domain);
169