Merge branch 'linux-next' of git://git.infradead.org/~dedekind/ubi-2.6
[linux-2.6] / 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
23 #include <asm/bootinfo.h>
24 #include <asm/machw.h>
25 #include <asm/macintosh.h>
26 #include <asm/macints.h>
27 #include <asm/mac_via.h>
28 #include <asm/mac_oss.h>
29
30 int oss_present;
31 volatile struct mac_oss *oss;
32
33 static irqreturn_t oss_irq(int, void *);
34 static irqreturn_t oss_nubus_irq(int, void *);
35
36 extern irqreturn_t via1_irq(int, void *);
37 extern irqreturn_t mac_scc_dispatch(int, void *);
38
39 /*
40  * Initialize the OSS
41  *
42  * The OSS "detection" code is actually in via_init() which is always called
43  * before us. Thus we can count on oss_present being valid on entry.
44  */
45
46 void __init oss_init(void)
47 {
48         int i;
49
50         if (!oss_present) return;
51
52         oss = (struct mac_oss *) OSS_BASE;
53
54         /* Disable all interrupts. Unlike a VIA it looks like we    */
55         /* do this by setting the source's interrupt level to zero. */
56
57         for (i = 0; i <= OSS_NUM_SOURCES; i++) {
58                 oss->irq_level[i] = OSS_IRQLEV_DISABLED;
59         }
60         /* If we disable VIA1 here, we never really handle it... */
61         oss->irq_level[OSS_VIA1] = OSS_IRQLEV_VIA1;
62 }
63
64 /*
65  * Register the OSS and NuBus interrupt dispatchers.
66  */
67
68 void __init oss_register_interrupts(void)
69 {
70         request_irq(OSS_IRQLEV_SCSI, oss_irq, IRQ_FLG_LOCK,
71                         "scsi", (void *) oss);
72         request_irq(OSS_IRQLEV_IOPSCC, mac_scc_dispatch, IRQ_FLG_LOCK,
73                         "scc", mac_scc_dispatch);
74         request_irq(OSS_IRQLEV_NUBUS, oss_nubus_irq, IRQ_FLG_LOCK,
75                         "nubus", (void *) oss);
76         request_irq(OSS_IRQLEV_SOUND, oss_irq, IRQ_FLG_LOCK,
77                         "sound", (void *) oss);
78         request_irq(OSS_IRQLEV_VIA1, via1_irq, IRQ_FLG_LOCK,
79                         "via1", (void *) via1);
80 }
81
82 /*
83  * Initialize OSS for Nubus access
84  */
85
86 void __init oss_nubus_init(void)
87 {
88 }
89
90 /*
91  * Handle miscellaneous OSS interrupts. Right now that's just sound
92  * and SCSI; everything else is routed to its own autovector IRQ.
93  */
94
95 static irqreturn_t oss_irq(int irq, void *dev_id)
96 {
97         int events;
98
99         events = oss->irq_pending & (OSS_IP_SOUND|OSS_IP_SCSI);
100         if (!events)
101                 return IRQ_NONE;
102
103 #ifdef DEBUG_IRQS
104         if ((console_loglevel == 10) && !(events & OSS_IP_SCSI)) {
105                 printk("oss_irq: irq %d events = 0x%04X\n", irq,
106                         (int) oss->irq_pending);
107         }
108 #endif
109         /* FIXME: how do you clear a pending IRQ?    */
110
111         if (events & OSS_IP_SOUND) {
112                 oss->irq_pending &= ~OSS_IP_SOUND;
113                 /* FIXME: call sound handler */
114         } else if (events & OSS_IP_SCSI) {
115                 oss->irq_pending &= ~OSS_IP_SCSI;
116                 m68k_handle_int(IRQ_MAC_SCSI);
117         } else {
118                 /* FIXME: error check here? */
119         }
120         return IRQ_HANDLED;
121 }
122
123 /*
124  * Nubus IRQ handler, OSS style
125  *
126  * Unlike the VIA/RBV this is on its own autovector interrupt level.
127  */
128
129 static irqreturn_t oss_nubus_irq(int irq, void *dev_id)
130 {
131         int events, irq_bit, i;
132
133         events = oss->irq_pending & OSS_IP_NUBUS;
134         if (!events)
135                 return IRQ_NONE;
136
137 #ifdef DEBUG_NUBUS_INT
138         if (console_loglevel > 7) {
139                 printk("oss_nubus_irq: events = 0x%04X\n", events);
140         }
141 #endif
142         /* There are only six slots on the OSS, not seven */
143
144         i = 6;
145         irq_bit = 0x40;
146         do {
147                 --i;
148                 irq_bit >>= 1;
149                 if (events & irq_bit) {
150                         oss->irq_pending &= ~irq_bit;
151                         m68k_handle_int(NUBUS_SOURCE_BASE + i);
152                 }
153         } while(events & (irq_bit - 1));
154         return IRQ_HANDLED;
155 }
156
157 /*
158  * Enable an OSS interrupt
159  *
160  * It looks messy but it's rather straightforward. The switch() statement
161  * just maps the machspec interrupt numbers to the right OSS interrupt
162  * source (if the OSS handles that interrupt) and then sets the interrupt
163  * level for that source to nonzero, thus enabling the interrupt.
164  */
165
166 void oss_irq_enable(int irq) {
167 #ifdef DEBUG_IRQUSE
168         printk("oss_irq_enable(%d)\n", irq);
169 #endif
170         switch(irq) {
171                 case IRQ_SCC:
172                 case IRQ_SCCA:
173                 case IRQ_SCCB:
174                         oss->irq_level[OSS_IOPSCC] = OSS_IRQLEV_IOPSCC;
175                         break;
176                 case IRQ_MAC_ADB:
177                         oss->irq_level[OSS_IOPISM] = OSS_IRQLEV_IOPISM;
178                         break;
179                 case IRQ_MAC_SCSI:
180                         oss->irq_level[OSS_SCSI] = OSS_IRQLEV_SCSI;
181                         break;
182                 case IRQ_NUBUS_9:
183                 case IRQ_NUBUS_A:
184                 case IRQ_NUBUS_B:
185                 case IRQ_NUBUS_C:
186                 case IRQ_NUBUS_D:
187                 case IRQ_NUBUS_E:
188                         irq -= NUBUS_SOURCE_BASE;
189                         oss->irq_level[irq] = OSS_IRQLEV_NUBUS;
190                         break;
191 #ifdef DEBUG_IRQUSE
192                 default:
193                         printk("%s unknown irq %d\n", __func__, irq);
194                         break;
195 #endif
196         }
197 }
198
199 /*
200  * Disable an OSS interrupt
201  *
202  * Same as above except we set the source's interrupt level to zero,
203  * to disable the interrupt.
204  */
205
206 void oss_irq_disable(int irq) {
207 #ifdef DEBUG_IRQUSE
208         printk("oss_irq_disable(%d)\n", irq);
209 #endif
210         switch(irq) {
211                 case IRQ_SCC:
212                 case IRQ_SCCA:
213                 case IRQ_SCCB:
214                         oss->irq_level[OSS_IOPSCC] = OSS_IRQLEV_DISABLED;
215                         break;
216                 case IRQ_MAC_ADB:
217                         oss->irq_level[OSS_IOPISM] = OSS_IRQLEV_DISABLED;
218                         break;
219                 case IRQ_MAC_SCSI:
220                         oss->irq_level[OSS_SCSI] = OSS_IRQLEV_DISABLED;
221                         break;
222                 case IRQ_NUBUS_9:
223                 case IRQ_NUBUS_A:
224                 case IRQ_NUBUS_B:
225                 case IRQ_NUBUS_C:
226                 case IRQ_NUBUS_D:
227                 case IRQ_NUBUS_E:
228                         irq -= NUBUS_SOURCE_BASE;
229                         oss->irq_level[irq] = OSS_IRQLEV_DISABLED;
230                         break;
231 #ifdef DEBUG_IRQUSE
232                 default:
233                         printk("%s unknown irq %d\n", __func__, irq);
234                         break;
235 #endif
236         }
237 }
238
239 /*
240  * Clear an OSS interrupt
241  *
242  * Not sure if this works or not but it's the only method I could
243  * think of based on the contents of the mac_oss structure.
244  */
245
246 void oss_irq_clear(int irq) {
247         /* FIXME: how to do this on OSS? */
248         switch(irq) {
249                 case IRQ_SCC:
250                 case IRQ_SCCA:
251                 case IRQ_SCCB:
252                         oss->irq_pending &= ~OSS_IP_IOPSCC;
253                         break;
254                 case IRQ_MAC_ADB:
255                         oss->irq_pending &= ~OSS_IP_IOPISM;
256                         break;
257                 case IRQ_MAC_SCSI:
258                         oss->irq_pending &= ~OSS_IP_SCSI;
259                         break;
260                 case IRQ_NUBUS_9:
261                 case IRQ_NUBUS_A:
262                 case IRQ_NUBUS_B:
263                 case IRQ_NUBUS_C:
264                 case IRQ_NUBUS_D:
265                 case IRQ_NUBUS_E:
266                         irq -= NUBUS_SOURCE_BASE;
267                         oss->irq_pending &= ~(1 << irq);
268                         break;
269         }
270 }
271
272 /*
273  * Check to see if a specific OSS interrupt is pending
274  */
275
276 int oss_irq_pending(int irq)
277 {
278         switch(irq) {
279                 case IRQ_SCC:
280                 case IRQ_SCCA:
281                 case IRQ_SCCB:
282                         return oss->irq_pending & OSS_IP_IOPSCC;
283                         break;
284                 case IRQ_MAC_ADB:
285                         return oss->irq_pending & OSS_IP_IOPISM;
286                         break;
287                 case IRQ_MAC_SCSI:
288                         return oss->irq_pending & OSS_IP_SCSI;
289                         break;
290                 case IRQ_NUBUS_9:
291                 case IRQ_NUBUS_A:
292                 case IRQ_NUBUS_B:
293                 case IRQ_NUBUS_C:
294                 case IRQ_NUBUS_D:
295                 case IRQ_NUBUS_E:
296                         irq -= NUBUS_SOURCE_BASE;
297                         return oss->irq_pending & (1 << irq);
298                         break;
299         }
300         return 0;
301 }