forked from Minki/linux
fca5bca487
This adds basic support for M-Audio's Fast Track Ultra series of USB audio interfaces. It is a refactored version of the patch Clemens Ladisch posted some time ago. Neither playback nor capturing work properly at 44100 Hz (don't know why). The other sampling rates work properly. There's no support for the DSP mixer, yet. Signed-off-by: Felix Homann <fexpop@web.de> Signed-off-by: Takashi Iwai <tiwai@suse.de>
362 lines
10 KiB
C
362 lines
10 KiB
C
/*
|
|
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/usb.h>
|
|
#include <linux/usb/audio.h>
|
|
#include <linux/usb/audio-v2.h>
|
|
|
|
#include <sound/core.h>
|
|
#include <sound/pcm.h>
|
|
|
|
#include "usbaudio.h"
|
|
#include "card.h"
|
|
#include "proc.h"
|
|
#include "quirks.h"
|
|
#include "endpoint.h"
|
|
#include "urb.h"
|
|
#include "pcm.h"
|
|
#include "helper.h"
|
|
#include "format.h"
|
|
|
|
/*
|
|
* free a substream
|
|
*/
|
|
static void free_substream(struct snd_usb_substream *subs)
|
|
{
|
|
struct list_head *p, *n;
|
|
|
|
if (!subs->num_formats)
|
|
return; /* not initialized */
|
|
list_for_each_safe(p, n, &subs->fmt_list) {
|
|
struct audioformat *fp = list_entry(p, struct audioformat, list);
|
|
kfree(fp->rate_table);
|
|
kfree(fp);
|
|
}
|
|
kfree(subs->rate_list.list);
|
|
}
|
|
|
|
|
|
/*
|
|
* free a usb stream instance
|
|
*/
|
|
static void snd_usb_audio_stream_free(struct snd_usb_stream *stream)
|
|
{
|
|
free_substream(&stream->substream[0]);
|
|
free_substream(&stream->substream[1]);
|
|
list_del(&stream->list);
|
|
kfree(stream);
|
|
}
|
|
|
|
static void snd_usb_audio_pcm_free(struct snd_pcm *pcm)
|
|
{
|
|
struct snd_usb_stream *stream = pcm->private_data;
|
|
if (stream) {
|
|
stream->pcm = NULL;
|
|
snd_usb_audio_stream_free(stream);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* add this endpoint to the chip instance.
|
|
* if a stream with the same endpoint already exists, append to it.
|
|
* if not, create a new pcm stream.
|
|
*/
|
|
int snd_usb_add_audio_endpoint(struct snd_usb_audio *chip, int stream, struct audioformat *fp)
|
|
{
|
|
struct list_head *p;
|
|
struct snd_usb_stream *as;
|
|
struct snd_usb_substream *subs;
|
|
struct snd_pcm *pcm;
|
|
int err;
|
|
|
|
list_for_each(p, &chip->pcm_list) {
|
|
as = list_entry(p, struct snd_usb_stream, list);
|
|
if (as->fmt_type != fp->fmt_type)
|
|
continue;
|
|
subs = &as->substream[stream];
|
|
if (!subs->endpoint)
|
|
continue;
|
|
if (subs->endpoint == fp->endpoint) {
|
|
list_add_tail(&fp->list, &subs->fmt_list);
|
|
subs->num_formats++;
|
|
subs->formats |= fp->formats;
|
|
return 0;
|
|
}
|
|
}
|
|
/* look for an empty stream */
|
|
list_for_each(p, &chip->pcm_list) {
|
|
as = list_entry(p, struct snd_usb_stream, list);
|
|
if (as->fmt_type != fp->fmt_type)
|
|
continue;
|
|
subs = &as->substream[stream];
|
|
if (subs->endpoint)
|
|
continue;
|
|
err = snd_pcm_new_stream(as->pcm, stream, 1);
|
|
if (err < 0)
|
|
return err;
|
|
snd_usb_init_substream(as, stream, fp);
|
|
return 0;
|
|
}
|
|
|
|
/* create a new pcm */
|
|
as = kzalloc(sizeof(*as), GFP_KERNEL);
|
|
if (!as)
|
|
return -ENOMEM;
|
|
as->pcm_index = chip->pcm_devs;
|
|
as->chip = chip;
|
|
as->fmt_type = fp->fmt_type;
|
|
err = snd_pcm_new(chip->card, "USB Audio", chip->pcm_devs,
|
|
stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0,
|
|
stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1,
|
|
&pcm);
|
|
if (err < 0) {
|
|
kfree(as);
|
|
return err;
|
|
}
|
|
as->pcm = pcm;
|
|
pcm->private_data = as;
|
|
pcm->private_free = snd_usb_audio_pcm_free;
|
|
pcm->info_flags = 0;
|
|
if (chip->pcm_devs > 0)
|
|
sprintf(pcm->name, "USB Audio #%d", chip->pcm_devs);
|
|
else
|
|
strcpy(pcm->name, "USB Audio");
|
|
|
|
snd_usb_init_substream(as, stream, fp);
|
|
|
|
list_add(&as->list, &chip->pcm_list);
|
|
chip->pcm_devs++;
|
|
|
|
snd_usb_proc_pcm_format_add(as);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int snd_usb_parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no)
|
|
{
|
|
struct usb_device *dev;
|
|
struct usb_interface *iface;
|
|
struct usb_host_interface *alts;
|
|
struct usb_interface_descriptor *altsd;
|
|
int i, altno, err, stream;
|
|
int format = 0, num_channels = 0;
|
|
struct audioformat *fp = NULL;
|
|
unsigned char *fmt, *csep;
|
|
int num, protocol;
|
|
|
|
dev = chip->dev;
|
|
|
|
/* parse the interface's altsettings */
|
|
iface = usb_ifnum_to_if(dev, iface_no);
|
|
|
|
num = iface->num_altsetting;
|
|
|
|
/*
|
|
* Dallas DS4201 workaround: It presents 5 altsettings, but the last
|
|
* one misses syncpipe, and does not produce any sound.
|
|
*/
|
|
if (chip->usb_id == USB_ID(0x04fa, 0x4201))
|
|
num = 4;
|
|
|
|
for (i = 0; i < num; i++) {
|
|
alts = &iface->altsetting[i];
|
|
altsd = get_iface_desc(alts);
|
|
protocol = altsd->bInterfaceProtocol;
|
|
/* skip invalid one */
|
|
if ((altsd->bInterfaceClass != USB_CLASS_AUDIO &&
|
|
altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) ||
|
|
(altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING &&
|
|
altsd->bInterfaceSubClass != USB_SUBCLASS_VENDOR_SPEC) ||
|
|
altsd->bNumEndpoints < 1 ||
|
|
le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) == 0)
|
|
continue;
|
|
/* must be isochronous */
|
|
if ((get_endpoint(alts, 0)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) !=
|
|
USB_ENDPOINT_XFER_ISOC)
|
|
continue;
|
|
/* check direction */
|
|
stream = (get_endpoint(alts, 0)->bEndpointAddress & USB_DIR_IN) ?
|
|
SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
|
|
altno = altsd->bAlternateSetting;
|
|
|
|
if (snd_usb_apply_interface_quirk(chip, iface_no, altno))
|
|
continue;
|
|
|
|
/* get audio formats */
|
|
switch (protocol) {
|
|
case UAC_VERSION_1: {
|
|
struct uac_as_header_descriptor_v1 *as =
|
|
snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL);
|
|
|
|
if (!as) {
|
|
snd_printk(KERN_ERR "%d:%u:%d : UAC_AS_GENERAL descriptor not found\n",
|
|
dev->devnum, iface_no, altno);
|
|
continue;
|
|
}
|
|
|
|
if (as->bLength < sizeof(*as)) {
|
|
snd_printk(KERN_ERR "%d:%u:%d : invalid UAC_AS_GENERAL desc\n",
|
|
dev->devnum, iface_no, altno);
|
|
continue;
|
|
}
|
|
|
|
format = le16_to_cpu(as->wFormatTag); /* remember the format value */
|
|
break;
|
|
}
|
|
|
|
case UAC_VERSION_2: {
|
|
struct uac_as_header_descriptor_v2 *as =
|
|
snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL);
|
|
|
|
if (!as) {
|
|
snd_printk(KERN_ERR "%d:%u:%d : UAC_AS_GENERAL descriptor not found\n",
|
|
dev->devnum, iface_no, altno);
|
|
continue;
|
|
}
|
|
|
|
if (as->bLength < sizeof(*as)) {
|
|
snd_printk(KERN_ERR "%d:%u:%d : invalid UAC_AS_GENERAL desc\n",
|
|
dev->devnum, iface_no, altno);
|
|
continue;
|
|
}
|
|
|
|
num_channels = as->bNrChannels;
|
|
format = le32_to_cpu(as->bmFormats);
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
snd_printk(KERN_ERR "%d:%u:%d : unknown interface protocol %04x\n",
|
|
dev->devnum, iface_no, altno, protocol);
|
|
continue;
|
|
}
|
|
|
|
/* get format type */
|
|
fmt = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_FORMAT_TYPE);
|
|
if (!fmt) {
|
|
snd_printk(KERN_ERR "%d:%u:%d : no UAC_FORMAT_TYPE desc\n",
|
|
dev->devnum, iface_no, altno);
|
|
continue;
|
|
}
|
|
if (((protocol == UAC_VERSION_1) && (fmt[0] < 8)) ||
|
|
((protocol == UAC_VERSION_2) && (fmt[0] != 6))) {
|
|
snd_printk(KERN_ERR "%d:%u:%d : invalid UAC_FORMAT_TYPE desc\n",
|
|
dev->devnum, iface_no, altno);
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Blue Microphones workaround: The last altsetting is identical
|
|
* with the previous one, except for a larger packet size, but
|
|
* is actually a mislabeled two-channel setting; ignore it.
|
|
*/
|
|
if (fmt[4] == 1 && fmt[5] == 2 && altno == 2 && num == 3 &&
|
|
fp && fp->altsetting == 1 && fp->channels == 1 &&
|
|
fp->formats == SNDRV_PCM_FMTBIT_S16_LE &&
|
|
protocol == UAC_VERSION_1 &&
|
|
le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) ==
|
|
fp->maxpacksize * 2)
|
|
continue;
|
|
|
|
csep = snd_usb_find_desc(alts->endpoint[0].extra, alts->endpoint[0].extralen, NULL, USB_DT_CS_ENDPOINT);
|
|
/* Creamware Noah has this descriptor after the 2nd endpoint */
|
|
if (!csep && altsd->bNumEndpoints >= 2)
|
|
csep = snd_usb_find_desc(alts->endpoint[1].extra, alts->endpoint[1].extralen, NULL, USB_DT_CS_ENDPOINT);
|
|
if (!csep || csep[0] < 7 || csep[2] != UAC_EP_GENERAL) {
|
|
snd_printk(KERN_WARNING "%d:%u:%d : no or invalid"
|
|
" class specific endpoint descriptor\n",
|
|
dev->devnum, iface_no, altno);
|
|
csep = NULL;
|
|
}
|
|
|
|
fp = kzalloc(sizeof(*fp), GFP_KERNEL);
|
|
if (! fp) {
|
|
snd_printk(KERN_ERR "cannot malloc\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
fp->iface = iface_no;
|
|
fp->altsetting = altno;
|
|
fp->altset_idx = i;
|
|
fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress;
|
|
fp->ep_attr = get_endpoint(alts, 0)->bmAttributes;
|
|
fp->datainterval = snd_usb_parse_datainterval(chip, alts);
|
|
fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
|
|
/* num_channels is only set for v2 interfaces */
|
|
fp->channels = num_channels;
|
|
if (snd_usb_get_speed(dev) == USB_SPEED_HIGH)
|
|
fp->maxpacksize = (((fp->maxpacksize >> 11) & 3) + 1)
|
|
* (fp->maxpacksize & 0x7ff);
|
|
fp->attributes = csep ? csep[3] : 0;
|
|
|
|
/* some quirks for attributes here */
|
|
|
|
switch (chip->usb_id) {
|
|
case USB_ID(0x0a92, 0x0053): /* AudioTrak Optoplay */
|
|
/* Optoplay sets the sample rate attribute although
|
|
* it seems not supporting it in fact.
|
|
*/
|
|
fp->attributes &= ~UAC_EP_CS_ATTR_SAMPLE_RATE;
|
|
break;
|
|
case USB_ID(0x041e, 0x3020): /* Creative SB Audigy 2 NX */
|
|
case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */
|
|
case USB_ID(0x0763, 0x2080): /* M-Audio Fast Track Ultra 8 */
|
|
case USB_ID(0x0763, 0x2081): /* M-Audio Fast Track Ultra 8R */
|
|
/* doesn't set the sample rate attribute, but supports it */
|
|
fp->attributes |= UAC_EP_CS_ATTR_SAMPLE_RATE;
|
|
break;
|
|
case USB_ID(0x047f, 0x0ca1): /* plantronics headset */
|
|
case USB_ID(0x077d, 0x07af): /* Griffin iMic (note that there is
|
|
an older model 77d:223) */
|
|
/*
|
|
* plantronics headset and Griffin iMic have set adaptive-in
|
|
* although it's really not...
|
|
*/
|
|
fp->ep_attr &= ~USB_ENDPOINT_SYNCTYPE;
|
|
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
fp->ep_attr |= USB_ENDPOINT_SYNC_ADAPTIVE;
|
|
else
|
|
fp->ep_attr |= USB_ENDPOINT_SYNC_SYNC;
|
|
break;
|
|
}
|
|
|
|
/* ok, let's parse further... */
|
|
if (snd_usb_parse_audio_format(chip, fp, format, fmt, stream, alts) < 0) {
|
|
kfree(fp->rate_table);
|
|
kfree(fp);
|
|
continue;
|
|
}
|
|
|
|
snd_printdd(KERN_INFO "%d:%u:%d: add audio endpoint %#x\n", dev->devnum, iface_no, altno, fp->endpoint);
|
|
err = snd_usb_add_audio_endpoint(chip, stream, fp);
|
|
if (err < 0) {
|
|
kfree(fp->rate_table);
|
|
kfree(fp);
|
|
return err;
|
|
}
|
|
/* try to set the interface... */
|
|
usb_set_interface(chip->dev, iface_no, altno);
|
|
snd_usb_init_pitch(chip, iface_no, alts, fp);
|
|
snd_usb_init_sample_rate(chip, iface_no, alts, fp, fp->rate_max);
|
|
}
|
|
return 0;
|
|
}
|
|
|