diff options
author | Dale Farnsworth <dale@farnsworth.org> | 2007-05-11 20:55:24 -0400 |
---|---|---|
committer | Paul Mackerras <paulus@samba.org> | 2007-05-11 21:32:49 -0400 |
commit | e44b8941908ec9ccf03b52713c9e7d3471bada8c (patch) | |
tree | 750f6cf76959a64592223b32d11883bb04b8aba1 /arch/powerpc/sysdev/mv64x60_pic.c | |
parent | ae4b3fbc7a91ea4e5685edb0310bb185a12e5943 (diff) |
[POWERPC] Add interrupt support for Marvell mv64x60 chips
There are 3 interrupt groups each with its own status/mask registers.
We use a separate struct irq_chip for each interrupt group and handle
interrupts in two stages or levels: level 1 selects the appropriate
struct irq_chip, and level 2 selects individual interrupts within
that irq_chip.
Signed-off-by: Dale Farnsworth <dale@farnsworth.org>
Signed-off-by: Paul Mackerras <paulus@samba.org>
Diffstat (limited to 'arch/powerpc/sysdev/mv64x60_pic.c')
-rw-r--r-- | arch/powerpc/sysdev/mv64x60_pic.c | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/arch/powerpc/sysdev/mv64x60_pic.c b/arch/powerpc/sysdev/mv64x60_pic.c new file mode 100644 index 000000000000..01d316287772 --- /dev/null +++ b/arch/powerpc/sysdev/mv64x60_pic.c | |||
@@ -0,0 +1,305 @@ | |||
1 | /* | ||
2 | * Interrupt handling for Marvell mv64360/mv64460 host bridges (Discovery) | ||
3 | * | ||
4 | * Author: Dale Farnsworth <dale@farnsworth.org> | ||
5 | * | ||
6 | * 2007 (c) MontaVista, Software, Inc. This file is licensed under | ||
7 | * the terms of the GNU General Public License version 2. This program | ||
8 | * is licensed "as is" without any warranty of any kind, whether express | ||
9 | * or implied. | ||
10 | */ | ||
11 | |||
12 | #include <linux/stddef.h> | ||
13 | #include <linux/kernel.h> | ||
14 | #include <linux/init.h> | ||
15 | #include <linux/irq.h> | ||
16 | #include <linux/interrupt.h> | ||
17 | #include <linux/spinlock.h> | ||
18 | |||
19 | #include <asm/byteorder.h> | ||
20 | #include <asm/io.h> | ||
21 | #include <asm/prom.h> | ||
22 | #include <asm/irq.h> | ||
23 | |||
24 | #include "mv64x60.h" | ||
25 | |||
26 | /* Interrupt Controller Interface Registers */ | ||
27 | #define MV64X60_IC_MAIN_CAUSE_LO 0x0004 | ||
28 | #define MV64X60_IC_MAIN_CAUSE_HI 0x000c | ||
29 | #define MV64X60_IC_CPU0_INTR_MASK_LO 0x0014 | ||
30 | #define MV64X60_IC_CPU0_INTR_MASK_HI 0x001c | ||
31 | #define MV64X60_IC_CPU0_SELECT_CAUSE 0x0024 | ||
32 | |||
33 | #define MV64X60_HIGH_GPP_GROUPS 0x0f000000 | ||
34 | #define MV64X60_SELECT_CAUSE_HIGH 0x40000000 | ||
35 | |||
36 | /* General Purpose Pins Controller Interface Registers */ | ||
37 | #define MV64x60_GPP_INTR_CAUSE 0x0008 | ||
38 | #define MV64x60_GPP_INTR_MASK 0x000c | ||
39 | |||
40 | #define MV64x60_LEVEL1_LOW 0 | ||
41 | #define MV64x60_LEVEL1_HIGH 1 | ||
42 | #define MV64x60_LEVEL1_GPP 2 | ||
43 | |||
44 | #define MV64x60_LEVEL1_MASK 0x00000060 | ||
45 | #define MV64x60_LEVEL1_OFFSET 5 | ||
46 | |||
47 | #define MV64x60_LEVEL2_MASK 0x0000001f | ||
48 | |||
49 | #define MV64x60_NUM_IRQS 96 | ||
50 | |||
51 | static DEFINE_SPINLOCK(mv64x60_lock); | ||
52 | |||
53 | static void __iomem *mv64x60_irq_reg_base; | ||
54 | static void __iomem *mv64x60_gpp_reg_base; | ||
55 | |||
56 | /* | ||
57 | * Interrupt Controller Handling | ||
58 | * | ||
59 | * The interrupt controller handles three groups of interrupts: | ||
60 | * main low: IRQ0-IRQ31 | ||
61 | * main high: IRQ32-IRQ63 | ||
62 | * gpp: IRQ64-IRQ95 | ||
63 | * | ||
64 | * This code handles interrupts in two levels. Level 1 selects the | ||
65 | * interrupt group, and level 2 selects an IRQ within that group. | ||
66 | * Each group has its own irq_chip structure. | ||
67 | */ | ||
68 | |||
69 | static u32 mv64x60_cached_low_mask; | ||
70 | static u32 mv64x60_cached_high_mask = MV64X60_HIGH_GPP_GROUPS; | ||
71 | static u32 mv64x60_cached_gpp_mask; | ||
72 | |||
73 | static struct irq_host *mv64x60_irq_host; | ||
74 | |||
75 | /* | ||
76 | * mv64x60_chip_low functions | ||
77 | */ | ||
78 | |||
79 | static void mv64x60_mask_low(unsigned int virq) | ||
80 | { | ||
81 | int level2 = irq_map[virq].hwirq & MV64x60_LEVEL2_MASK; | ||
82 | unsigned long flags; | ||
83 | |||
84 | spin_lock_irqsave(&mv64x60_lock, flags); | ||
85 | mv64x60_cached_low_mask &= ~(1 << level2); | ||
86 | out_le32(mv64x60_irq_reg_base + MV64X60_IC_CPU0_INTR_MASK_LO, | ||
87 | mv64x60_cached_low_mask); | ||
88 | spin_unlock_irqrestore(&mv64x60_lock, flags); | ||
89 | (void)in_le32(mv64x60_irq_reg_base + MV64X60_IC_CPU0_INTR_MASK_LO); | ||
90 | } | ||
91 | |||
92 | static void mv64x60_unmask_low(unsigned int virq) | ||
93 | { | ||
94 | int level2 = irq_map[virq].hwirq & MV64x60_LEVEL2_MASK; | ||
95 | unsigned long flags; | ||
96 | |||
97 | spin_lock_irqsave(&mv64x60_lock, flags); | ||
98 | mv64x60_cached_low_mask |= 1 << level2; | ||
99 | out_le32(mv64x60_irq_reg_base + MV64X60_IC_CPU0_INTR_MASK_LO, | ||
100 | mv64x60_cached_low_mask); | ||
101 | spin_unlock_irqrestore(&mv64x60_lock, flags); | ||
102 | (void)in_le32(mv64x60_irq_reg_base + MV64X60_IC_CPU0_INTR_MASK_LO); | ||
103 | } | ||
104 | |||
105 | static struct irq_chip mv64x60_chip_low = { | ||
106 | .name = "mv64x60_low", | ||
107 | .mask = mv64x60_mask_low, | ||
108 | .mask_ack = mv64x60_mask_low, | ||
109 | .unmask = mv64x60_unmask_low, | ||
110 | }; | ||
111 | |||
112 | /* | ||
113 | * mv64x60_chip_high functions | ||
114 | */ | ||
115 | |||
116 | static void mv64x60_mask_high(unsigned int virq) | ||
117 | { | ||
118 | int level2 = irq_map[virq].hwirq & MV64x60_LEVEL2_MASK; | ||
119 | unsigned long flags; | ||
120 | |||
121 | spin_lock_irqsave(&mv64x60_lock, flags); | ||
122 | mv64x60_cached_high_mask &= ~(1 << level2); | ||
123 | out_le32(mv64x60_irq_reg_base + MV64X60_IC_CPU0_INTR_MASK_HI, | ||
124 | mv64x60_cached_high_mask); | ||
125 | spin_unlock_irqrestore(&mv64x60_lock, flags); | ||
126 | (void)in_le32(mv64x60_irq_reg_base + MV64X60_IC_CPU0_INTR_MASK_HI); | ||
127 | } | ||
128 | |||
129 | static void mv64x60_unmask_high(unsigned int virq) | ||
130 | { | ||
131 | int level2 = irq_map[virq].hwirq & MV64x60_LEVEL2_MASK; | ||
132 | unsigned long flags; | ||
133 | |||
134 | spin_lock_irqsave(&mv64x60_lock, flags); | ||
135 | mv64x60_cached_high_mask |= 1 << level2; | ||
136 | out_le32(mv64x60_irq_reg_base + MV64X60_IC_CPU0_INTR_MASK_HI, | ||
137 | mv64x60_cached_high_mask); | ||
138 | spin_unlock_irqrestore(&mv64x60_lock, flags); | ||
139 | (void)in_le32(mv64x60_irq_reg_base + MV64X60_IC_CPU0_INTR_MASK_HI); | ||
140 | } | ||
141 | |||
142 | static struct irq_chip mv64x60_chip_high = { | ||
143 | .name = "mv64x60_high", | ||
144 | .mask = mv64x60_mask_high, | ||
145 | .mask_ack = mv64x60_mask_high, | ||
146 | .unmask = mv64x60_unmask_high, | ||
147 | }; | ||
148 | |||
149 | /* | ||
150 | * mv64x60_chip_gpp functions | ||
151 | */ | ||
152 | |||
153 | static void mv64x60_mask_gpp(unsigned int virq) | ||
154 | { | ||
155 | int level2 = irq_map[virq].hwirq & MV64x60_LEVEL2_MASK; | ||
156 | unsigned long flags; | ||
157 | |||
158 | spin_lock_irqsave(&mv64x60_lock, flags); | ||
159 | mv64x60_cached_gpp_mask &= ~(1 << level2); | ||
160 | out_le32(mv64x60_gpp_reg_base + MV64x60_GPP_INTR_MASK, | ||
161 | mv64x60_cached_gpp_mask); | ||
162 | spin_unlock_irqrestore(&mv64x60_lock, flags); | ||
163 | (void)in_le32(mv64x60_gpp_reg_base + MV64x60_GPP_INTR_MASK); | ||
164 | } | ||
165 | |||
166 | static void mv64x60_mask_ack_gpp(unsigned int virq) | ||
167 | { | ||
168 | int level2 = irq_map[virq].hwirq & MV64x60_LEVEL2_MASK; | ||
169 | unsigned long flags; | ||
170 | |||
171 | spin_lock_irqsave(&mv64x60_lock, flags); | ||
172 | mv64x60_cached_gpp_mask &= ~(1 << level2); | ||
173 | out_le32(mv64x60_gpp_reg_base + MV64x60_GPP_INTR_MASK, | ||
174 | mv64x60_cached_gpp_mask); | ||
175 | out_le32(mv64x60_gpp_reg_base + MV64x60_GPP_INTR_CAUSE, | ||
176 | ~(1 << level2)); | ||
177 | spin_unlock_irqrestore(&mv64x60_lock, flags); | ||
178 | (void)in_le32(mv64x60_gpp_reg_base + MV64x60_GPP_INTR_CAUSE); | ||
179 | } | ||
180 | |||
181 | static void mv64x60_unmask_gpp(unsigned int virq) | ||
182 | { | ||
183 | int level2 = irq_map[virq].hwirq & MV64x60_LEVEL2_MASK; | ||
184 | unsigned long flags; | ||
185 | |||
186 | spin_lock_irqsave(&mv64x60_lock, flags); | ||
187 | mv64x60_cached_gpp_mask |= 1 << level2; | ||
188 | out_le32(mv64x60_gpp_reg_base + MV64x60_GPP_INTR_MASK, | ||
189 | mv64x60_cached_gpp_mask); | ||
190 | spin_unlock_irqrestore(&mv64x60_lock, flags); | ||
191 | (void)in_le32(mv64x60_gpp_reg_base + MV64x60_GPP_INTR_MASK); | ||
192 | } | ||
193 | |||
194 | static struct irq_chip mv64x60_chip_gpp = { | ||
195 | .name = "mv64x60_gpp", | ||
196 | .mask = mv64x60_mask_gpp, | ||
197 | .mask_ack = mv64x60_mask_ack_gpp, | ||
198 | .unmask = mv64x60_unmask_gpp, | ||
199 | }; | ||
200 | |||
201 | /* | ||
202 | * mv64x60_host_ops functions | ||
203 | */ | ||
204 | |||
205 | static int mv64x60_host_match(struct irq_host *h, struct device_node *np) | ||
206 | { | ||
207 | return mv64x60_irq_host->host_data == np; | ||
208 | } | ||
209 | |||
210 | static struct irq_chip *mv64x60_chips[] = { | ||
211 | [MV64x60_LEVEL1_LOW] = &mv64x60_chip_low, | ||
212 | [MV64x60_LEVEL1_HIGH] = &mv64x60_chip_high, | ||
213 | [MV64x60_LEVEL1_GPP] = &mv64x60_chip_gpp, | ||
214 | }; | ||
215 | |||
216 | static int mv64x60_host_map(struct irq_host *h, unsigned int virq, | ||
217 | irq_hw_number_t hwirq) | ||
218 | { | ||
219 | int level1; | ||
220 | |||
221 | get_irq_desc(virq)->status |= IRQ_LEVEL; | ||
222 | |||
223 | level1 = (hwirq & MV64x60_LEVEL1_MASK) >> MV64x60_LEVEL1_OFFSET; | ||
224 | BUG_ON(level1 > MV64x60_LEVEL1_GPP); | ||
225 | set_irq_chip_and_handler(virq, mv64x60_chips[level1], handle_level_irq); | ||
226 | |||
227 | return 0; | ||
228 | } | ||
229 | |||
230 | static struct irq_host_ops mv64x60_host_ops = { | ||
231 | .match = mv64x60_host_match, | ||
232 | .map = mv64x60_host_map, | ||
233 | }; | ||
234 | |||
235 | /* | ||
236 | * Global functions | ||
237 | */ | ||
238 | |||
239 | void __init mv64x60_init_irq(void) | ||
240 | { | ||
241 | struct device_node *np; | ||
242 | phys_addr_t paddr; | ||
243 | unsigned int size; | ||
244 | const unsigned int *reg; | ||
245 | unsigned long flags; | ||
246 | |||
247 | np = of_find_compatible_node(NULL, NULL, "marvell,mv64x60-gpp"); | ||
248 | reg = of_get_property(np, "reg", &size); | ||
249 | paddr = of_translate_address(np, reg); | ||
250 | mv64x60_gpp_reg_base = ioremap(paddr, reg[1]); | ||
251 | of_node_put(np); | ||
252 | |||
253 | np = of_find_compatible_node(NULL, NULL, "marvell,mv64x60-pic"); | ||
254 | reg = of_get_property(np, "reg", &size); | ||
255 | paddr = of_translate_address(np, reg); | ||
256 | of_node_put(np); | ||
257 | mv64x60_irq_reg_base = ioremap(paddr, reg[1]); | ||
258 | |||
259 | mv64x60_irq_host = irq_alloc_host(IRQ_HOST_MAP_LINEAR, MV64x60_NUM_IRQS, | ||
260 | &mv64x60_host_ops, MV64x60_NUM_IRQS); | ||
261 | |||
262 | mv64x60_irq_host->host_data = np; | ||
263 | |||
264 | spin_lock_irqsave(&mv64x60_lock, flags); | ||
265 | out_le32(mv64x60_gpp_reg_base + MV64x60_GPP_INTR_MASK, | ||
266 | mv64x60_cached_gpp_mask); | ||
267 | out_le32(mv64x60_irq_reg_base + MV64X60_IC_CPU0_INTR_MASK_LO, | ||
268 | mv64x60_cached_low_mask); | ||
269 | out_le32(mv64x60_irq_reg_base + MV64X60_IC_CPU0_INTR_MASK_HI, | ||
270 | mv64x60_cached_high_mask); | ||
271 | |||
272 | out_le32(mv64x60_gpp_reg_base + MV64x60_GPP_INTR_CAUSE, 0); | ||
273 | out_le32(mv64x60_irq_reg_base + MV64X60_IC_MAIN_CAUSE_LO, 0); | ||
274 | out_le32(mv64x60_irq_reg_base + MV64X60_IC_MAIN_CAUSE_HI, 0); | ||
275 | spin_unlock_irqrestore(&mv64x60_lock, flags); | ||
276 | } | ||
277 | |||
278 | unsigned int mv64x60_get_irq(void) | ||
279 | { | ||
280 | u32 cause; | ||
281 | int level1; | ||
282 | irq_hw_number_t hwirq; | ||
283 | int virq = NO_IRQ; | ||
284 | |||
285 | cause = in_le32(mv64x60_irq_reg_base + MV64X60_IC_CPU0_SELECT_CAUSE); | ||
286 | if (cause & MV64X60_SELECT_CAUSE_HIGH) { | ||
287 | cause &= mv64x60_cached_high_mask; | ||
288 | level1 = MV64x60_LEVEL1_HIGH; | ||
289 | if (cause & MV64X60_HIGH_GPP_GROUPS) { | ||
290 | cause = in_le32(mv64x60_gpp_reg_base + | ||
291 | MV64x60_GPP_INTR_CAUSE); | ||
292 | cause &= mv64x60_cached_gpp_mask; | ||
293 | level1 = MV64x60_LEVEL1_GPP; | ||
294 | } | ||
295 | } else { | ||
296 | cause &= mv64x60_cached_low_mask; | ||
297 | level1 = MV64x60_LEVEL1_LOW; | ||
298 | } | ||
299 | if (cause) { | ||
300 | hwirq = (level1 << MV64x60_LEVEL1_OFFSET) | __ilog2(cause); | ||
301 | virq = irq_linear_revmap(mv64x60_irq_host, hwirq); | ||
302 | } | ||
303 | |||
304 | return virq; | ||
305 | } | ||