]>
Commit | Line | Data |
---|---|---|
9c21025c AH |
1 | /* |
2 | * arch/powerpc/platforms/embedded6xx/hlwd-pic.c | |
3 | * | |
4 | * Nintendo Wii "Hollywood" interrupt controller support. | |
5 | * Copyright (C) 2009 The GameCube Linux Team | |
6 | * Copyright (C) 2009 Albert Herranz | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU General Public License | |
10 | * as published by the Free Software Foundation; either version 2 | |
11 | * of the License, or (at your option) any later version. | |
12 | * | |
13 | */ | |
14 | #define DRV_MODULE_NAME "hlwd-pic" | |
15 | #define pr_fmt(fmt) DRV_MODULE_NAME ": " fmt | |
16 | ||
17 | #include <linux/kernel.h> | |
18 | #include <linux/init.h> | |
19 | #include <linux/irq.h> | |
20 | #include <linux/of.h> | |
21 | #include <asm/io.h> | |
22 | ||
23 | #include "hlwd-pic.h" | |
24 | ||
25 | #define HLWD_NR_IRQS 32 | |
26 | ||
27 | /* | |
28 | * Each interrupt has a corresponding bit in both | |
29 | * the Interrupt Cause (ICR) and Interrupt Mask (IMR) registers. | |
30 | * | |
31 | * Enabling/disabling an interrupt line involves asserting/clearing | |
32 | * the corresponding bit in IMR. ACK'ing a request simply involves | |
33 | * asserting the corresponding bit in ICR. | |
34 | */ | |
35 | #define HW_BROADWAY_ICR 0x00 | |
36 | #define HW_BROADWAY_IMR 0x04 | |
37 | ||
38 | ||
39 | /* | |
40 | * IRQ chip hooks. | |
41 | * | |
42 | */ | |
43 | ||
44 | static void hlwd_pic_mask_and_ack(unsigned int virq) | |
45 | { | |
46 | int irq = virq_to_hw(virq); | |
47 | void __iomem *io_base = get_irq_chip_data(virq); | |
48 | u32 mask = 1 << irq; | |
49 | ||
50 | clrbits32(io_base + HW_BROADWAY_IMR, mask); | |
51 | out_be32(io_base + HW_BROADWAY_ICR, mask); | |
52 | } | |
53 | ||
54 | static void hlwd_pic_ack(unsigned int virq) | |
55 | { | |
56 | int irq = virq_to_hw(virq); | |
57 | void __iomem *io_base = get_irq_chip_data(virq); | |
58 | ||
59 | out_be32(io_base + HW_BROADWAY_ICR, 1 << irq); | |
60 | } | |
61 | ||
62 | static void hlwd_pic_mask(unsigned int virq) | |
63 | { | |
64 | int irq = virq_to_hw(virq); | |
65 | void __iomem *io_base = get_irq_chip_data(virq); | |
66 | ||
67 | clrbits32(io_base + HW_BROADWAY_IMR, 1 << irq); | |
68 | } | |
69 | ||
70 | static void hlwd_pic_unmask(unsigned int virq) | |
71 | { | |
72 | int irq = virq_to_hw(virq); | |
73 | void __iomem *io_base = get_irq_chip_data(virq); | |
74 | ||
75 | setbits32(io_base + HW_BROADWAY_IMR, 1 << irq); | |
76 | } | |
77 | ||
78 | ||
79 | static struct irq_chip hlwd_pic = { | |
80 | .name = "hlwd-pic", | |
81 | .ack = hlwd_pic_ack, | |
82 | .mask_ack = hlwd_pic_mask_and_ack, | |
83 | .mask = hlwd_pic_mask, | |
84 | .unmask = hlwd_pic_unmask, | |
85 | }; | |
86 | ||
87 | /* | |
88 | * IRQ host hooks. | |
89 | * | |
90 | */ | |
91 | ||
92 | static struct irq_host *hlwd_irq_host; | |
93 | ||
94 | static int hlwd_pic_map(struct irq_host *h, unsigned int virq, | |
95 | irq_hw_number_t hwirq) | |
96 | { | |
97 | set_irq_chip_data(virq, h->host_data); | |
98 | get_irq_desc(virq)->status |= IRQ_LEVEL; | |
99 | set_irq_chip_and_handler(virq, &hlwd_pic, handle_level_irq); | |
100 | return 0; | |
101 | } | |
102 | ||
103 | static void hlwd_pic_unmap(struct irq_host *h, unsigned int irq) | |
104 | { | |
105 | set_irq_chip_data(irq, NULL); | |
106 | set_irq_chip(irq, NULL); | |
107 | } | |
108 | ||
109 | static struct irq_host_ops hlwd_irq_host_ops = { | |
110 | .map = hlwd_pic_map, | |
111 | .unmap = hlwd_pic_unmap, | |
112 | }; | |
113 | ||
114 | static unsigned int __hlwd_pic_get_irq(struct irq_host *h) | |
115 | { | |
116 | void __iomem *io_base = h->host_data; | |
117 | int irq; | |
118 | u32 irq_status; | |
119 | ||
120 | irq_status = in_be32(io_base + HW_BROADWAY_ICR) & | |
121 | in_be32(io_base + HW_BROADWAY_IMR); | |
122 | if (irq_status == 0) | |
123 | return NO_IRQ; /* no more IRQs pending */ | |
124 | ||
125 | irq = __ffs(irq_status); | |
126 | return irq_linear_revmap(h, irq); | |
127 | } | |
128 | ||
129 | static void hlwd_pic_irq_cascade(unsigned int cascade_virq, | |
130 | struct irq_desc *desc) | |
131 | { | |
132 | struct irq_host *irq_host = get_irq_data(cascade_virq); | |
133 | unsigned int virq; | |
134 | ||
7ccec3e7 | 135 | raw_spin_lock(&desc->lock); |
9c21025c | 136 | desc->chip->mask(cascade_virq); /* IRQ_LEVEL */ |
7ccec3e7 | 137 | raw_spin_unlock(&desc->lock); |
9c21025c AH |
138 | |
139 | virq = __hlwd_pic_get_irq(irq_host); | |
140 | if (virq != NO_IRQ) | |
141 | generic_handle_irq(virq); | |
142 | else | |
143 | pr_err("spurious interrupt!\n"); | |
144 | ||
7ccec3e7 | 145 | raw_spin_lock(&desc->lock); |
9c21025c AH |
146 | desc->chip->ack(cascade_virq); /* IRQ_LEVEL */ |
147 | if (!(desc->status & IRQ_DISABLED) && desc->chip->unmask) | |
148 | desc->chip->unmask(cascade_virq); | |
7ccec3e7 | 149 | raw_spin_unlock(&desc->lock); |
9c21025c AH |
150 | } |
151 | ||
152 | /* | |
153 | * Platform hooks. | |
154 | * | |
155 | */ | |
156 | ||
157 | static void __hlwd_quiesce(void __iomem *io_base) | |
158 | { | |
159 | /* mask and ack all IRQs */ | |
160 | out_be32(io_base + HW_BROADWAY_IMR, 0); | |
161 | out_be32(io_base + HW_BROADWAY_ICR, 0xffffffff); | |
162 | } | |
163 | ||
164 | struct irq_host *hlwd_pic_init(struct device_node *np) | |
165 | { | |
166 | struct irq_host *irq_host; | |
167 | struct resource res; | |
168 | void __iomem *io_base; | |
169 | int retval; | |
170 | ||
171 | retval = of_address_to_resource(np, 0, &res); | |
172 | if (retval) { | |
173 | pr_err("no io memory range found\n"); | |
174 | return NULL; | |
175 | } | |
176 | io_base = ioremap(res.start, resource_size(&res)); | |
177 | if (!io_base) { | |
178 | pr_err("ioremap failed\n"); | |
179 | return NULL; | |
180 | } | |
181 | ||
182 | pr_info("controller at 0x%08x mapped to 0x%p\n", res.start, io_base); | |
183 | ||
184 | __hlwd_quiesce(io_base); | |
185 | ||
186 | irq_host = irq_alloc_host(np, IRQ_HOST_MAP_LINEAR, HLWD_NR_IRQS, | |
187 | &hlwd_irq_host_ops, -1); | |
188 | if (!irq_host) { | |
189 | pr_err("failed to allocate irq_host\n"); | |
190 | return NULL; | |
191 | } | |
192 | irq_host->host_data = io_base; | |
193 | ||
194 | return irq_host; | |
195 | } | |
196 | ||
197 | unsigned int hlwd_pic_get_irq(void) | |
198 | { | |
199 | return __hlwd_pic_get_irq(hlwd_irq_host); | |
200 | } | |
201 | ||
202 | /* | |
203 | * Probe function. | |
204 | * | |
205 | */ | |
206 | ||
207 | void hlwd_pic_probe(void) | |
208 | { | |
209 | struct irq_host *host; | |
210 | struct device_node *np; | |
211 | const u32 *interrupts; | |
212 | int cascade_virq; | |
213 | ||
214 | for_each_compatible_node(np, NULL, "nintendo,hollywood-pic") { | |
215 | interrupts = of_get_property(np, "interrupts", NULL); | |
216 | if (interrupts) { | |
217 | host = hlwd_pic_init(np); | |
218 | BUG_ON(!host); | |
219 | cascade_virq = irq_of_parse_and_map(np, 0); | |
220 | set_irq_data(cascade_virq, host); | |
221 | set_irq_chained_handler(cascade_virq, | |
222 | hlwd_pic_irq_cascade); | |
223 | hlwd_irq_host = host; | |
224 | break; | |
225 | } | |
226 | } | |
227 | } | |
228 | ||
229 | /** | |
230 | * hlwd_quiesce() - quiesce hollywood irq controller | |
231 | * | |
232 | * Mask and ack all interrupt sources. | |
233 | * | |
234 | */ | |
235 | void hlwd_quiesce(void) | |
236 | { | |
237 | void __iomem *io_base = hlwd_irq_host->host_data; | |
238 | ||
239 | __hlwd_quiesce(io_base); | |
240 | } | |
241 |