aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCaesar Wang <wxt@rock-chips.com>2015-09-08 02:18:22 -0400
committerHeiko Stuebner <heiko@sntech.de>2015-10-06 03:53:28 -0400
commit7c696693a4f54d12714738b45aee3e4302884ade (patch)
treeb642fa5b9a6d3929927cda71a5c1d09adffb74a5
parentd64180fd5921be9db9cf63cc12c11086c444c0d0 (diff)
soc: rockchip: power-domain: Add power domain driver
This driver is found on RK3288 SoCs. In order to meet high performance and low power requirements, a power management unit is designed or saving power when RK3288 in low power mode. The RK3288 PMU is dedicated for managing the power of the whole chip. PMU can work in the Low Power Mode by setting bit[0] of PMU_PWRMODE_CON register. After setting the register, PMU would enter the Low Power mode. In the low power mode, pmu will auto power on/off the specified power domain, send idle req to specified power domain, shut down/up pll and so on. All of above are configurable by setting corresponding registers. Signed-off-by: Caesar Wang <wxt@rock-chips.com> Reviewed-by: Kevin Hilman <khilman@linaro.org> [replace dsb() with dsb(sy) for arm64 buildability; sy is the default, so no functional change; adapt to per-user clocks in genpd] Signed-off-by: Heiko Stuebner <heiko@sntech.de>
-rw-r--r--drivers/soc/Kconfig1
-rw-r--r--drivers/soc/Makefile1
-rw-r--r--drivers/soc/rockchip/Kconfig14
-rw-r--r--drivers/soc/rockchip/Makefile4
-rw-r--r--drivers/soc/rockchip/pm_domains.c490
5 files changed, 510 insertions, 0 deletions
diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig
index 96ddecb92254..ecb1a6c7f0d7 100644
--- a/drivers/soc/Kconfig
+++ b/drivers/soc/Kconfig
@@ -2,6 +2,7 @@ menu "SOC (System On Chip) specific Drivers"
2 2
3source "drivers/soc/mediatek/Kconfig" 3source "drivers/soc/mediatek/Kconfig"
4source "drivers/soc/qcom/Kconfig" 4source "drivers/soc/qcom/Kconfig"
5source "drivers/soc/rockchip/Kconfig"
5source "drivers/soc/sunxi/Kconfig" 6source "drivers/soc/sunxi/Kconfig"
6source "drivers/soc/ti/Kconfig" 7source "drivers/soc/ti/Kconfig"
7source "drivers/soc/versatile/Kconfig" 8source "drivers/soc/versatile/Kconfig"
diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile
index 0b12d777d3c4..c2b485f82415 100644
--- a/drivers/soc/Makefile
+++ b/drivers/soc/Makefile
@@ -5,6 +5,7 @@
5obj-$(CONFIG_MACH_DOVE) += dove/ 5obj-$(CONFIG_MACH_DOVE) += dove/
6obj-$(CONFIG_ARCH_MEDIATEK) += mediatek/ 6obj-$(CONFIG_ARCH_MEDIATEK) += mediatek/
7obj-$(CONFIG_ARCH_QCOM) += qcom/ 7obj-$(CONFIG_ARCH_QCOM) += qcom/
8obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/
8obj-$(CONFIG_ARCH_SUNXI) += sunxi/ 9obj-$(CONFIG_ARCH_SUNXI) += sunxi/
9obj-$(CONFIG_ARCH_TEGRA) += tegra/ 10obj-$(CONFIG_ARCH_TEGRA) += tegra/
10obj-$(CONFIG_SOC_TI) += ti/ 11obj-$(CONFIG_SOC_TI) += ti/
diff --git a/drivers/soc/rockchip/Kconfig b/drivers/soc/rockchip/Kconfig
new file mode 100644
index 000000000000..6ee03994801c
--- /dev/null
+++ b/drivers/soc/rockchip/Kconfig
@@ -0,0 +1,14 @@
1#
2# Rockchip Soc drivers
3#
4config ROCKCHIP_PM_DOMAINS
5 bool "Rockchip generic power domain"
6 depends on PM
7 select PM_GENERIC_DOMAINS
8 help
9 Say y here to enable power domain support.
10 In order to meet high performance and low power requirements, a power
11 management unit is designed or saving power when RK3288 in low power
12 mode. The RK3288 PMU is dedicated for managing the power of the whole chip.
13
14 If unsure, say N.
diff --git a/drivers/soc/rockchip/Makefile b/drivers/soc/rockchip/Makefile
new file mode 100644
index 000000000000..3d73d0672d22
--- /dev/null
+++ b/drivers/soc/rockchip/Makefile
@@ -0,0 +1,4 @@
1#
2# Rockchip Soc drivers
3#
4obj-$(CONFIG_ROCKCHIP_PM_DOMAINS) += pm_domains.o
diff --git a/drivers/soc/rockchip/pm_domains.c b/drivers/soc/rockchip/pm_domains.c
new file mode 100644
index 000000000000..8268d5d2b852
--- /dev/null
+++ b/drivers/soc/rockchip/pm_domains.c
@@ -0,0 +1,490 @@
1/*
2 * Rockchip Generic power domain support.
3 *
4 * Copyright (c) 2015 ROCKCHIP, Co. Ltd.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 */
10
11#include <linux/io.h>
12#include <linux/err.h>
13#include <linux/pm_clock.h>
14#include <linux/pm_domain.h>
15#include <linux/of_address.h>
16#include <linux/of_platform.h>
17#include <linux/clk.h>
18#include <linux/regmap.h>
19#include <linux/mfd/syscon.h>
20#include <dt-bindings/power/rk3288-power.h>
21
22struct rockchip_domain_info {
23 int pwr_mask;
24 int status_mask;
25 int req_mask;
26 int idle_mask;
27 int ack_mask;
28};
29
30struct rockchip_pmu_info {
31 u32 pwr_offset;
32 u32 status_offset;
33 u32 req_offset;
34 u32 idle_offset;
35 u32 ack_offset;
36
37 u32 core_pwrcnt_offset;
38 u32 gpu_pwrcnt_offset;
39
40 unsigned int core_power_transition_time;
41 unsigned int gpu_power_transition_time;
42
43 int num_domains;
44 const struct rockchip_domain_info *domain_info;
45};
46
47struct rockchip_pm_domain {
48 struct generic_pm_domain genpd;
49 const struct rockchip_domain_info *info;
50 struct rockchip_pmu *pmu;
51 int num_clks;
52 struct clk *clks[];
53};
54
55struct rockchip_pmu {
56 struct device *dev;
57 struct regmap *regmap;
58 const struct rockchip_pmu_info *info;
59 struct mutex mutex; /* mutex lock for pmu */
60 struct genpd_onecell_data genpd_data;
61 struct generic_pm_domain *domains[];
62};
63
64#define to_rockchip_pd(gpd) container_of(gpd, struct rockchip_pm_domain, genpd)
65
66#define DOMAIN(pwr, status, req, idle, ack) \
67{ \
68 .pwr_mask = BIT(pwr), \
69 .status_mask = BIT(status), \
70 .req_mask = BIT(req), \
71 .idle_mask = BIT(idle), \
72 .ack_mask = BIT(ack), \
73}
74
75#define DOMAIN_RK3288(pwr, status, req) \
76 DOMAIN(pwr, status, req, req, (req) + 16)
77
78static bool rockchip_pmu_domain_is_idle(struct rockchip_pm_domain *pd)
79{
80 struct rockchip_pmu *pmu = pd->pmu;
81 const struct rockchip_domain_info *pd_info = pd->info;
82 unsigned int val;
83
84 regmap_read(pmu->regmap, pmu->info->idle_offset, &val);
85 return (val & pd_info->idle_mask) == pd_info->idle_mask;
86}
87
88static int rockchip_pmu_set_idle_request(struct rockchip_pm_domain *pd,
89 bool idle)
90{
91 const struct rockchip_domain_info *pd_info = pd->info;
92 struct rockchip_pmu *pmu = pd->pmu;
93 unsigned int val;
94
95 regmap_update_bits(pmu->regmap, pmu->info->req_offset,
96 pd_info->req_mask, idle ? -1U : 0);
97
98 dsb(sy);
99
100 do {
101 regmap_read(pmu->regmap, pmu->info->ack_offset, &val);
102 } while ((val & pd_info->ack_mask) != (idle ? pd_info->ack_mask : 0));
103
104 while (rockchip_pmu_domain_is_idle(pd) != idle)
105 cpu_relax();
106
107 return 0;
108}
109
110static bool rockchip_pmu_domain_is_on(struct rockchip_pm_domain *pd)
111{
112 struct rockchip_pmu *pmu = pd->pmu;
113 unsigned int val;
114
115 regmap_read(pmu->regmap, pmu->info->status_offset, &val);
116
117 /* 1'b0: power on, 1'b1: power off */
118 return !(val & pd->info->status_mask);
119}
120
121static void rockchip_do_pmu_set_power_domain(struct rockchip_pm_domain *pd,
122 bool on)
123{
124 struct rockchip_pmu *pmu = pd->pmu;
125
126 regmap_update_bits(pmu->regmap, pmu->info->pwr_offset,
127 pd->info->pwr_mask, on ? 0 : -1U);
128
129 dsb(sy);
130
131 while (rockchip_pmu_domain_is_on(pd) != on)
132 cpu_relax();
133}
134
135static int rockchip_pd_power(struct rockchip_pm_domain *pd, bool power_on)
136{
137 int i;
138
139 mutex_lock(&pd->pmu->mutex);
140
141 if (rockchip_pmu_domain_is_on(pd) != power_on) {
142 for (i = 0; i < pd->num_clks; i++)
143 clk_enable(pd->clks[i]);
144
145 if (!power_on) {
146 /* FIXME: add code to save AXI_QOS */
147
148 /* if powering down, idle request to NIU first */
149 rockchip_pmu_set_idle_request(pd, true);
150 }
151
152 rockchip_do_pmu_set_power_domain(pd, power_on);
153
154 if (power_on) {
155 /* if powering up, leave idle mode */
156 rockchip_pmu_set_idle_request(pd, false);
157
158 /* FIXME: add code to restore AXI_QOS */
159 }
160
161 for (i = pd->num_clks - 1; i >= 0; i--)
162 clk_disable(pd->clks[i]);
163 }
164
165 mutex_unlock(&pd->pmu->mutex);
166 return 0;
167}
168
169static int rockchip_pd_power_on(struct generic_pm_domain *domain)
170{
171 struct rockchip_pm_domain *pd = to_rockchip_pd(domain);
172
173 return rockchip_pd_power(pd, true);
174}
175
176static int rockchip_pd_power_off(struct generic_pm_domain *domain)
177{
178 struct rockchip_pm_domain *pd = to_rockchip_pd(domain);
179
180 return rockchip_pd_power(pd, false);
181}
182
183static int rockchip_pd_attach_dev(struct generic_pm_domain *genpd,
184 struct device *dev)
185{
186 struct clk *clk;
187 int i;
188 int error;
189
190 dev_dbg(dev, "attaching to power domain '%s'\n", genpd->name);
191
192 error = pm_clk_create(dev);
193 if (error) {
194 dev_err(dev, "pm_clk_create failed %d\n", error);
195 return error;
196 }
197
198 i = 0;
199 while ((clk = of_clk_get(dev->of_node, i++)) && !IS_ERR(clk)) {
200 dev_dbg(dev, "adding clock '%pC' to list of PM clocks\n", clk);
201 error = pm_clk_add_clk(dev, clk);
202 if (error) {
203 dev_err(dev, "pm_clk_add_clk failed %d\n", error);
204 clk_put(clk);
205 pm_clk_destroy(dev);
206 return error;
207 }
208 }
209
210 return 0;
211}
212
213static void rockchip_pd_detach_dev(struct generic_pm_domain *genpd,
214 struct device *dev)
215{
216 dev_dbg(dev, "detaching from power domain '%s'\n", genpd->name);
217
218 pm_clk_destroy(dev);
219}
220
221static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu,
222 struct device_node *node)
223{
224 const struct rockchip_domain_info *pd_info;
225 struct rockchip_pm_domain *pd;
226 struct clk *clk;
227 int clk_cnt;
228 int i;
229 u32 id;
230 int error;
231
232 error = of_property_read_u32(node, "reg", &id);
233 if (error) {
234 dev_err(pmu->dev,
235 "%s: failed to retrieve domain id (reg): %d\n",
236 node->name, error);
237 return -EINVAL;
238 }
239
240 if (id >= pmu->info->num_domains) {
241 dev_err(pmu->dev, "%s: invalid domain id %d\n",
242 node->name, id);
243 return -EINVAL;
244 }
245
246 pd_info = &pmu->info->domain_info[id];
247 if (!pd_info) {
248 dev_err(pmu->dev, "%s: undefined domain id %d\n",
249 node->name, id);
250 return -EINVAL;
251 }
252
253 clk_cnt = of_count_phandle_with_args(node, "clocks", "#clock-cells");
254 pd = devm_kzalloc(pmu->dev,
255 sizeof(*pd) + clk_cnt * sizeof(pd->clks[0]),
256 GFP_KERNEL);
257 if (!pd)
258 return -ENOMEM;
259
260 pd->info = pd_info;
261 pd->pmu = pmu;
262
263 for (i = 0; i < clk_cnt; i++) {
264 clk = of_clk_get(node, i);
265 if (IS_ERR(clk)) {
266 error = PTR_ERR(clk);
267 dev_err(pmu->dev,
268 "%s: failed to get clk %pC (index %d): %d\n",
269 node->name, clk, i, error);
270 goto err_out;
271 }
272
273 error = clk_prepare(clk);
274 if (error) {
275 dev_err(pmu->dev,
276 "%s: failed to prepare clk %pC (index %d): %d\n",
277 node->name, clk, i, error);
278 clk_put(clk);
279 goto err_out;
280 }
281
282 pd->clks[pd->num_clks++] = clk;
283
284 dev_dbg(pmu->dev, "added clock '%pC' to domain '%s'\n",
285 clk, node->name);
286 }
287
288 error = rockchip_pd_power(pd, true);
289 if (error) {
290 dev_err(pmu->dev,
291 "failed to power on domain '%s': %d\n",
292 node->name, error);
293 goto err_out;
294 }
295
296 pd->genpd.name = node->name;
297 pd->genpd.power_off = rockchip_pd_power_off;
298 pd->genpd.power_on = rockchip_pd_power_on;
299 pd->genpd.attach_dev = rockchip_pd_attach_dev;
300 pd->genpd.detach_dev = rockchip_pd_detach_dev;
301 pd->genpd.flags = GENPD_FLAG_PM_CLK;
302 pm_genpd_init(&pd->genpd, NULL, false);
303
304 pmu->genpd_data.domains[id] = &pd->genpd;
305 return 0;
306
307err_out:
308 while (--i >= 0) {
309 clk_unprepare(pd->clks[i]);
310 clk_put(pd->clks[i]);
311 }
312 return error;
313}
314
315static void rockchip_pm_remove_one_domain(struct rockchip_pm_domain *pd)
316{
317 int i;
318
319 for (i = 0; i < pd->num_clks; i++) {
320 clk_unprepare(pd->clks[i]);
321 clk_put(pd->clks[i]);
322 }
323
324 /* protect the zeroing of pm->num_clks */
325 mutex_lock(&pd->pmu->mutex);
326 pd->num_clks = 0;
327 mutex_unlock(&pd->pmu->mutex);
328
329 /* devm will free our memory */
330}
331
332static void rockchip_pm_domain_cleanup(struct rockchip_pmu *pmu)
333{
334 struct generic_pm_domain *genpd;
335 struct rockchip_pm_domain *pd;
336 int i;
337
338 for (i = 0; i < pmu->genpd_data.num_domains; i++) {
339 genpd = pmu->genpd_data.domains[i];
340 if (genpd) {
341 pd = to_rockchip_pd(genpd);
342 rockchip_pm_remove_one_domain(pd);
343 }
344 }
345
346 /* devm will free our memory */
347}
348
349static void rockchip_configure_pd_cnt(struct rockchip_pmu *pmu,
350 u32 domain_reg_offset,
351 unsigned int count)
352{
353 /* First configure domain power down transition count ... */
354 regmap_write(pmu->regmap, domain_reg_offset, count);
355 /* ... and then power up count. */
356 regmap_write(pmu->regmap, domain_reg_offset + 4, count);
357}
358
359static int rockchip_pm_domain_probe(struct platform_device *pdev)
360{
361 struct device *dev = &pdev->dev;
362 struct device_node *np = dev->of_node;
363 struct device_node *node;
364 struct device *parent;
365 struct rockchip_pmu *pmu;
366 const struct of_device_id *match;
367 const struct rockchip_pmu_info *pmu_info;
368 int error;
369
370 if (!np) {
371 dev_err(dev, "device tree node not found\n");
372 return -ENODEV;
373 }
374
375 match = of_match_device(dev->driver->of_match_table, dev);
376 if (!match || !match->data) {
377 dev_err(dev, "missing pmu data\n");
378 return -EINVAL;
379 }
380
381 pmu_info = match->data;
382
383 pmu = devm_kzalloc(dev,
384 sizeof(*pmu) +
385 pmu_info->num_domains * sizeof(pmu->domains[0]),
386 GFP_KERNEL);
387 if (!pmu)
388 return -ENOMEM;
389
390 pmu->dev = &pdev->dev;
391 mutex_init(&pmu->mutex);
392
393 pmu->info = pmu_info;
394
395 pmu->genpd_data.domains = pmu->domains;
396 pmu->genpd_data.num_domains = pmu_info->num_domains;
397
398 parent = dev->parent;
399 if (!parent) {
400 dev_err(dev, "no parent for syscon devices\n");
401 return -ENODEV;
402 }
403
404 pmu->regmap = syscon_node_to_regmap(parent->of_node);
405
406 /*
407 * Configure power up and down transition delays for CORE
408 * and GPU domains.
409 */
410 rockchip_configure_pd_cnt(pmu, pmu_info->core_pwrcnt_offset,
411 pmu_info->core_power_transition_time);
412 rockchip_configure_pd_cnt(pmu, pmu_info->gpu_pwrcnt_offset,
413 pmu_info->gpu_power_transition_time);
414
415 error = -ENODEV;
416
417 for_each_available_child_of_node(np, node) {
418 error = rockchip_pm_add_one_domain(pmu, node);
419 if (error) {
420 dev_err(dev, "failed to handle node %s: %d\n",
421 node->name, error);
422 goto err_out;
423 }
424 }
425
426 if (error) {
427 dev_dbg(dev, "no power domains defined\n");
428 goto err_out;
429 }
430
431 of_genpd_add_provider_onecell(np, &pmu->genpd_data);
432
433 return 0;
434
435err_out:
436 rockchip_pm_domain_cleanup(pmu);
437 return error;
438}
439
440static const struct rockchip_domain_info rk3288_pm_domains[] = {
441 [RK3288_PD_VIO] = DOMAIN_RK3288(7, 7, 4),
442 [RK3288_PD_HEVC] = DOMAIN_RK3288(14, 10, 9),
443 [RK3288_PD_VIDEO] = DOMAIN_RK3288(8, 8, 3),
444 [RK3288_PD_GPU] = DOMAIN_RK3288(9, 9, 2),
445};
446
447static const struct rockchip_pmu_info rk3288_pmu = {
448 .pwr_offset = 0x08,
449 .status_offset = 0x0c,
450 .req_offset = 0x10,
451 .idle_offset = 0x14,
452 .ack_offset = 0x14,
453
454 .core_pwrcnt_offset = 0x34,
455 .gpu_pwrcnt_offset = 0x3c,
456
457 .core_power_transition_time = 24, /* 1us */
458 .gpu_power_transition_time = 24, /* 1us */
459
460 .num_domains = ARRAY_SIZE(rk3288_pm_domains),
461 .domain_info = rk3288_pm_domains,
462};
463
464static const struct of_device_id rockchip_pm_domain_dt_match[] = {
465 {
466 .compatible = "rockchip,rk3288-power-controller",
467 .data = (void *)&rk3288_pmu,
468 },
469 { /* sentinel */ },
470};
471
472static struct platform_driver rockchip_pm_domain_driver = {
473 .probe = rockchip_pm_domain_probe,
474 .driver = {
475 .name = "rockchip-pm-domain",
476 .of_match_table = rockchip_pm_domain_dt_match,
477 /*
478 * We can't forcibly eject devices form power domain,
479 * so we can't really remove power domains once they
480 * were added.
481 */
482 .suppress_bind_attrs = true,
483 },
484};
485
486static int __init rockchip_pm_domain_drv_register(void)
487{
488 return platform_driver_register(&rockchip_pm_domain_driver);
489}
490postcore_initcall(rockchip_pm_domain_drv_register);