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