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