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