ARM: EXYNOS: Remove calls to of_genpd_get_from_provider()
[cascardo/linux.git] / drivers / soc / samsung / pm_domains.c
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 MAX_CLK_PER_DOMAIN      4
27
28 struct exynos_pm_domain_config {
29         /* Value for LOCAL_PWR_CFG and STATUS fields for each domain */
30         u32 local_pwr_cfg;
31 };
32
33 /*
34  * Exynos specific wrapper around the generic power domain
35  */
36 struct exynos_pm_domain {
37         void __iomem *base;
38         char const *name;
39         bool is_off;
40         struct generic_pm_domain pd;
41         struct clk *oscclk;
42         struct clk *clk[MAX_CLK_PER_DOMAIN];
43         struct clk *pclk[MAX_CLK_PER_DOMAIN];
44         struct clk *asb_clk[MAX_CLK_PER_DOMAIN];
45         u32 local_pwr_cfg;
46 };
47
48 static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on)
49 {
50         struct exynos_pm_domain *pd;
51         void __iomem *base;
52         u32 timeout, pwr;
53         char *op;
54         int i;
55
56         pd = container_of(domain, struct exynos_pm_domain, pd);
57         base = pd->base;
58
59         for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) {
60                 if (IS_ERR(pd->asb_clk[i]))
61                         break;
62                 clk_prepare_enable(pd->asb_clk[i]);
63         }
64
65         /* Set oscclk before powering off a domain*/
66         if (!power_on) {
67                 for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) {
68                         if (IS_ERR(pd->clk[i]))
69                                 break;
70                         pd->pclk[i] = clk_get_parent(pd->clk[i]);
71                         if (clk_set_parent(pd->clk[i], pd->oscclk))
72                                 pr_err("%s: error setting oscclk as parent to clock %d\n",
73                                                 pd->name, i);
74                 }
75         }
76
77         pwr = power_on ? pd->local_pwr_cfg : 0;
78         writel_relaxed(pwr, base);
79
80         /* Wait max 1ms */
81         timeout = 10;
82
83         while ((readl_relaxed(base + 0x4) & pd->local_pwr_cfg) != pwr) {
84                 if (!timeout) {
85                         op = (power_on) ? "enable" : "disable";
86                         pr_err("Power domain %s %s failed\n", domain->name, op);
87                         return -ETIMEDOUT;
88                 }
89                 timeout--;
90                 cpu_relax();
91                 usleep_range(80, 100);
92         }
93
94         /* Restore clocks after powering on a domain*/
95         if (power_on) {
96                 for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) {
97                         if (IS_ERR(pd->clk[i]))
98                                 break;
99
100                         if (IS_ERR(pd->pclk[i]))
101                                 continue; /* Skip on first power up */
102                         if (clk_set_parent(pd->clk[i], pd->pclk[i]))
103                                 pr_err("%s: error setting parent to clock%d\n",
104                                                 pd->name, i);
105                 }
106         }
107
108         for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) {
109                 if (IS_ERR(pd->asb_clk[i]))
110                         break;
111                 clk_disable_unprepare(pd->asb_clk[i]);
112         }
113
114         return 0;
115 }
116
117 static int exynos_pd_power_on(struct generic_pm_domain *domain)
118 {
119         return exynos_pd_power(domain, true);
120 }
121
122 static int exynos_pd_power_off(struct generic_pm_domain *domain)
123 {
124         return exynos_pd_power(domain, false);
125 }
126
127 static const struct exynos_pm_domain_config exynos4210_cfg __initconst = {
128         .local_pwr_cfg          = 0x7,
129 };
130
131 static const struct of_device_id exynos_pm_domain_of_match[] __initconst = {
132         {
133                 .compatible = "samsung,exynos4210-pd",
134                 .data = &exynos4210_cfg,
135         },
136         { },
137 };
138
139 static __init int exynos4_pm_init_power_domain(void)
140 {
141         struct device_node *np;
142         const struct of_device_id *match;
143
144         for_each_matching_node_and_match(np, exynos_pm_domain_of_match, &match) {
145                 const struct exynos_pm_domain_config *pm_domain_cfg;
146                 struct exynos_pm_domain *pd;
147                 int on, i;
148
149                 pm_domain_cfg = match->data;
150
151                 pd = kzalloc(sizeof(*pd), GFP_KERNEL);
152                 if (!pd) {
153                         pr_err("%s: failed to allocate memory for domain\n",
154                                         __func__);
155                         of_node_put(np);
156                         return -ENOMEM;
157                 }
158                 pd->pd.name = kstrdup_const(strrchr(np->full_name, '/') + 1,
159                                             GFP_KERNEL);
160                 if (!pd->pd.name) {
161                         kfree(pd);
162                         of_node_put(np);
163                         return -ENOMEM;
164                 }
165
166                 pd->name = pd->pd.name;
167                 pd->base = of_iomap(np, 0);
168                 if (!pd->base) {
169                         pr_warn("%s: failed to map memory\n", __func__);
170                         kfree_const(pd->pd.name);
171                         kfree(pd);
172                         continue;
173                 }
174
175                 pd->pd.power_off = exynos_pd_power_off;
176                 pd->pd.power_on = exynos_pd_power_on;
177                 pd->local_pwr_cfg = pm_domain_cfg->local_pwr_cfg;
178
179                 for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) {
180                         char clk_name[8];
181
182                         snprintf(clk_name, sizeof(clk_name), "asb%d", i);
183                         pd->asb_clk[i] = of_clk_get_by_name(np, clk_name);
184                         if (IS_ERR(pd->asb_clk[i]))
185                                 break;
186                 }
187
188                 pd->oscclk = of_clk_get_by_name(np, "oscclk");
189                 if (IS_ERR(pd->oscclk))
190                         goto no_clk;
191
192                 for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) {
193                         char clk_name[8];
194
195                         snprintf(clk_name, sizeof(clk_name), "clk%d", i);
196                         pd->clk[i] = of_clk_get_by_name(np, clk_name);
197                         if (IS_ERR(pd->clk[i]))
198                                 break;
199                         /*
200                          * Skip setting parent on first power up.
201                          * The parent at this time may not be useful at all.
202                          */
203                         pd->pclk[i] = ERR_PTR(-EINVAL);
204                 }
205
206                 if (IS_ERR(pd->clk[0]))
207                         clk_put(pd->oscclk);
208
209 no_clk:
210                 on = readl_relaxed(pd->base + 0x4) & pd->local_pwr_cfg;
211
212                 pm_genpd_init(&pd->pd, NULL, !on);
213                 of_genpd_add_provider_simple(np, &pd->pd);
214         }
215
216         /* Assign the child power domains to their parents */
217         for_each_matching_node(np, exynos_pm_domain_of_match) {
218                 struct of_phandle_args child, parent;
219
220                 child.np = np;
221                 child.args_count = 0;
222
223                 if (of_parse_phandle_with_args(np, "power-domains",
224                                                "#power-domain-cells", 0,
225                                                &parent) != 0)
226                         continue;
227
228                 if (of_genpd_add_subdomain(&parent, &child))
229                         pr_warn("%s failed to add subdomain: %s\n",
230                                 parent.np->name, child.np->name);
231                 else
232                         pr_info("%s has as child subdomain: %s.\n",
233                                 parent.np->name, child.np->name);
234         }
235
236         return 0;
237 }
238 core_initcall(exynos4_pm_init_power_domain);