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> | |
25 | ||
26 | #include <asm/hardware/gic.h> | |
27 | ||
28 | #include <mach/omap-wakeupgen.h> | |
29 | ||
30 | #define NR_REG_BANKS 4 | |
31 | #define MAX_IRQS 128 | |
32 | #define WKG_MASK_ALL 0x00000000 | |
33 | #define WKG_UNMASK_ALL 0xffffffff | |
34 | #define CPU_ENA_OFFSET 0x400 | |
35 | #define CPU0_ID 0x0 | |
36 | #define CPU1_ID 0x1 | |
37 | ||
38 | static void __iomem *wakeupgen_base; | |
39 | static DEFINE_PER_CPU(u32 [NR_REG_BANKS], irqmasks); | |
40 | static DEFINE_SPINLOCK(wakeupgen_lock); | |
41 | static unsigned int irq_target_cpu[NR_IRQS]; | |
42 | ||
43 | /* | |
44 | * Static helper functions. | |
45 | */ | |
46 | static inline u32 wakeupgen_readl(u8 idx, u32 cpu) | |
47 | { | |
48 | return __raw_readl(wakeupgen_base + OMAP_WKG_ENB_A_0 + | |
49 | (cpu * CPU_ENA_OFFSET) + (idx * 4)); | |
50 | } | |
51 | ||
52 | static inline void wakeupgen_writel(u32 val, u8 idx, u32 cpu) | |
53 | { | |
54 | __raw_writel(val, wakeupgen_base + OMAP_WKG_ENB_A_0 + | |
55 | (cpu * CPU_ENA_OFFSET) + (idx * 4)); | |
56 | } | |
57 | ||
58 | static void _wakeupgen_set_all(unsigned int cpu, unsigned int reg) | |
59 | { | |
60 | u8 i; | |
61 | ||
62 | for (i = 0; i < NR_REG_BANKS; i++) | |
63 | wakeupgen_writel(reg, i, cpu); | |
64 | } | |
65 | ||
66 | static inline int _wakeupgen_get_irq_info(u32 irq, u32 *bit_posn, u8 *reg_index) | |
67 | { | |
68 | unsigned int spi_irq; | |
69 | ||
70 | /* | |
71 | * PPIs and SGIs are not supported. | |
72 | */ | |
73 | if (irq < OMAP44XX_IRQ_GIC_START) | |
74 | return -EINVAL; | |
75 | ||
76 | /* | |
77 | * Subtract the GIC offset. | |
78 | */ | |
79 | spi_irq = irq - OMAP44XX_IRQ_GIC_START; | |
80 | if (spi_irq > MAX_IRQS) { | |
81 | pr_err("omap wakeupGen: Invalid IRQ%d\n", irq); | |
82 | return -EINVAL; | |
83 | } | |
84 | ||
85 | /* | |
86 | * Each WakeupGen register controls 32 interrupt. | |
87 | * i.e. 1 bit per SPI IRQ | |
88 | */ | |
89 | *reg_index = spi_irq >> 5; | |
90 | *bit_posn = spi_irq %= 32; | |
91 | ||
92 | return 0; | |
93 | } | |
94 | ||
95 | static void _wakeupgen_clear(unsigned int irq, unsigned int cpu) | |
96 | { | |
97 | u32 val, bit_number; | |
98 | u8 i; | |
99 | ||
100 | if (_wakeupgen_get_irq_info(irq, &bit_number, &i)) | |
101 | return; | |
102 | ||
103 | val = wakeupgen_readl(i, cpu); | |
104 | val &= ~BIT(bit_number); | |
105 | wakeupgen_writel(val, i, cpu); | |
106 | } | |
107 | ||
108 | static void _wakeupgen_set(unsigned int irq, unsigned int cpu) | |
109 | { | |
110 | u32 val, bit_number; | |
111 | u8 i; | |
112 | ||
113 | if (_wakeupgen_get_irq_info(irq, &bit_number, &i)) | |
114 | return; | |
115 | ||
116 | val = wakeupgen_readl(i, cpu); | |
117 | val |= BIT(bit_number); | |
118 | wakeupgen_writel(val, i, cpu); | |
119 | } | |
120 | ||
121 | static void _wakeupgen_save_masks(unsigned int cpu) | |
122 | { | |
123 | u8 i; | |
124 | ||
125 | for (i = 0; i < NR_REG_BANKS; i++) | |
126 | per_cpu(irqmasks, cpu)[i] = wakeupgen_readl(i, cpu); | |
127 | } | |
128 | ||
129 | static void _wakeupgen_restore_masks(unsigned int cpu) | |
130 | { | |
131 | u8 i; | |
132 | ||
133 | for (i = 0; i < NR_REG_BANKS; i++) | |
134 | wakeupgen_writel(per_cpu(irqmasks, cpu)[i], i, cpu); | |
135 | } | |
136 | ||
137 | /* | |
138 | * Architecture specific Mask extension | |
139 | */ | |
140 | static void wakeupgen_mask(struct irq_data *d) | |
141 | { | |
142 | unsigned long flags; | |
143 | ||
144 | spin_lock_irqsave(&wakeupgen_lock, flags); | |
145 | _wakeupgen_clear(d->irq, irq_target_cpu[d->irq]); | |
146 | spin_unlock_irqrestore(&wakeupgen_lock, flags); | |
147 | } | |
148 | ||
149 | /* | |
150 | * Architecture specific Unmask extension | |
151 | */ | |
152 | static void wakeupgen_unmask(struct irq_data *d) | |
153 | { | |
154 | unsigned long flags; | |
155 | ||
156 | spin_lock_irqsave(&wakeupgen_lock, flags); | |
157 | _wakeupgen_set(d->irq, irq_target_cpu[d->irq]); | |
158 | spin_unlock_irqrestore(&wakeupgen_lock, flags); | |
159 | } | |
160 | ||
161 | /* | |
162 | * Mask or unmask all interrupts on given CPU. | |
163 | * 0 = Mask all interrupts on the 'cpu' | |
164 | * 1 = Unmask all interrupts on the 'cpu' | |
165 | * Ensure that the initial mask is maintained. This is faster than | |
166 | * iterating through GIC registers to arrive at the correct masks. | |
167 | */ | |
168 | static void wakeupgen_irqmask_all(unsigned int cpu, unsigned int set) | |
169 | { | |
170 | unsigned long flags; | |
171 | ||
172 | spin_lock_irqsave(&wakeupgen_lock, flags); | |
173 | if (set) { | |
174 | _wakeupgen_save_masks(cpu); | |
175 | _wakeupgen_set_all(cpu, WKG_MASK_ALL); | |
176 | } else { | |
177 | _wakeupgen_set_all(cpu, WKG_UNMASK_ALL); | |
178 | _wakeupgen_restore_masks(cpu); | |
179 | } | |
180 | spin_unlock_irqrestore(&wakeupgen_lock, flags); | |
181 | } | |
182 | ||
183 | /* | |
184 | * Initialise the wakeupgen module. | |
185 | */ | |
186 | int __init omap_wakeupgen_init(void) | |
187 | { | |
188 | int i; | |
189 | unsigned int boot_cpu = smp_processor_id(); | |
190 | ||
191 | /* Not supported on OMAP4 ES1.0 silicon */ | |
192 | if (omap_rev() == OMAP4430_REV_ES1_0) { | |
193 | WARN(1, "WakeupGen: Not supported on OMAP4430 ES1.0\n"); | |
194 | return -EPERM; | |
195 | } | |
196 | ||
197 | /* Static mapping, never released */ | |
198 | wakeupgen_base = ioremap(OMAP44XX_WKUPGEN_BASE, SZ_4K); | |
199 | if (WARN_ON(!wakeupgen_base)) | |
200 | return -ENOMEM; | |
201 | ||
202 | /* Clear all IRQ bitmasks at wakeupGen level */ | |
203 | for (i = 0; i < NR_REG_BANKS; i++) { | |
204 | wakeupgen_writel(0, i, CPU0_ID); | |
205 | wakeupgen_writel(0, i, CPU1_ID); | |
206 | } | |
207 | ||
208 | /* | |
209 | * Override GIC architecture specific functions to add | |
210 | * OMAP WakeupGen interrupt controller along with GIC | |
211 | */ | |
212 | gic_arch_extn.irq_mask = wakeupgen_mask; | |
213 | gic_arch_extn.irq_unmask = wakeupgen_unmask; | |
214 | gic_arch_extn.flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE; | |
215 | ||
216 | /* | |
217 | * FIXME: Add support to set_smp_affinity() once the core | |
218 | * GIC code has necessary hooks in place. | |
219 | */ | |
220 | ||
221 | /* Associate all the IRQs to boot CPU like GIC init does. */ | |
222 | for (i = 0; i < NR_IRQS; i++) | |
223 | irq_target_cpu[i] = boot_cpu; | |
224 | ||
225 | return 0; | |
226 | } |