Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 //
0003 // Exynos Generic power domain support.
0004 //
0005 // Copyright (c) 2012 Samsung Electronics Co., Ltd.
0006 //      http://www.samsung.com
0007 //
0008 // Implementation of Exynos specific power domain control which is used in
0009 // conjunction with runtime-pm. Support for both device-tree and non-device-tree
0010 // based power domain support is included.
0011 
0012 #include <linux/io.h>
0013 #include <linux/err.h>
0014 #include <linux/slab.h>
0015 #include <linux/pm_domain.h>
0016 #include <linux/delay.h>
0017 #include <linux/of_address.h>
0018 #include <linux/of_platform.h>
0019 #include <linux/pm_runtime.h>
0020 
0021 struct exynos_pm_domain_config {
0022     /* Value for LOCAL_PWR_CFG and STATUS fields for each domain */
0023     u32 local_pwr_cfg;
0024 };
0025 
0026 /*
0027  * Exynos specific wrapper around the generic power domain
0028  */
0029 struct exynos_pm_domain {
0030     void __iomem *base;
0031     struct generic_pm_domain pd;
0032     u32 local_pwr_cfg;
0033 };
0034 
0035 static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on)
0036 {
0037     struct exynos_pm_domain *pd;
0038     void __iomem *base;
0039     u32 timeout, pwr;
0040     char *op;
0041 
0042     pd = container_of(domain, struct exynos_pm_domain, pd);
0043     base = pd->base;
0044 
0045     pwr = power_on ? pd->local_pwr_cfg : 0;
0046     writel_relaxed(pwr, base);
0047 
0048     /* Wait max 1ms */
0049     timeout = 10;
0050 
0051     while ((readl_relaxed(base + 0x4) & pd->local_pwr_cfg) != pwr) {
0052         if (!timeout) {
0053             op = (power_on) ? "enable" : "disable";
0054             pr_err("Power domain %s %s failed\n", domain->name, op);
0055             return -ETIMEDOUT;
0056         }
0057         timeout--;
0058         cpu_relax();
0059         usleep_range(80, 100);
0060     }
0061 
0062     return 0;
0063 }
0064 
0065 static int exynos_pd_power_on(struct generic_pm_domain *domain)
0066 {
0067     return exynos_pd_power(domain, true);
0068 }
0069 
0070 static int exynos_pd_power_off(struct generic_pm_domain *domain)
0071 {
0072     return exynos_pd_power(domain, false);
0073 }
0074 
0075 static const struct exynos_pm_domain_config exynos4210_cfg = {
0076     .local_pwr_cfg      = 0x7,
0077 };
0078 
0079 static const struct exynos_pm_domain_config exynos5433_cfg = {
0080     .local_pwr_cfg      = 0xf,
0081 };
0082 
0083 static const struct of_device_id exynos_pm_domain_of_match[] = {
0084     {
0085         .compatible = "samsung,exynos4210-pd",
0086         .data = &exynos4210_cfg,
0087     }, {
0088         .compatible = "samsung,exynos5433-pd",
0089         .data = &exynos5433_cfg,
0090     },
0091     { },
0092 };
0093 
0094 static const char *exynos_get_domain_name(struct device_node *node)
0095 {
0096     const char *name;
0097 
0098     if (of_property_read_string(node, "label", &name) < 0)
0099         name = kbasename(node->full_name);
0100     return kstrdup_const(name, GFP_KERNEL);
0101 }
0102 
0103 static int exynos_pd_probe(struct platform_device *pdev)
0104 {
0105     const struct exynos_pm_domain_config *pm_domain_cfg;
0106     struct device *dev = &pdev->dev;
0107     struct device_node *np = dev->of_node;
0108     struct of_phandle_args child, parent;
0109     struct exynos_pm_domain *pd;
0110     int on, ret;
0111 
0112     pm_domain_cfg = of_device_get_match_data(dev);
0113     pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL);
0114     if (!pd)
0115         return -ENOMEM;
0116 
0117     pd->pd.name = exynos_get_domain_name(np);
0118     if (!pd->pd.name)
0119         return -ENOMEM;
0120 
0121     pd->base = of_iomap(np, 0);
0122     if (!pd->base) {
0123         kfree_const(pd->pd.name);
0124         return -ENODEV;
0125     }
0126 
0127     pd->pd.power_off = exynos_pd_power_off;
0128     pd->pd.power_on = exynos_pd_power_on;
0129     pd->local_pwr_cfg = pm_domain_cfg->local_pwr_cfg;
0130 
0131     on = readl_relaxed(pd->base + 0x4) & pd->local_pwr_cfg;
0132 
0133     pm_genpd_init(&pd->pd, NULL, !on);
0134     ret = of_genpd_add_provider_simple(np, &pd->pd);
0135 
0136     if (ret == 0 && of_parse_phandle_with_args(np, "power-domains",
0137                       "#power-domain-cells", 0, &parent) == 0) {
0138         child.np = np;
0139         child.args_count = 0;
0140 
0141         if (of_genpd_add_subdomain(&parent, &child))
0142             pr_warn("%pOF failed to add subdomain: %pOF\n",
0143                 parent.np, child.np);
0144         else
0145             pr_info("%pOF has as child subdomain: %pOF.\n",
0146                 parent.np, child.np);
0147     }
0148 
0149     pm_runtime_enable(dev);
0150     return ret;
0151 }
0152 
0153 static struct platform_driver exynos_pd_driver = {
0154     .probe  = exynos_pd_probe,
0155     .driver = {
0156         .name       = "exynos-pd",
0157         .of_match_table = exynos_pm_domain_of_match,
0158         .suppress_bind_attrs = true,
0159     }
0160 };
0161 
0162 static __init int exynos4_pm_init_power_domain(void)
0163 {
0164     return platform_driver_register(&exynos_pd_driver);
0165 }
0166 core_initcall(exynos4_pm_init_power_domain);