Merge ../linus
[pandora-kernel.git] / arch / i386 / mach-visws / visws_apic.c
1 /*
2  *      linux/arch/i386/mach_visws/visws_apic.c
3  *
4  *      Copyright (C) 1999 Bent Hagemark, Ingo Molnar
5  *
6  *  SGI Visual Workstation interrupt controller
7  *
8  *  The Cobalt system ASIC in the Visual Workstation contains a "Cobalt" APIC
9  *  which serves as the main interrupt controller in the system.  Non-legacy
10  *  hardware in the system uses this controller directly.  Legacy devices
11  *  are connected to the PIIX4 which in turn has its 8259(s) connected to
12  *  a of the Cobalt APIC entry.
13  *
14  *  09/02/2000 - Updated for 2.4 by jbarnes@sgi.com
15  *
16  *  25/11/2002 - Updated for 2.5 by Andrey Panin <pazke@orbita1.ru>
17  */
18
19 #include <linux/config.h>
20 #include <linux/kernel_stat.h>
21 #include <linux/interrupt.h>
22 #include <linux/smp_lock.h>
23 #include <linux/init.h>
24
25 #include <asm/io.h>
26 #include <asm/apic.h>
27 #include <asm/i8259.h>
28
29 #include "cobalt.h"
30 #include "irq_vectors.h"
31
32
33 static DEFINE_SPINLOCK(cobalt_lock);
34
35 /*
36  * Set the given Cobalt APIC Redirection Table entry to point
37  * to the given IDT vector/index.
38  */
39 static inline void co_apic_set(int entry, int irq)
40 {
41         co_apic_write(CO_APIC_LO(entry), CO_APIC_LEVEL | (irq + FIRST_EXTERNAL_VECTOR));
42         co_apic_write(CO_APIC_HI(entry), 0);
43 }
44
45 /*
46  * Cobalt (IO)-APIC functions to handle PCI devices.
47  */
48 static inline int co_apic_ide0_hack(void)
49 {
50         extern char visws_board_type;
51         extern char visws_board_rev;
52
53         if (visws_board_type == VISWS_320 && visws_board_rev == 5)
54                 return 5;
55         return CO_APIC_IDE0;
56 }
57
58 static int is_co_apic(unsigned int irq)
59 {
60         if (IS_CO_APIC(irq))
61                 return CO_APIC(irq);
62
63         switch (irq) {
64                 case 0: return CO_APIC_CPU;
65                 case CO_IRQ_IDE0: return co_apic_ide0_hack();
66                 case CO_IRQ_IDE1: return CO_APIC_IDE1;
67                 default: return -1;
68         }
69 }
70
71
72 /*
73  * This is the SGI Cobalt (IO-)APIC:
74  */
75
76 static void enable_cobalt_irq(unsigned int irq)
77 {
78         co_apic_set(is_co_apic(irq), irq);
79 }
80
81 static void disable_cobalt_irq(unsigned int irq)
82 {
83         int entry = is_co_apic(irq);
84
85         co_apic_write(CO_APIC_LO(entry), CO_APIC_MASK);
86         co_apic_read(CO_APIC_LO(entry));
87 }
88
89 /*
90  * "irq" really just serves to identify the device.  Here is where we
91  * map this to the Cobalt APIC entry where it's physically wired.
92  * This is called via request_irq -> setup_irq -> irq_desc->startup()
93  */
94 static unsigned int startup_cobalt_irq(unsigned int irq)
95 {
96         unsigned long flags;
97
98         spin_lock_irqsave(&cobalt_lock, flags);
99         if ((irq_desc[irq].status & (IRQ_DISABLED | IRQ_INPROGRESS | IRQ_WAITING)))
100                 irq_desc[irq].status &= ~(IRQ_DISABLED | IRQ_INPROGRESS | IRQ_WAITING);
101         enable_cobalt_irq(irq);
102         spin_unlock_irqrestore(&cobalt_lock, flags);
103         return 0;
104 }
105
106 static void ack_cobalt_irq(unsigned int irq)
107 {
108         unsigned long flags;
109
110         spin_lock_irqsave(&cobalt_lock, flags);
111         disable_cobalt_irq(irq);
112         apic_write(APIC_EOI, APIC_EIO_ACK);
113         spin_unlock_irqrestore(&cobalt_lock, flags);
114 }
115
116 static void end_cobalt_irq(unsigned int irq)
117 {
118         unsigned long flags;
119
120         spin_lock_irqsave(&cobalt_lock, flags);
121         if (!(irq_desc[irq].status & (IRQ_DISABLED | IRQ_INPROGRESS)))
122                 enable_cobalt_irq(irq);
123         spin_unlock_irqrestore(&cobalt_lock, flags);
124 }
125
126 static struct hw_interrupt_type cobalt_irq_type = {
127         .typename =     "Cobalt-APIC",
128         .startup =      startup_cobalt_irq,
129         .shutdown =     disable_cobalt_irq,
130         .enable =       enable_cobalt_irq,
131         .disable =      disable_cobalt_irq,
132         .ack =          ack_cobalt_irq,
133         .end =          end_cobalt_irq,
134 };
135
136
137 /*
138  * This is the PIIX4-based 8259 that is wired up indirectly to Cobalt
139  * -- not the manner expected by the code in i8259.c.
140  *
141  * there is a 'master' physical interrupt source that gets sent to
142  * the CPU. But in the chipset there are various 'virtual' interrupts
143  * waiting to be handled. We represent this to Linux through a 'master'
144  * interrupt controller type, and through a special virtual interrupt-
145  * controller. Device drivers only see the virtual interrupt sources.
146  */
147 static unsigned int startup_piix4_master_irq(unsigned int irq)
148 {
149         init_8259A(0);
150
151         return startup_cobalt_irq(irq);
152 }
153
154 static void end_piix4_master_irq(unsigned int irq)
155 {
156         unsigned long flags;
157
158         spin_lock_irqsave(&cobalt_lock, flags);
159         enable_cobalt_irq(irq);
160         spin_unlock_irqrestore(&cobalt_lock, flags);
161 }
162
163 static struct hw_interrupt_type piix4_master_irq_type = {
164         .typename =     "PIIX4-master",
165         .startup =      startup_piix4_master_irq,
166         .ack =          ack_cobalt_irq,
167         .end =          end_piix4_master_irq,
168 };
169
170
171 static struct hw_interrupt_type piix4_virtual_irq_type = {
172         .typename =     "PIIX4-virtual",
173         .startup =      startup_8259A_irq,
174         .shutdown =     disable_8259A_irq,
175         .enable =       enable_8259A_irq,
176         .disable =      disable_8259A_irq,
177 };
178
179
180 /*
181  * PIIX4-8259 master/virtual functions to handle interrupt requests
182  * from legacy devices: floppy, parallel, serial, rtc.
183  *
184  * None of these get Cobalt APIC entries, neither do they have IDT
185  * entries. These interrupts are purely virtual and distributed from
186  * the 'master' interrupt source: CO_IRQ_8259.
187  *
188  * When the 8259 interrupts its handler figures out which of these
189  * devices is interrupting and dispatches to its handler.
190  *
191  * CAREFUL: devices see the 'virtual' interrupt only. Thus disable/
192  * enable_irq gets the right irq. This 'master' irq is never directly
193  * manipulated by any driver.
194  */
195 static irqreturn_t piix4_master_intr(int irq, void *dev_id, struct pt_regs * regs)
196 {
197         int realirq;
198         irq_desc_t *desc;
199         unsigned long flags;
200
201         spin_lock_irqsave(&i8259A_lock, flags);
202
203         /* Find out what's interrupting in the PIIX4 master 8259 */
204         outb(0x0c, 0x20);               /* OCW3 Poll command */
205         realirq = inb(0x20);
206
207         /*
208          * Bit 7 == 0 means invalid/spurious
209          */
210         if (unlikely(!(realirq & 0x80)))
211                 goto out_unlock;
212
213         realirq &= 7;
214
215         if (unlikely(realirq == 2)) {
216                 outb(0x0c, 0xa0);
217                 realirq = inb(0xa0);
218
219                 if (unlikely(!(realirq & 0x80)))
220                         goto out_unlock;
221
222                 realirq = (realirq & 7) + 8;
223         }
224
225         /* mask and ack interrupt */
226         cached_irq_mask |= 1 << realirq;
227         if (unlikely(realirq > 7)) {
228                 inb(0xa1);
229                 outb(cached_slave_mask, 0xa1);
230                 outb(0x60 + (realirq & 7), 0xa0);
231                 outb(0x60 + 2, 0x20);
232         } else {
233                 inb(0x21);
234                 outb(cached_master_mask, 0x21);
235                 outb(0x60 + realirq, 0x20);
236         }
237
238         spin_unlock_irqrestore(&i8259A_lock, flags);
239
240         desc = irq_desc + realirq;
241
242         /*
243          * handle this 'virtual interrupt' as a Cobalt one now.
244          */
245         kstat_cpu(smp_processor_id()).irqs[realirq]++;
246
247         if (likely(desc->action != NULL))
248                 handle_IRQ_event(realirq, regs, desc->action);
249
250         if (!(desc->status & IRQ_DISABLED))
251                 enable_8259A_irq(realirq);
252
253         return IRQ_HANDLED;
254
255 out_unlock:
256         spin_unlock_irqrestore(&i8259A_lock, flags);
257         return IRQ_NONE;
258 }
259
260 static struct irqaction master_action = {
261         .handler =      piix4_master_intr,
262         .name =         "PIIX4-8259",
263 };
264
265 static struct irqaction cascade_action = {
266         .handler =      no_action,
267         .name =         "cascade",
268 };
269
270
271 void init_VISWS_APIC_irqs(void)
272 {
273         int i;
274
275         for (i = 0; i < CO_IRQ_APIC0 + CO_APIC_LAST + 1; i++) {
276                 irq_desc[i].status = IRQ_DISABLED;
277                 irq_desc[i].action = 0;
278                 irq_desc[i].depth = 1;
279
280                 if (i == 0) {
281                         irq_desc[i].chip = &cobalt_irq_type;
282                 }
283                 else if (i == CO_IRQ_IDE0) {
284                         irq_desc[i].chip = &cobalt_irq_type;
285                 }
286                 else if (i == CO_IRQ_IDE1) {
287                         irq_desc[i].chip = &cobalt_irq_type;
288                 }
289                 else if (i == CO_IRQ_8259) {
290                         irq_desc[i].chip = &piix4_master_irq_type;
291                 }
292                 else if (i < CO_IRQ_APIC0) {
293                         irq_desc[i].chip = &piix4_virtual_irq_type;
294                 }
295                 else if (IS_CO_APIC(i)) {
296                         irq_desc[i].chip = &cobalt_irq_type;
297                 }
298         }
299
300         setup_irq(CO_IRQ_8259, &master_action);
301         setup_irq(2, &cascade_action);
302 }