linux/drivers/soc/samsung/pm_domains.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2//
   3// Exynos Generic power domain support.
   4//
   5// Copyright (c) 2012 Samsung Electronics Co., Ltd.
   6//              http://www.samsung.com
   7//
   8// Implementation of Exynos specific power domain control which is used in
   9// conjunction with runtime-pm. Support for both device-tree and non-device-tree
  10// based power domain support is included.
  11
  12#include <linux/io.h>
  13#include <linux/err.h>
  14#include <linux/slab.h>
  15#include <linux/pm_domain.h>
  16#include <linux/delay.h>
  17#include <linux/of_address.h>
  18#include <linux/of_platform.h>
  19#include <linux/pm_runtime.h>
  20
  21struct exynos_pm_domain_config {
  22        /* Value for LOCAL_PWR_CFG and STATUS fields for each domain */
  23        u32 local_pwr_cfg;
  24};
  25
  26/*
  27 * Exynos specific wrapper around the generic power domain
  28 */
  29struct exynos_pm_domain {
  30        void __iomem *base;
  31        bool is_off;
  32        struct generic_pm_domain pd;
  33        u32 local_pwr_cfg;
  34};
  35
  36static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on)
  37{
  38        struct exynos_pm_domain *pd;
  39        void __iomem *base;
  40        u32 timeout, pwr;
  41        char *op;
  42
  43        pd = container_of(domain, struct exynos_pm_domain, pd);
  44        base = pd->base;
  45
  46        pwr = power_on ? pd->local_pwr_cfg : 0;
  47        writel_relaxed(pwr, base);
  48
  49        /* Wait max 1ms */
  50        timeout = 10;
  51
  52        while ((readl_relaxed(base + 0x4) & pd->local_pwr_cfg) != pwr) {
  53                if (!timeout) {
  54                        op = (power_on) ? "enable" : "disable";
  55                        pr_err("Power domain %s %s failed\n", domain->name, op);
  56                        return -ETIMEDOUT;
  57                }
  58                timeout--;
  59                cpu_relax();
  60                usleep_range(80, 100);
  61        }
  62
  63        return 0;
  64}
  65
  66static int exynos_pd_power_on(struct generic_pm_domain *domain)
  67{
  68        return exynos_pd_power(domain, true);
  69}
  70
  71static int exynos_pd_power_off(struct generic_pm_domain *domain)
  72{
  73        return exynos_pd_power(domain, false);
  74}
  75
  76static const struct exynos_pm_domain_config exynos4210_cfg = {
  77        .local_pwr_cfg          = 0x7,
  78};
  79
  80static const struct exynos_pm_domain_config exynos5433_cfg = {
  81        .local_pwr_cfg          = 0xf,
  82};
  83
  84static const struct of_device_id exynos_pm_domain_of_match[] = {
  85        {
  86                .compatible = "samsung,exynos4210-pd",
  87                .data = &exynos4210_cfg,
  88        }, {
  89                .compatible = "samsung,exynos5433-pd",
  90                .data = &exynos5433_cfg,
  91        },
  92        { },
  93};
  94
  95static const char *exynos_get_domain_name(struct device_node *node)
  96{
  97        const char *name;
  98
  99        if (of_property_read_string(node, "label", &name) < 0)
 100                name = kbasename(node->full_name);
 101        return kstrdup_const(name, GFP_KERNEL);
 102}
 103
 104static int exynos_pd_probe(struct platform_device *pdev)
 105{
 106        const struct exynos_pm_domain_config *pm_domain_cfg;
 107        struct device *dev = &pdev->dev;
 108        struct device_node *np = dev->of_node;
 109        struct of_phandle_args child, parent;
 110        struct exynos_pm_domain *pd;
 111        int on, ret;
 112
 113        pm_domain_cfg = of_device_get_match_data(dev);
 114        pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL);
 115        if (!pd)
 116                return -ENOMEM;
 117
 118        pd->pd.name = exynos_get_domain_name(np);
 119        if (!pd->pd.name)
 120                return -ENOMEM;
 121
 122        pd->base = of_iomap(np, 0);
 123        if (!pd->base) {
 124                kfree_const(pd->pd.name);
 125                return -ENODEV;
 126        }
 127
 128        pd->pd.power_off = exynos_pd_power_off;
 129        pd->pd.power_on = exynos_pd_power_on;
 130        pd->local_pwr_cfg = pm_domain_cfg->local_pwr_cfg;
 131
 132        on = readl_relaxed(pd->base + 0x4) & pd->local_pwr_cfg;
 133
 134        pm_genpd_init(&pd->pd, NULL, !on);
 135        ret = of_genpd_add_provider_simple(np, &pd->pd);
 136
 137        if (ret == 0 && of_parse_phandle_with_args(np, "power-domains",
 138                                      "#power-domain-cells", 0, &parent) == 0) {
 139                child.np = np;
 140                child.args_count = 0;
 141
 142                if (of_genpd_add_subdomain(&parent, &child))
 143                        pr_warn("%pOF failed to add subdomain: %pOF\n",
 144                                parent.np, child.np);
 145                else
 146                        pr_info("%pOF has as child subdomain: %pOF.\n",
 147                                parent.np, child.np);
 148        }
 149
 150        pm_runtime_enable(dev);
 151        return ret;
 152}
 153
 154static struct platform_driver exynos_pd_driver = {
 155        .probe  = exynos_pd_probe,
 156        .driver = {
 157                .name           = "exynos-pd",
 158                .of_match_table = exynos_pm_domain_of_match,
 159                .suppress_bind_attrs = true,
 160        }
 161};
 162
 163static __init int exynos4_pm_init_power_domain(void)
 164{
 165        return platform_driver_register(&exynos_pd_driver);
 166}
 167core_initcall(exynos4_pm_init_power_domain);
 168