[media] em28xx: Add support for devices with a separate audio interface

Some devices use a separate interface for the vendor audio class.

Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
This commit is contained in:
Mauro Carvalho Chehab 2011-06-17 15:15:12 -03:00
parent dff0f8c279
commit 4f83e7b3ef
4 changed files with 109 additions and 60 deletions

View File

@ -3,9 +3,9 @@
*
* Copyright (C) 2006 Markus Rechberger <mrechberger@gmail.com>
*
* Copyright (C) 2007 Mauro Carvalho Chehab <mchehab@infradead.org>
* Copyright (C) 2007-2011 Mauro Carvalho Chehab <mchehab@redhat.com>
* - Port to work with the in-kernel driver
* - Several cleanups
* - Cleanups, fixes, alsa-controls, etc.
*
* This driver is based on my previous au600 usb pstn audio driver
* and inherits all the copyrights
@ -281,23 +281,27 @@ static int snd_em28xx_capture_open(struct snd_pcm_substream *substream)
return -ENODEV;
}
/* Sets volume, mute, etc */
dev->mute = 0;
mutex_lock(&dev->lock);
ret = em28xx_audio_analog_set(dev);
if (ret < 0)
goto err;
runtime->hw = snd_em28xx_hw_capture;
if (dev->alt == 0 && dev->adev.users == 0) {
dev->alt = 7;
dprintk("changing alternate number to 7\n");
usb_set_interface(dev->udev, 0, 7);
}
if ((dev->alt == 0 || dev->audio_ifnum) && dev->adev.users == 0) {
if (dev->audio_ifnum)
dev->alt = 1;
else
dev->alt = 7;
dev->adev.users++;
mutex_unlock(&dev->lock);
dprintk("changing alternate number on interface %d to %d\n",
dev->audio_ifnum, dev->alt);
usb_set_interface(dev->udev, dev->audio_ifnum, dev->alt);
/* Sets volume, mute, etc */
dev->mute = 0;
mutex_lock(&dev->lock);
ret = em28xx_audio_analog_set(dev);
if (ret < 0)
goto err;
dev->adev.users++;
mutex_unlock(&dev->lock);
}
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
dev->adev.capture_pcm_substream = substream;
@ -635,17 +639,17 @@ static int em28xx_audio_init(struct em28xx *dev)
static int devnr;
int err;
if (dev->has_alsa_audio != 1) {
if (!dev->has_alsa_audio || dev->audio_ifnum < 0) {
/* 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: probing for em28xx Audio Vendor Class\n");
printk(KERN_INFO "em28xx-audio.c: Copyright (C) 2006 Markus "
"Rechberger\n");
printk(KERN_INFO "em28xx-audio.c: Copyright (C) 2007-2011 Mauro Carvalho Chehab\n");
err = snd_card_create(index[devnr], "Em28xx Audio", THIS_MODULE, 0,
&card);
@ -737,7 +741,7 @@ static void __exit em28xx_alsa_unregister(void)
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Markus Rechberger <mrechberger@gmail.com>");
MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@infradead.org>");
MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@redhat.com>");
MODULE_DESCRIPTION("Em28xx Audio driver");
module_init(em28xx_alsa_register);

View File

@ -2846,6 +2846,16 @@ static int em28xx_init_dev(struct em28xx **devhandle, struct usb_device *udev,
}
}
if (dev->is_audio_only) {
errCode = em28xx_audio_setup(dev);
if (errCode)
return -ENODEV;
em28xx_add_into_devlist(dev);
em28xx_init_extension(dev);
return 0;
}
/* Prepopulate cached GPO register content */
retval = em28xx_read_reg(dev, dev->reg_gpo_num);
if (retval >= 0)
@ -2946,6 +2956,9 @@ fail_reg_devices:
return retval;
}
/* high bandwidth multiplier, as encoded in highspeed endpoint descriptors */
#define hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03))
/*
* em28xx_usb_probe()
* checks for supported devices
@ -2955,15 +2968,15 @@ static int em28xx_usb_probe(struct usb_interface *interface,
{
const struct usb_endpoint_descriptor *endpoint;
struct usb_device *udev;
struct usb_interface *uif;
struct em28xx *dev = NULL;
int retval;
int i, nr, ifnum, isoc_pipe;
bool is_audio_only = false, has_audio = false;
int i, nr, isoc_pipe;
const int ifnum = interface->altsetting[0].desc.bInterfaceNumber;
char *speed;
char descr[255] = "";
udev = usb_get_dev(interface_to_usbdev(interface));
ifnum = interface->altsetting[0].desc.bInterfaceNumber;
/* Check to see next free device and mark as used */
nr = find_first_zero_bit(&em28xx_devused, EM28XX_MAXBOARDS);
@ -2983,6 +2996,19 @@ static int em28xx_usb_probe(struct usb_interface *interface,
goto err;
}
/* Get endpoints */
for (i = 0; i < interface->num_altsetting; i++) {
int ep;
for (ep = 0; ep < interface->altsetting[i].desc.bNumEndpoints; ep++) {
struct usb_host_endpoint *e;
e = &interface->altsetting[i].endpoint[ep];
if (e->desc.bEndpointAddress == 0x83)
has_audio = true;
}
}
endpoint = &interface->cur_altsetting->endpoint[0].desc;
/* check if the device has the iso in endpoint at the correct place */
@ -3002,19 +3028,22 @@ static int em28xx_usb_probe(struct usb_interface *interface,
check_interface = 0;
if (!check_interface) {
em28xx_err(DRIVER_NAME " video device (%04x:%04x): "
"interface %i, class %i found.\n",
le16_to_cpu(udev->descriptor.idVendor),
le16_to_cpu(udev->descriptor.idProduct),
ifnum,
interface->altsetting[0].desc.bInterfaceClass);
if (has_audio) {
is_audio_only = true;
} else {
em28xx_err(DRIVER_NAME " video device (%04x:%04x): "
"interface %i, class %i found.\n",
le16_to_cpu(udev->descriptor.idVendor),
le16_to_cpu(udev->descriptor.idProduct),
ifnum,
interface->altsetting[0].desc.bInterfaceClass);
em28xx_err(DRIVER_NAME " This is an anciliary "
"interface not used by the driver\n");
em28xx_err(DRIVER_NAME " This is an anciliary "
"interface not used by the driver\n");
em28xx_devused &= ~(1<<nr);
retval = -ENODEV;
goto err;
em28xx_devused &= ~(1<<nr);
retval = -ENODEV;
goto err;
}
}
}
@ -3044,8 +3073,8 @@ static int em28xx_usb_probe(struct usb_interface *interface,
if (*descr)
strlcat(descr, " ", sizeof(descr));
printk(DRIVER_NAME ": New device %s@ %s Mbps "
"(%04x:%04x, interface %d, class %d)\n",
printk(KERN_INFO DRIVER_NAME
": New device %s@ %s Mbps (%04x:%04x, interface %d, class %d)\n",
descr,
speed,
le16_to_cpu(udev->descriptor.idVendor),
@ -3053,6 +3082,11 @@ static int em28xx_usb_probe(struct usb_interface *interface,
ifnum,
interface->altsetting->desc.bInterfaceNumber);
if (has_audio)
printk(KERN_INFO DRIVER_NAME
": Audio Vendor Class interface %i found\n",
ifnum);
/*
* Make sure we have 480 Mbps of bandwidth, otherwise things like
* video stream wouldn't likely work, since 12 Mbps is generally
@ -3088,10 +3122,13 @@ static int em28xx_usb_probe(struct usb_interface *interface,
dev->devno = nr;
dev->model = id->driver_info;
dev->alt = -1;
dev->is_audio_only = is_audio_only;
dev->has_alsa_audio = has_audio;
dev->audio_ifnum = ifnum;
/* Checks if audio is provided by some interface */
for (i = 0; i < udev->config->desc.bNumInterfaces; i++) {
uif = udev->config->interface[i];
struct usb_interface *uif = udev->config->interface[i];
if (uif->altsetting[0].desc.bInterfaceClass == USB_CLASS_AUDIO) {
dev->has_audio_class = 1;
break;
@ -3099,9 +3136,7 @@ static int em28xx_usb_probe(struct usb_interface *interface,
}
/* compute alternate max packet sizes */
uif = udev->actconfig->interface[0];
dev->num_alt = uif->num_altsetting;
dev->num_alt = interface->num_altsetting;
dev->alt_max_pkt_size = kmalloc(32 * dev->num_alt, GFP_KERNEL);
if (dev->alt_max_pkt_size == NULL) {
@ -3113,14 +3148,21 @@ static int em28xx_usb_probe(struct usb_interface *interface,
}
for (i = 0; i < dev->num_alt ; i++) {
u16 tmp = le16_to_cpu(uif->altsetting[i].endpoint[isoc_pipe].desc.wMaxPacketSize);
dev->alt_max_pkt_size[i] =
(tmp & 0x07ff) * (((tmp & 0x1800) >> 11) + 1);
u16 tmp = le16_to_cpu(interface->altsetting[i].endpoint[isoc_pipe].desc.wMaxPacketSize);
unsigned int size = tmp & 0x7ff;
if (udev->speed == USB_SPEED_HIGH)
size = size * hb_mult(tmp);
dev->alt_max_pkt_size[i] = size;
}
if ((card[nr] >= 0) && (card[nr] < em28xx_bcount))
dev->model = card[nr];
/* save our data pointer in this interface device */
usb_set_intfdata(interface, dev);
/* allocate device struct */
mutex_init(&dev->lock);
mutex_lock(&dev->lock);
@ -3132,9 +3174,6 @@ static int em28xx_usb_probe(struct usb_interface *interface,
goto err;
}
/* save our data pointer in this interface device */
usb_set_intfdata(interface, dev);
request_modules(dev);
/* Should be the last thing to do, to avoid newer udev's to
@ -3163,6 +3202,13 @@ static void em28xx_usb_disconnect(struct usb_interface *interface)
if (!dev)
return;
if (dev->is_audio_only) {
mutex_lock(&dev->lock);
em28xx_close_extension(dev);
mutex_unlock(&dev->lock);
return;
}
em28xx_info("disconnecting %s\n", dev->vdev->name);
flush_request_modules(dev);

View File

@ -499,17 +499,13 @@ int em28xx_audio_setup(struct em28xx *dev)
if (dev->chip_id == CHIP_ID_EM2870 || dev->chip_id == CHIP_ID_EM2874
|| dev->chip_id == CHIP_ID_EM28174) {
/* Digital only device - don't load any alsa module */
dev->audio_mode.has_audio = 0;
dev->has_audio_class = 0;
dev->has_alsa_audio = 0;
dev->audio_mode.has_audio = false;
dev->has_audio_class = false;
dev->has_alsa_audio = false;
return 0;
}
/* If device doesn't support Usb Audio Class, use vendor class */
if (!dev->has_audio_class)
dev->has_alsa_audio = 1;
dev->audio_mode.has_audio = 1;
dev->audio_mode.has_audio = true;
/* See how this device is configured */
cfg = em28xx_read_reg(dev, EM28XX_R00_CHIPCFG);
@ -519,8 +515,8 @@ int em28xx_audio_setup(struct em28xx *dev)
cfg = EM28XX_CHIPCFG_AC97; /* Be conservative */
} else if ((cfg & EM28XX_CHIPCFG_AUDIOMASK) == 0x00) {
/* The device doesn't have vendor audio at all */
dev->has_alsa_audio = 0;
dev->audio_mode.has_audio = 0;
dev->has_alsa_audio = false;
dev->audio_mode.has_audio = false;
return 0;
} else if ((cfg & EM28XX_CHIPCFG_AUDIOMASK) ==
EM28XX_CHIPCFG_I2S_3_SAMPRATES) {
@ -549,8 +545,8 @@ int em28xx_audio_setup(struct em28xx *dev)
*/
em28xx_warn("AC97 chip type couldn't be determined\n");
dev->audio_mode.ac97 = EM28XX_NO_AC97;
dev->has_alsa_audio = 0;
dev->audio_mode.has_audio = 0;
dev->has_alsa_audio = false;
dev->audio_mode.has_audio = false;
goto init_audio;
}

View File

@ -487,6 +487,8 @@ struct em28xx {
int devno; /* marks the number of this device */
enum em28xx_chip_id chip_id;
int audio_ifnum;
struct v4l2_device v4l2_dev;
struct em28xx_board board;
@ -503,6 +505,7 @@ struct em28xx {
unsigned int has_audio_class:1;
unsigned int has_alsa_audio:1;
unsigned int is_audio_only:1;
/* Controls audio streaming */
struct work_struct wq_trigger; /* Trigger to start/stop audio for alsa module */