1/*
2 * BK Id: %F% %I% %G% %U% %#%
3 */
4/*
5 * linux/arch/m68k/amiga/amiints.c -- Amiga Linux interrupt handling code
6 *
7 * This file is subject to the terms and conditions of the GNU General Public
8 * License.  See the file COPYING in the main directory of this archive
9 * for more details.
10 *
11 * 11/07/96: rewritten interrupt handling, irq lists are exists now only for
12 *           this sources where it makes sense (VERTB/PORTS/EXTER) and you must
13 *           be careful that dev_id for this sources is unique since this the
14 *           only possibility to distinguish between different handlers for
15 *           free_irq. irq lists also have different irq flags:
16 *           - IRQ_FLG_FAST: handler is inserted at top of list (after other
17 *                           fast handlers)
18 *           - IRQ_FLG_SLOW: handler is inserted at bottom of list and before
19 *                           they're executed irq level is set to the previous
20 *                           one, but handlers don't need to be reentrant, if
21 *                           reentrance occurred, slow handlers will be just
22 *                           called again.
23 *           The whole interrupt handling for CIAs is moved to cia.c
24 *           /Roman Zippel
25 *
26 * 07/08/99: rewamp of the interrupt handling - we now have two types of
27 *           interrupts, normal and fast handlers, fast handlers being
28 *           marked with SA_INTERRUPT and runs with all other interrupts
29 *           disabled. Normal interrupts disable their own source but
30 *           run with all other interrupt sources enabled.
31 *           PORTS and EXTER interrupts are always shared even if the
32 *           drivers do not explicitly mark this when calling
33 *           request_irq which they really should do.
34 *           This is similar to the way interrupts are handled on all
35 *           other architectures and makes a ton of sense besides
36 *           having the advantage of making it easier to share
37 *           drivers.
38 *           /Jes
39 */
40
41#include <linux/config.h>
42#include <linux/types.h>
43#include <linux/kernel.h>
44#include <linux/sched.h>
45#include <linux/interrupt.h>
46#include <linux/irq.h>
47#include <linux/kernel_stat.h>
48#include <linux/init.h>
49
50#include <asm/system.h>
51#include <asm/irq.h>
52#include <asm/traps.h>
53#include <asm/amigahw.h>
54#include <asm/amigaints.h>
55#include <asm/amipcmcia.h>
56
57#ifdef CONFIG_APUS
58#include <asm/amigappc.h>
59#endif
60
61extern int cia_request_irq(int irq,
62                           void (*handler)(int, void *, struct pt_regs *),
63                           unsigned long flags, const char *devname, void *dev_id);
64extern void cia_free_irq(unsigned int irq, void *dev_id);
65extern void cia_init_IRQ(struct ciabase *base);
66extern int cia_get_irq_list(struct ciabase *base, char *buf);
67
68unsigned short ami_intena_vals[AMI_STD_IRQS] = {
69	IF_VERTB, IF_COPER, IF_AUD0, IF_AUD1, IF_AUD2, IF_AUD3, IF_BLIT,
70	IF_DSKSYN, IF_DSKBLK, IF_RBF, IF_TBE, IF_SOFT, IF_PORTS, IF_EXTER
71};
72static const unsigned char ami_servers[AMI_STD_IRQS] = {
73	1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1
74};
75
76static short ami_ablecount[AMI_IRQS];
77
78static void ami_badint(int irq, void *dev_id, struct pt_regs *fp)
79{
80/*	num_spurious += 1;*/
81}
82
83/*
84 * void amiga_init_IRQ(void)
85 *
86 * Parameters:	None
87 *
88 * Returns:	Nothing
89 *
90 * This function should be called during kernel startup to initialize
91 * the amiga IRQ handling routines.
92 */
93
94__init
95void amiga_init_IRQ(void)
96{
97	int i;
98
99	for (i = 0; i < AMI_IRQS; i++)
100		ami_ablecount[i] = 0;
101
102	/* turn off PCMCIA interrupts */
103	if (AMIGAHW_PRESENT(PCMCIA))
104		gayle.inten = GAYLE_IRQ_IDE;
105
106	/* turn off all interrupts... */
107	custom.intena = 0x7fff;
108	custom.intreq = 0x7fff;
109
110#ifdef CONFIG_APUS
111	/* Clear any inter-CPU interrupt requests. Circumvents bug in
112           Blizzard IPL emulation HW (or so it appears). */
113	APUS_WRITE(APUS_INT_LVL, INTLVL_SETRESET | INTLVL_MASK);
114
115	/* Init IPL emulation. */
116	APUS_WRITE(APUS_REG_INT, REGINT_INTMASTER | REGINT_ENABLEIPL);
117	APUS_WRITE(APUS_IPL_EMU, IPLEMU_DISABLEINT);
118	APUS_WRITE(APUS_IPL_EMU, IPLEMU_SETRESET | IPLEMU_IPLMASK);
119#endif
120	/* ... and enable the master interrupt bit */
121	custom.intena = IF_SETCLR | IF_INTEN;
122
123	cia_init_IRQ(&ciaa_base);
124	cia_init_IRQ(&ciab_base);
125}
126
127/*
128 * Enable/disable a particular machine specific interrupt source.
129 * Note that this may affect other interrupts in case of a shared interrupt.
130 * This function should only be called for a _very_ short time to change some
131 * internal data, that may not be changed by the interrupt at the same time.
132 * ami_(enable|disable)_irq calls may also be nested.
133 */
134
135void amiga_enable_irq(unsigned int irq)
136{
137	if (irq >= AMI_IRQS) {
138		printk("%s: Unknown IRQ %d\n", __FUNCTION__, irq);
139		return;
140	}
141
142	ami_ablecount[irq]--;
143	if (ami_ablecount[irq]<0)
144		ami_ablecount[irq]=0;
145	else if (ami_ablecount[irq])
146		return;
147
148	/* No action for auto-vector interrupts */
149	if (irq >= IRQ_AMIGA_AUTO){
150		printk("%s: Trying to enable auto-vector IRQ %i\n",
151		       __FUNCTION__, irq - IRQ_AMIGA_AUTO);
152		return;
153	}
154
155	if (irq >= IRQ_AMIGA_CIAA) {
156		cia_set_irq(irq, 0);
157		cia_able_irq(irq, 1);
158		return;
159	}
160
161	/* enable the interrupt */
162	custom.intena = IF_SETCLR | ami_intena_vals[irq];
163}
164
165void amiga_disable_irq(unsigned int irq)
166{
167	if (irq >= AMI_IRQS) {
168		printk("%s: Unknown IRQ %d\n", __FUNCTION__, irq);
169		return;
170	}
171
172	if (ami_ablecount[irq]++)
173		return;
174
175	/* No action for auto-vector interrupts */
176	if (irq >= IRQ_AMIGA_AUTO) {
177		printk("%s: Trying to disable auto-vector IRQ %i\n",
178		       __FUNCTION__, irq - IRQ_AMIGA_AUTO);
179		return;
180	}
181
182	if (irq >= IRQ_AMIGA_CIAA) {
183		cia_able_irq(irq, 0);
184		return;
185	}
186
187	/* disable the interrupt */
188	custom.intena = ami_intena_vals[irq];
189}
190
191inline void amiga_do_irq(int irq, struct pt_regs *fp)
192{
193	irq_desc_t *desc = irq_desc + irq;
194	struct irqaction *action = desc->action;
195
196	kstat.irqs[0][irq]++;
197	action->handler(irq, action->dev_id, fp);
198}
199
200void amiga_do_irq_list(int irq, struct pt_regs *fp)
201{
202	irq_desc_t *desc = irq_desc + irq;
203	struct irqaction *action;
204
205	kstat.irqs[0][irq]++;
206
207	custom.intreq = ami_intena_vals[irq];
208
209	for (action = desc->action; action; action = action->next)
210		action->handler(irq, action->dev_id, fp);
211}
212
213/*
214 * The builtin Amiga hardware interrupt handlers.
215 */
216
217static void ami_int1(int irq, void *dev_id, struct pt_regs *fp)
218{
219	unsigned short ints = custom.intreqr & custom.intenar;
220
221	/* if serial transmit buffer empty, interrupt */
222	if (ints & IF_TBE) {
223		custom.intreq = IF_TBE;
224		amiga_do_irq(IRQ_AMIGA_TBE, fp);
225	}
226
227	/* if floppy disk transfer complete, interrupt */
228	if (ints & IF_DSKBLK) {
229		custom.intreq = IF_DSKBLK;
230		amiga_do_irq(IRQ_AMIGA_DSKBLK, fp);
231	}
232
233	/* if software interrupt set, interrupt */
234	if (ints & IF_SOFT) {
235		custom.intreq = IF_SOFT;
236		amiga_do_irq(IRQ_AMIGA_SOFT, fp);
237	}
238}
239
240static void ami_int3(int irq, void *dev_id, struct pt_regs *fp)
241{
242	unsigned short ints = custom.intreqr & custom.intenar;
243
244	/* if a blitter interrupt */
245	if (ints & IF_BLIT) {
246		custom.intreq = IF_BLIT;
247		amiga_do_irq(IRQ_AMIGA_BLIT, fp);
248	}
249
250	/* if a copper interrupt */
251	if (ints & IF_COPER) {
252		custom.intreq = IF_COPER;
253		amiga_do_irq(IRQ_AMIGA_COPPER, fp);
254	}
255
256	/* if a vertical blank interrupt */
257	if (ints & IF_VERTB)
258		amiga_do_irq_list(IRQ_AMIGA_VERTB, fp);
259}
260
261static void ami_int4(int irq, void *dev_id, struct pt_regs *fp)
262{
263	unsigned short ints = custom.intreqr & custom.intenar;
264
265	/* if audio 0 interrupt */
266	if (ints & IF_AUD0) {
267		custom.intreq = IF_AUD0;
268		amiga_do_irq(IRQ_AMIGA_AUD0, fp);
269	}
270
271	/* if audio 1 interrupt */
272	if (ints & IF_AUD1) {
273		custom.intreq = IF_AUD1;
274		amiga_do_irq(IRQ_AMIGA_AUD1, fp);
275	}
276
277	/* if audio 2 interrupt */
278	if (ints & IF_AUD2) {
279		custom.intreq = IF_AUD2;
280		amiga_do_irq(IRQ_AMIGA_AUD2, fp);
281	}
282
283	/* if audio 3 interrupt */
284	if (ints & IF_AUD3) {
285		custom.intreq = IF_AUD3;
286		amiga_do_irq(IRQ_AMIGA_AUD3, fp);
287	}
288}
289
290static void ami_int5(int irq, void *dev_id, struct pt_regs *fp)
291{
292	unsigned short ints = custom.intreqr & custom.intenar;
293
294	/* if serial receive buffer full interrupt */
295	if (ints & IF_RBF) {
296		/* acknowledge of IF_RBF must be done by the serial interrupt */
297		amiga_do_irq(IRQ_AMIGA_RBF, fp);
298	}
299
300	/* if a disk sync interrupt */
301	if (ints & IF_DSKSYN) {
302		custom.intreq = IF_DSKSYN;
303		amiga_do_irq(IRQ_AMIGA_DSKSYN, fp);
304	}
305}
306
307static void ami_int7(int irq, void *dev_id, struct pt_regs *fp)
308{
309	panic ("level 7 interrupt received\n");
310}
311
312#ifdef CONFIG_APUS
313/* The PPC irq handling links all handlers requested on the same vector
314   and executes them in a loop. Having ami_badint at the end of the chain
315   is a bad idea. */
316struct irqaction amiga_sys_irqaction[AUTO_IRQS] = {
317	{ handler: ami_badint, name: "spurious int" },
318	{ handler: ami_int1, name: "int1 handler" },
319	{ 0, /* CIAA */ },
320	{ handler: ami_int3, name: "int3 handler" },
321	{ handler: ami_int4, name: "int4 handler" },
322	{ handler: ami_int5, name: "int5 handler" },
323	{ 0, /* CIAB */ },
324	{ handler: ami_int7, name: "int7 handler" },
325};
326#else
327void (*amiga_default_handler[SYS_IRQS])(int, void *, struct pt_regs *) = {
328	ami_badint, ami_int1, ami_badint, ami_int3,
329	ami_int4, ami_int5, ami_badint, ami_int7
330};
331#endif
332