diff options
Diffstat (limited to 'arch/arm/mach-tegra/powerdetect.c')
-rw-r--r-- | arch/arm/mach-tegra/powerdetect.c | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/powerdetect.c b/arch/arm/mach-tegra/powerdetect.c new file mode 100644 index 00000000000..6a2a5779458 --- /dev/null +++ b/arch/arm/mach-tegra/powerdetect.c | |||
@@ -0,0 +1,348 @@ | |||
1 | /* | ||
2 | * arch/arm/mach-tegra/powerdetect.c | ||
3 | * | ||
4 | * Copyright (c) 2011, NVIDIA Corporation. | ||
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 as published by | ||
8 | * the Free Software Foundation; version 2 of the License | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
13 | * more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU General Public License along | ||
16 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #include <linux/kernel.h> | ||
21 | #include <linux/delay.h> | ||
22 | #include <linux/spinlock.h> | ||
23 | #include <linux/io.h> | ||
24 | #include <linux/err.h> | ||
25 | #include <linux/notifier.h> | ||
26 | #include <linux/regulator/consumer.h> | ||
27 | |||
28 | #include <mach/iomap.h> | ||
29 | |||
30 | #include "board.h" | ||
31 | #include "fuse.h" | ||
32 | |||
33 | #define PMC_PWR_IO_DISABLE 0x44 | ||
34 | #define PMC_PWR_DET_ENABLE 0x48 | ||
35 | #define PMC_PWR_DET_LATCH 0x4C | ||
36 | #define PMC_PWR_DET_VAL 0xE4 | ||
37 | |||
38 | struct pwr_detect_cell { | ||
39 | const char *reg_id; | ||
40 | u32 pwrdet_mask; | ||
41 | u32 pwrio_mask; | ||
42 | u32 package_mask; | ||
43 | |||
44 | struct notifier_block regulator_nb; | ||
45 | }; | ||
46 | |||
47 | static bool pwrdet_rails_found; | ||
48 | static bool pwrdet_always_on; | ||
49 | static bool pwrio_always_on; | ||
50 | static u32 pwrdet_val; | ||
51 | static u32 pwrio_val; | ||
52 | static u32 pwrio_disabled_mask; | ||
53 | |||
54 | static DEFINE_SPINLOCK(pwr_lock); | ||
55 | |||
56 | static void __iomem *pmc_base = IO_ADDRESS(TEGRA_PMC_BASE); | ||
57 | |||
58 | static inline void pmc_writel(u32 val, unsigned long addr) | ||
59 | { | ||
60 | writel(val, (u32)pmc_base + addr); | ||
61 | } | ||
62 | static inline u32 pmc_readl(unsigned long addr) | ||
63 | { | ||
64 | return readl((u32)pmc_base + addr); | ||
65 | } | ||
66 | |||
67 | |||
68 | #define POWER_CELL(_reg_id, _pwrdet_mask, _pwrio_mask, _package_mask) \ | ||
69 | { \ | ||
70 | .reg_id = _reg_id, \ | ||
71 | .pwrdet_mask = _pwrdet_mask, \ | ||
72 | .pwrio_mask = _pwrio_mask, \ | ||
73 | .package_mask = _package_mask, \ | ||
74 | } | ||
75 | |||
76 | /* Some IO pads does not have power detect cells, but still can/should be | ||
77 | * turned off when no power - set pwrdet_mask=0 for such pads */ | ||
78 | static struct pwr_detect_cell pwr_detect_cells[] = { | ||
79 | POWER_CELL("pwrdet_nand", (0x1 << 1), (0x1 << 1), 0xFFFFFFFF), | ||
80 | POWER_CELL("pwrdet_uart", (0x1 << 2), (0x1 << 2), 0xFFFFFFFF), | ||
81 | POWER_CELL("pwrdet_bb", (0x1 << 3), (0x1 << 3), 0xFFFFFFFF), | ||
82 | #ifdef CONFIG_ARCH_TEGRA_2x_SOC | ||
83 | POWER_CELL("pwrdet_vi", 0, (0x1 << 4), 0xFFFFFFFF), | ||
84 | #else | ||
85 | /* Tegra3 VI is connected on MID package only (id = 1, mask = 0x2) */ | ||
86 | POWER_CELL("pwrdet_vi", 0, (0x1 << 4), 0x00000002), | ||
87 | #endif | ||
88 | POWER_CELL("pwrdet_audio", (0x1 << 5), (0x1 << 5), 0xFFFFFFFF), | ||
89 | POWER_CELL("pwrdet_lcd", (0x1 << 6), (0x1 << 6), 0xFFFFFFFF), | ||
90 | #ifdef CONFIG_ARCH_TEGRA_2x_SOC | ||
91 | POWER_CELL("pwrdet_sd", 0, (0x1 << 8), 0xFFFFFFFF), | ||
92 | #endif | ||
93 | POWER_CELL("pwrdet_mipi", 0, (0x1 << 9), 0xFFFFFFFF), | ||
94 | #ifndef CONFIG_ARCH_TEGRA_2x_SOC | ||
95 | POWER_CELL("pwrdet_cam", (0x1 << 10), (0x1 << 10), 0xFFFFFFFF), | ||
96 | POWER_CELL("pwrdet_pex_ctl", (0x1 << 11), (0x1 << 11), 0xFFFFFFFF), | ||
97 | POWER_CELL("pwrdet_sdmmc1", (0x1 << 12), (0x1 << 12), 0xFFFFFFFF), | ||
98 | POWER_CELL("pwrdet_sdmmc3", (0x1 << 13), (0x1 << 13), 0xFFFFFFFF), | ||
99 | POWER_CELL("pwrdet_sdmmc4", 0, (0x1 << 14), 0xFFFFFFFF), | ||
100 | #endif | ||
101 | }; | ||
102 | |||
103 | static void pwr_detect_reset(u32 pwrdet_mask) | ||
104 | { | ||
105 | pmc_writel(pwrdet_mask, PMC_PWR_DET_ENABLE); | ||
106 | barrier(); | ||
107 | pmc_writel(pwrdet_mask, PMC_PWR_DET_VAL); | ||
108 | |||
109 | pmc_readl(PMC_PWR_DET_VAL); | ||
110 | pmc_writel(0, PMC_PWR_DET_ENABLE); | ||
111 | } | ||
112 | |||
113 | static void pwr_detect_start(u32 pwrdet_mask) | ||
114 | { | ||
115 | pmc_writel(pwrdet_mask, PMC_PWR_DET_ENABLE); | ||
116 | udelay(4); | ||
117 | |||
118 | pmc_writel(1, PMC_PWR_DET_LATCH); | ||
119 | pmc_readl(PMC_PWR_DET_LATCH); | ||
120 | } | ||
121 | |||
122 | static void pwr_detect_latch(void) | ||
123 | { | ||
124 | pmc_writel(0, PMC_PWR_DET_LATCH); | ||
125 | |||
126 | pmc_readl(PMC_PWR_DET_VAL); | ||
127 | pmc_writel(0, PMC_PWR_DET_ENABLE); | ||
128 | } | ||
129 | |||
130 | static void pwr_io_enable(u32 pwrio_mask) | ||
131 | { | ||
132 | u32 val = pmc_readl(PMC_PWR_IO_DISABLE); | ||
133 | val &= ~pwrio_mask; | ||
134 | pmc_writel(val, PMC_PWR_IO_DISABLE); | ||
135 | } | ||
136 | |||
137 | static void pwr_io_disable(u32 pwrio_mask) | ||
138 | { | ||
139 | u32 val = pmc_readl(PMC_PWR_IO_DISABLE); | ||
140 | val |= pwrio_mask; | ||
141 | pmc_writel(val, PMC_PWR_IO_DISABLE); | ||
142 | } | ||
143 | |||
144 | static int pwrdet_always_on_set(const char *arg, const struct kernel_param *kp) | ||
145 | { | ||
146 | int ret; | ||
147 | unsigned long flags; | ||
148 | |||
149 | spin_lock_irqsave(&pwr_lock, flags); | ||
150 | |||
151 | ret = param_set_bool(arg, kp); | ||
152 | if (ret) { | ||
153 | spin_unlock_irqrestore(&pwr_lock, flags); | ||
154 | return ret; | ||
155 | } | ||
156 | |||
157 | if (pwrdet_always_on) | ||
158 | pwr_detect_start(0xFFFFFFFF); | ||
159 | else | ||
160 | pwr_detect_latch(); | ||
161 | |||
162 | spin_unlock_irqrestore(&pwr_lock, flags); | ||
163 | return 0; | ||
164 | } | ||
165 | |||
166 | static int pwrio_always_on_set(const char *arg, const struct kernel_param *kp) | ||
167 | { | ||
168 | int ret; | ||
169 | unsigned long flags; | ||
170 | |||
171 | spin_lock_irqsave(&pwr_lock, flags); | ||
172 | |||
173 | ret = param_set_bool(arg, kp); | ||
174 | if (ret) { | ||
175 | spin_unlock_irqrestore(&pwr_lock, flags); | ||
176 | return ret; | ||
177 | } | ||
178 | |||
179 | if (pwrio_always_on) | ||
180 | pwr_io_enable(0xFFFFFFFF); | ||
181 | else | ||
182 | pwr_io_disable(pwrio_disabled_mask); | ||
183 | |||
184 | spin_unlock_irqrestore(&pwr_lock, flags); | ||
185 | return 0; | ||
186 | } | ||
187 | |||
188 | static int pwrdet_always_on_get(char *buffer, const struct kernel_param *kp) | ||
189 | { | ||
190 | return param_get_bool(buffer, kp); | ||
191 | } | ||
192 | |||
193 | static struct kernel_param_ops pwrdet_always_on_ops = { | ||
194 | .set = pwrdet_always_on_set, | ||
195 | .get = pwrdet_always_on_get, | ||
196 | }; | ||
197 | static struct kernel_param_ops pwrio_always_on_ops = { | ||
198 | .set = pwrio_always_on_set, | ||
199 | .get = pwrdet_always_on_get, | ||
200 | }; | ||
201 | module_param_cb(pwrdet_always_on, &pwrdet_always_on_ops, | ||
202 | &pwrdet_always_on, 0644); | ||
203 | module_param_cb(pwrio_always_on, &pwrio_always_on_ops, | ||
204 | &pwrio_always_on, 0644); | ||
205 | |||
206 | static int pwrdet_val_get(char *buffer, const struct kernel_param *kp) | ||
207 | { | ||
208 | pwrdet_val = pmc_readl(PMC_PWR_DET_VAL); | ||
209 | return param_get_ulong(buffer, kp); | ||
210 | } | ||
211 | static struct kernel_param_ops pwrdet_val_ops = { | ||
212 | .get = pwrdet_val_get, | ||
213 | }; | ||
214 | module_param_cb(pwrdet_val, &pwrdet_val_ops, &pwrdet_val, 0444); | ||
215 | |||
216 | static int pwrio_val_get(char *buffer, const struct kernel_param *kp) | ||
217 | { | ||
218 | pwrio_val = pmc_readl(PMC_PWR_IO_DISABLE); | ||
219 | return param_get_ulong(buffer, kp); | ||
220 | } | ||
221 | static struct kernel_param_ops pwrio_val_ops = { | ||
222 | .get = pwrio_val_get, | ||
223 | }; | ||
224 | module_param_cb(pwrio_val, &pwrio_val_ops, &pwrio_val, 0444); | ||
225 | |||
226 | |||
227 | static int pwrdet_notify_cb( | ||
228 | struct notifier_block *nb, unsigned long event, void *v) | ||
229 | { | ||
230 | unsigned long flags; | ||
231 | struct pwr_detect_cell *cell; | ||
232 | |||
233 | if (!pwrdet_rails_found) | ||
234 | return NOTIFY_OK; | ||
235 | |||
236 | cell = container_of(nb, struct pwr_detect_cell, regulator_nb); | ||
237 | |||
238 | spin_lock_irqsave(&pwr_lock, flags); | ||
239 | |||
240 | if (event & REGULATOR_EVENT_PRE_ENABLE) { | ||
241 | pwrio_disabled_mask &= ~cell->pwrio_mask; | ||
242 | if (!pwrio_always_on) | ||
243 | pwr_io_enable(cell->pwrio_mask); | ||
244 | } | ||
245 | if (event & (REGULATOR_EVENT_PRE_ENABLE | | ||
246 | REGULATOR_EVENT_OUT_PRECHANGE)) { | ||
247 | if (!pwrdet_always_on && cell->pwrdet_mask) | ||
248 | pwr_detect_reset(cell->pwrdet_mask); | ||
249 | } | ||
250 | if (event & (REGULATOR_EVENT_POST_ENABLE | | ||
251 | REGULATOR_EVENT_OUT_POSTCHANGE)) { | ||
252 | if (!pwrdet_always_on && cell->pwrdet_mask) { | ||
253 | pwr_detect_start(cell->pwrdet_mask); | ||
254 | pwr_detect_latch(); | ||
255 | } | ||
256 | } | ||
257 | if (event & (REGULATOR_EVENT_DISABLE | | ||
258 | REGULATOR_EVENT_FORCE_DISABLE)) { | ||
259 | pwrio_disabled_mask |= cell->pwrio_mask; | ||
260 | if (!pwrio_always_on) | ||
261 | pwr_io_disable(cell->pwrio_mask); | ||
262 | } | ||
263 | |||
264 | pr_debug("tegra: %s: event %lu, pwrdet 0x%x, pwrio 0x%x\n", | ||
265 | cell->reg_id, event, | ||
266 | pmc_readl(PMC_PWR_DET_VAL), pmc_readl(PMC_PWR_IO_DISABLE)); | ||
267 | spin_unlock_irqrestore(&pwr_lock, flags); | ||
268 | |||
269 | return NOTIFY_OK; | ||
270 | } | ||
271 | |||
272 | static int __init pwr_detect_cell_init_one( | ||
273 | struct pwr_detect_cell *cell, u32 *disabled_mask) | ||
274 | { | ||
275 | int ret; | ||
276 | struct regulator *regulator = regulator_get(NULL, cell->reg_id); | ||
277 | |||
278 | if (IS_ERR(regulator)) | ||
279 | return PTR_ERR(regulator); | ||
280 | |||
281 | cell->regulator_nb.notifier_call = pwrdet_notify_cb; | ||
282 | ret = regulator_register_notifier(regulator, &cell->regulator_nb); | ||
283 | if (ret) { | ||
284 | regulator_put(regulator); | ||
285 | return ret; | ||
286 | } | ||
287 | |||
288 | if (!regulator_is_enabled(regulator)) | ||
289 | *disabled_mask |= cell->pwrio_mask; | ||
290 | |||
291 | regulator_put(regulator); | ||
292 | return 0; | ||
293 | } | ||
294 | |||
295 | int __init tegra_pwr_detect_cell_init(void) | ||
296 | { | ||
297 | int i, ret; | ||
298 | u32 package_mask; | ||
299 | unsigned long flags; | ||
300 | bool rails_found = true; | ||
301 | |||
302 | i = tegra_package_id(); | ||
303 | if ((i != -1) && (i & (~0x1F))) { | ||
304 | pr_err("tegra: not supported package id %d - io power detection" | ||
305 | " is left always on\n", i); | ||
306 | return 0; | ||
307 | } | ||
308 | package_mask = (i == -1) ? i : (0x1 << i); | ||
309 | |||
310 | for (i = 0; i < ARRAY_SIZE(pwr_detect_cells); i++) { | ||
311 | struct pwr_detect_cell *cell = &pwr_detect_cells[i]; | ||
312 | |||
313 | if (!(cell->package_mask & package_mask)) { | ||
314 | pwrio_disabled_mask |= cell->pwrio_mask; | ||
315 | continue; | ||
316 | } | ||
317 | |||
318 | ret = pwr_detect_cell_init_one(cell, &pwrio_disabled_mask); | ||
319 | if (ret) { | ||
320 | pr_err("tegra: failed to map regulator to power detect" | ||
321 | " cell %s(%d)\n", cell->reg_id, ret); | ||
322 | rails_found = false; | ||
323 | } | ||
324 | } | ||
325 | |||
326 | if (!rails_found) { | ||
327 | pr_err("tegra: failed regulators mapping - io power detection" | ||
328 | " is left always on\n"); | ||
329 | return 0; | ||
330 | } | ||
331 | pwrdet_rails_found = true; | ||
332 | |||
333 | /* Latch initial i/o power levels, disable all detection cells | ||
334 | and not powered interfaces */ | ||
335 | spin_lock_irqsave(&pwr_lock, flags); | ||
336 | if (!pwrdet_always_on) | ||
337 | pwr_detect_latch(); | ||
338 | if (!pwrio_always_on) | ||
339 | pwr_io_disable(pwrio_disabled_mask); | ||
340 | spin_unlock_irqrestore(&pwr_lock, flags); | ||
341 | |||
342 | pr_info("tegra: started io power detection dynamic control\n"); | ||
343 | pr_info("tegra: NO_IO_POWER setting 0x%x\n", pwrio_disabled_mask); | ||
344 | |||
345 | return 0; | ||
346 | } | ||
347 | |||
348 | fs_initcall(tegra_pwr_detect_cell_init); | ||