Commit | Line | Data |
---|---|---|
fcf6efa3 SS |
1 | /* |
2 | * OMAP WakeupGen Source file | |
3 | * | |
4 | * OMAP WakeupGen is the interrupt controller extension used along | |
5 | * with ARM GIC to wake the CPU out from low power states on | |
6 | * external interrupts. It is responsible for generating wakeup | |
7 | * event from the incoming interrupts and enable bits. It is | |
8 | * implemented in MPU always ON power domain. During normal operation, | |
9 | * WakeupGen delivers external interrupts directly to the GIC. | |
10 | * | |
11 | * Copyright (C) 2011 Texas Instruments, Inc. | |
12 | * Santosh Shilimkar <santosh.shilimkar@ti.com> | |
13 | * | |
14 | * This program is free software; you can redistribute it and/or modify | |
15 | * it under the terms of the GNU General Public License version 2 as | |
16 | * published by the Free Software Foundation. | |
17 | */ | |
18 | ||
19 | #include <linux/kernel.h> | |
20 | #include <linux/init.h> | |
21 | #include <linux/io.h> | |
22 | #include <linux/irq.h> | |
23 | #include <linux/platform_device.h> | |
24 | #include <linux/cpu.h> | |
0f3cf2ec SS |
25 | #include <linux/notifier.h> |
26 | #include <linux/cpu_pm.h> | |
fcf6efa3 SS |
27 | |
28 | #include <asm/hardware/gic.h> | |
29 | ||
30 | #include <mach/omap-wakeupgen.h> | |
0f3cf2ec SS |
31 | #include <mach/omap-secure.h> |
32 | ||
33 | #include "omap4-sar-layout.h" | |
34 | #include "common.h" | |
fcf6efa3 SS |
35 | |
36 | #define NR_REG_BANKS 4 | |
37 | #define MAX_IRQS 128 | |
38 | #define WKG_MASK_ALL 0x00000000 | |
39 | #define WKG_UNMASK_ALL 0xffffffff | |
40 | #define CPU_ENA_OFFSET 0x400 | |
41 | #define CPU0_ID 0x0 | |
42 | #define CPU1_ID 0x1 | |
43 | ||
44 | static void __iomem *wakeupgen_base; | |
0f3cf2ec | 45 | static void __iomem *sar_base; |
fcf6efa3 SS |
46 | static DEFINE_SPINLOCK(wakeupgen_lock); |
47 | static unsigned int irq_target_cpu[NR_IRQS]; | |
48 | ||
49 | /* | |
50 | * Static helper functions. | |
51 | */ | |
52 | static inline u32 wakeupgen_readl(u8 idx, u32 cpu) | |
53 | { | |
54 | return __raw_readl(wakeupgen_base + OMAP_WKG_ENB_A_0 + | |
55 | (cpu * CPU_ENA_OFFSET) + (idx * 4)); | |
56 | } | |
57 | ||
58 | static inline void wakeupgen_writel(u32 val, u8 idx, u32 cpu) | |
59 | { | |
60 | __raw_writel(val, wakeupgen_base + OMAP_WKG_ENB_A_0 + | |
61 | (cpu * CPU_ENA_OFFSET) + (idx * 4)); | |
62 | } | |
63 | ||
0f3cf2ec SS |
64 | static inline void sar_writel(u32 val, u32 offset, u8 idx) |
65 | { | |
66 | __raw_writel(val, sar_base + offset + (idx * 4)); | |
67 | } | |
68 | ||
fcf6efa3 SS |
69 | static inline int _wakeupgen_get_irq_info(u32 irq, u32 *bit_posn, u8 *reg_index) |
70 | { | |
71 | unsigned int spi_irq; | |
72 | ||
73 | /* | |
74 | * PPIs and SGIs are not supported. | |
75 | */ | |
76 | if (irq < OMAP44XX_IRQ_GIC_START) | |
77 | return -EINVAL; | |
78 | ||
79 | /* | |
80 | * Subtract the GIC offset. | |
81 | */ | |
82 | spi_irq = irq - OMAP44XX_IRQ_GIC_START; | |
83 | if (spi_irq > MAX_IRQS) { | |
84 | pr_err("omap wakeupGen: Invalid IRQ%d\n", irq); | |
85 | return -EINVAL; | |
86 | } | |
87 | ||
88 | /* | |
89 | * Each WakeupGen register controls 32 interrupt. | |
90 | * i.e. 1 bit per SPI IRQ | |
91 | */ | |
92 | *reg_index = spi_irq >> 5; | |
93 | *bit_posn = spi_irq %= 32; | |
94 | ||
95 | return 0; | |
96 | } | |
97 | ||
98 | static void _wakeupgen_clear(unsigned int irq, unsigned int cpu) | |
99 | { | |
100 | u32 val, bit_number; | |
101 | u8 i; | |
102 | ||
103 | if (_wakeupgen_get_irq_info(irq, &bit_number, &i)) | |
104 | return; | |
105 | ||
106 | val = wakeupgen_readl(i, cpu); | |
107 | val &= ~BIT(bit_number); | |
108 | wakeupgen_writel(val, i, cpu); | |
109 | } | |
110 | ||
111 | static void _wakeupgen_set(unsigned int irq, unsigned int cpu) | |
112 | { | |
113 | u32 val, bit_number; | |
114 | u8 i; | |
115 | ||
116 | if (_wakeupgen_get_irq_info(irq, &bit_number, &i)) | |
117 | return; | |
118 | ||
119 | val = wakeupgen_readl(i, cpu); | |
120 | val |= BIT(bit_number); | |
121 | wakeupgen_writel(val, i, cpu); | |
122 | } | |
123 | ||
fcf6efa3 SS |
124 | /* |
125 | * Architecture specific Mask extension | |
126 | */ | |
127 | static void wakeupgen_mask(struct irq_data *d) | |
128 | { | |
129 | unsigned long flags; | |
130 | ||
131 | spin_lock_irqsave(&wakeupgen_lock, flags); | |
132 | _wakeupgen_clear(d->irq, irq_target_cpu[d->irq]); | |
133 | spin_unlock_irqrestore(&wakeupgen_lock, flags); | |
134 | } | |
135 | ||
136 | /* | |
137 | * Architecture specific Unmask extension | |
138 | */ | |
139 | static void wakeupgen_unmask(struct irq_data *d) | |
140 | { | |
141 | unsigned long flags; | |
142 | ||
143 | spin_lock_irqsave(&wakeupgen_lock, flags); | |
144 | _wakeupgen_set(d->irq, irq_target_cpu[d->irq]); | |
145 | spin_unlock_irqrestore(&wakeupgen_lock, flags); | |
146 | } | |
147 | ||
bb1dbe7c KH |
148 | #ifdef CONFIG_HOTPLUG_CPU |
149 | static DEFINE_PER_CPU(u32 [NR_REG_BANKS], irqmasks); | |
150 | ||
151 | static void _wakeupgen_save_masks(unsigned int cpu) | |
152 | { | |
153 | u8 i; | |
154 | ||
155 | for (i = 0; i < NR_REG_BANKS; i++) | |
156 | per_cpu(irqmasks, cpu)[i] = wakeupgen_readl(i, cpu); | |
157 | } | |
158 | ||
159 | static void _wakeupgen_restore_masks(unsigned int cpu) | |
160 | { | |
161 | u8 i; | |
162 | ||
163 | for (i = 0; i < NR_REG_BANKS; i++) | |
164 | wakeupgen_writel(per_cpu(irqmasks, cpu)[i], i, cpu); | |
165 | } | |
166 | ||
167 | static void _wakeupgen_set_all(unsigned int cpu, unsigned int reg) | |
168 | { | |
169 | u8 i; | |
170 | ||
171 | for (i = 0; i < NR_REG_BANKS; i++) | |
172 | wakeupgen_writel(reg, i, cpu); | |
173 | } | |
174 | ||
fcf6efa3 SS |
175 | /* |
176 | * Mask or unmask all interrupts on given CPU. | |
177 | * 0 = Mask all interrupts on the 'cpu' | |
178 | * 1 = Unmask all interrupts on the 'cpu' | |
179 | * Ensure that the initial mask is maintained. This is faster than | |
180 | * iterating through GIC registers to arrive at the correct masks. | |
181 | */ | |
182 | static void wakeupgen_irqmask_all(unsigned int cpu, unsigned int set) | |
183 | { | |
184 | unsigned long flags; | |
185 | ||
186 | spin_lock_irqsave(&wakeupgen_lock, flags); | |
187 | if (set) { | |
188 | _wakeupgen_save_masks(cpu); | |
189 | _wakeupgen_set_all(cpu, WKG_MASK_ALL); | |
190 | } else { | |
191 | _wakeupgen_set_all(cpu, WKG_UNMASK_ALL); | |
192 | _wakeupgen_restore_masks(cpu); | |
193 | } | |
194 | spin_unlock_irqrestore(&wakeupgen_lock, flags); | |
195 | } | |
bb1dbe7c | 196 | #endif |
fcf6efa3 | 197 | |
0f3cf2ec SS |
198 | #ifdef CONFIG_CPU_PM |
199 | /* | |
200 | * Save WakeupGen interrupt context in SAR BANK3. Restore is done by | |
201 | * ROM code. WakeupGen IP is integrated along with GIC to manage the | |
202 | * interrupt wakeups from CPU low power states. It manages | |
203 | * masking/unmasking of Shared peripheral interrupts(SPI). So the | |
204 | * interrupt enable/disable control should be in sync and consistent | |
205 | * at WakeupGen and GIC so that interrupts are not lost. | |
206 | */ | |
207 | static void irq_save_context(void) | |
208 | { | |
209 | u32 i, val; | |
210 | ||
211 | if (omap_rev() == OMAP4430_REV_ES1_0) | |
212 | return; | |
213 | ||
214 | if (!sar_base) | |
215 | sar_base = omap4_get_sar_ram_base(); | |
216 | ||
217 | for (i = 0; i < NR_REG_BANKS; i++) { | |
218 | /* Save the CPUx interrupt mask for IRQ 0 to 127 */ | |
219 | val = wakeupgen_readl(i, 0); | |
220 | sar_writel(val, WAKEUPGENENB_OFFSET_CPU0, i); | |
221 | val = wakeupgen_readl(i, 1); | |
222 | sar_writel(val, WAKEUPGENENB_OFFSET_CPU1, i); | |
223 | ||
224 | /* | |
225 | * Disable the secure interrupts for CPUx. The restore | |
226 | * code blindly restores secure and non-secure interrupt | |
227 | * masks from SAR RAM. Secure interrupts are not suppose | |
228 | * to be enabled from HLOS. So overwrite the SAR location | |
229 | * so that the secure interrupt remains disabled. | |
230 | */ | |
231 | sar_writel(0x0, WAKEUPGENENB_SECURE_OFFSET_CPU0, i); | |
232 | sar_writel(0x0, WAKEUPGENENB_SECURE_OFFSET_CPU1, i); | |
233 | } | |
234 | ||
235 | /* Save AuxBoot* registers */ | |
236 | val = __raw_readl(wakeupgen_base + OMAP_AUX_CORE_BOOT_0); | |
237 | __raw_writel(val, sar_base + AUXCOREBOOT0_OFFSET); | |
238 | val = __raw_readl(wakeupgen_base + OMAP_AUX_CORE_BOOT_0); | |
239 | __raw_writel(val, sar_base + AUXCOREBOOT1_OFFSET); | |
240 | ||
241 | /* Save SyncReq generation logic */ | |
242 | val = __raw_readl(wakeupgen_base + OMAP_AUX_CORE_BOOT_0); | |
243 | __raw_writel(val, sar_base + AUXCOREBOOT0_OFFSET); | |
244 | val = __raw_readl(wakeupgen_base + OMAP_AUX_CORE_BOOT_0); | |
245 | __raw_writel(val, sar_base + AUXCOREBOOT1_OFFSET); | |
246 | ||
247 | /* Save SyncReq generation logic */ | |
248 | val = __raw_readl(wakeupgen_base + OMAP_PTMSYNCREQ_MASK); | |
249 | __raw_writel(val, sar_base + PTMSYNCREQ_MASK_OFFSET); | |
250 | val = __raw_readl(wakeupgen_base + OMAP_PTMSYNCREQ_EN); | |
251 | __raw_writel(val, sar_base + PTMSYNCREQ_EN_OFFSET); | |
252 | ||
253 | /* Set the Backup Bit Mask status */ | |
254 | val = __raw_readl(sar_base + SAR_BACKUP_STATUS_OFFSET); | |
255 | val |= SAR_BACKUP_STATUS_WAKEUPGEN; | |
256 | __raw_writel(val, sar_base + SAR_BACKUP_STATUS_OFFSET); | |
257 | } | |
258 | ||
259 | /* | |
260 | * Clear WakeupGen SAR backup status. | |
261 | */ | |
8c3d4534 | 262 | static void irq_sar_clear(void) |
0f3cf2ec SS |
263 | { |
264 | u32 val; | |
265 | val = __raw_readl(sar_base + SAR_BACKUP_STATUS_OFFSET); | |
266 | val &= ~SAR_BACKUP_STATUS_WAKEUPGEN; | |
267 | __raw_writel(val, sar_base + SAR_BACKUP_STATUS_OFFSET); | |
268 | } | |
269 | ||
270 | /* | |
271 | * Save GIC and Wakeupgen interrupt context using secure API | |
272 | * for HS/EMU devices. | |
273 | */ | |
274 | static void irq_save_secure_context(void) | |
275 | { | |
276 | u32 ret; | |
277 | ret = omap_secure_dispatcher(OMAP4_HAL_SAVEGIC_INDEX, | |
278 | FLAG_START_CRITICAL, | |
279 | 0, 0, 0, 0, 0); | |
280 | if (ret != API_HAL_RET_VALUE_OK) | |
281 | pr_err("GIC and Wakeupgen context save failed\n"); | |
282 | } | |
283 | #endif | |
284 | ||
b5b4f288 SS |
285 | #ifdef CONFIG_HOTPLUG_CPU |
286 | static int __cpuinit irq_cpu_hotplug_notify(struct notifier_block *self, | |
287 | unsigned long action, void *hcpu) | |
288 | { | |
289 | unsigned int cpu = (unsigned int)hcpu; | |
290 | ||
291 | switch (action) { | |
292 | case CPU_ONLINE: | |
293 | wakeupgen_irqmask_all(cpu, 0); | |
294 | break; | |
295 | case CPU_DEAD: | |
296 | wakeupgen_irqmask_all(cpu, 1); | |
297 | break; | |
298 | } | |
299 | return NOTIFY_OK; | |
300 | } | |
301 | ||
302 | static struct notifier_block __refdata irq_hotplug_notifier = { | |
303 | .notifier_call = irq_cpu_hotplug_notify, | |
304 | }; | |
305 | ||
306 | static void __init irq_hotplug_init(void) | |
307 | { | |
308 | register_hotcpu_notifier(&irq_hotplug_notifier); | |
309 | } | |
310 | #else | |
311 | static void __init irq_hotplug_init(void) | |
312 | {} | |
313 | #endif | |
314 | ||
0f3cf2ec SS |
315 | #ifdef CONFIG_CPU_PM |
316 | static int irq_notifier(struct notifier_block *self, unsigned long cmd, void *v) | |
317 | { | |
318 | switch (cmd) { | |
319 | case CPU_CLUSTER_PM_ENTER: | |
320 | if (omap_type() == OMAP2_DEVICE_TYPE_GP) | |
321 | irq_save_context(); | |
322 | else | |
323 | irq_save_secure_context(); | |
324 | break; | |
325 | case CPU_CLUSTER_PM_EXIT: | |
326 | if (omap_type() == OMAP2_DEVICE_TYPE_GP) | |
327 | irq_sar_clear(); | |
328 | break; | |
329 | } | |
330 | return NOTIFY_OK; | |
331 | } | |
332 | ||
333 | static struct notifier_block irq_notifier_block = { | |
334 | .notifier_call = irq_notifier, | |
335 | }; | |
336 | ||
337 | static void __init irq_pm_init(void) | |
338 | { | |
339 | cpu_pm_register_notifier(&irq_notifier_block); | |
340 | } | |
341 | #else | |
342 | static void __init irq_pm_init(void) | |
343 | {} | |
344 | #endif | |
345 | ||
fcf6efa3 SS |
346 | /* |
347 | * Initialise the wakeupgen module. | |
348 | */ | |
349 | int __init omap_wakeupgen_init(void) | |
350 | { | |
351 | int i; | |
352 | unsigned int boot_cpu = smp_processor_id(); | |
353 | ||
354 | /* Not supported on OMAP4 ES1.0 silicon */ | |
355 | if (omap_rev() == OMAP4430_REV_ES1_0) { | |
356 | WARN(1, "WakeupGen: Not supported on OMAP4430 ES1.0\n"); | |
357 | return -EPERM; | |
358 | } | |
359 | ||
360 | /* Static mapping, never released */ | |
361 | wakeupgen_base = ioremap(OMAP44XX_WKUPGEN_BASE, SZ_4K); | |
362 | if (WARN_ON(!wakeupgen_base)) | |
363 | return -ENOMEM; | |
364 | ||
365 | /* Clear all IRQ bitmasks at wakeupGen level */ | |
366 | for (i = 0; i < NR_REG_BANKS; i++) { | |
367 | wakeupgen_writel(0, i, CPU0_ID); | |
368 | wakeupgen_writel(0, i, CPU1_ID); | |
369 | } | |
370 | ||
371 | /* | |
372 | * Override GIC architecture specific functions to add | |
373 | * OMAP WakeupGen interrupt controller along with GIC | |
374 | */ | |
375 | gic_arch_extn.irq_mask = wakeupgen_mask; | |
376 | gic_arch_extn.irq_unmask = wakeupgen_unmask; | |
377 | gic_arch_extn.flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE; | |
378 | ||
379 | /* | |
380 | * FIXME: Add support to set_smp_affinity() once the core | |
381 | * GIC code has necessary hooks in place. | |
382 | */ | |
383 | ||
384 | /* Associate all the IRQs to boot CPU like GIC init does. */ | |
385 | for (i = 0; i < NR_IRQS; i++) | |
386 | irq_target_cpu[i] = boot_cpu; | |
387 | ||
b5b4f288 | 388 | irq_hotplug_init(); |
0f3cf2ec | 389 | irq_pm_init(); |
b5b4f288 | 390 | |
fcf6efa3 SS |
391 | return 0; |
392 | } |