blob: cc784c2ff6e8afd7b91ead94f2ff07c500d8fadb [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
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.
Simon Arlott0c79cf62007-10-20 01:20:32 +020011 * 990610 (jmt) - Now taking full advantage of the OSS. Interrupts are mapped
Linus Torvalds1da177e2005-04-16 15:20:36 -070012 * 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>
Geert Uytterhoevenddc7fd22011-07-13 21:48:30 +020022#ifdef CONFIG_GENERIC_HARDIRQS
23#include <linux/irq.h>
24#endif
Linus Torvalds1da177e2005-04-16 15:20:36 -070025
26#include <asm/bootinfo.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070027#include <asm/macintosh.h>
28#include <asm/macints.h>
29#include <asm/mac_via.h>
30#include <asm/mac_oss.h>
31
32int oss_present;
33volatile struct mac_oss *oss;
34
Geert Uytterhoeven9145db52011-08-10 12:48:29 +020035#ifdef CONFIG_GENERIC_HARDIRQS
36extern void via1_irq(unsigned int irq, struct irq_desc *desc);
37#else
Al Viro2850bc22006-10-07 14:16:45 +010038extern irqreturn_t via1_irq(int, void *);
Geert Uytterhoeven9145db52011-08-10 12:48:29 +020039#endif
Linus Torvalds1da177e2005-04-16 15:20:36 -070040
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
48void __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/*
Linus Torvalds1da177e2005-04-16 15:20:36 -070067 * Initialize OSS for Nubus access
68 */
69
70void __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
Geert Uytterhoeven9145db52011-08-10 12:48:29 +020079#ifdef CONFIG_GENERIC_HARDIRQS
80static 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
Adrian Bunk8dfbdf42008-07-17 21:16:25 +0200107static irqreturn_t oss_irq(int irq, void *dev_id)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700108{
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) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700124 oss->irq_pending &= ~OSS_IP_SOUND;
Finn Thain647b8042007-05-01 22:32:55 +0200125 /* FIXME: call sound handler */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700126 } else if (events & OSS_IP_SCSI) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700127 oss->irq_pending &= ~OSS_IP_SCSI;
Geert Uytterhoeven1425df82011-07-01 20:39:19 +0200128 generic_handle_irq(IRQ_MAC_SCSI);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700129 } else {
130 /* FIXME: error check here? */
131 }
132 return IRQ_HANDLED;
133}
Geert Uytterhoeven9145db52011-08-10 12:48:29 +0200134#endif
Linus Torvalds1da177e2005-04-16 15:20:36 -0700135
136/*
137 * Nubus IRQ handler, OSS style
138 *
139 * Unlike the VIA/RBV this is on its own autovector interrupt level.
140 */
141
Geert Uytterhoeven9145db52011-08-10 12:48:29 +0200142#ifdef CONFIG_GENERIC_HARDIRQS
143static 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
Adrian Bunk8dfbdf42008-07-17 21:16:25 +0200170static irqreturn_t oss_nubus_irq(int irq, void *dev_id)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700171{
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
Finn Thain67dfb152007-05-01 22:32:56 +0200185 i = 6;
186 irq_bit = 0x40;
187 do {
188 --i;
189 irq_bit >>= 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700190 if (events & irq_bit) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700191 oss->irq_pending &= ~irq_bit;
Geert Uytterhoeven1425df82011-07-01 20:39:19 +0200192 generic_handle_irq(NUBUS_SOURCE_BASE + i);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700193 }
Finn Thain67dfb152007-05-01 22:32:56 +0200194 } while(events & (irq_bit - 1));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700195 return IRQ_HANDLED;
196}
Geert Uytterhoeven9145db52011-08-10 12:48:29 +0200197#endif
198
199/*
200 * Register the OSS and NuBus interrupt dispatchers.
201 */
202
203void __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}
Linus Torvalds1da177e2005-04-16 15:20:36 -0700222
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
232void oss_irq_enable(int irq) {
233#ifdef DEBUG_IRQUSE
234 printk("oss_irq_enable(%d)\n", irq);
235#endif
236 switch(irq) {
Finn Thain80614e52009-11-17 20:06:48 +1100237 case IRQ_MAC_SCC:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700238 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:
Harvey Harrisonf85e7cd2008-04-28 02:13:49 -0700257 printk("%s unknown irq %d\n", __func__, irq);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700258 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
270void oss_irq_disable(int irq) {
271#ifdef DEBUG_IRQUSE
272 printk("oss_irq_disable(%d)\n", irq);
273#endif
274 switch(irq) {
Finn Thain80614e52009-11-17 20:06:48 +1100275 case IRQ_MAC_SCC:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700276 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:
Harvey Harrisonf85e7cd2008-04-28 02:13:49 -0700295 printk("%s unknown irq %d\n", __func__, irq);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700296 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
308void oss_irq_clear(int irq) {
309 /* FIXME: how to do this on OSS? */
310 switch(irq) {
Finn Thain80614e52009-11-17 20:06:48 +1100311 case IRQ_MAC_SCC:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700312 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
336int oss_irq_pending(int irq)
337{
338 switch(irq) {
Finn Thain80614e52009-11-17 20:06:48 +1100339 case IRQ_MAC_SCC:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700340 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}