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