Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 | /* * Audio Command Interface (ACI) driver (sound/aci.c) * * ACI is a protocol used to communicate with the microcontroller on * some sound cards produced by miro, e.g. the miroSOUND PCM12 and * PCM20. The ACI has been developed for miro by Norberto Pellicci * <pellicci@home.com>. Special thanks to both him and miro for * providing the ACI specification. * * The main function of the ACI is to control the mixer and to get a * product identification. On the PCM20, ACI also controls the radio * tuner on this card, this is supported in the Video for Linux * miropcm20 driver. * - * This is a fullfeatured implementation. Unsupported features * are bugs... (: * * It is not longer necessary to load the mad16 module first. The * user is currently responsible to set the mad16 mixer correctly. * * To toggle the solo mode for full duplex operation just use the OSS * record switch for the pcm ('wave') controller. Robert * - * * Revision history: * * 1995-11-10 Markus Kuhn <mskuhn@cip.informatik.uni-erlangen.de> * First version written. * 1995-12-31 Markus Kuhn * Second revision, general code cleanup. * 1996-05-16 Hannu Savolainen * Integrated with other parts of the driver. * 1996-05-28 Markus Kuhn * Initialize CS4231A mixer, make ACI first mixer, * use new private mixer API for solo mode. * 1998-08-18 Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl> * Small modification to export ACI functions and * complete modularisation. * 2000-06-20 Robert Siemer <Robert.Siemer@gmx.de> * Don't initialize the CS4231A mixer anymore, so the code is * working again, and other small changes to fit in todays * kernels. * 2000-08-26 Robert Siemer * Clean up and rewrite for 2.4.x. Maybe it's SMP safe now... (: * ioctl bugfix, and integration of solo-mode into OSS-API, * added (OSS-limited) equalizer support, return value bugfix, * changed param aci_reset to reset, new params: ide, wss. * 2001-04-20 Robert Siemer * even more cleanups... */ #include <linux/kernel.h> #include <linux/init.h> #include <linux/module.h> #include <linux/proc_fs.h> #include <linux/slab.h> #include <asm/semaphore.h> #include <asm/io.h> #include <asm/uaccess.h> #include "sound_config.h" int aci_port; /* as determined by bit 4 in the OPTi 929 MC4 register */ int aci_idcode[2]; /* manufacturer and product ID */ int aci_version; /* ACI firmware version */ EXPORT_SYMBOL(aci_port); EXPORT_SYMBOL(aci_idcode); EXPORT_SYMBOL(aci_version); #include "aci.h" static int aci_solo=0; /* status bit of the card that can't be * * checked with ACI versions prior to 0xb0 */ static int aci_amp=0; /* status bit for power-amp/line-out level but I have no docs about what is what... */ static int aci_micpreamp=3; /* microphone preamp-level that can't be * * checked with ACI versions prior to 0xb0 */ static int mixer_device; static struct semaphore aci_sem; #ifdef MODULE static int reset = 0; MODULE_PARM(reset,"i"); MODULE_PARM_DESC(reset,"When set to 1, reset aci mixer."); #else static int reset = 1; #endif static int ide=-1; MODULE_PARM(ide,"i"); MODULE_PARM_DESC(ide,"1 enable, 0 disable ide-port - untested" " default: do nothing"); static int wss=-1; MODULE_PARM(wss,"i"); MODULE_PARM_DESC(wss,"change between ACI/WSS-mixer; use 0 and 1 - untested" " default: do nothing; for PCM1-pro only"); #if DEBUG static void print_bits(unsigned char c) { int j; printk(KERN_DEBUG "aci: "); for (j=7; j>=0; j--) { printk("%d", (c >> j) & 0x1); } printk("\n"); } #endif /* * This busy wait code normally requires less than 15 loops and * practically always less than 100 loops on my i486/DX2 66 MHz. * * Warning: Waiting on the general status flag after reseting the MUTE * function can take a VERY long time, because the PCM12 does some kind * of fade-in effect. For this reason, access to the MUTE function has * not been implemented at all. * * - The OSS interface has no mute option. It takes about 3 seconds to * fade-in on my PCM20. busy_wait() handles it great now... Robert */ static int busy_wait(void) { #define MINTIME 500 long timeout; unsigned char byte; for (timeout = 1; timeout <= MINTIME+30; timeout++) { if (((byte=inb(BUSY_REGISTER)) & 1) == 0) { if (timeout >= MINTIME) printk(KERN_DEBUG "aci: Got READYFLAG in round %ld.\n", timeout-MINTIME); return byte; } if (timeout >= MINTIME) { long out=10*HZ; switch (timeout-MINTIME) { case 0 ... 9: out /= 10; case 10 ... 19: out /= 10; case 20 ... 30: out /= 10; default: current->state=TASK_UNINTERRUPTIBLE; schedule_timeout(out); break; } } } printk(KERN_WARNING "aci: busy_wait() time out.\n"); return -EBUSY; } /* The four ACI command types are fucked up. [-: * implied is: 1w - special case for INIT * write is: 2w1r * read is: x(1w1r) where x is 1 or 2 (1 CHECK_SIG, 1 CHECK_STER, * 1 VERSION, 2 IDCODE) * the command is only in the first write, rest is protocol overhead * * indexed is technically a write and used for STATUS * and the special case for TUNE is: 3w1r * * Here the new general sheme: TUNE --> aci_rw_cmd(x, y, z) * indexed and write --> aci_rw_cmd(x, y, -1) * implied and read (x=1) --> aci_rw_cmd(x, -1, -1) * * Read (x>=2) is not implemented (only used during initialization). * Use aci_idcode[2] and aci_version... Robert */ /* Some notes for error detection: theoretically it is possible. * But it doubles the I/O-traffic from ww(r) to wwwrw(r) in the normal * case and doesn't seem to be designed for that... Robert */ static inline int aci_rawwrite(unsigned char byte) { if (busy_wait() >= 0) { #if DEBUG printk(KERN_DEBUG "aci_rawwrite(%d)\n", byte); #endif outb(byte, COMMAND_REGISTER); return 0; } else return -EBUSY; } static inline int aci_rawread(void) { unsigned char byte; if (busy_wait() >= 0) { byte=inb(STATUS_REGISTER); #if DEBUG printk(KERN_DEBUG "%d = aci_rawread()\n", byte); #endif return byte; } else return -EBUSY; } int aci_rw_cmd(int write1, int write2, int write3) { int write[] = {write1, write2, write3}; int read, i; if (down_interruptible(&aci_sem)) return -EINTR; for (i=0; i<3; i++) { if (write[i]< 0 || write[i] > 255) break; else if (aci_rawwrite(write[i])<0) { up(&aci_sem); return -EBUSY; } } if ((read=aci_rawread())<0) { up(&aci_sem); return -EBUSY; } up(&aci_sem); return read; } EXPORT_SYMBOL(aci_rw_cmd); static int setvolume(caddr_t arg, unsigned char left_index, unsigned char right_index) { int vol, ret, uservol, buf; __get_user(uservol, (int *)arg); /* left channel */ vol = uservol & 0xff; if (vol > 100) vol = 100; vol = SCALE(100, 0x20, vol); if ((buf=aci_write_cmd(left_index, 0x20 - vol))<0) return buf; ret = SCALE(0x20, 100, vol); /* right channel */ vol = (uservol >> 8) & 0xff; if (vol > 100) vol = 100; vol = SCALE(100, 0x20, vol); if ((buf=aci_write_cmd(right_index, 0x20 - vol))<0) return buf; ret |= SCALE(0x20, 100, vol) << 8; __put_user(ret, (int *)arg); return 0; } static int getvolume(caddr_t arg, unsigned char left_index, unsigned char right_index) { int vol; int buf; /* left channel */ if ((buf=aci_indexed_cmd(ACI_STATUS, left_index))<0) return buf; vol = SCALE(0x20, 100, buf < 0x20 ? 0x20-buf : 0); /* right channel */ if ((buf=aci_indexed_cmd(ACI_STATUS, right_index))<0) return buf; vol |= SCALE(0x20, 100, buf < 0x20 ? 0x20-buf : 0) << 8; __put_user(vol, (int *)arg); return 0; } /* The equalizer is somewhat strange on the ACI. From -12dB to +12dB * write: 0xff..down.to..0x80==0x00..up.to..0x7f */ static inline unsigned int eq_oss2aci(unsigned int vol) { int boost=0; unsigned int ret; if (vol > 100) vol = 100; if (vol > 50) { vol -= 51; boost=1; } if (boost) ret=SCALE(49, 0x7e, vol)+1; else ret=0xff - SCALE(50, 0x7f, vol); return ret; } static inline unsigned int eq_aci2oss(unsigned int vol) { if (vol < 0x80) return SCALE(0x7f, 50, vol) + 50; else return SCALE(0x7f, 50, 0xff-vol); } static int setequalizer(caddr_t arg, unsigned char left_index, unsigned char right_index) { int buf; unsigned int vol; __get_user(vol, (int *)arg); /* left channel */ if ((buf=aci_write_cmd(left_index, eq_oss2aci(vol & 0xff)))<0) return buf; /* right channel */ if ((buf=aci_write_cmd(right_index, eq_oss2aci((vol>>8) & 0xff)))<0) return buf; /* the ACI equalizer is more precise */ return 0; } static int getequalizer(caddr_t arg, unsigned char left_index, unsigned char right_index) { int buf; unsigned int vol; /* left channel */ if ((buf=aci_indexed_cmd(ACI_STATUS, left_index))<0) return buf; vol = eq_aci2oss(buf); /* right channel */ if ((buf=aci_indexed_cmd(ACI_STATUS, right_index))<0) return buf; vol |= eq_aci2oss(buf) << 8; __put_user(vol, (int *)arg); return 0; } static int aci_mixer_ioctl (int dev, unsigned int cmd, caddr_t arg) { int vol, buf; switch (cmd) { case SOUND_MIXER_WRITE_VOLUME: return setvolume(arg, 0x01, 0x00); case SOUND_MIXER_WRITE_CD: return setvolume(arg, 0x3c, 0x34); case SOUND_MIXER_WRITE_MIC: return setvolume(arg, 0x38, 0x30); case SOUND_MIXER_WRITE_LINE: return setvolume(arg, 0x39, 0x31); case SOUND_MIXER_WRITE_SYNTH: return setvolume(arg, 0x3b, 0x33); case SOUND_MIXER_WRITE_PCM: return setvolume(arg, 0x3a, 0x32); case MIXER_WRITE(SOUND_MIXER_RADIO): /* fall through */ case SOUND_MIXER_WRITE_LINE1: /* AUX1 or radio */ return setvolume(arg, 0x3d, 0x35); case SOUND_MIXER_WRITE_LINE2: /* AUX2 */ return setvolume(arg, 0x3e, 0x36); case SOUND_MIXER_WRITE_BASS: /* set band one and two */ if (aci_idcode[1]=='C') { if ((buf=setequalizer(arg, 0x48, 0x40)) || (buf=setequalizer(arg, 0x49, 0x41))); return buf; } break; case SOUND_MIXER_WRITE_TREBLE: /* set band six and seven */ if (aci_idcode[1]=='C') { if ((buf=setequalizer(arg, 0x4d, 0x45)) || (buf=setequalizer(arg, 0x4e, 0x46))); return buf; } break; case SOUND_MIXER_WRITE_IGAIN: /* MIC pre-amp */ if (aci_idcode[1]=='B' || aci_idcode[1]=='C') { __get_user(vol, (int *)arg); vol = vol & 0xff; if (vol > 100) vol = 100; vol = SCALE(100, 3, vol); if ((buf=aci_write_cmd(ACI_WRITE_IGAIN, vol))<0) return buf; aci_micpreamp = vol; vol = SCALE(3, 100, vol); vol |= (vol << 8); __put_user(vol, (int *)arg); return 0; } break; case SOUND_MIXER_WRITE_OGAIN: /* Power-amp/line-out level */ if (aci_idcode[1]=='A' || aci_idcode[1]=='B') { __get_user(buf, (int *)arg); buf = buf & 0xff; if (buf > 50) vol = 1; else vol = 0; if ((buf=aci_write_cmd(ACI_SET_POWERAMP, vol))<0) return buf; aci_amp = vol; if (aci_amp) buf = (100 || 100<<8); else buf = 0; __put_user(buf, (int *)arg); return 0; } break; case SOUND_MIXER_WRITE_RECSRC: /* handle solo mode control */ __get_user(buf, (int *)arg); /* unset solo when RECSRC for PCM is requested */ if (aci_idcode[1]=='B' || aci_idcode[1]=='C') { vol = !(buf & SOUND_MASK_PCM); if ((buf=aci_write_cmd(ACI_SET_SOLOMODE, vol))<0) return buf; aci_solo = vol; } buf = (SOUND_MASK_CD| SOUND_MASK_MIC| SOUND_MASK_LINE| SOUND_MASK_SYNTH| SOUND_MASK_LINE2); if (aci_idcode[1] == 'C') /* PCM20 radio */ buf |= SOUND_MASK_RADIO; else buf |= SOUND_MASK_LINE1; if (!aci_solo) buf |= SOUND_MASK_PCM; __put_user(buf, (int *)arg); return 0; case SOUND_MIXER_READ_DEVMASK: buf = (SOUND_MASK_VOLUME | SOUND_MASK_CD | SOUND_MASK_MIC | SOUND_MASK_LINE | SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_LINE2); switch (aci_idcode[1]) { case 'C': /* PCM20 radio */ buf |= (SOUND_MASK_RADIO | SOUND_MASK_IGAIN | SOUND_MASK_BASS | SOUND_MASK_TREBLE); break; case 'B': /* PCM12 */ buf |= (SOUND_MASK_LINE1 | SOUND_MASK_IGAIN | SOUND_MASK_OGAIN); break; case 'A': /* PCM1-pro */ buf |= (SOUND_MASK_LINE1 | SOUND_MASK_OGAIN); break; default: buf |= SOUND_MASK_LINE1; } __put_user(buf, (int *)arg); return 0; case SOUND_MIXER_READ_STEREODEVS: buf = (SOUND_MASK_VOLUME | SOUND_MASK_CD | SOUND_MASK_MIC | SOUND_MASK_LINE | SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_LINE2); switch (aci_idcode[1]) { case 'C': /* PCM20 radio */ buf |= (SOUND_MASK_RADIO | SOUND_MASK_BASS | SOUND_MASK_TREBLE); break; default: buf |= SOUND_MASK_LINE1; } __put_user(buf, (int *)arg); return 0; case SOUND_MIXER_READ_RECMASK: buf = (SOUND_MASK_CD| SOUND_MASK_MIC| SOUND_MASK_LINE| SOUND_MASK_SYNTH| SOUND_MASK_LINE2| SOUND_MASK_PCM); if (aci_idcode[1] == 'C') /* PCM20 radio */ buf |= SOUND_MASK_RADIO; else buf |= SOUND_MASK_LINE1; __put_user(buf, (int *)arg); return 0; case SOUND_MIXER_READ_RECSRC: buf = (SOUND_MASK_CD | SOUND_MASK_MIC | SOUND_MASK_LINE | SOUND_MASK_SYNTH | SOUND_MASK_LINE2); /* do we need aci_solo or can I get it from the ACI? */ switch (aci_idcode[1]) { case 'B': /* PCM12 */ case 'C': /* PCM20 radio */ if (aci_version >= 0xb0) { if ((vol=aci_rw_cmd(ACI_STATUS, ACI_S_GENERAL, -1))<0) return vol; if (vol & 0x20) buf |= SOUND_MASK_PCM; } else if (!aci_solo) buf |= SOUND_MASK_PCM; break; default: buf |= SOUND_MASK_PCM; } if (aci_idcode[1] == 'C') /* PCM20 radio */ buf |= SOUND_MASK_RADIO; else buf |= SOUND_MASK_LINE1; __put_user(buf, (int *)arg); return 0; case SOUND_MIXER_READ_CAPS: __put_user(0, (int *)arg); return 0; case SOUND_MIXER_READ_VOLUME: return getvolume(arg, 0x04, 0x03); case SOUND_MIXER_READ_CD: return getvolume(arg, 0x0a, 0x09); case SOUND_MIXER_READ_MIC: return getvolume(arg, 0x06, 0x05); case SOUND_MIXER_READ_LINE: return getvolume(arg, 0x08, 0x07); case SOUND_MIXER_READ_SYNTH: return getvolume(arg, 0x0c, 0x0b); case SOUND_MIXER_READ_PCM: return getvolume(arg, 0x0e, 0x0d); case MIXER_READ(SOUND_MIXER_RADIO): /* fall through */ case SOUND_MIXER_READ_LINE1: /* AUX1 */ return getvolume(arg, 0x11, 0x10); case SOUND_MIXER_READ_LINE2: /* AUX2 */ return getvolume(arg, 0x13, 0x12); case SOUND_MIXER_READ_BASS: /* get band one */ if (aci_idcode[1]=='C') { return getequalizer(arg, 0x23, 0x22); } break; case SOUND_MIXER_READ_TREBLE: /* get band seven */ if (aci_idcode[1]=='C') { return getequalizer(arg, 0x2f, 0x2e); } break; case SOUND_MIXER_READ_IGAIN: /* MIC pre-amp */ if (aci_idcode[1]=='B' || aci_idcode[1]=='C') { /* aci_micpreamp or ACI? */ if (aci_version >= 0xb0) { if ((buf=aci_indexed_cmd(ACI_STATUS, ACI_S_READ_IGAIN))<0) return buf; } else buf=aci_micpreamp; vol = SCALE(3, 100, buf <= 3 ? buf : 3); vol |= vol << 8; __put_user(vol, (int *)arg); return 0; } break; case SOUND_MIXER_READ_OGAIN: if (aci_amp) buf = (100 || 100<<8); else buf = 0; __put_user(buf, (int *)arg); return 0; } return -EINVAL; } static struct mixer_operations aci_mixer_operations = { owner: THIS_MODULE, id: "ACI", ioctl: aci_mixer_ioctl }; /* * There is also an internal mixer in the codec (CS4231A or AD1845), * that deserves no purpose in an ACI based system which uses an * external ACI controlled stereo mixer. Make sure that this codec * mixer has the AUX1 input selected as the recording source, that the * input gain is set near maximum and that the other channels going * from the inputs to the codec output are muted. */ static int __init attach_aci(void) { char *boardname; int i; init_MUTEX(&aci_sem); outb(0xE3, 0xf8f); /* Write MAD16 password */ aci_port = (inb(0xf90) & 0x10) ? 0x344: 0x354; /* Get aci_port from MC4_PORT */ if (check_region(aci_port, 3)) { printk(KERN_NOTICE "aci: I/O area 0x%03x-0x%03x already used.\n", aci_port, aci_port+2); return -EBUSY; } /* force ACI into a known state */ for (i=0; i<3; i++) if (aci_rw_cmd(ACI_ERROR_OP, -1, -1)<0) return -EFAULT; /* official this is one aci read call: */ if ((aci_idcode[0]=aci_rw_cmd(ACI_READ_IDCODE, -1, -1))<0 || (aci_idcode[1]=aci_rw_cmd(ACI_READ_IDCODE, -1, -1))<0) { printk(KERN_ERR "aci: Failed to read idcode on 0x%03x.\n", aci_port); return -EFAULT; } if ((aci_version=aci_rw_cmd(ACI_READ_VERSION, -1, -1))<0) { printk(KERN_ERR "aci: Failed to read version on 0x%03x.\n", aci_port); return -EFAULT; } if (aci_idcode[0] == 'm') { /* It looks like a miro sound card. */ switch (aci_idcode[1]) { case 'A': boardname = "PCM1 pro / early PCM12"; break; case 'B': boardname = "PCM12"; break; case 'C': boardname = "PCM20 radio"; break; default: boardname = "unknown miro"; } } else { printk(KERN_WARNING "aci: Warning: unsupported card! - " "no hardware, no specs...\n"); boardname = "unknown Cardinal Technologies"; } printk(KERN_INFO "<ACI 0x%02x, id %02x/%02x \"%c/%c\", (%s)> at 0x%03x\n", aci_version, aci_idcode[0], aci_idcode[1], aci_idcode[0], aci_idcode[1], boardname, aci_port); if (reset) { /* first write()s after reset fail with my PCM20 */ if (aci_rw_cmd(ACI_INIT, -1, -1)<0 || aci_rw_cmd(ACI_ERROR_OP, ACI_ERROR_OP, ACI_ERROR_OP)<0 || aci_rw_cmd(ACI_ERROR_OP, ACI_ERROR_OP, ACI_ERROR_OP)<0) return -EBUSY; } /* the PCM20 is muted after reset (and reboot) */ if (aci_rw_cmd(ACI_SET_MUTE, 0x00, -1)<0) return -EBUSY; if (ide>=0) if (aci_rw_cmd(ACI_SET_IDE, !ide, -1)<0) return -EBUSY; if (wss>=0 && aci_idcode[1]=='A') if (aci_rw_cmd(ACI_SET_WSS, !!wss, -1)<0) return -EBUSY; if (!request_region(aci_port, 3, "sound mixer (ACI)")) return -ENOMEM; if ((mixer_device = sound_install_mixer(MIXER_DRIVER_VERSION, boardname, &aci_mixer_operations, sizeof(aci_mixer_operations), NULL)) >= 0) { /* Maybe initialize the CS4231A mixer here... */ } else { printk(KERN_ERR "aci: Failed to install mixer.\n"); release_region(aci_port, 3); return mixer_device; } return 0; } static void __exit unload_aci(void) { sound_unload_mixerdev(mixer_device); release_region(aci_port, 3); } module_init(attach_aci); module_exit(unload_aci); MODULE_LICENSE("GPL"); |