forked from Minki/linux
c09a3e6c97
When Exynos power domain driver was introduced, the only way to ensure that power domains will be instantiated before the devices which belongs to them was to initialize them early enough, before the devices are instantiated in the system. This in turn required not to use any platform device infrastructure at all, as there have been no way to ensure proper probe order between devices. This has been finally changed and upcomming patch "driver core: Set fw_devlink=on by default" ensures that each device will be probbed only when its resource providers are ready. This allows to convert Exynos power domain driver to regular platform driver. This is also required by the mentioned commit to enable probing any device which belongs to the Exynos power domains, as otherwise the core won't notice that the power domains are in fact available. Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com> Link: https://lore.kernel.org/r/20210113110320.13149-1-m.szyprowski@samsung.com Signed-off-by: Krzysztof Kozlowski <krzk@kernel.org>
168 lines
3.9 KiB
C
168 lines
3.9 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
//
|
|
// Exynos Generic power domain support.
|
|
//
|
|
// Copyright (c) 2012 Samsung Electronics Co., Ltd.
|
|
// http://www.samsung.com
|
|
//
|
|
// Implementation of Exynos specific power domain control which is used in
|
|
// conjunction with runtime-pm. Support for both device-tree and non-device-tree
|
|
// based power domain support is included.
|
|
|
|
#include <linux/io.h>
|
|
#include <linux/err.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/pm_domain.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/pm_runtime.h>
|
|
|
|
struct exynos_pm_domain_config {
|
|
/* Value for LOCAL_PWR_CFG and STATUS fields for each domain */
|
|
u32 local_pwr_cfg;
|
|
};
|
|
|
|
/*
|
|
* Exynos specific wrapper around the generic power domain
|
|
*/
|
|
struct exynos_pm_domain {
|
|
void __iomem *base;
|
|
bool is_off;
|
|
struct generic_pm_domain pd;
|
|
u32 local_pwr_cfg;
|
|
};
|
|
|
|
static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on)
|
|
{
|
|
struct exynos_pm_domain *pd;
|
|
void __iomem *base;
|
|
u32 timeout, pwr;
|
|
char *op;
|
|
|
|
pd = container_of(domain, struct exynos_pm_domain, pd);
|
|
base = pd->base;
|
|
|
|
pwr = power_on ? pd->local_pwr_cfg : 0;
|
|
writel_relaxed(pwr, base);
|
|
|
|
/* Wait max 1ms */
|
|
timeout = 10;
|
|
|
|
while ((readl_relaxed(base + 0x4) & pd->local_pwr_cfg) != pwr) {
|
|
if (!timeout) {
|
|
op = (power_on) ? "enable" : "disable";
|
|
pr_err("Power domain %s %s failed\n", domain->name, op);
|
|
return -ETIMEDOUT;
|
|
}
|
|
timeout--;
|
|
cpu_relax();
|
|
usleep_range(80, 100);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int exynos_pd_power_on(struct generic_pm_domain *domain)
|
|
{
|
|
return exynos_pd_power(domain, true);
|
|
}
|
|
|
|
static int exynos_pd_power_off(struct generic_pm_domain *domain)
|
|
{
|
|
return exynos_pd_power(domain, false);
|
|
}
|
|
|
|
static const struct exynos_pm_domain_config exynos4210_cfg = {
|
|
.local_pwr_cfg = 0x7,
|
|
};
|
|
|
|
static const struct exynos_pm_domain_config exynos5433_cfg = {
|
|
.local_pwr_cfg = 0xf,
|
|
};
|
|
|
|
static const struct of_device_id exynos_pm_domain_of_match[] = {
|
|
{
|
|
.compatible = "samsung,exynos4210-pd",
|
|
.data = &exynos4210_cfg,
|
|
}, {
|
|
.compatible = "samsung,exynos5433-pd",
|
|
.data = &exynos5433_cfg,
|
|
},
|
|
{ },
|
|
};
|
|
|
|
static const char *exynos_get_domain_name(struct device_node *node)
|
|
{
|
|
const char *name;
|
|
|
|
if (of_property_read_string(node, "label", &name) < 0)
|
|
name = kbasename(node->full_name);
|
|
return kstrdup_const(name, GFP_KERNEL);
|
|
}
|
|
|
|
static int exynos_pd_probe(struct platform_device *pdev)
|
|
{
|
|
const struct exynos_pm_domain_config *pm_domain_cfg;
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *np = dev->of_node;
|
|
struct of_phandle_args child, parent;
|
|
struct exynos_pm_domain *pd;
|
|
int on, ret;
|
|
|
|
pm_domain_cfg = of_device_get_match_data(dev);
|
|
pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL);
|
|
if (!pd)
|
|
return -ENOMEM;
|
|
|
|
pd->pd.name = exynos_get_domain_name(np);
|
|
if (!pd->pd.name)
|
|
return -ENOMEM;
|
|
|
|
pd->base = of_iomap(np, 0);
|
|
if (!pd->base) {
|
|
kfree_const(pd->pd.name);
|
|
return -ENODEV;
|
|
}
|
|
|
|
pd->pd.power_off = exynos_pd_power_off;
|
|
pd->pd.power_on = exynos_pd_power_on;
|
|
pd->local_pwr_cfg = pm_domain_cfg->local_pwr_cfg;
|
|
|
|
on = readl_relaxed(pd->base + 0x4) & pd->local_pwr_cfg;
|
|
|
|
pm_genpd_init(&pd->pd, NULL, !on);
|
|
ret = of_genpd_add_provider_simple(np, &pd->pd);
|
|
|
|
if (ret == 0 && of_parse_phandle_with_args(np, "power-domains",
|
|
"#power-domain-cells", 0, &parent) == 0) {
|
|
child.np = np;
|
|
child.args_count = 0;
|
|
|
|
if (of_genpd_add_subdomain(&parent, &child))
|
|
pr_warn("%pOF failed to add subdomain: %pOF\n",
|
|
parent.np, child.np);
|
|
else
|
|
pr_info("%pOF has as child subdomain: %pOF.\n",
|
|
parent.np, child.np);
|
|
}
|
|
|
|
pm_runtime_enable(dev);
|
|
return ret;
|
|
}
|
|
|
|
static struct platform_driver exynos_pd_driver = {
|
|
.probe = exynos_pd_probe,
|
|
.driver = {
|
|
.name = "exynos-pd",
|
|
.of_match_table = exynos_pm_domain_of_match,
|
|
.suppress_bind_attrs = true,
|
|
}
|
|
};
|
|
|
|
static __init int exynos4_pm_init_power_domain(void)
|
|
{
|
|
return platform_driver_register(&exynos_pd_driver);
|
|
}
|
|
core_initcall(exynos4_pm_init_power_domain);
|