Commit | Line | Data |
---|---|---|
cebf589c | 1 | /* |
f3f66f59 | 2 | * Cell Internal Interrupt Controller |
cebf589c | 3 | * |
0ebfff14 BH |
4 | * Copyright (C) 2006 Benjamin Herrenschmidt (benh@kernel.crashing.org) |
5 | * IBM, Corp. | |
6 | * | |
cebf589c AB |
7 | * (C) Copyright IBM Deutschland Entwicklung GmbH 2005 |
8 | * | |
9 | * Author: Arnd Bergmann <arndb@de.ibm.com> | |
10 | * | |
11 | * This program is free software; you can redistribute it and/or modify | |
12 | * it under the terms of the GNU General Public License as published by | |
13 | * the Free Software Foundation; either version 2, or (at your option) | |
14 | * any later version. | |
15 | * | |
16 | * This program is distributed in the hope that it will be useful, | |
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
19 | * GNU General Public License for more details. | |
20 | * | |
21 | * You should have received a copy of the GNU General Public License | |
22 | * along with this program; if not, write to the Free Software | |
23 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
24 | */ | |
25 | ||
cebf589c AB |
26 | #include <linux/interrupt.h> |
27 | #include <linux/irq.h> | |
2fb9d206 | 28 | #include <linux/module.h> |
cebf589c AB |
29 | #include <linux/percpu.h> |
30 | #include <linux/types.h> | |
0ebfff14 | 31 | #include <linux/ioport.h> |
cebf589c AB |
32 | |
33 | #include <asm/io.h> | |
34 | #include <asm/pgtable.h> | |
35 | #include <asm/prom.h> | |
36 | #include <asm/ptrace.h> | |
0ebfff14 | 37 | #include <asm/machdep.h> |
cebf589c | 38 | |
f3f66f59 | 39 | #include "interrupt.h" |
acf7d768 | 40 | #include "cbe_regs.h" |
cebf589c AB |
41 | |
42 | struct iic { | |
acf7d768 | 43 | struct cbe_iic_thread_regs __iomem *regs; |
2fb9d206 | 44 | u8 target_id; |
b9e5b4e6 BH |
45 | u8 eoi_stack[16]; |
46 | int eoi_ptr; | |
0ebfff14 | 47 | struct irq_host *host; |
cebf589c AB |
48 | }; |
49 | ||
50 | static DEFINE_PER_CPU(struct iic, iic); | |
0ebfff14 BH |
51 | #define IIC_NODE_COUNT 2 |
52 | static struct irq_host *iic_hosts[IIC_NODE_COUNT]; | |
53 | ||
54 | /* Convert between "pending" bits and hw irq number */ | |
55 | static irq_hw_number_t iic_pending_to_hwnum(struct cbe_iic_pending_bits bits) | |
56 | { | |
57 | unsigned char unit = bits.source & 0xf; | |
58 | ||
59 | if (bits.flags & CBE_IIC_IRQ_IPI) | |
60 | return IIC_IRQ_IPI0 | (bits.prio >> 4); | |
61 | else if (bits.class <= 3) | |
62 | return (bits.class << 4) | unit; | |
63 | else | |
64 | return IIC_IRQ_INVALID; | |
65 | } | |
cebf589c | 66 | |
b9e5b4e6 | 67 | static void iic_mask(unsigned int irq) |
cebf589c | 68 | { |
cebf589c AB |
69 | } |
70 | ||
b9e5b4e6 | 71 | static void iic_unmask(unsigned int irq) |
cebf589c AB |
72 | { |
73 | } | |
74 | ||
b9e5b4e6 | 75 | static void iic_eoi(unsigned int irq) |
cebf589c | 76 | { |
b9e5b4e6 BH |
77 | struct iic *iic = &__get_cpu_var(iic); |
78 | out_be64(&iic->regs->prio, iic->eoi_stack[--iic->eoi_ptr]); | |
79 | BUG_ON(iic->eoi_ptr < 0); | |
cebf589c AB |
80 | } |
81 | ||
b9e5b4e6 | 82 | static struct irq_chip iic_chip = { |
f3f66f59 | 83 | .typename = " CELL-IIC ", |
b9e5b4e6 BH |
84 | .mask = iic_mask, |
85 | .unmask = iic_unmask, | |
86 | .eoi = iic_eoi, | |
cebf589c AB |
87 | }; |
88 | ||
cebf589c | 89 | /* Get an IRQ number from the pending state register of the IIC */ |
0ebfff14 | 90 | static unsigned int iic_get_irq(struct pt_regs *regs) |
d0e57c68 | 91 | { |
0ebfff14 BH |
92 | struct cbe_iic_pending_bits pending; |
93 | struct iic *iic; | |
94 | ||
95 | iic = &__get_cpu_var(iic); | |
96 | *(unsigned long *) &pending = | |
97 | in_be64((unsigned long __iomem *) &iic->regs->pending_destr); | |
98 | iic->eoi_stack[++iic->eoi_ptr] = pending.prio; | |
99 | BUG_ON(iic->eoi_ptr > 15); | |
100 | if (pending.flags & CBE_IIC_IRQ_VALID) | |
101 | return irq_linear_revmap(iic->host, | |
102 | iic_pending_to_hwnum(pending)); | |
103 | return NO_IRQ; | |
cebf589c AB |
104 | } |
105 | ||
106 | #ifdef CONFIG_SMP | |
a84195f3 AB |
107 | |
108 | /* Use the highest interrupt priorities for IPI */ | |
109 | static inline int iic_ipi_to_irq(int ipi) | |
110 | { | |
0ebfff14 | 111 | return IIC_IRQ_IPI0 + IIC_NUM_IPIS - 1 - ipi; |
a84195f3 AB |
112 | } |
113 | ||
114 | static inline int iic_irq_to_ipi(int irq) | |
115 | { | |
0ebfff14 | 116 | return IIC_NUM_IPIS - 1 - (irq - IIC_IRQ_IPI0); |
a84195f3 AB |
117 | } |
118 | ||
cebf589c AB |
119 | void iic_setup_cpu(void) |
120 | { | |
121 | out_be64(&__get_cpu_var(iic).regs->prio, 0xff); | |
122 | } | |
123 | ||
124 | void iic_cause_IPI(int cpu, int mesg) | |
125 | { | |
a84195f3 | 126 | out_be64(&per_cpu(iic, cpu).regs->generate, (IIC_NUM_IPIS - 1 - mesg) << 4); |
cebf589c AB |
127 | } |
128 | ||
2fb9d206 AB |
129 | u8 iic_get_target_id(int cpu) |
130 | { | |
131 | return per_cpu(iic, cpu).target_id; | |
132 | } | |
133 | EXPORT_SYMBOL_GPL(iic_get_target_id); | |
134 | ||
0ebfff14 BH |
135 | struct irq_host *iic_get_irq_host(int node) |
136 | { | |
137 | if (node < 0 || node >= IIC_NODE_COUNT) | |
138 | return NULL; | |
139 | return iic_hosts[node]; | |
140 | } | |
141 | EXPORT_SYMBOL_GPL(iic_get_irq_host); | |
142 | ||
143 | ||
cebf589c AB |
144 | static irqreturn_t iic_ipi_action(int irq, void *dev_id, struct pt_regs *regs) |
145 | { | |
0ebfff14 BH |
146 | int ipi = (int)(long)dev_id; |
147 | ||
148 | smp_message_recv(ipi, regs); | |
149 | ||
cebf589c AB |
150 | return IRQ_HANDLED; |
151 | } | |
152 | ||
a84195f3 | 153 | static void iic_request_ipi(int ipi, const char *name) |
cebf589c | 154 | { |
0ebfff14 | 155 | int node, virq; |
a84195f3 | 156 | |
0ebfff14 BH |
157 | for (node = 0; node < IIC_NODE_COUNT; node++) { |
158 | char *rname; | |
159 | if (iic_hosts[node] == NULL) | |
160 | continue; | |
161 | virq = irq_create_mapping(iic_hosts[node], | |
6e99e458 | 162 | iic_ipi_to_irq(ipi)); |
0ebfff14 BH |
163 | if (virq == NO_IRQ) { |
164 | printk(KERN_ERR | |
165 | "iic: failed to map IPI %s on node %d\n", | |
166 | name, node); | |
167 | continue; | |
168 | } | |
169 | rname = kzalloc(strlen(name) + 16, GFP_KERNEL); | |
170 | if (rname) | |
171 | sprintf(rname, "%s node %d", name, node); | |
172 | else | |
173 | rname = (char *)name; | |
174 | if (request_irq(virq, iic_ipi_action, IRQF_DISABLED, | |
175 | rname, (void *)(long)ipi)) | |
176 | printk(KERN_ERR | |
177 | "iic: failed to request IPI %s on node %d\n", | |
178 | name, node); | |
179 | } | |
cebf589c AB |
180 | } |
181 | ||
182 | void iic_request_IPIs(void) | |
183 | { | |
a84195f3 AB |
184 | iic_request_ipi(PPC_MSG_CALL_FUNCTION, "IPI-call"); |
185 | iic_request_ipi(PPC_MSG_RESCHEDULE, "IPI-resched"); | |
cebf589c | 186 | #ifdef CONFIG_DEBUGGER |
a84195f3 | 187 | iic_request_ipi(PPC_MSG_DEBUGGER_BREAK, "IPI-debug"); |
cebf589c AB |
188 | #endif /* CONFIG_DEBUGGER */ |
189 | } | |
0ebfff14 | 190 | |
cebf589c AB |
191 | #endif /* CONFIG_SMP */ |
192 | ||
0ebfff14 BH |
193 | |
194 | static int iic_host_match(struct irq_host *h, struct device_node *node) | |
195 | { | |
196 | return h->host_data != NULL && node == h->host_data; | |
197 | } | |
198 | ||
199 | static int iic_host_map(struct irq_host *h, unsigned int virq, | |
6e99e458 | 200 | irq_hw_number_t hw) |
0ebfff14 BH |
201 | { |
202 | if (hw < IIC_IRQ_IPI0) | |
203 | set_irq_chip_and_handler(virq, &iic_chip, handle_fasteoi_irq); | |
204 | else | |
205 | set_irq_chip_and_handler(virq, &iic_chip, handle_percpu_irq); | |
206 | return 0; | |
207 | } | |
208 | ||
209 | static int iic_host_xlate(struct irq_host *h, struct device_node *ct, | |
210 | u32 *intspec, unsigned int intsize, | |
211 | irq_hw_number_t *out_hwirq, unsigned int *out_flags) | |
212 | ||
cebf589c | 213 | { |
0ebfff14 BH |
214 | /* Currently, we don't translate anything. That needs to be fixed as |
215 | * we get better defined device-trees. iic interrupts have to be | |
216 | * explicitely mapped by whoever needs them | |
217 | */ | |
218 | return -ENODEV; | |
219 | } | |
220 | ||
221 | static struct irq_host_ops iic_host_ops = { | |
222 | .match = iic_host_match, | |
223 | .map = iic_host_map, | |
224 | .xlate = iic_host_xlate, | |
225 | }; | |
226 | ||
227 | static void __init init_one_iic(unsigned int hw_cpu, unsigned long addr, | |
228 | struct irq_host *host) | |
229 | { | |
230 | /* XXX FIXME: should locate the linux CPU number from the HW cpu | |
231 | * number properly. We are lucky for now | |
232 | */ | |
233 | struct iic *iic = &per_cpu(iic, hw_cpu); | |
cebf589c | 234 | |
0ebfff14 BH |
235 | iic->regs = ioremap(addr, sizeof(struct cbe_iic_thread_regs)); |
236 | BUG_ON(iic->regs == NULL); | |
b9e5b4e6 | 237 | |
0ebfff14 BH |
238 | iic->target_id = ((hw_cpu & 2) << 3) | ((hw_cpu & 1) ? 0xf : 0xe); |
239 | iic->eoi_stack[0] = 0xff; | |
240 | iic->host = host; | |
241 | out_be64(&iic->regs->prio, 0); | |
242 | ||
243 | printk(KERN_INFO "IIC for CPU %d at %lx mapped to %p, target id 0x%x\n", | |
244 | hw_cpu, addr, iic->regs, iic->target_id); | |
245 | } | |
246 | ||
247 | static int __init setup_iic(void) | |
248 | { | |
249 | struct device_node *dn; | |
250 | struct resource r0, r1; | |
251 | struct irq_host *host; | |
252 | int found = 0; | |
c61c27d5 | 253 | const u32 *np; |
0ebfff14 BH |
254 | |
255 | for (dn = NULL; | |
256 | (dn = of_find_node_by_name(dn,"interrupt-controller")) != NULL;) { | |
257 | if (!device_is_compatible(dn, | |
258 | "IBM,CBEA-Internal-Interrupt-Controller")) | |
259 | continue; | |
c61c27d5 | 260 | np = get_property(dn, "ibm,interrupt-server-ranges", NULL); |
0ebfff14 BH |
261 | if (np == NULL) { |
262 | printk(KERN_WARNING "IIC: CPU association not found\n"); | |
263 | of_node_put(dn); | |
264 | return -ENODEV; | |
cebf589c | 265 | } |
0ebfff14 BH |
266 | if (of_address_to_resource(dn, 0, &r0) || |
267 | of_address_to_resource(dn, 1, &r1)) { | |
268 | printk(KERN_WARNING "IIC: Can't resolve addresses\n"); | |
269 | of_node_put(dn); | |
270 | return -ENODEV; | |
271 | } | |
272 | host = NULL; | |
273 | if (found < IIC_NODE_COUNT) { | |
274 | host = irq_alloc_host(IRQ_HOST_MAP_LINEAR, | |
275 | IIC_SOURCE_COUNT, | |
276 | &iic_host_ops, | |
277 | IIC_IRQ_INVALID); | |
278 | iic_hosts[found] = host; | |
279 | BUG_ON(iic_hosts[found] == NULL); | |
280 | iic_hosts[found]->host_data = of_node_get(dn); | |
281 | found++; | |
282 | } | |
283 | init_one_iic(np[0], r0.start, host); | |
284 | init_one_iic(np[1], r1.start, host); | |
cebf589c | 285 | } |
0ebfff14 BH |
286 | |
287 | if (found) | |
288 | return 0; | |
289 | else | |
290 | return -ENODEV; | |
cebf589c AB |
291 | } |
292 | ||
b9e5b4e6 | 293 | void __init iic_init_IRQ(void) |
cebf589c | 294 | { |
0ebfff14 | 295 | /* Discover and initialize iics */ |
d0e57c68 | 296 | if (setup_iic() < 0) |
0ebfff14 | 297 | panic("IIC: Failed to initialize !\n"); |
d0e57c68 | 298 | |
0ebfff14 BH |
299 | /* Set master interrupt handling function */ |
300 | ppc_md.get_irq = iic_get_irq; | |
b9e5b4e6 | 301 | |
0ebfff14 BH |
302 | /* Enable on current CPU */ |
303 | iic_setup_cpu(); | |
cebf589c | 304 | } |