96fbf771d8
Because the counters were not reset when starting up streaming, they would be reused from the previous run. This can result in cases such that when the second instance of streaming starts up, the "cnt" variable in em28xx_audio_isocirq() can end up being negative, resulting in attempting to write to memory before the start of runtime->dma_area (as well as having a negative number of bytes to copy). Signed-off-by: Devin Heitmueller <dheitmueller@kernellabs.com> CC: stable@kernel.org Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
553 lines
14 KiB
C
553 lines
14 KiB
C
/*
|
|
* Empiatech em28x1 audio extension
|
|
*
|
|
* Copyright (C) 2006 Markus Rechberger <mrechberger@gmail.com>
|
|
*
|
|
* Copyright (C) 2007 Mauro Carvalho Chehab <mchehab@infradead.org>
|
|
* - Port to work with the in-kernel driver
|
|
* - Several cleanups
|
|
*
|
|
* This driver is based on my previous au600 usb pstn audio driver
|
|
* and inherits all the copyrights
|
|
*
|
|
* This program 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.
|
|
*
|
|
* This program 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 program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/usb.h>
|
|
#include <linux/init.h>
|
|
#include <linux/sound.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/soundcard.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/module.h>
|
|
#include <sound/core.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/info.h>
|
|
#include <sound/initval.h>
|
|
#include <sound/control.h>
|
|
#include <media/v4l2-common.h>
|
|
#include "em28xx.h"
|
|
|
|
static int debug;
|
|
module_param(debug, int, 0644);
|
|
MODULE_PARM_DESC(debug, "activates debug info");
|
|
|
|
#define dprintk(fmt, arg...) do { \
|
|
if (debug) \
|
|
printk(KERN_INFO "em28xx-audio %s: " fmt, \
|
|
__func__, ##arg); \
|
|
} while (0)
|
|
|
|
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
|
|
|
|
static int em28xx_deinit_isoc_audio(struct em28xx *dev)
|
|
{
|
|
int i;
|
|
|
|
dprintk("Stopping isoc\n");
|
|
for (i = 0; i < EM28XX_AUDIO_BUFS; i++) {
|
|
if (!irqs_disabled())
|
|
usb_kill_urb(dev->adev.urb[i]);
|
|
else
|
|
usb_unlink_urb(dev->adev.urb[i]);
|
|
|
|
usb_free_urb(dev->adev.urb[i]);
|
|
dev->adev.urb[i] = NULL;
|
|
|
|
kfree(dev->adev.transfer_buffer[i]);
|
|
dev->adev.transfer_buffer[i] = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void em28xx_audio_isocirq(struct urb *urb)
|
|
{
|
|
struct em28xx *dev = urb->context;
|
|
int i;
|
|
unsigned int oldptr;
|
|
int period_elapsed = 0;
|
|
int status;
|
|
unsigned char *cp;
|
|
unsigned int stride;
|
|
struct snd_pcm_substream *substream;
|
|
struct snd_pcm_runtime *runtime;
|
|
|
|
switch (urb->status) {
|
|
case 0: /* success */
|
|
case -ETIMEDOUT: /* NAK */
|
|
break;
|
|
case -ECONNRESET: /* kill */
|
|
case -ENOENT:
|
|
case -ESHUTDOWN:
|
|
return;
|
|
default: /* error */
|
|
dprintk("urb completition error %d.\n", urb->status);
|
|
break;
|
|
}
|
|
|
|
if (dev->adev.capture_pcm_substream) {
|
|
substream = dev->adev.capture_pcm_substream;
|
|
runtime = substream->runtime;
|
|
stride = runtime->frame_bits >> 3;
|
|
|
|
for (i = 0; i < urb->number_of_packets; i++) {
|
|
int length =
|
|
urb->iso_frame_desc[i].actual_length / stride;
|
|
cp = (unsigned char *)urb->transfer_buffer +
|
|
urb->iso_frame_desc[i].offset;
|
|
|
|
if (!length)
|
|
continue;
|
|
|
|
oldptr = dev->adev.hwptr_done_capture;
|
|
if (oldptr + length >= runtime->buffer_size) {
|
|
unsigned int cnt =
|
|
runtime->buffer_size - oldptr;
|
|
memcpy(runtime->dma_area + oldptr * stride, cp,
|
|
cnt * stride);
|
|
memcpy(runtime->dma_area, cp + cnt * stride,
|
|
length * stride - cnt * stride);
|
|
} else {
|
|
memcpy(runtime->dma_area + oldptr * stride, cp,
|
|
length * stride);
|
|
}
|
|
|
|
snd_pcm_stream_lock(substream);
|
|
|
|
dev->adev.hwptr_done_capture += length;
|
|
if (dev->adev.hwptr_done_capture >=
|
|
runtime->buffer_size)
|
|
dev->adev.hwptr_done_capture -=
|
|
runtime->buffer_size;
|
|
|
|
dev->adev.capture_transfer_done += length;
|
|
if (dev->adev.capture_transfer_done >=
|
|
runtime->period_size) {
|
|
dev->adev.capture_transfer_done -=
|
|
runtime->period_size;
|
|
period_elapsed = 1;
|
|
}
|
|
|
|
snd_pcm_stream_unlock(substream);
|
|
}
|
|
if (period_elapsed)
|
|
snd_pcm_period_elapsed(substream);
|
|
}
|
|
urb->status = 0;
|
|
|
|
status = usb_submit_urb(urb, GFP_ATOMIC);
|
|
if (status < 0) {
|
|
em28xx_errdev("resubmit of audio urb failed (error=%i)\n",
|
|
status);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static int em28xx_init_audio_isoc(struct em28xx *dev)
|
|
{
|
|
int i, errCode;
|
|
const int sb_size = EM28XX_NUM_AUDIO_PACKETS *
|
|
EM28XX_AUDIO_MAX_PACKET_SIZE;
|
|
|
|
dprintk("Starting isoc transfers\n");
|
|
|
|
for (i = 0; i < EM28XX_AUDIO_BUFS; i++) {
|
|
struct urb *urb;
|
|
int j, k;
|
|
|
|
dev->adev.transfer_buffer[i] = kmalloc(sb_size, GFP_ATOMIC);
|
|
if (!dev->adev.transfer_buffer[i])
|
|
return -ENOMEM;
|
|
|
|
memset(dev->adev.transfer_buffer[i], 0x80, sb_size);
|
|
urb = usb_alloc_urb(EM28XX_NUM_AUDIO_PACKETS, GFP_ATOMIC);
|
|
if (!urb) {
|
|
em28xx_errdev("usb_alloc_urb failed!\n");
|
|
for (j = 0; j < i; j++) {
|
|
usb_free_urb(dev->adev.urb[j]);
|
|
kfree(dev->adev.transfer_buffer[j]);
|
|
}
|
|
return -ENOMEM;
|
|
}
|
|
|
|
urb->dev = dev->udev;
|
|
urb->context = dev;
|
|
urb->pipe = usb_rcvisocpipe(dev->udev, 0x83);
|
|
urb->transfer_flags = URB_ISO_ASAP;
|
|
urb->transfer_buffer = dev->adev.transfer_buffer[i];
|
|
urb->interval = 1;
|
|
urb->complete = em28xx_audio_isocirq;
|
|
urb->number_of_packets = EM28XX_NUM_AUDIO_PACKETS;
|
|
urb->transfer_buffer_length = sb_size;
|
|
|
|
for (j = k = 0; j < EM28XX_NUM_AUDIO_PACKETS;
|
|
j++, k += EM28XX_AUDIO_MAX_PACKET_SIZE) {
|
|
urb->iso_frame_desc[j].offset = k;
|
|
urb->iso_frame_desc[j].length =
|
|
EM28XX_AUDIO_MAX_PACKET_SIZE;
|
|
}
|
|
dev->adev.urb[i] = urb;
|
|
}
|
|
|
|
for (i = 0; i < EM28XX_AUDIO_BUFS; i++) {
|
|
errCode = usb_submit_urb(dev->adev.urb[i], GFP_ATOMIC);
|
|
if (errCode) {
|
|
em28xx_deinit_isoc_audio(dev);
|
|
return errCode;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int em28xx_cmd(struct em28xx *dev, int cmd, int arg)
|
|
{
|
|
dprintk("%s transfer\n", (dev->adev.capture_stream == STREAM_ON) ?
|
|
"stop" : "start");
|
|
|
|
switch (cmd) {
|
|
case EM28XX_CAPTURE_STREAM_EN:
|
|
if (dev->adev.capture_stream == STREAM_OFF &&
|
|
arg == EM28XX_START_AUDIO) {
|
|
dev->adev.capture_stream = STREAM_ON;
|
|
em28xx_init_audio_isoc(dev);
|
|
} else if (dev->adev.capture_stream == STREAM_ON &&
|
|
arg == EM28XX_STOP_AUDIO) {
|
|
dev->adev.capture_stream = STREAM_OFF;
|
|
em28xx_deinit_isoc_audio(dev);
|
|
} else {
|
|
em28xx_errdev("An underrun very likely occurred. "
|
|
"Ignoring it.\n");
|
|
}
|
|
return 0;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs,
|
|
size_t size)
|
|
{
|
|
struct snd_pcm_runtime *runtime = subs->runtime;
|
|
|
|
dprintk("Allocating vbuffer\n");
|
|
if (runtime->dma_area) {
|
|
if (runtime->dma_bytes > size)
|
|
return 0;
|
|
|
|
vfree(runtime->dma_area);
|
|
}
|
|
runtime->dma_area = vmalloc(size);
|
|
if (!runtime->dma_area)
|
|
return -ENOMEM;
|
|
|
|
runtime->dma_bytes = size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct snd_pcm_hardware snd_em28xx_hw_capture = {
|
|
.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_CONTINUOUS | SNDRV_PCM_RATE_KNOT,
|
|
|
|
.rate_min = 48000,
|
|
.rate_max = 48000,
|
|
.channels_min = 2,
|
|
.channels_max = 2,
|
|
.buffer_bytes_max = 62720 * 8, /* just about the value in usbaudio.c */
|
|
.period_bytes_min = 64, /* 12544/2, */
|
|
.period_bytes_max = 12544,
|
|
.periods_min = 2,
|
|
.periods_max = 98, /* 12544, */
|
|
};
|
|
|
|
static int snd_em28xx_capture_open(struct snd_pcm_substream *substream)
|
|
{
|
|
struct em28xx *dev = snd_pcm_substream_chip(substream);
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
int ret = 0;
|
|
|
|
dprintk("opening device and trying to acquire exclusive lock\n");
|
|
|
|
if (!dev) {
|
|
printk(KERN_ERR "BUG: em28xx can't find device struct."
|
|
" Can't proceed with open\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Sets volume, mute, etc */
|
|
|
|
dev->mute = 0;
|
|
mutex_lock(&dev->lock);
|
|
ret = em28xx_audio_analog_set(dev);
|
|
mutex_unlock(&dev->lock);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
runtime->hw = snd_em28xx_hw_capture;
|
|
if (dev->alt == 0 && dev->adev.users == 0) {
|
|
int errCode;
|
|
dev->alt = 7;
|
|
errCode = usb_set_interface(dev->udev, 0, 7);
|
|
dprintk("changing alternate number to 7\n");
|
|
}
|
|
|
|
mutex_lock(&dev->lock);
|
|
dev->adev.users++;
|
|
mutex_unlock(&dev->lock);
|
|
|
|
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
|
|
dev->adev.capture_pcm_substream = substream;
|
|
runtime->private_data = dev;
|
|
|
|
return 0;
|
|
err:
|
|
printk(KERN_ERR "Error while configuring em28xx mixer\n");
|
|
return ret;
|
|
}
|
|
|
|
static int snd_em28xx_pcm_close(struct snd_pcm_substream *substream)
|
|
{
|
|
struct em28xx *dev = snd_pcm_substream_chip(substream);
|
|
|
|
dprintk("closing device\n");
|
|
|
|
dev->mute = 1;
|
|
mutex_lock(&dev->lock);
|
|
dev->adev.users--;
|
|
em28xx_audio_analog_set(dev);
|
|
if (substream->runtime->dma_area) {
|
|
dprintk("freeing\n");
|
|
vfree(substream->runtime->dma_area);
|
|
substream->runtime->dma_area = NULL;
|
|
}
|
|
mutex_unlock(&dev->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int snd_em28xx_hw_capture_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *hw_params)
|
|
{
|
|
unsigned int channels, rate, format;
|
|
int ret;
|
|
|
|
dprintk("Setting capture parameters\n");
|
|
|
|
ret = snd_pcm_alloc_vmalloc_buffer(substream,
|
|
params_buffer_bytes(hw_params));
|
|
format = params_format(hw_params);
|
|
rate = params_rate(hw_params);
|
|
channels = params_channels(hw_params);
|
|
|
|
/* TODO: set up em28xx audio chip to deliver the correct audio format,
|
|
current default is 48000hz multiplexed => 96000hz mono
|
|
which shouldn't matter since analogue TV only supports mono */
|
|
return 0;
|
|
}
|
|
|
|
static int snd_em28xx_hw_capture_free(struct snd_pcm_substream *substream)
|
|
{
|
|
struct em28xx *dev = snd_pcm_substream_chip(substream);
|
|
|
|
dprintk("Stop capture, if needed\n");
|
|
|
|
if (dev->adev.capture_stream == STREAM_ON)
|
|
em28xx_cmd(dev, EM28XX_CAPTURE_STREAM_EN, EM28XX_STOP_AUDIO);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int snd_em28xx_prepare(struct snd_pcm_substream *substream)
|
|
{
|
|
struct em28xx *dev = snd_pcm_substream_chip(substream);
|
|
|
|
dev->adev.hwptr_done_capture = 0;
|
|
dev->adev.capture_transfer_done = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int snd_em28xx_capture_trigger(struct snd_pcm_substream *substream,
|
|
int cmd)
|
|
{
|
|
struct em28xx *dev = snd_pcm_substream_chip(substream);
|
|
int retval;
|
|
|
|
dprintk("Should %s capture\n", (cmd == SNDRV_PCM_TRIGGER_START) ?
|
|
"start" : "stop");
|
|
|
|
spin_lock(&dev->adev.slock);
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
em28xx_cmd(dev, EM28XX_CAPTURE_STREAM_EN, EM28XX_START_AUDIO);
|
|
retval = 0;
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
em28xx_cmd(dev, EM28XX_CAPTURE_STREAM_EN, EM28XX_STOP_AUDIO);
|
|
retval = 0;
|
|
break;
|
|
default:
|
|
retval = -EINVAL;
|
|
}
|
|
|
|
spin_unlock(&dev->adev.slock);
|
|
return retval;
|
|
}
|
|
|
|
static snd_pcm_uframes_t snd_em28xx_capture_pointer(struct snd_pcm_substream
|
|
*substream)
|
|
{
|
|
unsigned long flags;
|
|
struct em28xx *dev;
|
|
snd_pcm_uframes_t hwptr_done;
|
|
|
|
dev = snd_pcm_substream_chip(substream);
|
|
spin_lock_irqsave(&dev->adev.slock, flags);
|
|
hwptr_done = dev->adev.hwptr_done_capture;
|
|
spin_unlock_irqrestore(&dev->adev.slock, flags);
|
|
|
|
return hwptr_done;
|
|
}
|
|
|
|
static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs,
|
|
unsigned long offset)
|
|
{
|
|
void *pageptr = subs->runtime->dma_area + offset;
|
|
|
|
return vmalloc_to_page(pageptr);
|
|
}
|
|
|
|
static struct snd_pcm_ops snd_em28xx_pcm_capture = {
|
|
.open = snd_em28xx_capture_open,
|
|
.close = snd_em28xx_pcm_close,
|
|
.ioctl = snd_pcm_lib_ioctl,
|
|
.hw_params = snd_em28xx_hw_capture_params,
|
|
.hw_free = snd_em28xx_hw_capture_free,
|
|
.prepare = snd_em28xx_prepare,
|
|
.trigger = snd_em28xx_capture_trigger,
|
|
.pointer = snd_em28xx_capture_pointer,
|
|
.page = snd_pcm_get_vmalloc_page,
|
|
};
|
|
|
|
static int em28xx_audio_init(struct em28xx *dev)
|
|
{
|
|
struct em28xx_audio *adev = &dev->adev;
|
|
struct snd_pcm *pcm;
|
|
struct snd_card *card;
|
|
static int devnr;
|
|
int err;
|
|
|
|
if (dev->has_alsa_audio != 1) {
|
|
/* This device does not support the extension (in this case
|
|
the device is expecting the snd-usb-audio module or
|
|
doesn't have analog audio support at all) */
|
|
return 0;
|
|
}
|
|
|
|
printk(KERN_INFO "em28xx-audio.c: probing for em28x1 "
|
|
"non standard usbaudio\n");
|
|
printk(KERN_INFO "em28xx-audio.c: Copyright (C) 2006 Markus "
|
|
"Rechberger\n");
|
|
|
|
err = snd_card_create(index[devnr], "Em28xx Audio", THIS_MODULE, 0,
|
|
&card);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
spin_lock_init(&adev->slock);
|
|
err = snd_pcm_new(card, "Em28xx Audio", 0, 0, 1, &pcm);
|
|
if (err < 0) {
|
|
snd_card_free(card);
|
|
return err;
|
|
}
|
|
|
|
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_em28xx_pcm_capture);
|
|
pcm->info_flags = 0;
|
|
pcm->private_data = dev;
|
|
strcpy(pcm->name, "Empia 28xx Capture");
|
|
|
|
snd_card_set_dev(card, &dev->udev->dev);
|
|
strcpy(card->driver, "Empia Em28xx Audio");
|
|
strcpy(card->shortname, "Em28xx Audio");
|
|
strcpy(card->longname, "Empia Em28xx Audio");
|
|
|
|
err = snd_card_register(card);
|
|
if (err < 0) {
|
|
snd_card_free(card);
|
|
return err;
|
|
}
|
|
adev->sndcard = card;
|
|
adev->udev = dev->udev;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int em28xx_audio_fini(struct em28xx *dev)
|
|
{
|
|
if (dev == NULL)
|
|
return 0;
|
|
|
|
if (dev->has_alsa_audio != 1) {
|
|
/* This device does not support the extension (in this case
|
|
the device is expecting the snd-usb-audio module or
|
|
doesn't have analog audio support at all) */
|
|
return 0;
|
|
}
|
|
|
|
if (dev->adev.sndcard) {
|
|
snd_card_free(dev->adev.sndcard);
|
|
dev->adev.sndcard = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct em28xx_ops audio_ops = {
|
|
.id = EM28XX_AUDIO,
|
|
.name = "Em28xx Audio Extension",
|
|
.init = em28xx_audio_init,
|
|
.fini = em28xx_audio_fini,
|
|
};
|
|
|
|
static int __init em28xx_alsa_register(void)
|
|
{
|
|
return em28xx_register_extension(&audio_ops);
|
|
}
|
|
|
|
static void __exit em28xx_alsa_unregister(void)
|
|
{
|
|
em28xx_unregister_extension(&audio_ops);
|
|
}
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Markus Rechberger <mrechberger@gmail.com>");
|
|
MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@infradead.org>");
|
|
MODULE_DESCRIPTION("Em28xx Audio driver");
|
|
|
|
module_init(em28xx_alsa_register);
|
|
module_exit(em28xx_alsa_unregister);
|