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/sched.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 __initconst = {
  77        .local_pwr_cfg          = 0x7,
  78};
  79
  80static const struct exynos_pm_domain_config exynos5433_cfg __initconst = {
  81        .local_pwr_cfg          = 0xf,
  82};
  83
  84static const struct of_device_id exynos_pm_domain_of_match[] __initconst = {
  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 __init 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 __init int exynos4_pm_init_power_domain(void)
 105{
 106        struct device_node *np;
 107        const struct of_device_id *match;
 108
 109        for_each_matching_node_and_match(np, exynos_pm_domain_of_match, &match) {
 110                const struct exynos_pm_domain_config *pm_domain_cfg;
 111                struct exynos_pm_domain *pd;
 112                int on;
 113
 114                pm_domain_cfg = match->data;
 115
 116                pd = kzalloc(sizeof(*pd), GFP_KERNEL);
 117                if (!pd) {
 118                        of_node_put(np);
 119                        return -ENOMEM;
 120                }
 121                pd->pd.name = exynos_get_domain_name(np);
 122                if (!pd->pd.name) {
 123                        kfree(pd);
 124                        of_node_put(np);
 125                        return -ENOMEM;
 126                }
 127
 128                pd->base = of_iomap(np, 0);
 129                if (!pd->base) {
 130                        pr_warn("%s: failed to map memory\n", __func__);
 131                        kfree_const(pd->pd.name);
 132                        kfree(pd);
 133                        continue;
 134                }
 135
 136                pd->pd.power_off = exynos_pd_power_off;
 137                pd->pd.power_on = exynos_pd_power_on;
 138                pd->local_pwr_cfg = pm_domain_cfg->local_pwr_cfg;
 139
 140                on = readl_relaxed(pd->base + 0x4) & pd->local_pwr_cfg;
 141
 142                pm_genpd_init(&pd->pd, NULL, !on);
 143                of_genpd_add_provider_simple(np, &pd->pd);
 144        }
 145
 146        /* Assign the child power domains to their parents */
 147        for_each_matching_node(np, exynos_pm_domain_of_match) {
 148                struct of_phandle_args child, parent;
 149
 150                child.np = np;
 151                child.args_count = 0;
 152
 153                if (of_parse_phandle_with_args(np, "power-domains",
 154                                               "#power-domain-cells", 0,
 155                                               &parent) != 0)
 156                        continue;
 157
 158                if (of_genpd_add_subdomain(&parent, &child))
 159                        pr_warn("%pOF failed to add subdomain: %pOF\n",
 160                                parent.np, child.np);
 161                else
 162                        pr_info("%pOF has as child subdomain: %pOF.\n",
 163                                parent.np, child.np);
 164        }
 165
 166        return 0;
 167}
 168core_initcall(exynos4_pm_init_power_domain);
 169