1 | /* |
2 | * linux/arch/arm/mach-omap1/irq.c |
3 | * |
4 | * Interrupt handler for all OMAP boards |
5 | * |
6 | * Copyright (C) 2004 Nokia Corporation |
7 | * Written by Tony Lindgren <tony@atomide.com> |
8 | * Major cleanups by Juha Yrjölä <juha.yrjola@nokia.com> |
9 | * |
10 | * Completely re-written to support various OMAP chips with bank specific |
11 | * interrupt handlers. |
12 | * |
13 | * Some snippets of the code taken from the older OMAP interrupt handler |
14 | * Copyright (C) 2001 RidgeRun, Inc. Greg Lonnon <glonnon@ridgerun.com> |
15 | * |
16 | * GPIO interrupt handler moved to gpio.c by Juha Yrjola |
17 | * |
18 | * This program is free software; you can redistribute it and/or modify it |
19 | * under the terms of the GNU General Public License as published by the |
20 | * Free Software Foundation; either version 2 of the License, or (at your |
21 | * option) any later version. |
22 | * |
23 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED |
24 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
25 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN |
26 | * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
27 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
28 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
29 | * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
30 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
31 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
32 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
33 | * |
34 | * You should have received a copy of the GNU General Public License along |
35 | * with this program; if not, write to the Free Software Foundation, Inc., |
36 | * 675 Mass Ave, Cambridge, MA 02139, USA. |
37 | */ |
38 | #include <linux/init.h> |
39 | #include <linux/irq.h> |
40 | #include <linux/module.h> |
41 | #include <linux/sched.h> |
42 | #include <linux/interrupt.h> |
43 | #include <linux/io.h> |
44 | #include <linux/irqdomain.h> |
45 | |
46 | #include <asm/irq.h> |
47 | #include <asm/exception.h> |
48 | #include <asm/mach/irq.h> |
49 | |
50 | #include "soc.h" |
51 | #include "hardware.h" |
52 | #include "common.h" |
53 | |
54 | #define IRQ_BANK(irq) ((irq) >> 5) |
55 | #define IRQ_BIT(irq) ((irq) & 0x1f) |
56 | |
57 | struct omap_irq_bank { |
58 | unsigned long base_reg; |
59 | void __iomem *va; |
60 | unsigned long trigger_map; |
61 | unsigned long wake_enable; |
62 | }; |
63 | |
64 | static u32 omap_l2_irq; |
65 | static unsigned int irq_bank_count; |
66 | static struct omap_irq_bank *irq_banks; |
67 | static struct irq_domain *domain; |
68 | |
69 | static inline unsigned int irq_bank_readl(int bank, int offset) |
70 | { |
71 | return readl_relaxed(irq_banks[bank].va + offset); |
72 | } |
73 | static inline void irq_bank_writel(unsigned long value, int bank, int offset) |
74 | { |
75 | writel_relaxed(value, irq_banks[bank].va + offset); |
76 | } |
77 | |
78 | static void omap_ack_irq(int irq) |
79 | { |
80 | if (irq > 31) |
81 | writel_relaxed(0x1, irq_banks[1].va + IRQ_CONTROL_REG_OFFSET); |
82 | |
83 | writel_relaxed(0x1, irq_banks[0].va + IRQ_CONTROL_REG_OFFSET); |
84 | } |
85 | |
86 | static void omap_mask_ack_irq(struct irq_data *d) |
87 | { |
88 | struct irq_chip_type *ct = irq_data_get_chip_type(d); |
89 | |
90 | ct->chip.irq_mask(d); |
91 | omap_ack_irq(irq: d->irq); |
92 | } |
93 | |
94 | /* |
95 | * Allows tuning the IRQ type and priority |
96 | * |
97 | * NOTE: There is currently no OMAP fiq handler for Linux. Read the |
98 | * mailing list threads on FIQ handlers if you are planning to |
99 | * add a FIQ handler for OMAP. |
100 | */ |
101 | static void omap_irq_set_cfg(int irq, int fiq, int priority, int trigger) |
102 | { |
103 | signed int bank; |
104 | unsigned long val, offset; |
105 | |
106 | bank = IRQ_BANK(irq); |
107 | /* FIQ is only available on bank 0 interrupts */ |
108 | fiq = bank ? 0 : (fiq & 0x1); |
109 | val = fiq | ((priority & 0x1f) << 2) | ((trigger & 0x1) << 1); |
110 | offset = IRQ_ILR0_REG_OFFSET + IRQ_BIT(irq) * 0x4; |
111 | irq_bank_writel(value: val, bank, offset); |
112 | } |
113 | |
114 | #ifdef CONFIG_ARCH_OMAP15XX |
115 | static struct omap_irq_bank omap1510_irq_banks[] = { |
116 | { .base_reg = OMAP_IH1_BASE, .trigger_map = 0xb3febfff }, |
117 | { .base_reg = OMAP_IH2_BASE, .trigger_map = 0xffbfffed }, |
118 | }; |
119 | static struct omap_irq_bank omap310_irq_banks[] = { |
120 | { .base_reg = OMAP_IH1_BASE, .trigger_map = 0xb3faefc3 }, |
121 | { .base_reg = OMAP_IH2_BASE, .trigger_map = 0x65b3c061 }, |
122 | }; |
123 | #endif |
124 | |
125 | #if defined(CONFIG_ARCH_OMAP16XX) |
126 | |
127 | static struct omap_irq_bank omap1610_irq_banks[] = { |
128 | { .base_reg = OMAP_IH1_BASE, .trigger_map = 0xb3fefe8f }, |
129 | { .base_reg = OMAP_IH2_BASE, .trigger_map = 0xfdb7c1fd }, |
130 | { .base_reg = OMAP_IH2_BASE + 0x100, .trigger_map = 0xffffb7ff }, |
131 | { .base_reg = OMAP_IH2_BASE + 0x200, .trigger_map = 0xffffffff }, |
132 | }; |
133 | #endif |
134 | |
135 | asmlinkage void __exception_irq_entry omap1_handle_irq(struct pt_regs *regs) |
136 | { |
137 | void __iomem *l1 = irq_banks[0].va; |
138 | void __iomem *l2 = irq_banks[1].va; |
139 | u32 irqnr; |
140 | |
141 | do { |
142 | irqnr = readl_relaxed(l1 + IRQ_ITR_REG_OFFSET); |
143 | irqnr &= ~(readl_relaxed(l1 + IRQ_MIR_REG_OFFSET) & 0xffffffff); |
144 | if (!irqnr) |
145 | break; |
146 | |
147 | irqnr = readl_relaxed(l1 + IRQ_SIR_FIQ_REG_OFFSET); |
148 | if (irqnr) |
149 | goto irq; |
150 | |
151 | irqnr = readl_relaxed(l1 + IRQ_SIR_IRQ_REG_OFFSET); |
152 | if (irqnr == omap_l2_irq) { |
153 | irqnr = readl_relaxed(l2 + IRQ_SIR_IRQ_REG_OFFSET); |
154 | if (irqnr) |
155 | irqnr += 32; |
156 | } |
157 | irq: |
158 | if (irqnr) |
159 | generic_handle_domain_irq(domain, irqnr); |
160 | else |
161 | break; |
162 | } while (irqnr); |
163 | } |
164 | |
165 | static __init void |
166 | omap_alloc_gc(void __iomem *base, unsigned int irq_start, unsigned int num) |
167 | { |
168 | struct irq_chip_generic *gc; |
169 | struct irq_chip_type *ct; |
170 | |
171 | gc = irq_alloc_generic_chip(name: "MPU" , nr_ct: 1, irq_base: irq_start, reg_base: base, |
172 | handler: handle_level_irq); |
173 | ct = gc->chip_types; |
174 | ct->chip.irq_ack = omap_mask_ack_irq; |
175 | ct->chip.irq_mask = irq_gc_mask_set_bit; |
176 | ct->chip.irq_unmask = irq_gc_mask_clr_bit; |
177 | ct->chip.irq_set_wake = irq_gc_set_wake; |
178 | ct->regs.mask = IRQ_MIR_REG_OFFSET; |
179 | irq_setup_generic_chip(gc, IRQ_MSK(num), flags: IRQ_GC_INIT_MASK_CACHE, |
180 | clr: IRQ_NOREQUEST | IRQ_NOPROBE, set: 0); |
181 | } |
182 | |
183 | void __init omap1_init_irq(void) |
184 | { |
185 | struct irq_chip_type *ct; |
186 | struct irq_data *d = NULL; |
187 | int i, j, irq_base; |
188 | unsigned long nr_irqs; |
189 | |
190 | #ifdef CONFIG_ARCH_OMAP15XX |
191 | if (cpu_is_omap1510()) { |
192 | irq_banks = omap1510_irq_banks; |
193 | irq_bank_count = ARRAY_SIZE(omap1510_irq_banks); |
194 | } |
195 | if (cpu_is_omap310()) { |
196 | irq_banks = omap310_irq_banks; |
197 | irq_bank_count = ARRAY_SIZE(omap310_irq_banks); |
198 | } |
199 | #endif |
200 | #if defined(CONFIG_ARCH_OMAP16XX) |
201 | if (cpu_is_omap16xx()) { |
202 | irq_banks = omap1610_irq_banks; |
203 | irq_bank_count = ARRAY_SIZE(omap1610_irq_banks); |
204 | } |
205 | #endif |
206 | |
207 | for (i = 0; i < irq_bank_count; i++) { |
208 | irq_banks[i].va = ioremap(offset: irq_banks[i].base_reg, size: 0xff); |
209 | if (WARN_ON(!irq_banks[i].va)) |
210 | return; |
211 | } |
212 | |
213 | nr_irqs = irq_bank_count * 32; |
214 | |
215 | irq_base = irq_alloc_descs(-1, 0, nr_irqs, 0); |
216 | if (irq_base < 0) { |
217 | pr_warn("Couldn't allocate IRQ numbers\n" ); |
218 | irq_base = 0; |
219 | } |
220 | omap_l2_irq = irq_base; |
221 | omap_l2_irq -= NR_IRQS_LEGACY; |
222 | |
223 | domain = irq_domain_add_legacy(NULL, size: nr_irqs, first_irq: irq_base, first_hwirq: 0, |
224 | ops: &irq_domain_simple_ops, NULL); |
225 | |
226 | pr_info("Total of %lu interrupts in %i interrupt banks\n" , |
227 | nr_irqs, irq_bank_count); |
228 | |
229 | /* Mask and clear all interrupts */ |
230 | for (i = 0; i < irq_bank_count; i++) { |
231 | irq_bank_writel(value: ~0x0, bank: i, offset: IRQ_MIR_REG_OFFSET); |
232 | irq_bank_writel(value: 0x0, bank: i, offset: IRQ_ITR_REG_OFFSET); |
233 | } |
234 | |
235 | /* Clear any pending interrupts */ |
236 | irq_bank_writel(value: 0x03, bank: 0, offset: IRQ_CONTROL_REG_OFFSET); |
237 | irq_bank_writel(value: 0x03, bank: 1, offset: IRQ_CONTROL_REG_OFFSET); |
238 | |
239 | /* Install the interrupt handlers for each bank */ |
240 | for (i = 0; i < irq_bank_count; i++) { |
241 | for (j = i * 32; j < (i + 1) * 32; j++) { |
242 | int irq_trigger; |
243 | |
244 | irq_trigger = irq_banks[i].trigger_map >> IRQ_BIT(j); |
245 | omap_irq_set_cfg(irq: j, fiq: 0, priority: 0, trigger: irq_trigger); |
246 | irq_clear_status_flags(irq: j, clr: IRQ_NOREQUEST); |
247 | } |
248 | omap_alloc_gc(base: irq_banks[i].va, irq_start: irq_base + i * 32, num: 32); |
249 | } |
250 | |
251 | /* Unmask level 2 handler */ |
252 | d = irq_get_irq_data(irq: irq_find_mapping(domain, hwirq: omap_l2_irq)); |
253 | if (d) { |
254 | ct = irq_data_get_chip_type(d); |
255 | ct->chip.irq_unmask(d); |
256 | } |
257 | |
258 | set_handle_irq(omap1_handle_irq); |
259 | } |
260 | |