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