diff options
author | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
---|---|---|
committer | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
commit | fcc9d2e5a6c89d22b8b773a64fb4ad21ac318446 (patch) | |
tree | a57612d1888735a2ec7972891b68c1ac5ec8faea /arch/arm/mach-tegra/pm-irq.c | |
parent | 8dea78da5cee153b8af9c07a2745f6c55057fe12 (diff) |
Diffstat (limited to 'arch/arm/mach-tegra/pm-irq.c')
-rw-r--r-- | arch/arm/mach-tegra/pm-irq.c | 366 |
1 files changed, 366 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/pm-irq.c b/arch/arm/mach-tegra/pm-irq.c new file mode 100644 index 00000000000..57d21361ca1 --- /dev/null +++ b/arch/arm/mach-tegra/pm-irq.c | |||
@@ -0,0 +1,366 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2011 Google, Inc. | ||
3 | * | ||
4 | * Author: | ||
5 | * Colin Cross <ccross@android.com> | ||
6 | * | ||
7 | * This software is licensed under the terms of the GNU General Public | ||
8 | * License version 2, as published by the Free Software Foundation, and | ||
9 | * may be copied, distributed, and modified under those terms. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | */ | ||
17 | |||
18 | #include <linux/kernel.h> | ||
19 | #include <linux/debugfs.h> | ||
20 | #include <linux/delay.h> | ||
21 | #include <linux/init.h> | ||
22 | #include <linux/interrupt.h> | ||
23 | #include <linux/irq.h> | ||
24 | #include <linux/io.h> | ||
25 | #include <linux/moduleparam.h> | ||
26 | #include <linux/seq_file.h> | ||
27 | #include <linux/syscore_ops.h> | ||
28 | |||
29 | #include <mach/iomap.h> | ||
30 | |||
31 | #include "pm-irq.h" | ||
32 | |||
33 | #define PMC_CTRL 0x0 | ||
34 | #define PMC_CTRL_LATCH_WAKEUPS (1 << 5) | ||
35 | #define PMC_WAKE_MASK 0xc | ||
36 | #define PMC_WAKE_LEVEL 0x10 | ||
37 | #define PMC_WAKE_STATUS 0x14 | ||
38 | #define PMC_SW_WAKE_STATUS 0x18 | ||
39 | #ifndef CONFIG_ARCH_TEGRA_2x_SOC | ||
40 | #define PMC_WAKE2_MASK 0x160 | ||
41 | #define PMC_WAKE2_LEVEL 0x164 | ||
42 | #define PMC_WAKE2_STATUS 0x168 | ||
43 | #define PMC_SW_WAKE2_STATUS 0x16C | ||
44 | #endif | ||
45 | |||
46 | #define PMC_MAX_WAKE_COUNT 64 | ||
47 | |||
48 | static void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE); | ||
49 | |||
50 | static u64 tegra_lp0_wake_enb; | ||
51 | static u64 tegra_lp0_wake_level; | ||
52 | static u64 tegra_lp0_wake_level_any; | ||
53 | static int tegra_prevent_lp0; | ||
54 | |||
55 | static unsigned int tegra_wake_irq_count[PMC_MAX_WAKE_COUNT]; | ||
56 | |||
57 | static bool debug_lp0; | ||
58 | module_param(debug_lp0, bool, S_IRUGO | S_IWUSR); | ||
59 | |||
60 | static bool warn_prevent_lp0; | ||
61 | module_param(warn_prevent_lp0, bool, S_IRUGO | S_IWUSR); | ||
62 | |||
63 | bool tegra_pm_irq_lp0_allowed(void) | ||
64 | { | ||
65 | return (tegra_prevent_lp0 == 0); | ||
66 | } | ||
67 | |||
68 | /* ensures that sufficient time is passed for a register write to | ||
69 | * serialize into the 32KHz domain */ | ||
70 | static void pmc_32kwritel(u32 val, unsigned long offs) | ||
71 | { | ||
72 | writel(val, pmc + offs); | ||
73 | udelay(130); | ||
74 | } | ||
75 | |||
76 | static inline void write_pmc_wake_mask(u64 value) | ||
77 | { | ||
78 | pr_info("Wake[31-0] enable=0x%x\n", (u32)(value & 0xFFFFFFFF)); | ||
79 | writel((u32)value, pmc + PMC_WAKE_MASK); | ||
80 | #ifndef CONFIG_ARCH_TEGRA_2x_SOC | ||
81 | pr_info("Tegra3 wake[63-32] enable=0x%x\n", (u32)((value >> 32) & | ||
82 | 0xFFFFFFFF)); | ||
83 | __raw_writel((u32)(value >> 32), pmc + PMC_WAKE2_MASK); | ||
84 | #endif | ||
85 | } | ||
86 | |||
87 | static inline u64 read_pmc_wake_level(void) | ||
88 | { | ||
89 | u64 reg; | ||
90 | |||
91 | #ifdef CONFIG_ARCH_TEGRA_2x_SOC | ||
92 | reg = readl(pmc + PMC_WAKE_LEVEL); | ||
93 | #else | ||
94 | reg = __raw_readl(pmc + PMC_WAKE_LEVEL); | ||
95 | reg |= ((u64)readl(pmc + PMC_WAKE2_LEVEL)) << 32; | ||
96 | #endif | ||
97 | return reg; | ||
98 | } | ||
99 | |||
100 | static inline void write_pmc_wake_level(u64 value) | ||
101 | { | ||
102 | pr_info("Wake[31-0] level=0x%x\n", (u32)(value & 0xFFFFFFFF)); | ||
103 | writel((u32)value, pmc + PMC_WAKE_LEVEL); | ||
104 | #ifndef CONFIG_ARCH_TEGRA_2x_SOC | ||
105 | pr_info("Tegra3 wake[63-32] level=0x%x\n", (u32)((value >> 32) & | ||
106 | 0xFFFFFFFF)); | ||
107 | __raw_writel((u32)(value >> 32), pmc + PMC_WAKE2_LEVEL); | ||
108 | #endif | ||
109 | } | ||
110 | |||
111 | static inline u64 read_pmc_wake_status(void) | ||
112 | { | ||
113 | u64 reg; | ||
114 | |||
115 | #ifdef CONFIG_ARCH_TEGRA_2x_SOC | ||
116 | reg = readl(pmc + PMC_WAKE_STATUS); | ||
117 | #else | ||
118 | reg = __raw_readl(pmc + PMC_WAKE_STATUS); | ||
119 | reg |= ((u64)readl(pmc + PMC_WAKE2_STATUS)) << 32; | ||
120 | #endif | ||
121 | return reg; | ||
122 | } | ||
123 | |||
124 | static inline u64 read_pmc_sw_wake_status(void) | ||
125 | { | ||
126 | u64 reg; | ||
127 | |||
128 | #ifdef CONFIG_ARCH_TEGRA_2x_SOC | ||
129 | reg = readl(pmc + PMC_SW_WAKE_STATUS); | ||
130 | #else | ||
131 | reg = __raw_readl(pmc + PMC_SW_WAKE_STATUS); | ||
132 | reg |= ((u64)readl(pmc + PMC_SW_WAKE2_STATUS)) << 32; | ||
133 | #endif | ||
134 | return reg; | ||
135 | } | ||
136 | |||
137 | static inline void clear_pmc_sw_wake_status(void) | ||
138 | { | ||
139 | pmc_32kwritel(0, PMC_SW_WAKE_STATUS); | ||
140 | #ifndef CONFIG_ARCH_TEGRA_2x_SOC | ||
141 | pmc_32kwritel(0, PMC_SW_WAKE2_STATUS); | ||
142 | #endif | ||
143 | } | ||
144 | |||
145 | int tegra_pm_irq_set_wake(int irq, int enable) | ||
146 | { | ||
147 | int wake = tegra_irq_to_wake(irq); | ||
148 | |||
149 | if (wake == -EALREADY) { | ||
150 | /* EALREADY means wakeup event already accounted for */ | ||
151 | return 0; | ||
152 | } else if (wake == -ENOTSUPP) { | ||
153 | /* ENOTSUPP means LP0 not supported with this wake source */ | ||
154 | WARN(enable && warn_prevent_lp0, "irq %d prevents lp0\n", irq); | ||
155 | if (enable) | ||
156 | tegra_prevent_lp0++; | ||
157 | else if (!WARN_ON(tegra_prevent_lp0 == 0)) | ||
158 | tegra_prevent_lp0--; | ||
159 | return 0; | ||
160 | } else if (wake < 0) { | ||
161 | return -EINVAL; | ||
162 | } | ||
163 | |||
164 | if (enable) { | ||
165 | tegra_lp0_wake_enb |= 1ull << wake; | ||
166 | pr_info("Enabling wake%d\n", wake); | ||
167 | } else { | ||
168 | tegra_lp0_wake_enb &= ~(1ull << wake); | ||
169 | pr_info("Disabling wake%d\n", wake); | ||
170 | } | ||
171 | |||
172 | return 0; | ||
173 | } | ||
174 | |||
175 | int tegra_pm_irq_set_wake_type(int irq, int flow_type) | ||
176 | { | ||
177 | int wake = tegra_irq_to_wake(irq); | ||
178 | |||
179 | if (wake < 0) | ||
180 | return 0; | ||
181 | |||
182 | switch (flow_type) { | ||
183 | case IRQF_TRIGGER_FALLING: | ||
184 | case IRQF_TRIGGER_LOW: | ||
185 | tegra_lp0_wake_level &= ~(1ull << wake); | ||
186 | tegra_lp0_wake_level_any &= ~(1ull << wake); | ||
187 | break; | ||
188 | case IRQF_TRIGGER_HIGH: | ||
189 | case IRQF_TRIGGER_RISING: | ||
190 | tegra_lp0_wake_level |= (1ull << wake); | ||
191 | tegra_lp0_wake_level_any &= ~(1ull << wake); | ||
192 | break; | ||
193 | |||
194 | case IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING: | ||
195 | tegra_lp0_wake_level_any |= (1ull << wake); | ||
196 | break; | ||
197 | default: | ||
198 | return -EINVAL; | ||
199 | } | ||
200 | |||
201 | return 0; | ||
202 | } | ||
203 | |||
204 | /* translate lp0 wake sources back into irqs to catch edge triggered wakeups */ | ||
205 | static void tegra_pm_irq_syscore_resume_helper( | ||
206 | unsigned long wake_status, | ||
207 | unsigned int index) | ||
208 | { | ||
209 | int wake; | ||
210 | int irq; | ||
211 | struct irq_desc *desc; | ||
212 | |||
213 | for_each_set_bit(wake, &wake_status, sizeof(wake_status) * 8) { | ||
214 | irq = tegra_wake_to_irq(wake + 32 * index); | ||
215 | if (!irq) { | ||
216 | pr_info("Resume caused by WAKE%d\n", | ||
217 | (wake + 32 * index)); | ||
218 | continue; | ||
219 | } | ||
220 | |||
221 | desc = irq_to_desc(irq); | ||
222 | if (!desc || !desc->action || !desc->action->name) { | ||
223 | pr_info("Resume caused by WAKE%d, irq %d\n", | ||
224 | (wake + 32 * index), irq); | ||
225 | continue; | ||
226 | } | ||
227 | |||
228 | pr_info("Resume caused by WAKE%d, %s\n", (wake + 32 * index), | ||
229 | desc->action->name); | ||
230 | |||
231 | tegra_wake_irq_count[wake + 32 * index]++; | ||
232 | |||
233 | generic_handle_irq(irq); | ||
234 | } | ||
235 | } | ||
236 | |||
237 | static void tegra_pm_irq_syscore_resume(void) | ||
238 | { | ||
239 | unsigned long long wake_status = read_pmc_wake_status(); | ||
240 | |||
241 | pr_info(" legacy wake status=0x%x\n", (u32)wake_status); | ||
242 | tegra_pm_irq_syscore_resume_helper((unsigned long)wake_status, 0); | ||
243 | #ifndef CONFIG_ARCH_TEGRA_2x_SOC | ||
244 | pr_info(" tegra3 wake status=0x%x\n", (u32)(wake_status >> 32)); | ||
245 | tegra_pm_irq_syscore_resume_helper( | ||
246 | (unsigned long)(wake_status >> 32), 1); | ||
247 | #endif | ||
248 | } | ||
249 | |||
250 | /* set up lp0 wake sources */ | ||
251 | static int tegra_pm_irq_syscore_suspend(void) | ||
252 | { | ||
253 | u32 temp; | ||
254 | u64 status; | ||
255 | u64 lvl; | ||
256 | u64 wake_level; | ||
257 | u64 wake_enb; | ||
258 | |||
259 | clear_pmc_sw_wake_status(); | ||
260 | |||
261 | temp = readl(pmc + PMC_CTRL); | ||
262 | temp |= PMC_CTRL_LATCH_WAKEUPS; | ||
263 | pmc_32kwritel(temp, PMC_CTRL); | ||
264 | |||
265 | temp &= ~PMC_CTRL_LATCH_WAKEUPS; | ||
266 | pmc_32kwritel(temp, PMC_CTRL); | ||
267 | |||
268 | status = read_pmc_sw_wake_status(); | ||
269 | |||
270 | lvl = read_pmc_wake_level(); | ||
271 | |||
272 | /* flip the wakeup trigger for any-edge triggered pads | ||
273 | * which are currently asserting as wakeups */ | ||
274 | lvl ^= status; | ||
275 | |||
276 | lvl &= tegra_lp0_wake_level_any; | ||
277 | |||
278 | wake_level = lvl | tegra_lp0_wake_level; | ||
279 | wake_enb = tegra_lp0_wake_enb; | ||
280 | |||
281 | if (debug_lp0) { | ||
282 | wake_level = lvl ^ status; | ||
283 | wake_enb = 0xffffffff; | ||
284 | } | ||
285 | |||
286 | /* Clear PMC Wake Status register while going to suspend */ | ||
287 | temp = readl(pmc + PMC_WAKE_STATUS); | ||
288 | if (temp) | ||
289 | pmc_32kwritel(temp, PMC_WAKE_STATUS); | ||
290 | |||
291 | write_pmc_wake_level(wake_level); | ||
292 | |||
293 | write_pmc_wake_mask(wake_enb); | ||
294 | |||
295 | return 0; | ||
296 | } | ||
297 | |||
298 | static struct syscore_ops tegra_pm_irq_syscore_ops = { | ||
299 | .suspend = tegra_pm_irq_syscore_suspend, | ||
300 | .resume = tegra_pm_irq_syscore_resume, | ||
301 | }; | ||
302 | |||
303 | static int tegra_pm_irq_syscore_init(void) | ||
304 | { | ||
305 | register_syscore_ops(&tegra_pm_irq_syscore_ops); | ||
306 | |||
307 | return 0; | ||
308 | } | ||
309 | subsys_initcall(tegra_pm_irq_syscore_init); | ||
310 | |||
311 | #ifdef CONFIG_DEBUG_FS | ||
312 | static int tegra_pm_irq_debug_show(struct seq_file *s, void *data) | ||
313 | { | ||
314 | int wake; | ||
315 | int irq; | ||
316 | struct irq_desc *desc; | ||
317 | const char *irq_name; | ||
318 | |||
319 | seq_printf(s, "wake irq count name\n"); | ||
320 | seq_printf(s, "----------------------\n"); | ||
321 | for (wake = 0; wake < PMC_MAX_WAKE_COUNT; wake++) { | ||
322 | irq = tegra_wake_to_irq(wake); | ||
323 | if (irq < 0) | ||
324 | continue; | ||
325 | |||
326 | desc = irq_to_desc(irq); | ||
327 | if (tegra_wake_irq_count[wake] == 0 && desc->action == NULL) | ||
328 | continue; | ||
329 | |||
330 | irq_name = (desc->action && desc->action->name) ? | ||
331 | desc->action->name : "???"; | ||
332 | |||
333 | seq_printf(s, "%4d %3d %5d %s\n", | ||
334 | wake, irq, tegra_wake_irq_count[wake], irq_name); | ||
335 | } | ||
336 | return 0; | ||
337 | } | ||
338 | |||
339 | static int tegra_pm_irq_debug_open(struct inode *inode, struct file *file) | ||
340 | { | ||
341 | return single_open(file, tegra_pm_irq_debug_show, NULL); | ||
342 | } | ||
343 | |||
344 | static const struct file_operations tegra_pm_irq_debug_fops = { | ||
345 | .open = tegra_pm_irq_debug_open, | ||
346 | .read = seq_read, | ||
347 | .llseek = seq_lseek, | ||
348 | .release = single_release, | ||
349 | }; | ||
350 | |||
351 | static int __init tegra_pm_irq_debug_init(void) | ||
352 | { | ||
353 | struct dentry *d; | ||
354 | |||
355 | d = debugfs_create_file("wake_irq", S_IRUGO, NULL, NULL, | ||
356 | &tegra_pm_irq_debug_fops); | ||
357 | if (!d) { | ||
358 | pr_err("Failed to create suspend_mode debug file\n"); | ||
359 | return -ENOMEM; | ||
360 | } | ||
361 | |||
362 | return 0; | ||
363 | } | ||
364 | |||
365 | late_initcall(tegra_pm_irq_debug_init); | ||
366 | #endif | ||