File: /usr/src/linux/arch/ppc/amiga/amiints.c

1     /*
2      * BK Id: SCCS/s.amiints.c 1.8 05/21/01 00:48:24 cort
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/kernel_stat.h>
46     #include <linux/init.h>
47     
48     #include <asm/system.h>
49     #include <asm/irq.h>
50     #include <asm/traps.h>
51     #include <asm/amigahw.h>
52     #include <asm/amigaints.h>
53     #include <asm/amipcmcia.h>
54     
55     #ifdef CONFIG_APUS
56     #include <asm/amigappc.h>
57     #endif
58     
59     extern int cia_request_irq(int irq,
60                                void (*handler)(int, void *, struct pt_regs *),
61                                unsigned long flags, const char *devname, void *dev_id);
62     extern void cia_free_irq(unsigned int irq, void *dev_id);
63     extern void cia_init_IRQ(struct ciabase *base);
64     extern int cia_get_irq_list(struct ciabase *base, char *buf);
65     
66     /* irq node variables for amiga interrupt sources */
67     static irq_node_t *ami_irq_list[AMI_STD_IRQS];
68     
69     unsigned short ami_intena_vals[AMI_STD_IRQS] = {
70     	IF_VERTB, IF_COPER, IF_AUD0, IF_AUD1, IF_AUD2, IF_AUD3, IF_BLIT,
71     	IF_DSKSYN, IF_DSKBLK, IF_RBF, IF_TBE, IF_SOFT, IF_PORTS, IF_EXTER
72     };
73     static const unsigned char ami_servers[AMI_STD_IRQS] = {
74     	1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1
75     };
76     
77     static short ami_ablecount[AMI_IRQS];
78     
79     static void ami_badint(int irq, void *dev_id, struct pt_regs *fp)
80     {
81     	num_spurious += 1;
82     }
83     
84     /*
85      * void amiga_init_IRQ(void)
86      *
87      * Parameters:	None
88      *
89      * Returns:	Nothing
90      *
91      * This function should be called during kernel startup to initialize
92      * the amiga IRQ handling routines.
93      */
94     
95     __init
96     void amiga_init_IRQ(void)
97     {
98     	int i;
99     
100     	/* initialize handlers */
101     	for (i = 0; i < AMI_STD_IRQS; i++) {
102     		if (ami_servers[i]) {
103     			ami_irq_list[i] = NULL;
104     		} else {
105     			ami_irq_list[i] = new_irq_node();
106     			ami_irq_list[i]->handler = ami_badint;
107     			ami_irq_list[i]->flags   = 0;
108     			ami_irq_list[i]->dev_id  = NULL;
109     			ami_irq_list[i]->devname = NULL;
110     			ami_irq_list[i]->next    = NULL;
111     		}
112     	}
113     	for (i = 0; i < AMI_IRQS; i++)
114     		ami_ablecount[i] = 0;
115     
116     	/* turn off PCMCIA interrupts */
117     	if (AMIGAHW_PRESENT(PCMCIA))
118     		pcmcia_disable_irq();
119     
120     	/* turn off all interrupts... */
121     	custom.intena = 0x7fff;
122     	custom.intreq = 0x7fff;
123     
124     #ifdef CONFIG_APUS
125     	/* Clear any inter-CPU interrupt requests. Circumvents bug in
126                Blizzard IPL emulation HW (or so it appears). */
127     	APUS_WRITE(APUS_INT_LVL, INTLVL_SETRESET | INTLVL_MASK);
128     
129     	/* Init IPL emulation. */
130     	APUS_WRITE(APUS_REG_INT, REGINT_INTMASTER | REGINT_ENABLEIPL);
131     	APUS_WRITE(APUS_IPL_EMU, IPLEMU_DISABLEINT);
132     	APUS_WRITE(APUS_IPL_EMU, IPLEMU_SETRESET | IPLEMU_IPLMASK);
133     #endif
134     	/* ... and enable the master interrupt bit */
135     	custom.intena = IF_SETCLR | IF_INTEN;
136     
137     	cia_init_IRQ(&ciaa_base);
138     	cia_init_IRQ(&ciab_base);
139     }
140     
141     static inline int amiga_insert_irq(irq_node_t **list, irq_node_t *node)
142     {
143     	unsigned long flags;
144     	irq_node_t *cur;
145     
146     	if (!node->dev_id)
147     		printk("%s: Warning: dev_id of %s is zero\n",
148     		       __FUNCTION__, node->devname);
149     
150     	save_flags(flags);
151     	cli();
152     
153     	cur = *list;
154     
155     	if (node->flags & SA_INTERRUPT) {
156     		if (node->flags & SA_SHIRQ)
157     			return -EBUSY;
158     		/*
159     		 * There should never be more than one
160     		 */
161     		while (cur && cur->flags & SA_INTERRUPT) {
162     			list = &cur->next;
163     			cur = cur->next;
164     		}
165     	} else {
166     		while (cur) {
167     			list = &cur->next;
168     			cur = cur->next;
169     		}
170     	}
171     
172     	node->next = cur;
173     	*list = node;
174     
175     	restore_flags(flags);
176     	return 0;
177     }
178     
179     static inline void amiga_delete_irq(irq_node_t **list, void *dev_id)
180     {
181     	unsigned long flags;
182     	irq_node_t *node;
183     
184     	save_flags(flags);
185     	cli();
186     
187     	for (node = *list; node; list = &node->next, node = *list) {
188     		if (node->dev_id == dev_id) {
189     			*list = node->next;
190     			/* Mark it as free. */
191     			node->handler = NULL;
192     			restore_flags(flags);
193     			return;
194     		}
195     	}
196     	restore_flags(flags);
197     	printk ("%s: tried to remove invalid irq\n", __FUNCTION__);
198     }
199     
200     /*
201      * amiga_request_irq : add an interrupt service routine for a particular
202      *                     machine specific interrupt source.
203      *                     If the addition was successful, it returns 0.
204      */
205     
206     int amiga_request_irq(unsigned int irq,
207     		      void (*handler)(int, void *, struct pt_regs *),
208                           unsigned long flags, const char *devname, void *dev_id)
209     {
210     	irq_node_t *node;
211     	int error = 0;
212     
213     	if (irq >= AMI_IRQS) {
214     		printk ("%s: Unknown IRQ %d from %s\n", __FUNCTION__,
215     			irq, devname);
216     		return -ENXIO;
217     	}
218     
219     	if (irq >= IRQ_AMIGA_AUTO)
220     		return sys_request_irq(irq - IRQ_AMIGA_AUTO, handler,
221     		                       flags, devname, dev_id);
222     
223     	if (irq >= IRQ_AMIGA_CIAA)
224     		return cia_request_irq(irq, handler, flags, devname, dev_id);
225     
226     	/*
227     	 * IRQ_AMIGA_PORTS & IRQ_AMIGA_EXTER defaults to shared,
228     	 * we could add a check here for the SA_SHIRQ flag but all drivers
229     	 * should be aware of sharing anyway.
230     	 */
231     	if (ami_servers[irq]) {
232     		if (!(node = new_irq_node()))
233     			return -ENOMEM;
234     		node->handler = handler;
235     		node->flags   = flags;
236     		node->dev_id  = dev_id;
237     		node->devname = devname;
238     		node->next    = NULL;
239     		error = amiga_insert_irq(&ami_irq_list[irq], node);
240     	} else {
241     		ami_irq_list[irq]->handler = handler;
242     		ami_irq_list[irq]->flags   = flags;
243     		ami_irq_list[irq]->dev_id  = dev_id;
244     		ami_irq_list[irq]->devname = devname;
245     	}
246     
247     	/* enable the interrupt */
248     	if (irq < IRQ_AMIGA_PORTS && !ami_ablecount[irq])
249     		custom.intena = IF_SETCLR | ami_intena_vals[irq];
250     
251     	return error;
252     }
253     
254     void amiga_free_irq(unsigned int irq, void *dev_id)
255     {
256     	if (irq >= AMI_IRQS) {
257     		printk ("%s: Unknown IRQ %d\n", __FUNCTION__, irq);
258     		return;
259     	}
260     
261     	if (irq >= IRQ_AMIGA_AUTO) {
262     		sys_free_irq(irq - IRQ_AMIGA_AUTO, dev_id);
263     		return;
264     	}
265     	if (irq >= IRQ_AMIGA_CIAA) {
266     		cia_free_irq(irq, dev_id);
267     		return;
268     	}
269     
270     	if (ami_servers[irq]) {
271     		amiga_delete_irq(&ami_irq_list[irq], dev_id);
272     		/* if server list empty, disable the interrupt */
273     		if (!ami_irq_list[irq] && irq < IRQ_AMIGA_PORTS)
274     			custom.intena = ami_intena_vals[irq];
275     	} else {
276     		if (ami_irq_list[irq]->dev_id != dev_id)
277     			printk("%s: removing probably wrong IRQ %d from %s\n",
278     			       __FUNCTION__, irq, ami_irq_list[irq]->devname);
279     		ami_irq_list[irq]->handler = ami_badint;
280     		ami_irq_list[irq]->flags   = 0;
281     		ami_irq_list[irq]->dev_id  = NULL;
282     		ami_irq_list[irq]->devname = NULL;
283     		custom.intena = ami_intena_vals[irq];
284     	}
285     }
286     
287     /*
288      * Enable/disable a particular machine specific interrupt source.
289      * Note that this may affect other interrupts in case of a shared interrupt.
290      * This function should only be called for a _very_ short time to change some
291      * internal data, that may not be changed by the interrupt at the same time.
292      * ami_(enable|disable)_irq calls may also be nested.
293      */
294     
295     void amiga_enable_irq(unsigned int irq)
296     {
297     	if (irq >= AMI_IRQS) {
298     		printk("%s: Unknown IRQ %d\n", __FUNCTION__, irq);
299     		return;
300     	}
301     
302     	ami_ablecount[irq]--;
303     	if (ami_ablecount[irq]<0)
304     		ami_ablecount[irq]=0;
305     	else if (ami_ablecount[irq])
306     		return;
307     
308     	/* No action for auto-vector interrupts */
309     	if (irq >= IRQ_AMIGA_AUTO){
310     		printk("%s: Trying to enable auto-vector IRQ %i\n",
311     		       __FUNCTION__, irq - IRQ_AMIGA_AUTO);
312     		return;
313     	}
314     
315     	if (irq >= IRQ_AMIGA_CIAA) {
316     		cia_set_irq(irq, 0);
317     		cia_able_irq(irq, 1);
318     		return;
319     	}
320     
321     	/* enable the interrupt */
322     	custom.intena = IF_SETCLR | ami_intena_vals[irq];
323     }
324     
325     void amiga_disable_irq(unsigned int irq)
326     {
327     	if (irq >= AMI_IRQS) {
328     		printk("%s: Unknown IRQ %d\n", __FUNCTION__, irq);
329     		return;
330     	}
331     
332     	if (ami_ablecount[irq]++)
333     		return;
334     
335     	/* No action for auto-vector interrupts */
336     	if (irq >= IRQ_AMIGA_AUTO) {
337     		printk("%s: Trying to disable auto-vector IRQ %i\n",
338     		       __FUNCTION__, irq - IRQ_AMIGA_AUTO);
339     		return;
340     	}
341     
342     	if (irq >= IRQ_AMIGA_CIAA) {
343     		cia_able_irq(irq, 0);
344     		return;
345     	}
346     
347     	/* disable the interrupt */
348     	custom.intena = ami_intena_vals[irq];
349     }
350     
351     inline void amiga_do_irq(int irq, struct pt_regs *fp)
352     {
353     	kstat.irqs[0][SYS_IRQS + irq]++;
354     	ami_irq_list[irq]->handler(irq, ami_irq_list[irq]->dev_id, fp);
355     }
356     
357     void amiga_do_irq_list(int irq, struct pt_regs *fp)
358     {
359     	irq_node_t *node;
360     
361     	kstat.irqs[0][SYS_IRQS + irq]++;
362     
363     	custom.intreq = ami_intena_vals[irq];
364     
365     	for (node = ami_irq_list[irq]; node; node = node->next)
366     		node->handler(irq, node->dev_id, fp);
367     }
368     
369     /*
370      * The builtin Amiga hardware interrupt handlers.
371      */
372     
373     static void ami_int1(int irq, void *dev_id, struct pt_regs *fp)
374     {
375     	unsigned short ints = custom.intreqr & custom.intenar;
376     
377     	/* if serial transmit buffer empty, interrupt */
378     	if (ints & IF_TBE) {
379     		custom.intreq = IF_TBE;
380     		amiga_do_irq(IRQ_AMIGA_TBE, fp);
381     	}
382     
383     	/* if floppy disk transfer complete, interrupt */
384     	if (ints & IF_DSKBLK) {
385     		custom.intreq = IF_DSKBLK;
386     		amiga_do_irq(IRQ_AMIGA_DSKBLK, fp);
387     	}
388     
389     	/* if software interrupt set, interrupt */
390     	if (ints & IF_SOFT) {
391     		custom.intreq = IF_SOFT;
392     		amiga_do_irq(IRQ_AMIGA_SOFT, fp);
393     	}
394     }
395     
396     static void ami_int3(int irq, void *dev_id, struct pt_regs *fp)
397     {
398     	unsigned short ints = custom.intreqr & custom.intenar;
399     
400     	/* if a blitter interrupt */
401     	if (ints & IF_BLIT) {
402     		custom.intreq = IF_BLIT;
403     		amiga_do_irq(IRQ_AMIGA_BLIT, fp);
404     	}
405     
406     	/* if a copper interrupt */
407     	if (ints & IF_COPER) {
408     		custom.intreq = IF_COPER;
409     		amiga_do_irq(IRQ_AMIGA_COPPER, fp);
410     	}
411     
412     	/* if a vertical blank interrupt */
413     	if (ints & IF_VERTB)
414     		amiga_do_irq_list(IRQ_AMIGA_VERTB, fp);
415     }
416     
417     static void ami_int4(int irq, void *dev_id, struct pt_regs *fp)
418     {
419     	unsigned short ints = custom.intreqr & custom.intenar;
420     
421     	/* if audio 0 interrupt */
422     	if (ints & IF_AUD0) {
423     		custom.intreq = IF_AUD0;
424     		amiga_do_irq(IRQ_AMIGA_AUD0, fp);
425     	}
426     
427     	/* if audio 1 interrupt */
428     	if (ints & IF_AUD1) {
429     		custom.intreq = IF_AUD1;
430     		amiga_do_irq(IRQ_AMIGA_AUD1, fp);
431     	}
432     
433     	/* if audio 2 interrupt */
434     	if (ints & IF_AUD2) {
435     		custom.intreq = IF_AUD2;
436     		amiga_do_irq(IRQ_AMIGA_AUD2, fp);
437     	}
438     
439     	/* if audio 3 interrupt */
440     	if (ints & IF_AUD3) {
441     		custom.intreq = IF_AUD3;
442     		amiga_do_irq(IRQ_AMIGA_AUD3, fp);
443     	}
444     }
445     
446     static void ami_int5(int irq, void *dev_id, struct pt_regs *fp)
447     {
448     	unsigned short ints = custom.intreqr & custom.intenar;
449     
450     	/* if serial receive buffer full interrupt */
451     	if (ints & IF_RBF) {
452     		/* acknowledge of IF_RBF must be done by the serial interrupt */
453     		amiga_do_irq(IRQ_AMIGA_RBF, fp);
454     	}
455     
456     	/* if a disk sync interrupt */
457     	if (ints & IF_DSKSYN) {
458     		custom.intreq = IF_DSKSYN;
459     		amiga_do_irq(IRQ_AMIGA_DSKSYN, fp);
460     	}
461     }
462     
463     static void ami_int7(int irq, void *dev_id, struct pt_regs *fp)
464     {
465     	panic ("level 7 interrupt received\n");
466     }
467     
468     #ifdef CONFIG_APUS
469     /* The PPC irq handling links all handlers requested on the same vector
470        and executes them in a loop. Having ami_badint at the end of the chain
471        is a bad idea. */
472     void (*amiga_default_handler[SYS_IRQS])(int, void *, struct pt_regs *) = {
473     	NULL, ami_int1, NULL, ami_int3,
474     	ami_int4, ami_int5, NULL, ami_int7
475     };
476     #else
477     void (*amiga_default_handler[SYS_IRQS])(int, void *, struct pt_regs *) = {
478     	ami_badint, ami_int1, ami_badint, ami_int3,
479     	ami_int4, ami_int5, ami_badint, ami_int7
480     };
481     #endif
482     
483     int amiga_get_irq_list(char *buf)
484     {
485     	int i, len = 0;
486     	irq_node_t *node;
487     
488     	for (i = 0; i < AMI_STD_IRQS; i++) {
489     		if (!(node = ami_irq_list[i]))
490     			continue;
491     		len += sprintf(buf+len, "ami  %2d: %10u ", i,
492     		               kstat.irqs[0][SYS_IRQS + i]);
493     		do {
494     			if (node->flags & SA_INTERRUPT)
495     				len += sprintf(buf+len, "F ");
496     			else
497     				len += sprintf(buf+len, "  ");
498     			len += sprintf(buf+len, "%s\n", node->devname);
499     			if ((node = node->next))
500     				len += sprintf(buf+len, "                    ");
501     		} while (node);
502     	}
503     
504     	len += cia_get_irq_list(&ciaa_base, buf+len);
505     	len += cia_get_irq_list(&ciab_base, buf+len);
506     	return len;
507     }
508