Home | History | Annotate | Line # | Download | only in iomd
vidcaudio.c revision 1.2.4.2
      1 /*	$NetBSD: vidcaudio.c,v 1.2.4.2 2002/01/08 00:23:16 nathanw Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1995 Melvin Tang-Richardson
      5  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions
      8  * are met:
      9  * 1. Redistributions of source code must retain the above copyright
     10  *    notice, this list of conditions and the following disclaimer.
     11  * 2. Redistributions in binary form must reproduce the above copyright
     12  *    notice, this list of conditions and the following disclaimer in the
     13  *    documentation and/or other materials provided with the distribution.
     14  * 3. All advertising materials mentioning features or use of this software
     15  *    must display the following acknowledgement:
     16  *	This product includes software developed by the RiscBSD team.
     17  * 4. The name of the author may not be used to endorse or promote products
     18  *    derived from this software without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     21  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     22  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     23  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     25  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     29  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 /*
     33  * audio driver for the RiscPC 16 bit sound
     34  *
     35  * Interfaces with the NetBSD generic audio driver to provide SUN
     36  * /dev/audio (partial) compatibility.
     37  */
     38 
     39 #include <sys/param.h>	/* proc.h */
     40 #include <sys/conf.h>   /* autoconfig functions */
     41 #include <sys/device.h> /* device calls */
     42 #include <sys/proc.h>	/* device calls */
     43 #include <sys/audioio.h>
     44 #include <sys/errno.h>
     45 #include <sys/systm.h>
     46 
     47 #include <uvm/uvm_extern.h>
     48 
     49 #include <dev/audio_if.h>
     50 
     51 #include <machine/intr.h>
     52 #include <arm/arm32/katelib.h>
     53 
     54 #include <arm/iomd/iomdreg.h>
     55 #include <arm/iomd/iomdvar.h>
     56 #include <arm/iomd/vidc.h>
     57 #include <arm/mainbus/mainbus.h>
     58 #include <arm/iomd/waveform.h>
     59 #include "vidcaudio.h"
     60 
     61 extern int *vidc_base;
     62 
     63 #undef DEBUG
     64 
     65 struct audio_general {
     66 	vm_offset_t silence;
     67 	irqhandler_t ih;
     68 
     69 	void (*intr) ();
     70 	void *arg;
     71 
     72 	vm_offset_t next_cur;
     73 	vm_offset_t next_end;
     74 	void (*next_intr) ();
     75 	void *next_arg;
     76 
     77 	int buffer;
     78 	int in_progress;
     79 
     80 	int open;
     81 } ag;
     82 
     83 struct vidcaudio_softc {
     84 	struct device device;
     85 	int iobase;
     86 
     87 	int open;
     88 };
     89 
     90 int  vidcaudio_probe	__P((struct device *parent, struct cfdata *cf, void *aux));
     91 void vidcaudio_attach	__P((struct device *parent, struct device *self, void *aux));
     92 int  vidcaudio_open	__P((void *addr, int flags));
     93 void vidcaudio_close	__P((void *addr));
     94 
     95 int vidcaudio_intr	__P((void *arg));
     96 int vidcaudio_dma_program	__P((vm_offset_t cur, vm_offset_t end, void (*intr)(), void *arg));
     97 void vidcaudio_dummy_routine	__P((void *arg));
     98 int vidcaudio_stereo	__P((int channel, int position));
     99 int vidcaudio_rate	__P((int rate));
    100 void vidcaudio_shutdown	__P((void));
    101 
    102 static int sound_dma_intr;
    103 
    104 struct cfattach vidcaudio_ca = {
    105 	sizeof(struct vidcaudio_softc), vidcaudio_probe, vidcaudio_attach
    106 };
    107 
    108 int    vidcaudio_query_encoding  __P((void *, struct audio_encoding *));
    109 int    vidcaudio_set_params	 __P((void *, int, int, struct audio_params *, struct audio_params *));
    110 int    vidcaudio_round_blocksize __P((void *, int));
    111 int    vidcaudio_start_output	 __P((void *, void *, int, void (*)(), void *));
    112 int    vidcaudio_start_input	 __P((void *, void *, int, void (*)(), void *));
    113 int    vidcaudio_halt_output	 __P((void *));
    114 int    vidcaudio_halt_input 	 __P((void *));
    115 int    vidcaudio_speaker_ctl	 __P((void *, int));
    116 int    vidcaudio_getdev		 __P((void *, struct audio_device *));
    117 int    vidcaudio_set_port	 __P((void *, mixer_ctrl_t *));
    118 int    vidcaudio_get_port	 __P((void *, mixer_ctrl_t *));
    119 int    vidcaudio_query_devinfo	 __P((void *, mixer_devinfo_t *));
    120 int    vidcaudio_get_props	 __P((void *));
    121 
    122 struct audio_device vidcaudio_device = {
    123 	"VidcAudio 8-bit",
    124 	"x",
    125 	"vidcaudio"
    126 };
    127 
    128 struct audio_hw_if vidcaudio_hw_if = {
    129 	vidcaudio_open,
    130 	vidcaudio_close,
    131 	0,
    132 	vidcaudio_query_encoding,
    133 	vidcaudio_set_params,
    134 	vidcaudio_round_blocksize,
    135 	0,
    136 	0,
    137 	0,
    138 	vidcaudio_start_output,
    139 	vidcaudio_start_input,
    140 	vidcaudio_halt_output,
    141 	vidcaudio_halt_input,
    142 	vidcaudio_speaker_ctl,
    143 	vidcaudio_getdev,
    144 	0,
    145 	vidcaudio_set_port,
    146 	vidcaudio_get_port,
    147 	vidcaudio_query_devinfo,
    148 	0,
    149 	0,
    150 	0,
    151 	0,
    152 	vidcaudio_get_props,
    153 	0,
    154 	0,
    155 	0,
    156 };
    157 
    158 
    159 void
    160 vidcaudio_beep_generate()
    161 {
    162 	vidcaudio_dma_program(ag.silence, ag.silence+sizeof(beep_waveform)-16,
    163 	    vidcaudio_dummy_routine, NULL);
    164 }
    165 
    166 
    167 int
    168 vidcaudio_probe(parent, cf, aux)
    169 	struct device *parent;
    170 	struct cfdata* cf;
    171 	void *aux;
    172 {
    173 	int id;
    174 
    175 	id = IOMD_ID;
    176 
    177 	/* So far I only know about this IOMD */
    178 	switch (id) {
    179 	case RPC600_IOMD_ID:
    180 		return(1);
    181 		break;
    182 	case ARM7500_IOC_ID:
    183 	case ARM7500FE_IOC_ID:
    184 		return(1);
    185 		break;
    186 	default:
    187 		printf("vidcaudio: Unknown IOMD id=%04x", id);
    188 		break;
    189 	}
    190 
    191 	return (0);
    192 }
    193 
    194 
    195 void
    196 vidcaudio_attach(parent, self, aux)
    197 	struct device *parent;
    198 	struct device *self;
    199 	void *aux;
    200 {
    201 	struct mainbus_attach_args *mb = aux;
    202 	struct vidcaudio_softc *sc = (void *)self;
    203 	int id;
    204 
    205 	sc->iobase = mb->mb_iobase;
    206 
    207 	sc->open = 0;
    208 	ag.in_progress = 0;
    209 
    210 	ag.next_cur = 0;
    211 	ag.next_end = 0;
    212 	ag.next_intr = NULL;
    213 	ag.next_arg = NULL;
    214 
    215 	vidcaudio_rate(32); /* 24*1024*/
    216 
    217 	/* Program the silence buffer and reset the DMA channel */
    218 	ag.silence = uvm_km_alloc(kernel_map, NBPG);
    219 	if (ag.silence == NULL)
    220 		panic("vidcaudio: Cannot allocate memory\n");
    221 
    222 	memset((char *)ag.silence, 0, NBPG);
    223 	memcpy((char *)ag.silence, (char *)beep_waveform, sizeof(beep_waveform));
    224 
    225 	ag.buffer = 0;
    226 
    227 	/* Install the irq handler for the DMA interrupt */
    228 	ag.ih.ih_func = vidcaudio_intr;
    229 	ag.ih.ih_arg = NULL;
    230 	ag.ih.ih_level = IPL_AUDIO;
    231 	ag.ih.ih_name = "vidcaudio";
    232 
    233 	ag.intr = NULL;
    234 /*	ag.nextintr = NULL;*/
    235 
    236 	id = IOMD_ID;
    237 
    238 	switch (id) {
    239 	case RPC600_IOMD_ID:
    240 		sound_dma_intr = IRQ_DMASCH0;
    241 		break;
    242 	case ARM7500_IOC_ID:
    243 	case ARM7500FE_IOC_ID:
    244 		sound_dma_intr = IRQ_SDMA;
    245 		break;
    246 	}
    247 
    248 	disable_irq(sound_dma_intr);
    249 
    250 	if (irq_claim(sound_dma_intr, &(ag.ih)))
    251 		panic("vidcaudio: couldn't claim IRQ %d\n", sound_dma_intr);
    252 
    253 	disable_irq(sound_dma_intr);
    254 
    255 	printf("\n");
    256 
    257 	vidcaudio_dma_program(ag.silence, ag.silence+NBPG-16,
    258 	    vidcaudio_dummy_routine, NULL);
    259 
    260 	audio_attach_mi(&vidcaudio_hw_if, sc, &sc->device);
    261 
    262 #ifdef DEBUG
    263 	printf(" UNDER DEVELOPMENT (nuts)\n");
    264 #endif
    265 }
    266 
    267 int
    268 vidcaudio_open(addr, flags)
    269 	void *addr;
    270 	int flags;
    271 {
    272 	struct vidcaudio_softc *sc = addr;
    273 
    274 #ifdef DEBUG
    275 	printf("DEBUG: vidcaudio_open called\n");
    276 #endif
    277 
    278 	if (sc->open)
    279 		return EBUSY;
    280 
    281 	sc->open = 1;
    282 	ag.open = 1;
    283 
    284 	return 0;
    285 }
    286 
    287 void
    288 vidcaudio_close(addr)
    289 	void *addr;
    290 {
    291 	struct vidcaudio_softc *sc = addr;
    292 
    293 	vidcaudio_shutdown();
    294 
    295 #ifdef DEBUG
    296 	printf("DEBUG: vidcaudio_close called\n");
    297 #endif
    298 
    299 	sc->open = 0;
    300 	ag.open = 0;
    301 }
    302 
    303 /* ************************************************************************* *
    304  | Interface to the generic audio driver                                     |
    305  * ************************************************************************* */
    306 
    307 int
    308 vidcaudio_query_encoding(addr, fp)
    309 	void *addr;
    310 	struct audio_encoding *fp;
    311 {
    312 	switch (fp->index) {
    313 	case 0:
    314 		strcpy(fp->name, "vidc");
    315 		fp->encoding = AUDIO_ENCODING_ULAW;
    316 		fp->precision = 8;
    317 		fp->flags = 0;
    318 		break;
    319 
    320 	default:
    321 		return(EINVAL);
    322 	}
    323 	return 0;
    324 }
    325 
    326 int
    327 vidcaudio_set_params(addr, setmode, usemode, p, r)
    328 	void *addr;
    329 	int setmode, usemode;
    330 	struct audio_params *p, *r;
    331 {
    332 	if (p->encoding != AUDIO_ENCODING_ULAW ||
    333 	    p->channels != 8)
    334 		return EINVAL;
    335 	vidcaudio_rate(4 * p->sample_rate / (3 * 1024)); /* XXX probably wrong */
    336 
    337 	return 0;
    338 }
    339 
    340 int
    341 vidcaudio_round_blocksize(addr, blk)
    342 	void *addr;
    343 	int blk;
    344 {
    345 	if (blk > NBPG)
    346 		blk = NBPG;
    347 	return (blk);
    348 }
    349 
    350 #define ROUND(s)  ( ((int)s) & (~(NBPG-1)) )
    351 
    352 int
    353 vidcaudio_start_output(addr, p, cc, intr, arg)
    354 	void *addr;
    355 	void *p;
    356 	int cc;
    357 	void (*intr)();
    358 	void *arg;
    359 {
    360 	/* I can only DMA inside 1 page */
    361 
    362 #ifdef DEBUG
    363 	printf("vidcaudio_start_output (%d) %08x %08x\n", cc, intr, arg);
    364 #endif
    365 
    366 	if (ROUND(p) != ROUND(p+cc)) {
    367 		/*
    368 		 * If it's over a page I can fix it up by copying it into
    369 		 * my buffer
    370 		 */
    371 
    372 #ifdef DEBUG
    373 		printf("vidcaudio: DMA over page boundary requested."
    374 		    "  Fixing up\n");
    375 #endif
    376 		memcpy(p, (char *)ag.silence, cc > NBPG ? NBPG : cc);
    377 		p = (void *)ag.silence;
    378 
    379 		/*
    380 		 * I can't DMA any more than that, but it is possible to
    381 		 * fix it up by handling multiple buffers and only
    382 		 * interrupting the audio driver after sending out all the
    383 		 * stuff it gave me.  That it more than I can be bothered
    384 		 * to do right now and it probablly wont happen so I'll just
    385 		 * truncate the buffer and tell the user.
    386 		 */
    387 
    388 		if (cc > NBPG) {
    389 			printf("vidcaudio: DMA buffer truncated. I could fix this up\n");
    390 			cc = NBPG;
    391 		}
    392 	}
    393 	vidcaudio_dma_program((vm_offset_t)p, (vm_offset_t)((char *)p+cc),
    394 	    intr, arg);
    395 	return 0;
    396 }
    397 
    398 int
    399 vidcaudio_start_input(addr, p, cc, intr, arg)
    400 	void *addr;
    401 	void *p;
    402 	int cc;
    403 	void (*intr)();
    404 	void *arg;
    405 {
    406 	return EIO;
    407 }
    408 
    409 int
    410 vidcaudio_halt_output(addr)
    411 	void *addr;
    412 {
    413 #ifdef DEBUG
    414 	printf("DEBUG: vidcaudio_halt_output\n");
    415 #endif
    416 	return EIO;
    417 }
    418 
    419 int
    420 vidcaudio_halt_input(addr)
    421 	void *addr;
    422 {
    423 #ifdef DEBUG
    424 	printf("DEBUG: vidcaudio_halt_input\n");
    425 #endif
    426 	return EIO;
    427 }
    428 
    429 int
    430 vidcaudio_speaker_ctl(addr, newstate)
    431 	void *addr;
    432 	int newstate;
    433 {
    434 #ifdef DEBUG
    435 	printf("DEBUG: vidcaudio_speaker_ctl\n");
    436 #endif
    437 	return 0;
    438 }
    439 
    440 int
    441 vidcaudio_getdev(addr, retp)
    442 	void *addr;
    443 	struct audio_device *retp;
    444 {
    445 	*retp = vidcaudio_device;
    446 	return 0;
    447 }
    448 
    449 
    450 int
    451 vidcaudio_set_port(addr, cp)
    452 	void *addr;
    453 	mixer_ctrl_t *cp;
    454 {
    455 	return EINVAL;
    456 }
    457 
    458 int
    459 vidcaudio_get_port(addr, cp)
    460 	void *addr;
    461 	mixer_ctrl_t *cp;
    462 {
    463 	return EINVAL;
    464 }
    465 
    466 int
    467 vidcaudio_query_devinfo(addr, dip)
    468 	void *addr;
    469 	mixer_devinfo_t *dip;
    470 {
    471 	return ENXIO;
    472 }
    473 
    474 int
    475 vidcaudio_get_props(addr)
    476 	void *addr;
    477 {
    478 	return 0;
    479 }
    480 void
    481 vidcaudio_dummy_routine(arg)
    482 	void *arg;
    483 {
    484 #ifdef DEBUG
    485 	printf("vidcaudio_dummy_routine\n");
    486 #endif
    487 }
    488 
    489 int
    490 vidcaudio_rate(rate)
    491 	int rate;
    492 {
    493 	WriteWord(vidc_base, VIDC_SFR | rate);
    494 	return 0;
    495 }
    496 
    497 int
    498 vidcaudio_stereo(channel, position)
    499 	int channel;
    500 	int position;
    501 {
    502 	if (channel < 0) return EINVAL;
    503 	if (channel > 7) return EINVAL;
    504 	channel = channel<<24 | VIDC_SIR0;
    505 	WriteWord(vidc_base, channel | position);
    506 	return 0;
    507 }
    508 
    509 #define PHYS(x, y) pmap_extract(pmap_kernel(), ((x)&PG_FRAME), (paddr_t *)(y))
    510 
    511 /*
    512  * Program the next buffer to be used
    513  * This function must be re-entrant, maximum re-entrancy of 2
    514  */
    515 
    516 #define FLAGS (0)
    517 
    518 int
    519 vidcaudio_dma_program(cur, end, intr, arg)
    520 	vm_offset_t cur;
    521 	vm_offset_t end;
    522 	void (*intr)();
    523 	void *arg;
    524 {
    525 	paddr_t pa1, pa2;
    526 
    527 	/* If there isn't a transfer in progress then start a new one */
    528 	if (ag.in_progress == 0) {
    529 		ag.buffer = 0;
    530 		IOMD_WRITE_WORD(IOMD_SD0CR, 0x90);	/* Reset State Machine */
    531 		IOMD_WRITE_WORD(IOMD_SD0CR, 0x30);	/* Reset State Machine */
    532 
    533 		PHYS(cur, &pa1);
    534 		PHYS(end - 16, &pa2);
    535 
    536 		IOMD_WRITE_WORD(IOMD_SD0CURB, pa1);
    537 		IOMD_WRITE_WORD(IOMD_SD0ENDB, pa2|FLAGS);
    538 		IOMD_WRITE_WORD(IOMD_SD0CURA, pa1);
    539 		IOMD_WRITE_WORD(IOMD_SD0ENDA, pa2|FLAGS);
    540 
    541 		ag.in_progress = 1;
    542 
    543 		ag.next_cur = ag.next_end = 0;
    544 		ag.next_intr = ag.next_arg = 0;
    545 
    546 		ag.intr = intr;
    547 		ag.arg = arg;
    548 
    549 		/*
    550 		 * The driver 'clicks' between buffer swaps, leading me
    551 		 * to think  that the fifo is much small than on other
    552 		 * sound cards so I'm going to have to do some tricks here
    553 		 */
    554 
    555 		(*ag.intr)(ag.arg);			/* Schedule the next buffer */
    556 		ag.intr = vidcaudio_dummy_routine;	/* Already done this        */
    557 		ag.arg = NULL;
    558 
    559 #ifdef PRINT
    560 		printf("vidcaudio: start output\n");
    561 #endif
    562 #ifdef DEBUG
    563 		printf("SE");
    564 #endif
    565 		enable_irq(sound_dma_intr);
    566 	} else {
    567 		/* Otherwise schedule the next one */
    568 		if (ag.next_cur != 0) {
    569 			/* If there's one scheduled then complain */
    570 			printf("vidcaudio: Buffer already Q'ed\n");
    571 			return EIO;
    572 		} else {
    573 			/* We're OK to schedule it now */
    574 			ag.buffer = (++ag.buffer) & 1;
    575 			PHYS(cur, &ag.next_cur);
    576 			PHYS(end - 16, &ag.next_end);
    577 			ag.next_intr = intr;
    578 			ag.next_arg = arg;
    579 #ifdef DEBUG
    580 			printf("s");
    581 #endif
    582 		}
    583 	}
    584 	return 0;
    585 }
    586 
    587 void
    588 vidcaudio_shutdown(void)
    589 {
    590 	/* Shut down the channel */
    591 	ag.intr = NULL;
    592 	ag.in_progress = 0;
    593 #ifdef PRINT
    594 	printf("vidcaudio: stop output\n");
    595 #endif
    596 	IOMD_WRITE_WORD(IOMD_SD0CURB, ag.silence);
    597 	IOMD_WRITE_WORD(IOMD_SD0ENDB, (ag.silence + NBPG - 16) | (1<<30));
    598 	disable_irq(sound_dma_intr);
    599 }
    600 
    601 int
    602 vidcaudio_intr(arg)
    603 	void *arg;
    604 {
    605 	int status = IOMD_READ_BYTE(IOMD_SD0ST);
    606 	void (*nintr)();
    607 	void *narg;
    608 	void (*xintr)();
    609 	void *xarg;
    610 	int xcur, xend;
    611 	IOMD_WRITE_WORD(IOMD_DMARQ, 0x10);
    612 
    613 #ifdef PRINT
    614 	printf ( "I" );
    615 #endif
    616 
    617 	if (ag.open == 0) {
    618 		vidcaudio_shutdown();
    619 		return 0;
    620 	}
    621 
    622 	/* Have I got the generic audio device attached */
    623 
    624 #ifdef DEBUG
    625 	printf ( "[B%01x]", status );
    626 #endif
    627 
    628 	nintr = ag.intr;
    629 	narg = ag.arg;
    630 	ag.intr = NULL;
    631 
    632 	xintr = ag.next_intr;
    633 	xarg = ag.next_arg;
    634 	xcur = ag.next_cur;
    635 	xend = ag.next_end;
    636 	ag.next_cur = 0;
    637 	ag.intr = xintr;
    638 	ag.arg = xarg;
    639 
    640 	if (nintr) {
    641 #ifdef DEBUG
    642 		printf("i");
    643 #endif
    644 		(*nintr)(narg);
    645 	}
    646 
    647 	if (xcur == 0) {
    648 		vidcaudio_shutdown ();
    649 	} else {
    650 #define OVERRUN 	(0x04)
    651 #define INTERRUPT	(0x02)
    652 #define BANK_A		(0x00)
    653 #define BANK_B		(0x01)
    654 		switch (status & 0x7) {
    655 		case (INTERRUPT|BANK_A):
    656 #ifdef PRINT
    657 			printf("B");
    658 #endif
    659 			IOMD_WRITE_WORD(IOMD_SD0CURB, xcur);
    660 			IOMD_WRITE_WORD(IOMD_SD0ENDB, xend|FLAGS);
    661 			break;
    662 
    663 		case (INTERRUPT|BANK_B):
    664 #ifdef PRINT
    665 			printf("A");
    666 #endif
    667 			IOMD_WRITE_WORD(IOMD_SD0CURA, xcur);
    668 			IOMD_WRITE_WORD(IOMD_SD0ENDA, xend|FLAGS);
    669 			break;
    670 
    671 		case (OVERRUN|INTERRUPT|BANK_A):
    672 #ifdef PRINT
    673 			printf("A");
    674 #endif
    675 			IOMD_WRITE_WORD(IOMD_SD0CURA, xcur);
    676 			IOMD_WRITE_WORD(IOMD_SD0ENDA, xend|FLAGS);
    677 			break;
    678 
    679 		case (OVERRUN|INTERRUPT|BANK_B):
    680 #ifdef PRINT
    681 			printf("B");
    682 #endif
    683 			IOMD_WRITE_WORD(IOMD_SD0CURB, xcur);
    684 			IOMD_WRITE_WORD(IOMD_SD0ENDB, xend|FLAGS);
    685 			break;
    686 		}
    687 /*
    688 	ag.next_cur = 0;
    689 	ag.intr = xintr;
    690 	ag.arg = xarg;
    691 */
    692 	}
    693 #ifdef PRINT
    694 	printf ( "i" );
    695 #endif
    696 
    697 	if (ag.next_cur == 0) {
    698 		(*ag.intr)(ag.arg);			/* Schedule the next buffer */
    699 		ag.intr = vidcaudio_dummy_routine;	/* Already done this        */
    700 		ag.arg = NULL;
    701 	}
    702 	return(0);	/* Pass interrupt on down the chain */
    703 }
    704