xref: /openbmc/linux/arch/m68k/mac/oss.c (revision 56e63689)
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  * Check to see if a specific OSS interrupt is pending
225  */
226 
227 int oss_irq_pending(int irq)
228 {
229 	switch(irq) {
230 		case IRQ_MAC_SCC:
231 			return oss->irq_pending & OSS_IP_IOPSCC;
232 			break;
233 		case IRQ_MAC_ADB:
234 			return oss->irq_pending & OSS_IP_IOPISM;
235 			break;
236 		case IRQ_MAC_SCSI:
237 			return oss->irq_pending & OSS_IP_SCSI;
238 			break;
239 		case IRQ_NUBUS_9:
240 		case IRQ_NUBUS_A:
241 		case IRQ_NUBUS_B:
242 		case IRQ_NUBUS_C:
243 		case IRQ_NUBUS_D:
244 		case IRQ_NUBUS_E:
245 			irq -= NUBUS_SOURCE_BASE;
246 			return oss->irq_pending & (1 << irq);
247 			break;
248 	}
249 	return 0;
250 }
251