diff options
author | Ricardo Cerqueira <devnull@localhost> | 2005-12-13 04:01:49 +0000 |
---|---|---|
committer | Ricardo Cerqueira <devnull@localhost> | 2005-12-13 04:01:49 +0000 |
commit | 6635914a834de9bb84d044c5a3403ec8a39b61d2 (patch) | |
tree | 7f652cda083d901ed44cc778c9e3b12e6a01e229 /linux/drivers/media/video | |
parent | 227d01d09f86548134030d82e3c1cdfb1c7e2d93 (diff) | |
download | mediapointer-dvb-s2-6635914a834de9bb84d044c5a3403ec8a39b61d2.tar.gz mediapointer-dvb-s2-6635914a834de9bb84d044c5a3403ec8a39b61d2.tar.bz2 |
More cx88-alsa changes. Doesn't crash on removal anymore
From: Ricardo Cerqueira <v4l@cerqueira.org>
- Bugfixes to allow the module to be removed without hard-locking the system
Signed-off-by:
Diffstat (limited to 'linux/drivers/media/video')
-rw-r--r-- | linux/drivers/media/video/cx88/cx88-alsa.c | 402 |
1 files changed, 299 insertions, 103 deletions
diff --git a/linux/drivers/media/video/cx88/cx88-alsa.c b/linux/drivers/media/video/cx88/cx88-alsa.c index ecc289433..b9f248c48 100644 --- a/linux/drivers/media/video/cx88/cx88-alsa.c +++ b/linux/drivers/media/video/cx88/cx88-alsa.c @@ -1,5 +1,5 @@ /* - * $Id: cx88-alsa.c,v 1.13 2005/12/10 11:15:54 mchehab Exp $ + * $Id: cx88-alsa.c,v 1.14 2005/12/13 04:01:49 rmcc Exp $ * * Support for audio capture * PCI function #1 of the cx2388x. @@ -41,6 +41,9 @@ #include "cx88-reg.h" #define dprintk(level,fmt, arg...) if (debug >= level) \ + printk(KERN_INFO "%s/1: " fmt, chip->core->name , ## arg) + +#define dprintk_core(level,fmt, arg...) if (debug >= level) \ printk(KERN_DEBUG "%s/1: " fmt, chip->core->name , ## arg) @@ -84,11 +87,24 @@ struct cx88_audio_dev { int mixer_volume[MIXER_ADDR_LAST+1][2]; int capture_source[MIXER_ADDR_LAST+1][2]; + long int bufsize; + long int read_offset; + + u32 __iomem *mmio; long opened; snd_pcm_substream_t *substream; }; typedef struct cx88_audio_dev snd_cx88_card_t; +typedef struct snd_card_cx88_pcm { + struct cx88_dev *dev; + + spinlock_t lock; + + snd_pcm_substream_t *substream; +} snd_card_cx88_pcm_t; + + #if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,8) #define chip_t snd_cx88_card_t #endif @@ -105,6 +121,7 @@ static int pcm_devs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; static int pcm_substreams[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8}; static snd_cx88_card_t *snd_dummy_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; #endif +static snd_card_t *snd_cx88_cards[SNDRV_CARDS]; #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,10) static unsigned int dummy; @@ -140,10 +157,7 @@ int _cx88_start_audio_dma(snd_cx88_card_t *chip) { struct cx88_core *core=chip->core; - /* setup fifo + format - out channel */ - cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH25], - chip->buf->bpl, chip->buf->risc.dma); - + dprintk(1, "Starting audio DMA\n"); /* sets bpl size */ cx_write(MO_AUDD_LNGTH, chip->buf->bpl); @@ -172,6 +186,7 @@ int _cx88_start_audio_dma(snd_cx88_card_t *chip) int _cx88_stop_audio_dma(snd_cx88_card_t *chip) { struct cx88_core *core=chip->core; + dprintk(1, "Stopping audio DMA\n"); /* stop dma */ cx_clear(MO_AUD_DMACNTRL, 0x11); @@ -183,6 +198,9 @@ int _cx88_stop_audio_dma(snd_cx88_card_t *chip) #define MAX_IRQ_LOOP 10 +/* + * BOARD Specific: Threats timeouts + */ static void cx8801_timeout(unsigned long data) { snd_cx88_card_t *chip = (snd_cx88_card_t *)data; @@ -197,19 +215,25 @@ static void cx8801_timeout(unsigned long data) #endif } +/* + * BOARD Specific: IRQ dma bits + */ static char *cx88_aud_irqs[32] = { "dn_risci1", "up_risci1", "rds_dn_risc1", /* 0-2 */ - "", /* reserved */ + NULL, /* reserved */ "dn_risci2", "up_risci2", "rds_dn_risc2", /* 4-6 */ - "", /* reserved */ + NULL, /* reserved */ "dnf_of", "upf_uf", "rds_dnf_uf", /* 8-10 */ - "", /* reserved */ + NULL, /* reserved */ "dn_sync", "up_sync", "rds_dn_sync", /* 12-14 */ - "", /* reserved */ + NULL, /* reserved */ "opc_err", "par_err", "rip_err", /* 16-18 */ "pci_abort", "ber_irq", "mchg_irq" /* 19-21 */ }; +/* + * BOARD Specific: Threats IRQ audio specific calls + */ static void cx8801_aud_irq(snd_cx88_card_t *chip) { struct cx88_core *core = chip->core; @@ -236,15 +260,18 @@ static void cx8801_aud_irq(snd_cx88_card_t *chip) /* risc1 downstream */ if (status & 0x01) { - spin_lock_irq(&chip->reg_lock); + spin_lock(&chip->reg_lock); count = cx_read(MO_AUDD_GPCNT); cx88_wakeup(core, &chip->q, count); - spin_unlock_irq(&chip->reg_lock); + spin_unlock(&chip->reg_lock); } /* FIXME: Any other status should deserve a special handling? */ } +/* + * BOARD Specific: Handles IRQ calls + */ static irqreturn_t cx8801_irq(int irq, void *dev_id, struct pt_regs *regs) { snd_cx88_card_t *chip = dev_id; @@ -252,21 +279,28 @@ static irqreturn_t cx8801_irq(int irq, void *dev_id, struct pt_regs *regs) u32 status; int loop, handled = 0; - for (loop = 0; loop < MAX_IRQ_LOOP; loop++) { - status = cx_read(MO_PCI_INTSTAT) & (core->pci_irqmask | 0x02); - if (0 == status) - goto out; - dprintk( 1, "cx8801_irq\n" ); - dprintk( 1, " loop: %d/%d\n", loop, MAX_IRQ_LOOP ); - dprintk( 1, " status: %d\n", status ); - handled = 1; - cx_write(MO_PCI_INTSTAT, status); - - if (status & core->pci_irqmask) - cx88_core_irq(core,status); - if (status & 0x02) - cx8801_aud_irq(chip); - }; + for (loop = 0; loop < MAX_IRQ_LOOP; loop++) { + status = cx_read(MO_PCI_INTSTAT) & (core->pci_irqmask | 0x02); + if (0 == status) + goto out; + dprintk( 1, "cx8801_irq\n" ); + dprintk( 1, " loop: %d/%d\n", loop, MAX_IRQ_LOOP ); + dprintk( 1, " status: %d\n", status ); + handled = 1; + cx_write(MO_PCI_INTSTAT, status); + + if (status & core->pci_irqmask) + { + dprintk( 1, " passing to core\n" ); + cx88_core_irq(core,status); + } + if (status & 0x02) + { + dprintk( 1, " ALSA IRQ handling\n" ); + cx8801_aud_irq(chip); + } + }; + if (MAX_IRQ_LOOP == loop) { dprintk( 0, "clearing mask\n" ); dprintk(1,"%s/0: irq loop -- clearing mask\n", @@ -312,6 +346,162 @@ static void snd_cx88_pci_error(bt87x_t *chip, unsigned int status) #endif /**************************************************************************** + Videobuf setup code + ****************************************************************************/ +#if 0 + +static int +buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) +{ + struct cx8800_fh *fh = q->priv_data; + + *size = fh->fmt->depth*fh->width*fh->height >> 3; + if (0 == *count) + *count = 32; + while (*size * *count > vid_limit * 1024 * 1024) + (*count)--; + return 0; +} + +static int +buffer_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct cx8800_fh *fh = q->priv_data; + struct cx8800_dev *dev = fh->dev; + struct cx88_core *core = dev->core; + struct cx88_buffer *buf = container_of(vb,struct cx88_buffer,vb); + int rc, init_buffer = 0; + unsigned int size; + + BUG_ON(NULL == fh->fmt); + size = VBI_LINE_COUNT * VBI_LINE_LENGTH * 2; + + if (0 != buf->vb.baddr && buf->vb.bsize < size) + return -EINVAL; + + if (STATE_NEEDS_INIT == buf->vb.state) { + buf->vb.width = VBI_LINE_LENGTH; + buf->vb.height = VBI_LINE_COUNT; + buf->vb.size = size; + buf->vb.field = V4L2_FIELD_SEQ_TB; + + if (0 != (rc = videobuf_iolock(dev->pci,&buf->vb,NULL))) + goto fail; + cx88_risc_buffer(dev->pci, &buf->risc, + buf->vb.dma.sglist, + 0, buf->vb.width * buf->vb.height, + buf->vb.width, 0, + buf->vb.height); + } + buf->vb.state = STATE_PREPARED; + + dprintk_core(2,"[%p/%d] buffer_prepare - %dx%d %dbpp \"%s\" - dma=0x%08lx\n", + buf, buf->vb.i, + fh->width, fh->height, fh->fmt->depth, fh->fmt->name, + (unsigned long)buf->risc.dma); + + return 0; + + fail: + cx88_free_buffer(dev->pci,buf); + return rc; +} + +static void +buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb) +{ + struct cx88_buffer *buf = container_of(vb,struct cx88_buffer,vb); + struct cx88_buffer *prev; + struct cx8800_fh *fh = vq->priv_data; + struct cx8800_dev *dev = fh->dev; + struct cx88_core *core = dev->core; + struct cx88_dmaqueue *q = &dev->vidq; + + /* add jump to stopper */ + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_IRQ1 | RISC_CNT_INC); + buf->risc.jmp[1] = cpu_to_le32(q->stopper.dma); + + if (!list_empty(&q->queued)) { + list_add_tail(&buf->vb.queue,&q->queued); + buf->vb.state = STATE_QUEUED; + dprintk(2,"[%p/%d] buffer_queue - append to queued\n", + buf, buf->vb.i); + + } else if (list_empty(&q->active)) { + list_add_tail(&buf->vb.queue,&q->active); + start_video_dma(dev, q, buf); + buf->vb.state = STATE_ACTIVE; + buf->count = q->count++; + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + dprintk(2,"[%p/%d] buffer_queue - first active\n", + buf, buf->vb.i); + + } else { + prev = list_entry(q->active.prev, struct cx88_buffer, vb.queue); + if (prev->vb.width == buf->vb.width && + prev->vb.height == buf->vb.height && + prev->fmt == buf->fmt) { + list_add_tail(&buf->vb.queue,&q->active); + buf->vb.state = STATE_ACTIVE; + buf->count = q->count++; + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + dprintk(2,"[%p/%d] buffer_queue - append to active\n", + buf, buf->vb.i); + + } else { + list_add_tail(&buf->vb.queue,&q->queued); + buf->vb.state = STATE_QUEUED; + dprintk(2,"[%p/%d] buffer_queue - first queued\n", + buf, buf->vb.i); + } + } +} + +static void buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct cx88_buffer *buf = container_of(vb,struct cx88_buffer,vb); + struct cx8800_fh *fh = q->priv_data; + + cx88_free_buffer(fh->dev->pci,buf); +} + +static struct videobuf_queue_ops cx8800_video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +static int dsp_buffer_init(snd_cx88_card_t *chip) +{ + int err; + + BUG_ON(!chip->dmasound.bufsize); + + /* allocate + initialize per filehandle data */ + fh = kmalloc(sizeof(*fh),GFP_KERNEL); + if (NULL == fh) + return -ENOMEM; + memset(fh,0,sizeof(*fh)); + file->private_data = fh; + fh->dev = dev; + + videobuf_queue_init(&fh->mpegq, &blackbird_qops, + dev->pci, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct cx88_buffer), + fh); + videobuf_dma_init(&chip->dmasound.dma); + err = videobuf_dma_init_kernel(&chip->dmasound.dma, PCI_DMA_FROMDEVICE, + (chip->dmasound.bufsize + PAGE_SIZE) >> PAGE_SHIFT); + if (0 != err) + return err; + return 0; +} +#endif +/**************************************************************************** ALSA PCM Interface ****************************************************************************/ @@ -324,7 +514,10 @@ static snd_pcm_hardware_t snd_cx88_digital_hw = { SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID, .formats = SNDRV_PCM_FMTBIT_S16_LE, - .rates = 0, /* set at runtime */ + + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, .channels_min = 2, .channels_max = 2, .buffer_bytes_max = 255 * 4092, @@ -335,55 +528,27 @@ static snd_pcm_hardware_t snd_cx88_digital_hw = { }; /* - * Sets board to provide digital audio + * audio pcm capture runtime free */ -static int snd_cx88_set_digital_hw(snd_cx88_card_t *chip, snd_pcm_runtime_t *runtime) -{ - static struct { - int rate; - unsigned int bit; - } ratebits[] = { - {8000, SNDRV_PCM_RATE_8000}, - {11025, SNDRV_PCM_RATE_11025}, - {16000, SNDRV_PCM_RATE_16000}, - {22050, SNDRV_PCM_RATE_22050}, - {32000, SNDRV_PCM_RATE_32000}, - {44100, SNDRV_PCM_RATE_44100}, - {48000, SNDRV_PCM_RATE_48000} - }; - int i; -#if 0 - chip->reg_control |= CTL_DA_IOM_DA; -#endif - runtime->hw = snd_cx88_digital_hw; - runtime->hw.rates = SNDRV_PCM_RATE_KNOT; - for (i = 0; i < ARRAY_SIZE(ratebits); ++i) - if (chip->dig_rate == ratebits[i].rate) { - runtime->hw.rates = ratebits[i].bit; - break; - } - runtime->hw.rate_min = chip->dig_rate; - runtime->hw.rate_max = chip->dig_rate; - return 0; -} +static void snd_card_cx88_runtime_free(snd_pcm_runtime_t *runtime) +{ + snd_pcm_hardware_t *pcm = runtime->private_data; + kfree(pcm); +} /* - * audio open callback + * audio pcm capture open callback */ static int snd_cx88_pcm_open(snd_pcm_substream_t *substream) { snd_cx88_card_t *chip = snd_pcm_substream_chip(substream); snd_pcm_runtime_t *runtime = substream->runtime; + snd_card_cx88_pcm_t *pcm; int err; if (test_and_set_bit(0, &chip->opened)) return -EBUSY; - err = snd_cx88_set_digital_hw(chip, runtime); - - if (err < 0) - goto _error; - err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); if (err < 0) goto _error; @@ -391,6 +556,19 @@ static int snd_cx88_pcm_open(snd_pcm_substream_t *substream) chip->substream = substream; return 0; + pcm = kzalloc(sizeof(*pcm), GFP_KERNEL); + if (pcm == NULL) { + err=-ENOMEM; + goto _error; + } + + spin_lock_init(&pcm->lock); + + pcm->substream = substream; + runtime->private_data = pcm; + runtime->private_free = snd_card_cx88_runtime_free; + runtime->hw = snd_cx88_digital_hw; + _error: clear_bit(0, &chip->opened); smp_mb__after_clear_bit(); @@ -433,30 +611,17 @@ static int snd_cx88_hw_free(snd_pcm_substream_t * substream) static int snd_cx88_prepare(snd_pcm_substream_t *substream) { snd_cx88_card_t *chip = snd_pcm_substream_chip(substream); -// snd_pcm_runtime_t *runtime = substream->runtime; -// int decimation; spin_lock_irq(&chip->reg_lock); chip->dma_size = snd_pcm_lib_buffer_bytes(substream); chip->period_size = snd_pcm_lib_period_bytes(substream); -#if 0 - chip->reg_control &= ~(CTL_DA_SDR_MASK | CTL_DA_SBR); - decimation = (ANALOG_CLOCK + runtime->rate / 4) / runtime->rate; - chip->reg_control |= decimation << CTL_DA_SDR_SHIFT; - if (runtime->format == SNDRV_PCM_FORMAT_S8) - chip->reg_control |= CTL_DA_SBR; - snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control); - - outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL)); - outl(ES_MEM_PAGEO(ES_PAGE_DAC), ES_REG(ensoniq, MEM_PAGE)); - outl(runtime->dma_addr, ES_REG(ensoniq, DAC1_FRAME)); - outl((ensoniq->p1_dma_size >> 2) - 1, ES_REG(ensoniq, DAC1_SIZE)); - ensoniq->sctrl &= ~(ES_P1_LOOP_SEL | ES_P1_PAUSE | ES_P1_SCT_RLD | ES_P1_MODEM); - ensoniq->sctrl |= ES_P1_INT_EN | ES_P1_MODEO(mode); - outl(ensoniq->sctrl, ES_REG(ensoniq, SERIAL)); - outl((ensoniq->p1_period_size >> snd_ensoniq_sample_shift[mode]) - 1, ES_REG(ensoniq, DAC1_COUNT)); -#endif + + + /* setup fifo + format - out channel */ + cx88_sram_channel_setup(chip->core, &cx88_sram_channels[SRAM_CH25], + chip->buf->bpl, chip->buf->risc.dma); + spin_unlock_irq(&chip->reg_lock); return 0; } @@ -467,16 +632,23 @@ static int snd_cx88_prepare(snd_pcm_substream_t *substream) */ static int snd_cx88_card_trigger(snd_pcm_substream_t *substream, int cmd) { -// snd_cx88_card_t *chip = snd_pcm_substream_chip(substream); + snd_cx88_card_t *chip = snd_pcm_substream_chip(substream); + int err; + + spin_lock(&chip->reg_lock); switch (cmd) { case SNDRV_PCM_TRIGGER_START: -// return snd_cx88_start(chip); + err=_cx88_start_audio_dma(chip); case SNDRV_PCM_TRIGGER_STOP: -// return snd_cx88_stop(chip); + err=_cx88_stop_audio_dma(chip); default: - return -EINVAL; + err=-EINVAL; } + + spin_unlock(&chip->reg_lock); + + return err; } /* @@ -484,10 +656,10 @@ static int snd_cx88_card_trigger(snd_pcm_substream_t *substream, int cmd) */ static snd_pcm_uframes_t snd_cx88_pointer(snd_pcm_substream_t *substream) { -// snd_cx88_card_t *chip = snd_pcm_substream_chip(substream); -// snd_pcm_runtime_t *runtime = substream->runtime; + snd_cx88_card_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; -// return (snd_pcm_uframes_t)bytes_to_frames(runtime, chip->current_line * chip->line_bytes); + //return (snd_pcm_uframes_t)bytes_to_frames(runtime, chip->current_line * chip->line_bytes); } /* @@ -650,6 +822,7 @@ MODULE_DEVICE_TABLE(pci, cx88_audio_pci_tbl); static int snd_cx88_free(snd_cx88_card_t *chip) { + dprintk(1,"cx88 free\n"); #if 0 if (chip->mmio) { snd_bt87x_stop(chip); @@ -659,16 +832,20 @@ static int snd_cx88_free(snd_cx88_card_t *chip) iounmap(chip->mmio); } #endif - if (chip->irq >= 0) + pci_disable_device(chip->pci); + + if (chip->irq >= 0){ + synchronize_irq(chip->irq); free_irq(chip->irq, chip); + } /* free memory */ - cx88_core_put(chip->core,chip->pci); - pci_release_regions(chip->pci); - pci_disable_device(chip->pci); + //pci_release_regions(chip->pci); - kfree(chip); +// btcx_riscmem_free(chip->pci,&chip->q.stopper); +// cx88_core_put(chip->core,chip->pci); + //kfree(chip); return 0; } @@ -678,6 +855,8 @@ static int snd_cx88_free(snd_cx88_card_t *chip) static int snd_cx88_dev_free(snd_device_t *device) { snd_cx88_card_t *chip = device->device_data; + + dprintk(1,"cx88 dev free\n"); return snd_cx88_free(chip); } @@ -702,6 +881,13 @@ static int __devinit snd_cx88_create(snd_card_t *card, struct pci_dev *pci, err = pci_enable_device(pci); if (err < 0) return err; + pci_set_master(pci); + + if (!pci_dma_supported(pci,0xffffffff)) { + dprintk(0, "%s/1: Oops: no 32bit PCI DMA ???\n",core->name); + err = -EIO; + return err; + } chip = kmalloc(sizeof(*chip),GFP_KERNEL); if (NULL == chip) { @@ -710,12 +896,6 @@ static int __devinit snd_cx88_create(snd_card_t *card, struct pci_dev *pci, } memset(chip,0,sizeof(*chip)); - if (!pci_dma_supported(pci,0xffffffff)) { - dprintk(0, "%s/1: Oops: no 32bit PCI DMA ???\n",core->name); - err = -EIO; - kfree (chip); - return err; - } /* pci init */ chip->card = card; @@ -724,7 +904,12 @@ static int __devinit snd_cx88_create(snd_card_t *card, struct pci_dev *pci, spin_lock_init(&chip->reg_lock); #if 0 - if ((err = pci_request_regions(pci, "CX88 audio")) < 0) { + if (!request_mem_region(pci_resource_start(pci,0), + pci_resource_len(pci,0), + "CX88 audio")) { + err = -EBUSY; + printk(KERN_ERR "%s: can't get MMIO memory @ 0x%lx\n", + "CX88 audio",pci_resource_start(pci,0)); kfree(chip); pci_disable_device(pci); return err; @@ -732,7 +917,7 @@ static int __devinit snd_cx88_create(snd_card_t *card, struct pci_dev *pci, chip->mmio = ioremap_nocache(pci_resource_start(pci, 0), pci_resource_len(pci, 0)); if (!chip->mmio) { - snd_bt87x_free(chip); + kfree(chip); snd_printk(KERN_ERR "cannot remap io memory\n"); return -ENOMEM; } @@ -745,6 +930,8 @@ static int __devinit snd_cx88_create(snd_card_t *card, struct pci_dev *pci, return err; } chip->core = core; + + #if 1 /* Should be tested if it is wright */ chip->dig_rate=48000; #endif @@ -780,7 +967,7 @@ static int __devinit snd_cx88_create(snd_card_t *card, struct pci_dev *pci, pci_name(pci), chip->pci_rev, pci->irq, chip->pci_lat,pci_resource_start(pci,0)); - pci_set_master(pci); + chip->irq = pci->irq; synchronize_irq(chip->irq); err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); @@ -843,6 +1030,7 @@ static int __devinit cx88_audio_initdev(struct pci_dev *pci, snd_card_free(card); return (err); } + snd_cx88_cards[devno] = card; pci_set_drvdata(pci,card); @@ -901,6 +1089,14 @@ static int cx88_audio_init(void) */ static void cx88_audio_fini(void) { + int idx; + +/* printk(KERN_INFO "cx88 fini\n"); + for (idx = 0; idx < SNDRV_CARDS; idx++) { + snd_card_free(snd_cx88_cards[idx]); + snd_cx88_cards[idx] = NULL; + }*/ + pci_unregister_driver(&cx88_audio_pci_driver); } |