sc6000.c (17060B)
1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * Driver for Gallant SC-6000 soundcard. This card is also known as 4 * Audio Excel DSP 16 or Zoltrix AV302. 5 * These cards use CompuMedia ASC-9308 chip + AD1848 codec. 6 * SC-6600 and SC-7000 cards are also supported. They are based on 7 * CompuMedia ASC-9408 chip and CS4231 codec. 8 * 9 * Copyright (C) 2007 Krzysztof Helt <krzysztof.h1@wp.pl> 10 * 11 * I don't have documentation for this card. I used the driver 12 * for OSS/Free included in the kernel source as reference. 13 */ 14 15#include <linux/module.h> 16#include <linux/delay.h> 17#include <linux/isa.h> 18#include <linux/io.h> 19#include <asm/dma.h> 20#include <sound/core.h> 21#include <sound/wss.h> 22#include <sound/opl3.h> 23#include <sound/mpu401.h> 24#include <sound/control.h> 25#define SNDRV_LEGACY_FIND_FREE_IRQ 26#define SNDRV_LEGACY_FIND_FREE_DMA 27#include <sound/initval.h> 28 29MODULE_AUTHOR("Krzysztof Helt"); 30MODULE_DESCRIPTION("Gallant SC-6000"); 31MODULE_LICENSE("GPL"); 32 33static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ 34static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ 35static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ 36static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220, 0x240 */ 37static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5, 7, 9, 10, 11 */ 38static long mss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x530, 0xe80 */ 39static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; 40 /* 0x300, 0x310, 0x320, 0x330 */ 41static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5, 7, 9, 10, 0 */ 42static int dma[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0, 1, 3 */ 43static bool joystick[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = false }; 44 45module_param_array(index, int, NULL, 0444); 46MODULE_PARM_DESC(index, "Index value for sc-6000 based soundcard."); 47module_param_array(id, charp, NULL, 0444); 48MODULE_PARM_DESC(id, "ID string for sc-6000 based soundcard."); 49module_param_array(enable, bool, NULL, 0444); 50MODULE_PARM_DESC(enable, "Enable sc-6000 based soundcard."); 51module_param_hw_array(port, long, ioport, NULL, 0444); 52MODULE_PARM_DESC(port, "Port # for sc-6000 driver."); 53module_param_hw_array(mss_port, long, ioport, NULL, 0444); 54MODULE_PARM_DESC(mss_port, "MSS Port # for sc-6000 driver."); 55module_param_hw_array(mpu_port, long, ioport, NULL, 0444); 56MODULE_PARM_DESC(mpu_port, "MPU-401 port # for sc-6000 driver."); 57module_param_hw_array(irq, int, irq, NULL, 0444); 58MODULE_PARM_DESC(irq, "IRQ # for sc-6000 driver."); 59module_param_hw_array(mpu_irq, int, irq, NULL, 0444); 60MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for sc-6000 driver."); 61module_param_hw_array(dma, int, dma, NULL, 0444); 62MODULE_PARM_DESC(dma, "DMA # for sc-6000 driver."); 63module_param_array(joystick, bool, NULL, 0444); 64MODULE_PARM_DESC(joystick, "Enable gameport."); 65 66/* 67 * Commands of SC6000's DSP (SBPRO+special). 68 * Some of them are COMMAND_xx, in the future they may change. 69 */ 70#define WRITE_MDIRQ_CFG 0x50 /* Set M&I&DRQ mask (the real config) */ 71#define COMMAND_52 0x52 /* */ 72#define READ_HARD_CFG 0x58 /* Read Hardware Config (I/O base etc) */ 73#define COMMAND_5C 0x5c /* */ 74#define COMMAND_60 0x60 /* */ 75#define COMMAND_66 0x66 /* */ 76#define COMMAND_6C 0x6c /* */ 77#define COMMAND_6E 0x6e /* */ 78#define COMMAND_88 0x88 /* Unknown command */ 79#define DSP_INIT_MSS 0x8c /* Enable Microsoft Sound System mode */ 80#define COMMAND_C5 0xc5 /* */ 81#define GET_DSP_VERSION 0xe1 /* Get DSP Version */ 82#define GET_DSP_COPYRIGHT 0xe3 /* Get DSP Copyright */ 83 84/* 85 * Offsets of SC6000 DSP I/O ports. The offset is added to base I/O port 86 * to have the actual I/O port. 87 * Register permissions are: 88 * (wo) == Write Only 89 * (ro) == Read Only 90 * (w-) == Write 91 * (r-) == Read 92 */ 93#define DSP_RESET 0x06 /* offset of DSP RESET (wo) */ 94#define DSP_READ 0x0a /* offset of DSP READ (ro) */ 95#define DSP_WRITE 0x0c /* offset of DSP WRITE (w-) */ 96#define DSP_COMMAND 0x0c /* offset of DSP COMMAND (w-) */ 97#define DSP_STATUS 0x0c /* offset of DSP STATUS (r-) */ 98#define DSP_DATAVAIL 0x0e /* offset of DSP DATA AVAILABLE (ro) */ 99 100#define PFX "sc6000: " 101#define DRV_NAME "SC-6000" 102 103/* hardware dependent functions */ 104 105/* 106 * sc6000_irq_to_softcfg - Decode irq number into cfg code. 107 */ 108static unsigned char sc6000_irq_to_softcfg(int irq) 109{ 110 unsigned char val = 0; 111 112 switch (irq) { 113 case 5: 114 val = 0x28; 115 break; 116 case 7: 117 val = 0x8; 118 break; 119 case 9: 120 val = 0x10; 121 break; 122 case 10: 123 val = 0x18; 124 break; 125 case 11: 126 val = 0x20; 127 break; 128 default: 129 break; 130 } 131 return val; 132} 133 134/* 135 * sc6000_dma_to_softcfg - Decode dma number into cfg code. 136 */ 137static unsigned char sc6000_dma_to_softcfg(int dma) 138{ 139 unsigned char val = 0; 140 141 switch (dma) { 142 case 0: 143 val = 1; 144 break; 145 case 1: 146 val = 2; 147 break; 148 case 3: 149 val = 3; 150 break; 151 default: 152 break; 153 } 154 return val; 155} 156 157/* 158 * sc6000_mpu_irq_to_softcfg - Decode MPU-401 irq number into cfg code. 159 */ 160static unsigned char sc6000_mpu_irq_to_softcfg(int mpu_irq) 161{ 162 unsigned char val = 0; 163 164 switch (mpu_irq) { 165 case 5: 166 val = 4; 167 break; 168 case 7: 169 val = 0x44; 170 break; 171 case 9: 172 val = 0x84; 173 break; 174 case 10: 175 val = 0xc4; 176 break; 177 default: 178 break; 179 } 180 return val; 181} 182 183static int sc6000_wait_data(char __iomem *vport) 184{ 185 int loop = 1000; 186 unsigned char val = 0; 187 188 do { 189 val = ioread8(vport + DSP_DATAVAIL); 190 if (val & 0x80) 191 return 0; 192 cpu_relax(); 193 } while (loop--); 194 195 return -EAGAIN; 196} 197 198static int sc6000_read(char __iomem *vport) 199{ 200 if (sc6000_wait_data(vport)) 201 return -EBUSY; 202 203 return ioread8(vport + DSP_READ); 204 205} 206 207static int sc6000_write(char __iomem *vport, int cmd) 208{ 209 unsigned char val; 210 int loop = 500000; 211 212 do { 213 val = ioread8(vport + DSP_STATUS); 214 /* 215 * DSP ready to receive data if bit 7 of val == 0 216 */ 217 if (!(val & 0x80)) { 218 iowrite8(cmd, vport + DSP_COMMAND); 219 return 0; 220 } 221 cpu_relax(); 222 } while (loop--); 223 224 snd_printk(KERN_ERR "DSP Command (0x%x) timeout.\n", cmd); 225 226 return -EIO; 227} 228 229static int sc6000_dsp_get_answer(char __iomem *vport, int command, 230 char *data, int data_len) 231{ 232 int len = 0; 233 234 if (sc6000_write(vport, command)) { 235 snd_printk(KERN_ERR "CMD 0x%x: failed!\n", command); 236 return -EIO; 237 } 238 239 do { 240 int val = sc6000_read(vport); 241 242 if (val < 0) 243 break; 244 245 data[len++] = val; 246 247 } while (len < data_len); 248 249 /* 250 * If no more data available, return to the caller, no error if len>0. 251 * We have no other way to know when the string is finished. 252 */ 253 return len ? len : -EIO; 254} 255 256static int sc6000_dsp_reset(char __iomem *vport) 257{ 258 iowrite8(1, vport + DSP_RESET); 259 udelay(10); 260 iowrite8(0, vport + DSP_RESET); 261 udelay(20); 262 if (sc6000_read(vport) == 0xaa) 263 return 0; 264 return -ENODEV; 265} 266 267/* detection and initialization */ 268static int sc6000_hw_cfg_write(char __iomem *vport, const int *cfg) 269{ 270 if (sc6000_write(vport, COMMAND_6C) < 0) { 271 snd_printk(KERN_WARNING "CMD 0x%x: failed!\n", COMMAND_6C); 272 return -EIO; 273 } 274 if (sc6000_write(vport, COMMAND_5C) < 0) { 275 snd_printk(KERN_ERR "CMD 0x%x: failed!\n", COMMAND_5C); 276 return -EIO; 277 } 278 if (sc6000_write(vport, cfg[0]) < 0) { 279 snd_printk(KERN_ERR "DATA 0x%x: failed!\n", cfg[0]); 280 return -EIO; 281 } 282 if (sc6000_write(vport, cfg[1]) < 0) { 283 snd_printk(KERN_ERR "DATA 0x%x: failed!\n", cfg[1]); 284 return -EIO; 285 } 286 if (sc6000_write(vport, COMMAND_C5) < 0) { 287 snd_printk(KERN_ERR "CMD 0x%x: failed!\n", COMMAND_C5); 288 return -EIO; 289 } 290 291 return 0; 292} 293 294static int sc6000_cfg_write(char __iomem *vport, unsigned char softcfg) 295{ 296 297 if (sc6000_write(vport, WRITE_MDIRQ_CFG)) { 298 snd_printk(KERN_ERR "CMD 0x%x: failed!\n", WRITE_MDIRQ_CFG); 299 return -EIO; 300 } 301 if (sc6000_write(vport, softcfg)) { 302 snd_printk(KERN_ERR "sc6000_cfg_write: failed!\n"); 303 return -EIO; 304 } 305 return 0; 306} 307 308static int sc6000_setup_board(char __iomem *vport, int config) 309{ 310 int loop = 10; 311 312 do { 313 if (sc6000_write(vport, COMMAND_88)) { 314 snd_printk(KERN_ERR "CMD 0x%x: failed!\n", 315 COMMAND_88); 316 return -EIO; 317 } 318 } while ((sc6000_wait_data(vport) < 0) && loop--); 319 320 if (sc6000_read(vport) < 0) { 321 snd_printk(KERN_ERR "sc6000_read after CMD 0x%x: failed\n", 322 COMMAND_88); 323 return -EIO; 324 } 325 326 if (sc6000_cfg_write(vport, config)) 327 return -ENODEV; 328 329 return 0; 330} 331 332static int sc6000_init_mss(char __iomem *vport, int config, 333 char __iomem *vmss_port, int mss_config) 334{ 335 if (sc6000_write(vport, DSP_INIT_MSS)) { 336 snd_printk(KERN_ERR "sc6000_init_mss [0x%x]: failed!\n", 337 DSP_INIT_MSS); 338 return -EIO; 339 } 340 341 msleep(10); 342 343 if (sc6000_cfg_write(vport, config)) 344 return -EIO; 345 346 iowrite8(mss_config, vmss_port); 347 348 return 0; 349} 350 351static void sc6000_hw_cfg_encode(char __iomem *vport, int *cfg, 352 long xport, long xmpu, 353 long xmss_port, int joystick) 354{ 355 cfg[0] = 0; 356 cfg[1] = 0; 357 if (xport == 0x240) 358 cfg[0] |= 1; 359 if (xmpu != SNDRV_AUTO_PORT) { 360 cfg[0] |= (xmpu & 0x30) >> 2; 361 cfg[1] |= 0x20; 362 } 363 if (xmss_port == 0xe80) 364 cfg[0] |= 0x10; 365 cfg[0] |= 0x40; /* always set */ 366 if (!joystick) 367 cfg[0] |= 0x02; 368 cfg[1] |= 0x80; /* enable WSS system */ 369 cfg[1] &= ~0x40; /* disable IDE */ 370 snd_printd("hw cfg %x, %x\n", cfg[0], cfg[1]); 371} 372 373static int sc6000_init_board(char __iomem *vport, 374 char __iomem *vmss_port, int dev) 375{ 376 char answer[15]; 377 char version[2]; 378 int mss_config = sc6000_irq_to_softcfg(irq[dev]) | 379 sc6000_dma_to_softcfg(dma[dev]); 380 int config = mss_config | 381 sc6000_mpu_irq_to_softcfg(mpu_irq[dev]); 382 int err; 383 int old = 0; 384 385 err = sc6000_dsp_reset(vport); 386 if (err < 0) { 387 snd_printk(KERN_ERR "sc6000_dsp_reset: failed!\n"); 388 return err; 389 } 390 391 memset(answer, 0, sizeof(answer)); 392 err = sc6000_dsp_get_answer(vport, GET_DSP_COPYRIGHT, answer, 15); 393 if (err <= 0) { 394 snd_printk(KERN_ERR "sc6000_dsp_copyright: failed!\n"); 395 return -ENODEV; 396 } 397 /* 398 * My SC-6000 card return "SC-6000" in DSPCopyright, so 399 * if we have something different, we have to be warned. 400 */ 401 if (strncmp("SC-6000", answer, 7)) 402 snd_printk(KERN_WARNING "Warning: non SC-6000 audio card!\n"); 403 404 if (sc6000_dsp_get_answer(vport, GET_DSP_VERSION, version, 2) < 2) { 405 snd_printk(KERN_ERR "sc6000_dsp_version: failed!\n"); 406 return -ENODEV; 407 } 408 printk(KERN_INFO PFX "Detected model: %s, DSP version %d.%d\n", 409 answer, version[0], version[1]); 410 411 /* set configuration */ 412 sc6000_write(vport, COMMAND_5C); 413 if (sc6000_read(vport) < 0) 414 old = 1; 415 416 if (!old) { 417 int cfg[2]; 418 sc6000_hw_cfg_encode(vport, &cfg[0], port[dev], mpu_port[dev], 419 mss_port[dev], joystick[dev]); 420 if (sc6000_hw_cfg_write(vport, cfg) < 0) { 421 snd_printk(KERN_ERR "sc6000_hw_cfg_write: failed!\n"); 422 return -EIO; 423 } 424 } 425 err = sc6000_setup_board(vport, config); 426 if (err < 0) { 427 snd_printk(KERN_ERR "sc6000_setup_board: failed!\n"); 428 return -ENODEV; 429 } 430 431 sc6000_dsp_reset(vport); 432 433 if (!old) { 434 sc6000_write(vport, COMMAND_60); 435 sc6000_write(vport, 0x02); 436 sc6000_dsp_reset(vport); 437 } 438 439 err = sc6000_setup_board(vport, config); 440 if (err < 0) { 441 snd_printk(KERN_ERR "sc6000_setup_board: failed!\n"); 442 return -ENODEV; 443 } 444 err = sc6000_init_mss(vport, config, vmss_port, mss_config); 445 if (err < 0) { 446 snd_printk(KERN_ERR "Cannot initialize " 447 "Microsoft Sound System mode.\n"); 448 return -ENODEV; 449 } 450 451 return 0; 452} 453 454static int snd_sc6000_mixer(struct snd_wss *chip) 455{ 456 struct snd_card *card = chip->card; 457 struct snd_ctl_elem_id id1, id2; 458 int err; 459 460 memset(&id1, 0, sizeof(id1)); 461 memset(&id2, 0, sizeof(id2)); 462 id1.iface = SNDRV_CTL_ELEM_IFACE_MIXER; 463 id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; 464 /* reassign AUX0 to FM */ 465 strcpy(id1.name, "Aux Playback Switch"); 466 strcpy(id2.name, "FM Playback Switch"); 467 err = snd_ctl_rename_id(card, &id1, &id2); 468 if (err < 0) 469 return err; 470 strcpy(id1.name, "Aux Playback Volume"); 471 strcpy(id2.name, "FM Playback Volume"); 472 err = snd_ctl_rename_id(card, &id1, &id2); 473 if (err < 0) 474 return err; 475 /* reassign AUX1 to CD */ 476 strcpy(id1.name, "Aux Playback Switch"); id1.index = 1; 477 strcpy(id2.name, "CD Playback Switch"); 478 err = snd_ctl_rename_id(card, &id1, &id2); 479 if (err < 0) 480 return err; 481 strcpy(id1.name, "Aux Playback Volume"); 482 strcpy(id2.name, "CD Playback Volume"); 483 err = snd_ctl_rename_id(card, &id1, &id2); 484 if (err < 0) 485 return err; 486 return 0; 487} 488 489static int snd_sc6000_match(struct device *devptr, unsigned int dev) 490{ 491 if (!enable[dev]) 492 return 0; 493 if (port[dev] == SNDRV_AUTO_PORT) { 494 printk(KERN_ERR PFX "specify IO port\n"); 495 return 0; 496 } 497 if (mss_port[dev] == SNDRV_AUTO_PORT) { 498 printk(KERN_ERR PFX "specify MSS port\n"); 499 return 0; 500 } 501 if (port[dev] != 0x220 && port[dev] != 0x240) { 502 printk(KERN_ERR PFX "Port must be 0x220 or 0x240\n"); 503 return 0; 504 } 505 if (mss_port[dev] != 0x530 && mss_port[dev] != 0xe80) { 506 printk(KERN_ERR PFX "MSS port must be 0x530 or 0xe80\n"); 507 return 0; 508 } 509 if (irq[dev] != SNDRV_AUTO_IRQ && !sc6000_irq_to_softcfg(irq[dev])) { 510 printk(KERN_ERR PFX "invalid IRQ %d\n", irq[dev]); 511 return 0; 512 } 513 if (dma[dev] != SNDRV_AUTO_DMA && !sc6000_dma_to_softcfg(dma[dev])) { 514 printk(KERN_ERR PFX "invalid DMA %d\n", dma[dev]); 515 return 0; 516 } 517 if (mpu_port[dev] != SNDRV_AUTO_PORT && 518 (mpu_port[dev] & ~0x30L) != 0x300) { 519 printk(KERN_ERR PFX "invalid MPU-401 port %lx\n", 520 mpu_port[dev]); 521 return 0; 522 } 523 if (mpu_port[dev] != SNDRV_AUTO_PORT && 524 mpu_irq[dev] != SNDRV_AUTO_IRQ && mpu_irq[dev] != 0 && 525 !sc6000_mpu_irq_to_softcfg(mpu_irq[dev])) { 526 printk(KERN_ERR PFX "invalid MPU-401 IRQ %d\n", mpu_irq[dev]); 527 return 0; 528 } 529 return 1; 530} 531 532static void snd_sc6000_free(struct snd_card *card) 533{ 534 char __iomem *vport = (char __force __iomem *)card->private_data; 535 536 if (vport) 537 sc6000_setup_board(vport, 0); 538} 539 540static int __snd_sc6000_probe(struct device *devptr, unsigned int dev) 541{ 542 static const int possible_irqs[] = { 5, 7, 9, 10, 11, -1 }; 543 static const int possible_dmas[] = { 1, 3, 0, -1 }; 544 int err; 545 int xirq = irq[dev]; 546 int xdma = dma[dev]; 547 struct snd_card *card; 548 struct snd_wss *chip; 549 struct snd_opl3 *opl3; 550 char __iomem *vport; 551 char __iomem *vmss_port; 552 553 err = snd_devm_card_new(devptr, index[dev], id[dev], THIS_MODULE, 554 0, &card); 555 if (err < 0) 556 return err; 557 558 if (xirq == SNDRV_AUTO_IRQ) { 559 xirq = snd_legacy_find_free_irq(possible_irqs); 560 if (xirq < 0) { 561 snd_printk(KERN_ERR PFX "unable to find a free IRQ\n"); 562 return -EBUSY; 563 } 564 } 565 566 if (xdma == SNDRV_AUTO_DMA) { 567 xdma = snd_legacy_find_free_dma(possible_dmas); 568 if (xdma < 0) { 569 snd_printk(KERN_ERR PFX "unable to find a free DMA\n"); 570 return -EBUSY; 571 } 572 } 573 574 if (!devm_request_region(devptr, port[dev], 0x10, DRV_NAME)) { 575 snd_printk(KERN_ERR PFX 576 "I/O port region is already in use.\n"); 577 return -EBUSY; 578 } 579 vport = devm_ioport_map(devptr, port[dev], 0x10); 580 if (!vport) { 581 snd_printk(KERN_ERR PFX 582 "I/O port cannot be iomapped.\n"); 583 return -EBUSY; 584 } 585 card->private_data = (void __force *)vport; 586 587 /* to make it marked as used */ 588 if (!devm_request_region(devptr, mss_port[dev], 4, DRV_NAME)) { 589 snd_printk(KERN_ERR PFX 590 "SC-6000 port I/O port region is already in use.\n"); 591 return -EBUSY; 592 } 593 vmss_port = devm_ioport_map(devptr, mss_port[dev], 4); 594 if (!vmss_port) { 595 snd_printk(KERN_ERR PFX 596 "MSS port I/O cannot be iomapped.\n"); 597 return -EBUSY; 598 } 599 600 snd_printd("Initializing BASE[0x%lx] IRQ[%d] DMA[%d] MIRQ[%d]\n", 601 port[dev], xirq, xdma, 602 mpu_irq[dev] == SNDRV_AUTO_IRQ ? 0 : mpu_irq[dev]); 603 604 err = sc6000_init_board(vport, vmss_port, dev); 605 if (err < 0) 606 return err; 607 card->private_free = snd_sc6000_free; 608 609 err = snd_wss_create(card, mss_port[dev] + 4, -1, xirq, xdma, -1, 610 WSS_HW_DETECT, 0, &chip); 611 if (err < 0) 612 return err; 613 614 err = snd_wss_pcm(chip, 0); 615 if (err < 0) { 616 snd_printk(KERN_ERR PFX 617 "error creating new WSS PCM device\n"); 618 return err; 619 } 620 err = snd_wss_mixer(chip); 621 if (err < 0) { 622 snd_printk(KERN_ERR PFX "error creating new WSS mixer\n"); 623 return err; 624 } 625 err = snd_sc6000_mixer(chip); 626 if (err < 0) { 627 snd_printk(KERN_ERR PFX "the mixer rewrite failed\n"); 628 return err; 629 } 630 if (snd_opl3_create(card, 631 0x388, 0x388 + 2, 632 OPL3_HW_AUTO, 0, &opl3) < 0) { 633 snd_printk(KERN_ERR PFX "no OPL device at 0x%x-0x%x ?\n", 634 0x388, 0x388 + 2); 635 } else { 636 err = snd_opl3_hwdep_new(opl3, 0, 1, NULL); 637 if (err < 0) 638 return err; 639 } 640 641 if (mpu_port[dev] != SNDRV_AUTO_PORT) { 642 if (mpu_irq[dev] == SNDRV_AUTO_IRQ) 643 mpu_irq[dev] = -1; 644 if (snd_mpu401_uart_new(card, 0, 645 MPU401_HW_MPU401, 646 mpu_port[dev], 0, 647 mpu_irq[dev], NULL) < 0) 648 snd_printk(KERN_ERR "no MPU-401 device at 0x%lx ?\n", 649 mpu_port[dev]); 650 } 651 652 strcpy(card->driver, DRV_NAME); 653 strcpy(card->shortname, "SC-6000"); 654 sprintf(card->longname, "Gallant SC-6000 at 0x%lx, irq %d, dma %d", 655 mss_port[dev], xirq, xdma); 656 657 err = snd_card_register(card); 658 if (err < 0) 659 return err; 660 661 dev_set_drvdata(devptr, card); 662 return 0; 663} 664 665static int snd_sc6000_probe(struct device *devptr, unsigned int dev) 666{ 667 return snd_card_free_on_error(devptr, __snd_sc6000_probe(devptr, dev)); 668} 669 670static struct isa_driver snd_sc6000_driver = { 671 .match = snd_sc6000_match, 672 .probe = snd_sc6000_probe, 673 /* FIXME: suspend/resume */ 674 .driver = { 675 .name = DRV_NAME, 676 }, 677}; 678 679 680module_isa_driver(snd_sc6000_driver, SNDRV_CARDS);