]> git.kernelconcepts.de Git - karo-tx-linux.git/blob - arch/m68k/mac/oss.c
m68k/mac: Optimize interrupts using chain handlers
[karo-tx-linux.git] / arch / m68k / mac / oss.c
1 /*
2  *      OSS handling
3  *      Written by Joshua M. Thompson (funaho@jurai.org)
4  *
5  *
6  *      This chip is used in the IIfx in place of VIA #2. It acts like a fancy
7  *      VIA chip with prorammable interrupt levels.
8  *
9  * 990502 (jmt) - Major rewrite for new interrupt architecture as well as some
10  *                recent insights into OSS operational details.
11  * 990610 (jmt) - Now taking full advantage of the OSS. Interrupts are mapped
12  *                to mostly match the A/UX interrupt scheme supported on the
13  *                VIA side. Also added support for enabling the ISM irq again
14  *                since we now have a functional IOP manager.
15  */
16
17 #include <linux/types.h>
18 #include <linux/kernel.h>
19 #include <linux/mm.h>
20 #include <linux/delay.h>
21 #include <linux/init.h>
22 #ifdef CONFIG_GENERIC_HARDIRQS
23 #include <linux/irq.h>
24 #endif
25
26 #include <asm/bootinfo.h>
27 #include <asm/macintosh.h>
28 #include <asm/macints.h>
29 #include <asm/mac_via.h>
30 #include <asm/mac_oss.h>
31
32 int oss_present;
33 volatile struct mac_oss *oss;
34
35 #ifdef CONFIG_GENERIC_HARDIRQS
36 extern void via1_irq(unsigned int irq, struct irq_desc *desc);
37 #else
38 extern irqreturn_t via1_irq(int, void *);
39 #endif
40
41 /*
42  * Initialize the OSS
43  *
44  * The OSS "detection" code is actually in via_init() which is always called
45  * before us. Thus we can count on oss_present being valid on entry.
46  */
47
48 void __init oss_init(void)
49 {
50         int i;
51
52         if (!oss_present) return;
53
54         oss = (struct mac_oss *) OSS_BASE;
55
56         /* Disable all interrupts. Unlike a VIA it looks like we    */
57         /* do this by setting the source's interrupt level to zero. */
58
59         for (i = 0; i <= OSS_NUM_SOURCES; i++) {
60                 oss->irq_level[i] = OSS_IRQLEV_DISABLED;
61         }
62         /* If we disable VIA1 here, we never really handle it... */
63         oss->irq_level[OSS_VIA1] = OSS_IRQLEV_VIA1;
64 }
65
66 /*
67  * Initialize OSS for Nubus access
68  */
69
70 void __init oss_nubus_init(void)
71 {
72 }
73
74 /*
75  * Handle miscellaneous OSS interrupts. Right now that's just sound
76  * and SCSI; everything else is routed to its own autovector IRQ.
77  */
78
79 #ifdef CONFIG_GENERIC_HARDIRQS
80 static void oss_irq(unsigned int irq, struct irq_desc *desc)
81 {
82         int events;
83
84         events = oss->irq_pending & (OSS_IP_SOUND|OSS_IP_SCSI);
85         if (!events)
86                 return;
87
88 #ifdef DEBUG_IRQS
89         if ((console_loglevel == 10) && !(events & OSS_IP_SCSI)) {
90                 printk("oss_irq: irq %u events = 0x%04X\n", irq,
91                         (int) oss->irq_pending);
92         }
93 #endif
94         /* FIXME: how do you clear a pending IRQ?    */
95
96         if (events & OSS_IP_SOUND) {
97                 oss->irq_pending &= ~OSS_IP_SOUND;
98                 /* FIXME: call sound handler */
99         } else if (events & OSS_IP_SCSI) {
100                 oss->irq_pending &= ~OSS_IP_SCSI;
101                 generic_handle_irq(IRQ_MAC_SCSI);
102         } else {
103                 /* FIXME: error check here? */
104         }
105 }
106 #else
107 static irqreturn_t oss_irq(int irq, void *dev_id)
108 {
109         int events;
110
111         events = oss->irq_pending & (OSS_IP_SOUND|OSS_IP_SCSI);
112         if (!events)
113                 return IRQ_NONE;
114
115 #ifdef DEBUG_IRQS
116         if ((console_loglevel == 10) && !(events & OSS_IP_SCSI)) {
117                 printk("oss_irq: irq %d events = 0x%04X\n", irq,
118                         (int) oss->irq_pending);
119         }
120 #endif
121         /* FIXME: how do you clear a pending IRQ?    */
122
123         if (events & OSS_IP_SOUND) {
124                 oss->irq_pending &= ~OSS_IP_SOUND;
125                 /* FIXME: call sound handler */
126         } else if (events & OSS_IP_SCSI) {
127                 oss->irq_pending &= ~OSS_IP_SCSI;
128                 generic_handle_irq(IRQ_MAC_SCSI);
129         } else {
130                 /* FIXME: error check here? */
131         }
132         return IRQ_HANDLED;
133 }
134 #endif
135
136 /*
137  * Nubus IRQ handler, OSS style
138  *
139  * Unlike the VIA/RBV this is on its own autovector interrupt level.
140  */
141
142 #ifdef CONFIG_GENERIC_HARDIRQS
143 static void oss_nubus_irq(unsigned int irq, struct irq_desc *desc)
144 {
145         int events, irq_bit, i;
146
147         events = oss->irq_pending & OSS_IP_NUBUS;
148         if (!events)
149                 return;
150
151 #ifdef DEBUG_NUBUS_INT
152         if (console_loglevel > 7) {
153                 printk("oss_nubus_irq: events = 0x%04X\n", events);
154         }
155 #endif
156         /* There are only six slots on the OSS, not seven */
157
158         i = 6;
159         irq_bit = 0x40;
160         do {
161                 --i;
162                 irq_bit >>= 1;
163                 if (events & irq_bit) {
164                         oss->irq_pending &= ~irq_bit;
165                         generic_handle_irq(NUBUS_SOURCE_BASE + i);
166                 }
167         } while(events & (irq_bit - 1));
168 }
169 #else
170 static irqreturn_t oss_nubus_irq(int irq, void *dev_id)
171 {
172         int events, irq_bit, i;
173
174         events = oss->irq_pending & OSS_IP_NUBUS;
175         if (!events)
176                 return IRQ_NONE;
177
178 #ifdef DEBUG_NUBUS_INT
179         if (console_loglevel > 7) {
180                 printk("oss_nubus_irq: events = 0x%04X\n", events);
181         }
182 #endif
183         /* There are only six slots on the OSS, not seven */
184
185         i = 6;
186         irq_bit = 0x40;
187         do {
188                 --i;
189                 irq_bit >>= 1;
190                 if (events & irq_bit) {
191                         oss->irq_pending &= ~irq_bit;
192                         generic_handle_irq(NUBUS_SOURCE_BASE + i);
193                 }
194         } while(events & (irq_bit - 1));
195         return IRQ_HANDLED;
196 }
197 #endif
198
199 /*
200  * Register the OSS and NuBus interrupt dispatchers.
201  */
202
203 void __init oss_register_interrupts(void)
204 {
205 #ifdef CONFIG_GENERIC_HARDIRQS
206         irq_set_chained_handler(OSS_IRQLEV_SCSI, oss_irq);
207         irq_set_chained_handler(OSS_IRQLEV_NUBUS, oss_nubus_irq);
208         irq_set_chained_handler(OSS_IRQLEV_SOUND, oss_irq);
209         irq_set_chained_handler(OSS_IRQLEV_VIA1, via1_irq);
210 #else /* !CONFIG_GENERIC_HARDIRQS */
211         if (request_irq(OSS_IRQLEV_SCSI, oss_irq, 0, "scsi", (void *)oss))
212                 pr_err("Couldn't register %s interrupt\n", "scsi");
213         if (request_irq(OSS_IRQLEV_NUBUS, oss_nubus_irq, 0, "nubus",
214                         (void *)oss))
215                 pr_err("Couldn't register %s interrupt\n", "nubus");
216         if (request_irq(OSS_IRQLEV_SOUND, oss_irq, 0, "sound", (void *)oss))
217                 pr_err("Couldn't register %s interrupt\n", "sound");
218         if (request_irq(OSS_IRQLEV_VIA1, via1_irq, 0, "via1", (void *)via1))
219                 pr_err("Couldn't register %s interrupt\n", "via1");
220 #endif /* !CONFIG_GENERIC_HARDIRQS */
221 }
222
223 /*
224  * Enable an OSS interrupt
225  *
226  * It looks messy but it's rather straightforward. The switch() statement
227  * just maps the machspec interrupt numbers to the right OSS interrupt
228  * source (if the OSS handles that interrupt) and then sets the interrupt
229  * level for that source to nonzero, thus enabling the interrupt.
230  */
231
232 void oss_irq_enable(int irq) {
233 #ifdef DEBUG_IRQUSE
234         printk("oss_irq_enable(%d)\n", irq);
235 #endif
236         switch(irq) {
237                 case IRQ_MAC_SCC:
238                         oss->irq_level[OSS_IOPSCC] = OSS_IRQLEV_IOPSCC;
239                         break;
240                 case IRQ_MAC_ADB:
241                         oss->irq_level[OSS_IOPISM] = OSS_IRQLEV_IOPISM;
242                         break;
243                 case IRQ_MAC_SCSI:
244                         oss->irq_level[OSS_SCSI] = OSS_IRQLEV_SCSI;
245                         break;
246                 case IRQ_NUBUS_9:
247                 case IRQ_NUBUS_A:
248                 case IRQ_NUBUS_B:
249                 case IRQ_NUBUS_C:
250                 case IRQ_NUBUS_D:
251                 case IRQ_NUBUS_E:
252                         irq -= NUBUS_SOURCE_BASE;
253                         oss->irq_level[irq] = OSS_IRQLEV_NUBUS;
254                         break;
255 #ifdef DEBUG_IRQUSE
256                 default:
257                         printk("%s unknown irq %d\n", __func__, irq);
258                         break;
259 #endif
260         }
261 }
262
263 /*
264  * Disable an OSS interrupt
265  *
266  * Same as above except we set the source's interrupt level to zero,
267  * to disable the interrupt.
268  */
269
270 void oss_irq_disable(int irq) {
271 #ifdef DEBUG_IRQUSE
272         printk("oss_irq_disable(%d)\n", irq);
273 #endif
274         switch(irq) {
275                 case IRQ_MAC_SCC:
276                         oss->irq_level[OSS_IOPSCC] = OSS_IRQLEV_DISABLED;
277                         break;
278                 case IRQ_MAC_ADB:
279                         oss->irq_level[OSS_IOPISM] = OSS_IRQLEV_DISABLED;
280                         break;
281                 case IRQ_MAC_SCSI:
282                         oss->irq_level[OSS_SCSI] = OSS_IRQLEV_DISABLED;
283                         break;
284                 case IRQ_NUBUS_9:
285                 case IRQ_NUBUS_A:
286                 case IRQ_NUBUS_B:
287                 case IRQ_NUBUS_C:
288                 case IRQ_NUBUS_D:
289                 case IRQ_NUBUS_E:
290                         irq -= NUBUS_SOURCE_BASE;
291                         oss->irq_level[irq] = OSS_IRQLEV_DISABLED;
292                         break;
293 #ifdef DEBUG_IRQUSE
294                 default:
295                         printk("%s unknown irq %d\n", __func__, irq);
296                         break;
297 #endif
298         }
299 }
300
301 /*
302  * Clear an OSS interrupt
303  *
304  * Not sure if this works or not but it's the only method I could
305  * think of based on the contents of the mac_oss structure.
306  */
307
308 void oss_irq_clear(int irq) {
309         /* FIXME: how to do this on OSS? */
310         switch(irq) {
311                 case IRQ_MAC_SCC:
312                         oss->irq_pending &= ~OSS_IP_IOPSCC;
313                         break;
314                 case IRQ_MAC_ADB:
315                         oss->irq_pending &= ~OSS_IP_IOPISM;
316                         break;
317                 case IRQ_MAC_SCSI:
318                         oss->irq_pending &= ~OSS_IP_SCSI;
319                         break;
320                 case IRQ_NUBUS_9:
321                 case IRQ_NUBUS_A:
322                 case IRQ_NUBUS_B:
323                 case IRQ_NUBUS_C:
324                 case IRQ_NUBUS_D:
325                 case IRQ_NUBUS_E:
326                         irq -= NUBUS_SOURCE_BASE;
327                         oss->irq_pending &= ~(1 << irq);
328                         break;
329         }
330 }
331
332 /*
333  * Check to see if a specific OSS interrupt is pending
334  */
335
336 int oss_irq_pending(int irq)
337 {
338         switch(irq) {
339                 case IRQ_MAC_SCC:
340                         return oss->irq_pending & OSS_IP_IOPSCC;
341                         break;
342                 case IRQ_MAC_ADB:
343                         return oss->irq_pending & OSS_IP_IOPISM;
344                         break;
345                 case IRQ_MAC_SCSI:
346                         return oss->irq_pending & OSS_IP_SCSI;
347                         break;
348                 case IRQ_NUBUS_9:
349                 case IRQ_NUBUS_A:
350                 case IRQ_NUBUS_B:
351                 case IRQ_NUBUS_C:
352                 case IRQ_NUBUS_D:
353                 case IRQ_NUBUS_E:
354                         irq -= NUBUS_SOURCE_BASE;
355                         return oss->irq_pending & (1 << irq);
356                         break;
357         }
358         return 0;
359 }