Merge branch 'topic/usb-audio' into for-next
[cascardo/linux.git] / arch / arm / mach-exynos / 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/delay.h>
21 #include <linux/of_address.h>
22 #include <linux/of_platform.h>
23 #include <linux/sched.h>
24
25 #include "regs-pmu.h"
26
27 /*
28  * Exynos specific wrapper around the generic power domain
29  */
30 struct exynos_pm_domain {
31         void __iomem *base;
32         char const *name;
33         bool is_off;
34         struct generic_pm_domain pd;
35 };
36
37 static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on)
38 {
39         struct exynos_pm_domain *pd;
40         void __iomem *base;
41         u32 timeout, pwr;
42         char *op;
43
44         pd = container_of(domain, struct exynos_pm_domain, pd);
45         base = pd->base;
46
47         pwr = power_on ? S5P_INT_LOCAL_PWR_EN : 0;
48         __raw_writel(pwr, base);
49
50         /* Wait max 1ms */
51         timeout = 10;
52
53         while ((__raw_readl(base + 0x4) & S5P_INT_LOCAL_PWR_EN) != pwr) {
54                 if (!timeout) {
55                         op = (power_on) ? "enable" : "disable";
56                         pr_err("Power domain %s %s failed\n", domain->name, op);
57                         return -ETIMEDOUT;
58                 }
59                 timeout--;
60                 cpu_relax();
61                 usleep_range(80, 100);
62         }
63         return 0;
64 }
65
66 static int exynos_pd_power_on(struct generic_pm_domain *domain)
67 {
68         return exynos_pd_power(domain, true);
69 }
70
71 static int exynos_pd_power_off(struct generic_pm_domain *domain)
72 {
73         return exynos_pd_power(domain, false);
74 }
75
76 static void exynos_add_device_to_domain(struct exynos_pm_domain *pd,
77                                          struct device *dev)
78 {
79         int ret;
80
81         dev_dbg(dev, "adding to power domain %s\n", pd->pd.name);
82
83         while (1) {
84                 ret = pm_genpd_add_device(&pd->pd, dev);
85                 if (ret != -EAGAIN)
86                         break;
87                 cond_resched();
88         }
89
90         pm_genpd_dev_need_restore(dev, true);
91 }
92
93 static void exynos_remove_device_from_domain(struct device *dev)
94 {
95         struct generic_pm_domain *genpd = dev_to_genpd(dev);
96         int ret;
97
98         dev_dbg(dev, "removing from power domain %s\n", genpd->name);
99
100         while (1) {
101                 ret = pm_genpd_remove_device(genpd, dev);
102                 if (ret != -EAGAIN)
103                         break;
104                 cond_resched();
105         }
106 }
107
108 static void exynos_read_domain_from_dt(struct device *dev)
109 {
110         struct platform_device *pd_pdev;
111         struct exynos_pm_domain *pd;
112         struct device_node *node;
113
114         node = of_parse_phandle(dev->of_node, "samsung,power-domain", 0);
115         if (!node)
116                 return;
117         pd_pdev = of_find_device_by_node(node);
118         if (!pd_pdev)
119                 return;
120         pd = platform_get_drvdata(pd_pdev);
121         exynos_add_device_to_domain(pd, dev);
122 }
123
124 static int exynos_pm_notifier_call(struct notifier_block *nb,
125                                     unsigned long event, void *data)
126 {
127         struct device *dev = data;
128
129         switch (event) {
130         case BUS_NOTIFY_BIND_DRIVER:
131                 if (dev->of_node)
132                         exynos_read_domain_from_dt(dev);
133
134                 break;
135
136         case BUS_NOTIFY_UNBOUND_DRIVER:
137                 exynos_remove_device_from_domain(dev);
138
139                 break;
140         }
141         return NOTIFY_DONE;
142 }
143
144 static struct notifier_block platform_nb = {
145         .notifier_call = exynos_pm_notifier_call,
146 };
147
148 static __init int exynos4_pm_init_power_domain(void)
149 {
150         struct platform_device *pdev;
151         struct device_node *np;
152
153         for_each_compatible_node(np, NULL, "samsung,exynos4210-pd") {
154                 struct exynos_pm_domain *pd;
155                 int on;
156
157                 pdev = of_find_device_by_node(np);
158
159                 pd = kzalloc(sizeof(*pd), GFP_KERNEL);
160                 if (!pd) {
161                         pr_err("%s: failed to allocate memory for domain\n",
162                                         __func__);
163                         return -ENOMEM;
164                 }
165
166                 pd->pd.name = kstrdup(np->name, GFP_KERNEL);
167                 pd->name = pd->pd.name;
168                 pd->base = of_iomap(np, 0);
169                 pd->pd.power_off = exynos_pd_power_off;
170                 pd->pd.power_on = exynos_pd_power_on;
171                 pd->pd.of_node = np;
172
173                 platform_set_drvdata(pdev, pd);
174
175                 on = __raw_readl(pd->base + 0x4) & S5P_INT_LOCAL_PWR_EN;
176
177                 pm_genpd_init(&pd->pd, NULL, !on);
178         }
179
180         bus_register_notifier(&platform_bus_type, &platform_nb);
181
182         return 0;
183 }
184 arch_initcall(exynos4_pm_init_power_domain);