diff options
Diffstat (limited to 'arch/ppc64/kernel/bpa_iic.c')
-rw-r--r-- | arch/ppc64/kernel/bpa_iic.c | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/arch/ppc64/kernel/bpa_iic.c b/arch/ppc64/kernel/bpa_iic.c new file mode 100644 index 000000000000..c8f3dc3fad70 --- /dev/null +++ b/arch/ppc64/kernel/bpa_iic.c | |||
@@ -0,0 +1,270 @@ | |||
1 | /* | ||
2 | * BPA Internal Interrupt Controller | ||
3 | * | ||
4 | * (C) Copyright IBM Deutschland Entwicklung GmbH 2005 | ||
5 | * | ||
6 | * Author: Arnd Bergmann <arndb@de.ibm.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2, or (at your option) | ||
11 | * any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU General Public License for more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License | ||
19 | * along with this program; if not, write to the Free Software | ||
20 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
21 | */ | ||
22 | |||
23 | #include <linux/config.h> | ||
24 | #include <linux/interrupt.h> | ||
25 | #include <linux/irq.h> | ||
26 | #include <linux/percpu.h> | ||
27 | #include <linux/types.h> | ||
28 | |||
29 | #include <asm/io.h> | ||
30 | #include <asm/pgtable.h> | ||
31 | #include <asm/prom.h> | ||
32 | #include <asm/ptrace.h> | ||
33 | |||
34 | #include "bpa_iic.h" | ||
35 | |||
36 | struct iic_pending_bits { | ||
37 | u32 data; | ||
38 | u8 flags; | ||
39 | u8 class; | ||
40 | u8 source; | ||
41 | u8 prio; | ||
42 | }; | ||
43 | |||
44 | enum iic_pending_flags { | ||
45 | IIC_VALID = 0x80, | ||
46 | IIC_IPI = 0x40, | ||
47 | }; | ||
48 | |||
49 | struct iic_regs { | ||
50 | struct iic_pending_bits pending; | ||
51 | struct iic_pending_bits pending_destr; | ||
52 | u64 generate; | ||
53 | u64 prio; | ||
54 | }; | ||
55 | |||
56 | struct iic { | ||
57 | struct iic_regs __iomem *regs; | ||
58 | }; | ||
59 | |||
60 | static DEFINE_PER_CPU(struct iic, iic); | ||
61 | |||
62 | void iic_local_enable(void) | ||
63 | { | ||
64 | out_be64(&__get_cpu_var(iic).regs->prio, 0xff); | ||
65 | } | ||
66 | |||
67 | void iic_local_disable(void) | ||
68 | { | ||
69 | out_be64(&__get_cpu_var(iic).regs->prio, 0x0); | ||
70 | } | ||
71 | |||
72 | static unsigned int iic_startup(unsigned int irq) | ||
73 | { | ||
74 | return 0; | ||
75 | } | ||
76 | |||
77 | static void iic_enable(unsigned int irq) | ||
78 | { | ||
79 | iic_local_enable(); | ||
80 | } | ||
81 | |||
82 | static void iic_disable(unsigned int irq) | ||
83 | { | ||
84 | } | ||
85 | |||
86 | static void iic_end(unsigned int irq) | ||
87 | { | ||
88 | iic_local_enable(); | ||
89 | } | ||
90 | |||
91 | static struct hw_interrupt_type iic_pic = { | ||
92 | .typename = " BPA-IIC ", | ||
93 | .startup = iic_startup, | ||
94 | .enable = iic_enable, | ||
95 | .disable = iic_disable, | ||
96 | .end = iic_end, | ||
97 | }; | ||
98 | |||
99 | static int iic_external_get_irq(struct iic_pending_bits pending) | ||
100 | { | ||
101 | int irq; | ||
102 | unsigned char node, unit; | ||
103 | |||
104 | node = pending.source >> 4; | ||
105 | unit = pending.source & 0xf; | ||
106 | irq = -1; | ||
107 | |||
108 | /* | ||
109 | * This mapping is specific to the Broadband | ||
110 | * Engine. We might need to get the numbers | ||
111 | * from the device tree to support future CPUs. | ||
112 | */ | ||
113 | switch (unit) { | ||
114 | case 0x00: | ||
115 | case 0x0b: | ||
116 | /* | ||
117 | * One of these units can be connected | ||
118 | * to an external interrupt controller. | ||
119 | */ | ||
120 | if (pending.prio > 0x3f || | ||
121 | pending.class != 2) | ||
122 | break; | ||
123 | irq = IIC_EXT_OFFSET | ||
124 | + spider_get_irq(pending.prio + node * IIC_NODE_STRIDE) | ||
125 | + node * IIC_NODE_STRIDE; | ||
126 | break; | ||
127 | case 0x01 ... 0x04: | ||
128 | case 0x07 ... 0x0a: | ||
129 | /* | ||
130 | * These units are connected to the SPEs | ||
131 | */ | ||
132 | if (pending.class > 2) | ||
133 | break; | ||
134 | irq = IIC_SPE_OFFSET | ||
135 | + pending.class * IIC_CLASS_STRIDE | ||
136 | + node * IIC_NODE_STRIDE | ||
137 | + unit; | ||
138 | break; | ||
139 | } | ||
140 | if (irq == -1) | ||
141 | printk(KERN_WARNING "Unexpected interrupt class %02x, " | ||
142 | "source %02x, prio %02x, cpu %02x\n", pending.class, | ||
143 | pending.source, pending.prio, smp_processor_id()); | ||
144 | return irq; | ||
145 | } | ||
146 | |||
147 | /* Get an IRQ number from the pending state register of the IIC */ | ||
148 | int iic_get_irq(struct pt_regs *regs) | ||
149 | { | ||
150 | struct iic *iic; | ||
151 | int irq; | ||
152 | struct iic_pending_bits pending; | ||
153 | |||
154 | iic = &__get_cpu_var(iic); | ||
155 | *(unsigned long *) &pending = | ||
156 | in_be64((unsigned long __iomem *) &iic->regs->pending_destr); | ||
157 | |||
158 | irq = -1; | ||
159 | if (pending.flags & IIC_VALID) { | ||
160 | if (pending.flags & IIC_IPI) { | ||
161 | irq = IIC_IPI_OFFSET + (pending.prio >> 4); | ||
162 | /* | ||
163 | if (irq > 0x80) | ||
164 | printk(KERN_WARNING "Unexpected IPI prio %02x" | ||
165 | "on CPU %02x\n", pending.prio, | ||
166 | smp_processor_id()); | ||
167 | */ | ||
168 | } else { | ||
169 | irq = iic_external_get_irq(pending); | ||
170 | } | ||
171 | } | ||
172 | return irq; | ||
173 | } | ||
174 | |||
175 | static struct iic_regs __iomem *find_iic(int cpu) | ||
176 | { | ||
177 | struct device_node *np; | ||
178 | int nodeid = cpu / 2; | ||
179 | unsigned long regs; | ||
180 | struct iic_regs __iomem *iic_regs; | ||
181 | |||
182 | for (np = of_find_node_by_type(NULL, "cpu"); | ||
183 | np; | ||
184 | np = of_find_node_by_type(np, "cpu")) { | ||
185 | if (nodeid == *(int *)get_property(np, "node-id", NULL)) | ||
186 | break; | ||
187 | } | ||
188 | |||
189 | if (!np) { | ||
190 | printk(KERN_WARNING "IIC: CPU %d not found\n", cpu); | ||
191 | iic_regs = NULL; | ||
192 | } else { | ||
193 | regs = *(long *)get_property(np, "iic", NULL); | ||
194 | |||
195 | /* hack until we have decided on the devtree info */ | ||
196 | regs += 0x400; | ||
197 | if (cpu & 1) | ||
198 | regs += 0x20; | ||
199 | |||
200 | printk(KERN_DEBUG "IIC for CPU %d at %lx\n", cpu, regs); | ||
201 | iic_regs = __ioremap(regs, sizeof(struct iic_regs), | ||
202 | _PAGE_NO_CACHE); | ||
203 | } | ||
204 | return iic_regs; | ||
205 | } | ||
206 | |||
207 | #ifdef CONFIG_SMP | ||
208 | void iic_setup_cpu(void) | ||
209 | { | ||
210 | out_be64(&__get_cpu_var(iic).regs->prio, 0xff); | ||
211 | } | ||
212 | |||
213 | void iic_cause_IPI(int cpu, int mesg) | ||
214 | { | ||
215 | out_be64(&per_cpu(iic, cpu).regs->generate, mesg); | ||
216 | } | ||
217 | |||
218 | static irqreturn_t iic_ipi_action(int irq, void *dev_id, struct pt_regs *regs) | ||
219 | { | ||
220 | |||
221 | smp_message_recv(irq - IIC_IPI_OFFSET, regs); | ||
222 | return IRQ_HANDLED; | ||
223 | } | ||
224 | |||
225 | static void iic_request_ipi(int irq, const char *name) | ||
226 | { | ||
227 | /* IPIs are marked SA_INTERRUPT as they must run with irqs | ||
228 | * disabled */ | ||
229 | get_irq_desc(irq)->handler = &iic_pic; | ||
230 | get_irq_desc(irq)->status |= IRQ_PER_CPU; | ||
231 | request_irq(irq, iic_ipi_action, SA_INTERRUPT, name, NULL); | ||
232 | } | ||
233 | |||
234 | void iic_request_IPIs(void) | ||
235 | { | ||
236 | iic_request_ipi(IIC_IPI_OFFSET + PPC_MSG_CALL_FUNCTION, "IPI-call"); | ||
237 | iic_request_ipi(IIC_IPI_OFFSET + PPC_MSG_RESCHEDULE, "IPI-resched"); | ||
238 | #ifdef CONFIG_DEBUGGER | ||
239 | iic_request_ipi(IIC_IPI_OFFSET + PPC_MSG_DEBUGGER_BREAK, "IPI-debug"); | ||
240 | #endif /* CONFIG_DEBUGGER */ | ||
241 | } | ||
242 | #endif /* CONFIG_SMP */ | ||
243 | |||
244 | static void iic_setup_spe_handlers(void) | ||
245 | { | ||
246 | int be, isrc; | ||
247 | |||
248 | /* Assume two threads per BE are present */ | ||
249 | for (be=0; be < num_present_cpus() / 2; be++) { | ||
250 | for (isrc = 0; isrc < IIC_CLASS_STRIDE * 3; isrc++) { | ||
251 | int irq = IIC_NODE_STRIDE * be + IIC_SPE_OFFSET + isrc; | ||
252 | get_irq_desc(irq)->handler = &iic_pic; | ||
253 | } | ||
254 | } | ||
255 | } | ||
256 | |||
257 | void iic_init_IRQ(void) | ||
258 | { | ||
259 | int cpu, irq_offset; | ||
260 | struct iic *iic; | ||
261 | |||
262 | irq_offset = 0; | ||
263 | for_each_cpu(cpu) { | ||
264 | iic = &per_cpu(iic, cpu); | ||
265 | iic->regs = find_iic(cpu); | ||
266 | if (iic->regs) | ||
267 | out_be64(&iic->regs->prio, 0xff); | ||
268 | } | ||
269 | iic_setup_spe_handlers(); | ||
270 | } | ||