1 /* $NetBSD: vidcaudio.c,v 1.13 2002/10/02 15:45:13 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.13 2002/10/02 15:45:13 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