linux/arch/arm/mach-exynos/pm_domains.c
<<
>>
Prefs
   1/*
   2 * Exynos Generic power domain support.
   3 *
   4 * Copyright (c) 2012 Samsung Electronics Co., Ltd.
   5 *              http://www.samsung.com
   6 *
   7 * Implementation of Exynos specific power domain control which is used in
   8 * conjunction with runtime-pm. Support for both device-tree and non-device-tree
   9 * based power domain support is included.
  10 *
  11 * This program is free software; you can redistribute it and/or modify
  12 * it under the terms of the GNU General Public License version 2 as
  13 * published by the Free Software Foundation.
  14*/
  15
  16#include <linux/io.h>
  17#include <linux/err.h>
  18#include <linux/slab.h>
  19#include <linux/pm_domain.h>
  20#include <linux/clk.h>
  21#include <linux/delay.h>
  22#include <linux/of_address.h>
  23#include <linux/of_platform.h>
  24#include <linux/sched.h>
  25
  26#define INT_LOCAL_PWR_EN        0x7
  27#define MAX_CLK_PER_DOMAIN      4
  28
  29/*
  30 * Exynos specific wrapper around the generic power domain
  31 */
  32struct exynos_pm_domain {
  33        void __iomem *base;
  34        char const *name;
  35        bool is_off;
  36        struct generic_pm_domain pd;
  37        struct clk *oscclk;
  38        struct clk *clk[MAX_CLK_PER_DOMAIN];
  39        struct clk *pclk[MAX_CLK_PER_DOMAIN];
  40        struct clk *asb_clk[MAX_CLK_PER_DOMAIN];
  41};
  42
  43static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on)
  44{
  45        struct exynos_pm_domain *pd;
  46        void __iomem *base;
  47        u32 timeout, pwr;
  48        char *op;
  49        int i;
  50
  51        pd = container_of(domain, struct exynos_pm_domain, pd);
  52        base = pd->base;
  53
  54        for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) {
  55                if (IS_ERR(pd->asb_clk[i]))
  56                        break;
  57                clk_prepare_enable(pd->asb_clk[i]);
  58        }
  59
  60        /* Set oscclk before powering off a domain*/
  61        if (!power_on) {
  62                for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) {
  63                        if (IS_ERR(pd->clk[i]))
  64                                break;
  65                        pd->pclk[i] = clk_get_parent(pd->clk[i]);
  66                        if (clk_set_parent(pd->clk[i], pd->oscclk))
  67                                pr_err("%s: error setting oscclk as parent to clock %d\n",
  68                                                pd->name, i);
  69                }
  70        }
  71
  72        pwr = power_on ? INT_LOCAL_PWR_EN : 0;
  73        __raw_writel(pwr, base);
  74
  75        /* Wait max 1ms */
  76        timeout = 10;
  77
  78        while ((__raw_readl(base + 0x4) & INT_LOCAL_PWR_EN) != pwr) {
  79                if (!timeout) {
  80                        op = (power_on) ? "enable" : "disable";
  81                        pr_err("Power domain %s %s failed\n", domain->name, op);
  82                        return -ETIMEDOUT;
  83                }
  84                timeout--;
  85                cpu_relax();
  86                usleep_range(80, 100);
  87        }
  88
  89        /* Restore clocks after powering on a domain*/
  90        if (power_on) {
  91                for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) {
  92                        if (IS_ERR(pd->clk[i]))
  93                                break;
  94
  95                        if (IS_ERR(pd->pclk[i]))
  96                                continue; /* Skip on first power up */
  97                        if (clk_set_parent(pd->clk[i], pd->pclk[i]))
  98                                pr_err("%s: error setting parent to clock%d\n",
  99                                                pd->name, i);
 100                }
 101        }
 102
 103        for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) {
 104                if (IS_ERR(pd->asb_clk[i]))
 105                        break;
 106                clk_disable_unprepare(pd->asb_clk[i]);
 107        }
 108
 109        return 0;
 110}
 111
 112static int exynos_pd_power_on(struct generic_pm_domain *domain)
 113{
 114        return exynos_pd_power(domain, true);
 115}
 116
 117static int exynos_pd_power_off(struct generic_pm_domain *domain)
 118{
 119        return exynos_pd_power(domain, false);
 120}
 121
 122static __init int exynos4_pm_init_power_domain(void)
 123{
 124        struct device_node *np;
 125
 126        for_each_compatible_node(np, NULL, "samsung,exynos4210-pd") {
 127                struct exynos_pm_domain *pd;
 128                int on, i;
 129
 130                pd = kzalloc(sizeof(*pd), GFP_KERNEL);
 131                if (!pd) {
 132                        pr_err("%s: failed to allocate memory for domain\n",
 133                                        __func__);
 134                        of_node_put(np);
 135                        return -ENOMEM;
 136                }
 137                pd->pd.name = kstrdup_const(strrchr(np->full_name, '/') + 1,
 138                                            GFP_KERNEL);
 139                if (!pd->pd.name) {
 140                        kfree(pd);
 141                        of_node_put(np);
 142                        return -ENOMEM;
 143                }
 144
 145                pd->name = pd->pd.name;
 146                pd->base = of_iomap(np, 0);
 147                if (!pd->base) {
 148                        pr_warn("%s: failed to map memory\n", __func__);
 149                        kfree_const(pd->pd.name);
 150                        kfree(pd);
 151                        continue;
 152                }
 153
 154                pd->pd.power_off = exynos_pd_power_off;
 155                pd->pd.power_on = exynos_pd_power_on;
 156
 157                for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) {
 158                        char clk_name[8];
 159
 160                        snprintf(clk_name, sizeof(clk_name), "asb%d", i);
 161                        pd->asb_clk[i] = of_clk_get_by_name(np, clk_name);
 162                        if (IS_ERR(pd->asb_clk[i]))
 163                                break;
 164                }
 165
 166                pd->oscclk = of_clk_get_by_name(np, "oscclk");
 167                if (IS_ERR(pd->oscclk))
 168                        goto no_clk;
 169
 170                for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) {
 171                        char clk_name[8];
 172
 173                        snprintf(clk_name, sizeof(clk_name), "clk%d", i);
 174                        pd->clk[i] = of_clk_get_by_name(np, clk_name);
 175                        if (IS_ERR(pd->clk[i]))
 176                                break;
 177                        /*
 178                         * Skip setting parent on first power up.
 179                         * The parent at this time may not be useful at all.
 180                         */
 181                        pd->pclk[i] = ERR_PTR(-EINVAL);
 182                }
 183
 184                if (IS_ERR(pd->clk[0]))
 185                        clk_put(pd->oscclk);
 186
 187no_clk:
 188                on = __raw_readl(pd->base + 0x4) & INT_LOCAL_PWR_EN;
 189
 190                pm_genpd_init(&pd->pd, NULL, !on);
 191                of_genpd_add_provider_simple(np, &pd->pd);
 192        }
 193
 194        /* Assign the child power domains to their parents */
 195        for_each_compatible_node(np, NULL, "samsung,exynos4210-pd") {
 196                struct generic_pm_domain *child_domain, *parent_domain;
 197                struct of_phandle_args args;
 198
 199                args.np = np;
 200                args.args_count = 0;
 201                child_domain = of_genpd_get_from_provider(&args);
 202                if (IS_ERR(child_domain))
 203                        continue;
 204
 205                if (of_parse_phandle_with_args(np, "power-domains",
 206                                         "#power-domain-cells", 0, &args) != 0)
 207                        continue;
 208
 209                parent_domain = of_genpd_get_from_provider(&args);
 210                if (IS_ERR(parent_domain))
 211                        continue;
 212
 213                if (pm_genpd_add_subdomain(parent_domain, child_domain))
 214                        pr_warn("%s failed to add subdomain: %s\n",
 215                                parent_domain->name, child_domain->name);
 216                else
 217                        pr_info("%s has as child subdomain: %s.\n",
 218                                parent_domain->name, child_domain->name);
 219        }
 220
 221        return 0;
 222}
 223core_initcall(exynos4_pm_init_power_domain);
 224