/****************************************************************************** * * * easycap_sound.c * * * * Audio driver for EasyCAP USB2.0 Video Capture Device DC60 * * * * * ******************************************************************************/ /* * * Copyright (C) 2010 R.M. Thomas * * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * The software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /*****************************************************************************/ #include "easycap.h" /*--------------------------------------------------------------------------*/ /* * PARAMETERS USED WHEN REGISTERING THE AUDIO INTERFACE */ /*--------------------------------------------------------------------------*/ static const struct snd_pcm_hardware alsa_hardware = { .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP_VALID, .formats = SNDRV_PCM_FMTBIT_S16_LE, .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000, .rate_min = 32000, .rate_max = 48000, .channels_min = 2, .channels_max = 2, .buffer_bytes_max = PAGE_SIZE * PAGES_PER_AUDIO_FRAGMENT * AUDIO_FRAGMENT_MANY, .period_bytes_min = PAGE_SIZE * PAGES_PER_AUDIO_FRAGMENT, .period_bytes_max = PAGE_SIZE * PAGES_PER_AUDIO_FRAGMENT * 2, .periods_min = AUDIO_FRAGMENT_MANY, .periods_max = AUDIO_FRAGMENT_MANY * 2, }; /*****************************************************************************/ /*---------------------------------------------------------------------------*/ /* * ON COMPLETION OF AN AUDIO URB ITS DATA IS COPIED TO THE DAM BUFFER * PROVIDED peasycap->audio_idle IS ZERO. REGARDLESS OF THIS BEING TRUE, * IT IS RESUBMITTED PROVIDED peasycap->audio_isoc_streaming IS NOT ZERO. */ /*---------------------------------------------------------------------------*/ void easycap_alsa_complete(struct urb *purb) { struct easycap *peasycap; struct snd_pcm_substream *pss; struct snd_pcm_runtime *prt; int dma_bytes, fragment_bytes; int isfragment; u8 *p1, *p2; s16 tmp; int i, j, more, much, rc; #ifdef UPSAMPLE int k; s16 oldaudio, newaudio, delta; #endif /*UPSAMPLE*/ JOT(16, "\n"); if (!purb) { SAY("ERROR: purb is NULL\n"); return; } peasycap = purb->context; if (!peasycap) { SAY("ERROR: peasycap is NULL\n"); return; } much = 0; if (peasycap->audio_idle) { JOM(16, "%i=audio_idle %i=audio_isoc_streaming\n", peasycap->audio_idle, peasycap->audio_isoc_streaming); if (peasycap->audio_isoc_streaming) goto resubmit; } /*---------------------------------------------------------------------------*/ pss = peasycap->psubstream; if (!pss) goto resubmit; prt = pss->runtime; if (!prt) goto resubmit; dma_bytes = (int)prt->dma_bytes; if (0 == dma_bytes) goto resubmit; fragment_bytes = 4 * ((int)prt->period_size); if (0 == fragment_bytes) goto resubmit; /* -------------------------------------------------------------------------*/ if (purb->status) { if ((-ESHUTDOWN == purb->status) || (-ENOENT == purb->status)) { JOM(16, "urb status -ESHUTDOWN or -ENOENT\n"); return; } SAM("ERROR: non-zero urb status: -%s: %d\n", strerror(purb->status), purb->status); goto resubmit; } /*---------------------------------------------------------------------------*/ /* * PROCEED HERE WHEN NO ERROR */ /*---------------------------------------------------------------------------*/ #ifdef UPSAMPLE oldaudio = peasycap->oldaudio; #endif /*UPSAMPLE*/ for (i = 0; i < purb->number_of_packets; i++) { if (purb->iso_frame_desc[i].status < 0) { SAM("-%s: %d\n", strerror(purb->iso_frame_desc[i].status), purb->iso_frame_desc[i].status); } if (purb->iso_frame_desc[i].status) { JOM(12, "discarding audio samples because " "%i=purb->iso_frame_desc[i].status\n", purb->iso_frame_desc[i].status); continue; } more = purb->iso_frame_desc[i].actual_length; if (more == 0) { peasycap->audio_mt++; continue; } if (0 > more) { SAM("MISTAKE: more is negative\n"); return; } if (peasycap->audio_mt) { JOM(12, "%4i empty audio urb frames\n", peasycap->audio_mt); peasycap->audio_mt = 0; } p1 = (u8 *)(purb->transfer_buffer + purb->iso_frame_desc[i].offset); /* * COPY more BYTES FROM ISOC BUFFER * TO THE DMA BUFFER, CONVERTING * 8-BIT MONO TO 16-BIT SIGNED * LITTLE-ENDIAN SAMPLES IF NECESSARY */ while (more) { much = dma_bytes - peasycap->dma_fill; if (0 > much) { SAM("MISTAKE: much is negative\n"); return; } if (0 == much) { peasycap->dma_fill = 0; peasycap->dma_next = fragment_bytes; JOM(8, "wrapped dma buffer\n"); } if (!peasycap->microphone) { if (much > more) much = more; memcpy(prt->dma_area + peasycap->dma_fill, p1, much); p1 += much; more -= much; } else { #ifdef UPSAMPLE if (much % 16) JOM(8, "MISTAKE? much" " is not divisible by 16\n"); if (much > (16 * more)) much = 16 * more; p2 = (u8 *)(prt->dma_area + peasycap->dma_fill); for (j = 0; j < (much / 16); j++) { newaudio = ((int) *p1) - 128; newaudio = 128 * newaudio; delta = (newaudio - oldaudio) / 4; tmp = oldaudio + delta; for (k = 0; k < 4; k++) { *p2 = (0x00FF & tmp); *(p2 + 1) = (0xFF00 & tmp) >> 8; p2 += 2; *p2 = (0x00FF & tmp); *(p2 + 1) = (0xFF00 & tmp) >> 8; p2 += 2; tmp += delta; } p1++; more--; oldaudio = tmp; } #else /*!UPSAMPLE*/ if (much > (2 * more)) much = 2 * more; p2 = (u8 *)(prt->dma_area + peasycap->dma_fill); for (j = 0; j < (much / 2); j++) { tmp = ((int) *p1) - 128; tmp = 128 * tmp; *p2 = (0x00FF & tmp); *(p2 + 1) = (0xFF00 & tmp) >> 8; p1++; p2 += 2; more--; } #endif /*UPSAMPLE*/ } peasycap->dma_fill += much; if (peasycap->dma_fill >= peasycap->dma_next) { isfragment = peasycap->dma_fill / fragment_bytes; if (0 > isfragment) { SAM("MISTAKE: isfragment is negative\n"); return; } peasycap->dma_read = (isfragment - 1) * fragment_bytes; peasycap->dma_next = (isfragment + 1) * fragment_bytes; if (dma_bytes < peasycap->dma_next) peasycap->dma_next = fragment_bytes; if (0 <= peasycap->dma_read) { JOM(8, "snd_pcm_period_elapsed(), %i=" "isfragment\n", isfragment); snd_pcm_period_elapsed(pss); } } } #ifdef UPSAMPLE peasycap->oldaudio = oldaudio; #endif /*UPSAMPLE*/ } /*---------------------------------------------------------------------------*/ /* * RESUBMIT THIS URB */ /*---------------------------------------------------------------------------*/ resubmit: if (peasycap->audio_isoc_streaming == 0) return; rc = usb_submit_urb(purb, GFP_ATOMIC); if (rc) { if ((-ENODEV != rc) && (-ENOENT != rc)) { SAM("ERROR: while %i=audio_idle, usb_submit_urb failed " "with rc: -%s :%d\n", peasycap->audio_idle, strerror(rc), rc); } if (0 < peasycap->audio_isoc_streaming) peasycap->audio_isoc_streaming--; } return; } /*****************************************************************************/ static int easycap_alsa_open(struct snd_pcm_substream *pss) { struct snd_pcm *psnd_pcm; struct snd_card *psnd_card; struct easycap *peasycap; JOT(4, "\n"); if (!pss) { SAY("ERROR: pss is NULL\n"); return -EFAULT; } psnd_pcm = pss->pcm; if (!psnd_pcm) { SAY("ERROR: psnd_pcm is NULL\n"); return -EFAULT; } psnd_card = psnd_pcm->card; if (!psnd_card) { SAY("ERROR: psnd_card is NULL\n"); return -EFAULT; } peasycap = psnd_card->private_data; if (!peasycap) { SAY("ERROR: peasycap is NULL\n"); return -EFAULT; } if (peasycap->psnd_card != psnd_card) { SAM("ERROR: bad peasycap->psnd_card\n"); return -EFAULT; } if (peasycap->psubstream) { SAM("ERROR: bad peasycap->psubstream\n"); return -EFAULT; } pss->private_data = peasycap; peasycap->psubstream = pss; pss->runtime->hw = peasycap->alsa_hardware; pss->runtime->private_data = peasycap; pss->private_data = peasycap; if (0 != easycap_sound_setup(peasycap)) { JOM(4, "ending unsuccessfully\n"); return -EFAULT; } JOM(4, "ending successfully\n"); return 0; } /*****************************************************************************/ static int easycap_alsa_close(struct snd_pcm_substream *pss) { struct easycap *peasycap; JOT(4, "\n"); if (!pss) { SAY("ERROR: pss is NULL\n"); return -EFAULT; } peasycap = snd_pcm_substream_chip(pss); if (!peasycap) { SAY("ERROR: peasycap is NULL\n"); return -EFAULT; } pss->private_data = NULL; peasycap->psubstream = NULL; JOT(4, "ending successfully\n"); return 0; } /*****************************************************************************/ static int easycap_alsa_vmalloc(struct snd_pcm_substream *pss, size_t sz) { struct snd_pcm_runtime *prt; JOT(4, "\n"); if (!pss) { SAY("ERROR: pss is NULL\n"); return -EFAULT; } prt = pss->runtime; if (!prt) { SAY("ERROR: substream.runtime is NULL\n"); return -EFAULT; } if (prt->dma_area) { if (prt->dma_bytes > sz) return 0; vfree(prt->dma_area); } prt->dma_area = vmalloc(sz); if (!prt->dma_area) return -ENOMEM; prt->dma_bytes = sz; return 0; } /*****************************************************************************/ static int easycap_alsa_hw_params(struct snd_pcm_substream *pss, struct snd_pcm_hw_params *phw) { int rc; JOT(4, "%i\n", (params_buffer_bytes(phw))); if (!pss) { SAY("ERROR: pss is NULL\n"); return -EFAULT; } rc = easycap_alsa_vmalloc(pss, params_buffer_bytes(phw)); if (rc) return rc; return 0; } /*****************************************************************************/ static int easycap_alsa_hw_free(struct snd_pcm_substream *pss) { struct snd_pcm_runtime *prt; JOT(4, "\n"); if (!pss) { SAY("ERROR: pss is NULL\n"); return -EFAULT; } prt = pss->runtime; if (!prt) { SAY("ERROR: substream.runtime is NULL\n"); return -EFAULT; } if (prt->dma_area) { JOT(8, "prt->dma_area = %p\n", prt->dma_area); vfree(prt->dma_area); prt->dma_area = NULL; } else JOT(8, "dma_area already freed\n"); return 0; } /*****************************************************************************/ static int easycap_alsa_prepare(struct snd_pcm_substream *pss) { struct easycap *peasycap; struct snd_pcm_runtime *prt; JOT(4, "\n"); if (!pss) { SAY("ERROR: pss is NULL\n"); return -EFAULT; } prt = pss->runtime; peasycap = snd_pcm_substream_chip(pss); if (!peasycap) { SAY("ERROR: peasycap is NULL\n"); return -EFAULT; } JOM(16, "ALSA decides %8i Hz=rate\n", pss->runtime->rate); JOM(16, "ALSA decides %8ld =period_size\n", pss->runtime->period_size); JOM(16, "ALSA decides %8i =periods\n", pss->runtime->periods); JOM(16, "ALSA decides %8ld =buffer_size\n", pss->runtime->buffer_size); JOM(16, "ALSA decides %8zd =dma_bytes\n", pss->runtime->dma_bytes); JOM(16, "ALSA decides %8ld =boundary\n", pss->runtime->boundary); JOM(16, "ALSA decides %8i =period_step\n", pss->runtime->period_step); JOM(16, "ALSA decides %8i =sample_bits\n", pss->runtime->sample_bits); JOM(16, "ALSA decides %8i =frame_bits\n", pss->runtime->frame_bits); JOM(16, "ALSA decides %8ld =min_align\n", pss->runtime->min_align); JOM(12, "ALSA decides %8ld =hw_ptr_base\n", pss->runtime->hw_ptr_base); JOM(12, "ALSA decides %8ld =hw_ptr_interrupt\n", pss->runtime->hw_ptr_interrupt); if (prt->dma_bytes != 4 * ((int)prt->period_size) * ((int)prt->periods)) { SAY("MISTAKE: unexpected ALSA parameters\n"); return -ENOENT; } return 0; } /*****************************************************************************/ static int easycap_alsa_ack(struct snd_pcm_substream *pss) { return 0; } /*****************************************************************************/ static int easycap_alsa_trigger(struct snd_pcm_substream *pss, int cmd) { struct easycap *peasycap; int retval; JOT(4, "%i=cmd cf %i=START %i=STOP\n", cmd, SNDRV_PCM_TRIGGER_START, SNDRV_PCM_TRIGGER_STOP); if (!pss) { SAY("ERROR: pss is NULL\n"); return -EFAULT; } peasycap = snd_pcm_substream_chip(pss); if (!peasycap) { SAY("ERROR: peasycap is NULL\n"); return -EFAULT; } switch (cmd) { case SNDRV_PCM_TRIGGER_START: { peasycap->audio_idle = 0; break; } case SNDRV_PCM_TRIGGER_STOP: { peasycap->audio_idle = 1; break; } default: retval = -EINVAL; } return 0; } /*****************************************************************************/ static snd_pcm_uframes_t easycap_alsa_pointer(struct snd_pcm_substream *pss) { struct easycap *peasycap; snd_pcm_uframes_t offset; JOT(16, "\n"); if (!pss) { SAY("ERROR: pss is NULL\n"); return -EFAULT; } peasycap = snd_pcm_substream_chip(pss); if (!peasycap) { SAY("ERROR: peasycap is NULL\n"); return -EFAULT; } if ((0 != peasycap->audio_eof) || (0 != peasycap->audio_idle)) { JOM(8, "returning -EIO because " "%i=audio_idle %i=audio_eof\n", peasycap->audio_idle, peasycap->audio_eof); return -EIO; } /*---------------------------------------------------------------------------*/ if (0 > peasycap->dma_read) { JOM(8, "returning -EBUSY\n"); return -EBUSY; } offset = ((snd_pcm_uframes_t)peasycap->dma_read)/4; JOM(8, "ALSA decides %8i =hw_ptr_base\n", (int)pss->runtime->hw_ptr_base); JOM(8, "ALSA decides %8i =hw_ptr_interrupt\n", (int)pss->runtime->hw_ptr_interrupt); JOM(8, "%7i=offset %7i=dma_read %7i=dma_next\n", (int)offset, peasycap->dma_read, peasycap->dma_next); return offset; } /*****************************************************************************/ static struct page * easycap_alsa_page(struct snd_pcm_substream *pss, unsigned long offset) { return vmalloc_to_page(pss->runtime->dma_area + offset); } /*****************************************************************************/ static struct snd_pcm_ops easycap_alsa_pcm_ops = { .open = easycap_alsa_open, .close = easycap_alsa_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = easycap_alsa_hw_params, .hw_free = easycap_alsa_hw_free, .prepare = easycap_alsa_prepare, .ack = easycap_alsa_ack, .trigger = easycap_alsa_trigger, .pointer = easycap_alsa_pointer, .page = easycap_alsa_page, }; /*****************************************************************************/ /*---------------------------------------------------------------------------*/ /* * THE FUNCTION snd_card_create() HAS THIS_MODULE AS AN ARGUMENT. THIS * MEANS MODULE easycap. BEWARE. */ /*---------------------------------------------------------------------------*/ int easycap_alsa_probe(struct easycap *peasycap) { int rc; struct snd_card *psnd_card; struct snd_pcm *psnd_pcm; if (!peasycap) { SAY("ERROR: peasycap is NULL\n"); return -ENODEV; } if (0 > peasycap->minor) { SAY("ERROR: no minor\n"); return -ENODEV; } peasycap->alsa_hardware = alsa_hardware; if (peasycap->microphone) { peasycap->alsa_hardware.rates = SNDRV_PCM_RATE_32000; peasycap->alsa_hardware.rate_min = 32000; peasycap->alsa_hardware.rate_max = 32000; } else { peasycap->alsa_hardware.rates = SNDRV_PCM_RATE_48000; peasycap->alsa_hardware.rate_min = 48000; peasycap->alsa_hardware.rate_max = 48000; } if (0 != snd_card_create(SNDRV_DEFAULT_IDX1, "easycap_alsa", THIS_MODULE, 0, &psnd_card)) { SAY("ERROR: Cannot do ALSA snd_card_create()\n"); return -EFAULT; } sprintf(&psnd_card->id[0], "EasyALSA%i", peasycap->minor); strcpy(&psnd_card->driver[0], EASYCAP_DRIVER_DESCRIPTION); strcpy(&psnd_card->shortname[0], "easycap_alsa"); sprintf(&psnd_card->longname[0], "%s", &psnd_card->shortname[0]); psnd_card->dev = &peasycap->pusb_device->dev; psnd_card->private_data = peasycap; peasycap->psnd_card = psnd_card; rc = snd_pcm_new(psnd_card, "easycap_pcm", 0, 0, 1, &psnd_pcm); if (rc) { SAM("ERROR: Cannot do ALSA snd_pcm_new()\n"); snd_card_free(psnd_card); return -EFAULT; } snd_pcm_set_ops(psnd_pcm, SNDRV_PCM_STREAM_CAPTURE, &easycap_alsa_pcm_ops); psnd_pcm->info_flags = 0; strcpy(&psnd_pcm->name[0], &psnd_card->id[0]); psnd_pcm->private_data = peasycap; peasycap->psnd_pcm = psnd_pcm; peasycap->psubstream = NULL; rc = snd_card_register(psnd_card); if (rc) { SAM("ERROR: Cannot do ALSA snd_card_register()\n"); snd_card_free(psnd_card); return -EFAULT; } SAM("registered %s\n", &psnd_card->id[0]); return 0; } /*****************************************************************************/ /*****************************************************************************/ /*****************************************************************************/ /*****************************************************************************/ /*****************************************************************************/ /*****************************************************************************/ /*---------------------------------------------------------------------------*/ /* * COMMON AUDIO INITIALIZATION */ /*---------------------------------------------------------------------------*/ int easycap_sound_setup(struct easycap *peasycap) { int rc; JOM(4, "starting initialization\n"); if (!peasycap) { SAY("ERROR: peasycap is NULL.\n"); return -EFAULT; } if (!peasycap->pusb_device) { SAM("ERROR: peasycap->pusb_device is NULL\n"); return -ENODEV; } JOM(16, "0x%08lX=peasycap->pusb_device\n", (long int)peasycap->pusb_device); rc = audio_setup(peasycap); JOM(8, "audio_setup() returned %i\n", rc); if (!peasycap->pusb_device) { SAM("ERROR: peasycap->pusb_device has become NULL\n"); return -ENODEV; } /*---------------------------------------------------------------------------*/ if (!peasycap->pusb_device) { SAM("ERROR: peasycap->pusb_device has become NULL\n"); return -ENODEV; } rc = usb_set_interface(peasycap->pusb_device, peasycap->audio_interface, peasycap->audio_altsetting_on); JOM(8, "usb_set_interface(.,%i,%i) returned %i\n", peasycap->audio_interface, peasycap->audio_altsetting_on, rc); rc = wakeup_device(peasycap->pusb_device); JOM(8, "wakeup_device() returned %i\n", rc); peasycap->audio_eof = 0; peasycap->audio_idle = 0; submit_audio_urbs(peasycap); JOM(4, "finished initialization\n"); return 0; } /*****************************************************************************/ /*---------------------------------------------------------------------------*/ /* * SUBMIT ALL AUDIO URBS. */ /*---------------------------------------------------------------------------*/ int submit_audio_urbs(struct easycap *peasycap) { struct data_urb *pdata_urb; struct urb *purb; struct list_head *plist_head; int j, isbad, nospc, m, rc; int isbuf; if (!peasycap) { SAY("ERROR: peasycap is NULL\n"); return -EFAULT; } if (!peasycap->purb_audio_head) { SAM("ERROR: peasycap->urb_audio_head uninitialized\n"); return -EFAULT; } if (!peasycap->pusb_device) { SAM("ERROR: peasycap->pusb_device is NULL\n"); return -EFAULT; } if (peasycap->audio_isoc_streaming) { JOM(4, "already streaming audio urbs\n"); return 0; } JOM(4, "initial submission of all audio urbs\n"); rc = usb_set_interface(peasycap->pusb_device, peasycap->audio_interface, peasycap->audio_altsetting_on); JOM(8, "usb_set_interface(.,%i,%i) returned %i\n", peasycap->audio_interface, peasycap->audio_altsetting_on, rc); isbad = 0; nospc = 0; m = 0; list_for_each(plist_head, peasycap->purb_audio_head) { pdata_urb = list_entry(plist_head, struct data_urb, list_head); if (pdata_urb && pdata_urb->purb) { purb = pdata_urb->purb; isbuf = pdata_urb->isbuf; purb->interval = 1; purb->dev = peasycap->pusb_device; purb->pipe = usb_rcvisocpipe(peasycap->pusb_device, peasycap->audio_endpointnumber); purb->transfer_flags = URB_ISO_ASAP; purb->transfer_buffer = peasycap->audio_isoc_buffer[isbuf].pgo; purb->transfer_buffer_length = peasycap->audio_isoc_buffer_size; purb->complete = easycap_alsa_complete; purb->context = peasycap; purb->start_frame = 0; purb->number_of_packets = peasycap->audio_isoc_framesperdesc; for (j = 0; j < peasycap->audio_isoc_framesperdesc; j++) { purb->iso_frame_desc[j].offset = j * peasycap->audio_isoc_maxframesize; purb->iso_frame_desc[j].length = peasycap->audio_isoc_maxframesize; } rc = usb_submit_urb(purb, GFP_KERNEL); if (rc) { isbad++; SAM("ERROR: usb_submit_urb() failed" " for urb with rc: -%s: %d\n", strerror(rc), rc); } else { m++; } } else { isbad++; } } if (nospc) { SAM("-ENOSPC=usb_submit_urb() for %i urbs\n", nospc); SAM("..... possibly inadequate USB bandwidth\n"); peasycap->audio_eof = 1; } if (isbad) { JOM(4, "attempting cleanup instead of submitting\n"); list_for_each(plist_head, (peasycap->purb_audio_head)) { pdata_urb = list_entry(plist_head, struct data_urb, list_head); if (pdata_urb && pdata_urb->purb) usb_kill_urb(pdata_urb->purb); } peasycap->audio_isoc_streaming = 0; } else { peasycap->audio_isoc_streaming = m; JOM(4, "submitted %i audio urbs\n", m); } return 0; } /*****************************************************************************/ /*---------------------------------------------------------------------------*/ /* * KILL ALL AUDIO URBS. */ /*---------------------------------------------------------------------------*/ int kill_audio_urbs(struct easycap *peasycap) { int m; struct list_head *plist_head; struct data_urb *pdata_urb; if (!peasycap) { SAY("ERROR: peasycap is NULL\n"); return -EFAULT; } if (!peasycap->audio_isoc_streaming) { JOM(8, "%i=audio_isoc_streaming, no audio urbs killed\n", peasycap->audio_isoc_streaming); return 0; } if (!peasycap->purb_audio_head) { SAM("ERROR: peasycap->purb_audio_head is NULL\n"); return -EFAULT; } peasycap->audio_isoc_streaming = 0; JOM(4, "killing audio urbs\n"); m = 0; list_for_each(plist_head, (peasycap->purb_audio_head)) { pdata_urb = list_entry(plist_head, struct data_urb, list_head); if (pdata_urb && pdata_urb->purb) { usb_kill_urb(pdata_urb->purb); m++; } } JOM(4, "%i audio urbs killed\n", m); return 0; } /*****************************************************************************/