Commit 53ee98fe authored by Daniel Mack's avatar Daniel Mack Committed by Takashi Iwai

ALSA: usbaudio: implement basic set of class v2.0 parser

This adds a number of parsers for audio class v2.0. In particular, the
following internals are different and now handled by the code:

* the number of streaming interfaces is now reported by an interface
  association descriptor. The old approach using a proprietary
  descriptor is deprecated.

* The number of channels per interface is now stored in the AS_GENERAL
  descriptor (used to be part of the FORMAT_TYPE descriptor).

* The list of supported sample rates is no longer stored in a variable
  length appendix of the format_type descriptor but is retrieved from
  the device using a class specific GET_RANGE command.

* Supported sample formats are now reported as 32bit bitmap rather than
  a fixed value. For now, this is worked around by choosing just one of
  them.

* A devices needs to have at least one CLOCK_SOURCE descriptor which
  denotes a clockID that is needed im the class request command.

* Many descriptors (format_type, ...) have changed their layout. Handle
  this by casting the descriptors to the appropriate structs.
Signed-off-by: default avatarDaniel Mack <daniel@caiaq.de>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent 8fee4aff
......@@ -2422,17 +2422,53 @@ static int is_big_endian_format(struct snd_usb_audio *chip, struct audioformat *
* @format: the format tag (wFormatTag)
* @fmt: the format type descriptor
*/
static int parse_audio_format_i_type(struct snd_usb_audio *chip, struct audioformat *fp,
int format, void *fmt_raw)
static int parse_audio_format_i_type(struct snd_usb_audio *chip,
struct audioformat *fp,
int format, void *_fmt,
int protocol)
{
int pcm_format;
int pcm_format, i;
int sample_width, sample_bytes;
struct uac_format_type_i_discrete_descriptor *fmt = fmt_raw;
/* FIXME: correct endianess and sign? */
pcm_format = -1;
switch (protocol) {
case UAC_VERSION_1: {
struct uac_format_type_i_discrete_descriptor *fmt = _fmt;
sample_width = fmt->bBitResolution;
sample_bytes = fmt->bSubframeSize;
break;
}
case UAC_VERSION_2: {
struct uac_format_type_i_ext_descriptor *fmt = _fmt;
sample_width = fmt->bBitResolution;
sample_bytes = fmt->bSubslotSize;
/*
* FIXME
* USB audio class v2 devices specify a bitmap of possible
* audio formats rather than one fix value. For now, we just
* pick one of them and report that as the only possible
* value for this setting.
* The bit allocation map is in fact compatible to the
* wFormatTag of the v1 AS streaming descriptors, which is why
* we can simply map the matrix.
*/
for (i = 0; i < 5; i++)
if (format & (1UL << i)) {
format = i + 1;
break;
}
break;
}
default:
return -EINVAL;
}
/* FIXME: correct endianess and sign? */
pcm_format = -1;
switch (format) {
case 0: /* some devices don't define this correctly... */
......@@ -2446,6 +2482,7 @@ static int parse_audio_format_i_type(struct snd_usb_audio *chip, struct audiofor
sample_width, sample_bytes);
}
/* check the format byte size */
printk(" XXXXX SAMPLE BYTES %d\n", sample_bytes);
switch (sample_bytes) {
case 1:
pcm_format = SNDRV_PCM_FORMAT_S8;
......@@ -2500,7 +2537,7 @@ static int parse_audio_format_i_type(struct snd_usb_audio *chip, struct audiofor
/*
* parse the format descriptor and stores the possible sample rates
* on the audioformat table.
* on the audioformat table (audio class v1).
*
* @dev: usb device
* @fp: audioformat record
......@@ -2508,7 +2545,7 @@ static int parse_audio_format_i_type(struct snd_usb_audio *chip, struct audiofor
* @offset: the start offset of descriptor pointing the rate type
* (7 for type I and II, 8 for type II)
*/
static int parse_audio_format_rates(struct snd_usb_audio *chip, struct audioformat *fp,
static int parse_audio_format_rates_v1(struct snd_usb_audio *chip, struct audioformat *fp,
unsigned char *fmt, int offset)
{
int nr_rates = fmt[offset];
......@@ -2564,14 +2601,86 @@ static int parse_audio_format_rates(struct snd_usb_audio *chip, struct audioform
return 0;
}
/*
* parse the format descriptor and stores the possible sample rates
* on the audioformat table (audio class v2).
*/
static int parse_audio_format_rates_v2(struct snd_usb_audio *chip,
struct audioformat *fp,
struct usb_host_interface *iface)
{
struct usb_device *dev = chip->dev;
unsigned char tmp[2], *data;
int i, nr_rates, data_size, ret = 0;
/* get the number of sample rates first by only fetching 2 bytes */
ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), CS_RANGE,
USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
0x0100, chip->clock_id << 8, tmp, sizeof(tmp), 1000);
if (ret < 0) {
snd_printk(KERN_ERR "unable to retrieve number of sample rates\n");
goto err;
}
nr_rates = (tmp[1] << 8) | tmp[0];
data_size = 2 + 12 * nr_rates;
data = kzalloc(data_size, GFP_KERNEL);
if (!data) {
ret = -ENOMEM;
goto err;
}
/* now get the full information */
ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), CS_RANGE,
USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
0x0100, chip->clock_id << 8, data, data_size, 1000);
if (ret < 0) {
snd_printk(KERN_ERR "unable to retrieve sample rate range\n");
ret = -EINVAL;
goto err_free;
}
fp->rate_table = kmalloc(sizeof(int) * nr_rates, GFP_KERNEL);
if (!fp->rate_table) {
ret = -ENOMEM;
goto err_free;
}
fp->nr_rates = 0;
fp->rate_min = fp->rate_max = 0;
for (i = 0; i < nr_rates; i++) {
int rate = combine_quad(&data[2 + 12 * i]);
fp->rate_table[fp->nr_rates] = rate;
if (!fp->rate_min || rate < fp->rate_min)
fp->rate_min = rate;
if (!fp->rate_max || rate > fp->rate_max)
fp->rate_max = rate;
fp->rates |= snd_pcm_rate_to_rate_bit(rate);
fp->nr_rates++;
}
err_free:
kfree(data);
err:
return ret;
}
/*
* parse the format type I and III descriptors
*/
static int parse_audio_format_i(struct snd_usb_audio *chip, struct audioformat *fp,
int format, void *fmt_raw)
static int parse_audio_format_i(struct snd_usb_audio *chip,
struct audioformat *fp,
int format, void *_fmt,
struct usb_host_interface *iface)
{
int pcm_format;
struct uac_format_type_i_discrete_descriptor *fmt = fmt_raw;
struct usb_interface_descriptor *altsd = get_iface_desc(iface);
struct uac_format_type_i_discrete_descriptor *fmt = _fmt;
int protocol = altsd->bInterfaceProtocol;
int pcm_format, ret;
if (fmt->bFormatType == USB_FORMAT_TYPE_III) {
/* FIXME: the format type is really IECxxx
......@@ -2591,30 +2700,49 @@ static int parse_audio_format_i(struct snd_usb_audio *chip, struct audioformat *
pcm_format = SNDRV_PCM_FORMAT_S16_LE;
}
} else {
pcm_format = parse_audio_format_i_type(chip, fp, format, fmt);
pcm_format = parse_audio_format_i_type(chip, fp, format, fmt, protocol);
if (pcm_format < 0)
return -1;
}
fp->format = pcm_format;
/* gather possible sample rates */
/* audio class v1 reports possible sample rates as part of the
* proprietary class specific descriptor.
* audio class v2 uses class specific EP0 range requests for that.
*/
switch (protocol) {
case UAC_VERSION_1:
fp->channels = fmt->bNrChannels;
ret = parse_audio_format_rates_v1(chip, fp, _fmt, 7);
break;
case UAC_VERSION_2:
/* fp->channels is already set in this case */
ret = parse_audio_format_rates_v2(chip, fp, iface);
break;
}
if (fp->channels < 1) {
snd_printk(KERN_ERR "%d:%u:%d : invalid channels %d\n",
chip->dev->devnum, fp->iface, fp->altsetting, fp->channels);
return -1;
}
return parse_audio_format_rates(chip, fp, fmt_raw, 7);
return ret;
}
/*
* parse the format type II descriptor
*/
static int parse_audio_format_ii(struct snd_usb_audio *chip, struct audioformat *fp,
int format, void *fmt_raw)
static int parse_audio_format_ii(struct snd_usb_audio *chip,
struct audioformat *fp,
int format, void *_fmt,
struct usb_host_interface *iface)
{
int brate, framesize;
struct uac_format_type_ii_discrete_descriptor *fmt = fmt_raw;
int brate, framesize, ret;
struct usb_interface_descriptor *altsd = get_iface_desc(iface);
int protocol = altsd->bInterfaceProtocol;
switch (format) {
case USB_AUDIO_FORMAT_AC3:
......@@ -2634,35 +2762,50 @@ static int parse_audio_format_ii(struct snd_usb_audio *chip, struct audioformat
fp->channels = 1;
switch (protocol) {
case UAC_VERSION_1: {
struct uac_format_type_ii_discrete_descriptor *fmt = _fmt;
brate = le16_to_cpu(fmt->wMaxBitRate);
framesize = le16_to_cpu(fmt->wSamplesPerFrame);
snd_printd(KERN_INFO "found format II with max.bitrate = %d, frame size=%d\n", brate, framesize);
fp->frame_size = framesize;
ret = parse_audio_format_rates_v1(chip, fp, _fmt, 8); /* fmt[8..] sample rates */
break;
}
case UAC_VERSION_2: {
struct uac_format_type_ii_ext_descriptor *fmt = _fmt;
brate = le16_to_cpu(fmt->wMaxBitRate);
framesize = le16_to_cpu(fmt->wSamplesPerFrame);
snd_printd(KERN_INFO "found format II with max.bitrate = %d, frame size=%d\n", brate, framesize);
fp->frame_size = framesize;
return parse_audio_format_rates(chip, fp, fmt_raw, 8); /* fmt[8..] sample rates */
ret = parse_audio_format_rates_v2(chip, fp, iface);
break;
}
}
return ret;
}
static int parse_audio_format(struct snd_usb_audio *chip, struct audioformat *fp,
int format, void *fmt_raw, int stream)
int format, unsigned char *fmt, int stream,
struct usb_host_interface *iface)
{
int err;
/* we only parse the common header of all format types here,
* so it is safe to take a type_i struct */
struct uac_format_type_i_discrete_descriptor *fmt = fmt_raw;
switch (fmt->bFormatType) {
switch (fmt[3]) {
case USB_FORMAT_TYPE_I:
case USB_FORMAT_TYPE_III:
err = parse_audio_format_i(chip, fp, format, fmt);
err = parse_audio_format_i(chip, fp, format, fmt, iface);
break;
case USB_FORMAT_TYPE_II:
err = parse_audio_format_ii(chip, fp, format, fmt);
err = parse_audio_format_ii(chip, fp, format, fmt, iface);
break;
default:
snd_printd(KERN_INFO "%d:%u:%d : format type %d is not supported yet\n",
chip->dev->devnum, fp->iface, fp->altsetting, fmt->bFormatType);
chip->dev->devnum, fp->iface, fp->altsetting, fmt[3]);
return -1;
}
fp->fmt_type = fmt->bFormatType;
fp->fmt_type = fmt[3];
if (err < 0)
return err;
#if 1
......@@ -2673,7 +2816,7 @@ static int parse_audio_format(struct snd_usb_audio *chip, struct audioformat *fp
if (chip->usb_id == USB_ID(0x041e, 0x3000) ||
chip->usb_id == USB_ID(0x041e, 0x3020) ||
chip->usb_id == USB_ID(0x041e, 0x3061)) {
if (fmt->bFormatType == USB_FORMAT_TYPE_I &&
if (fmt[3] == USB_FORMAT_TYPE_I &&
fp->rates != SNDRV_PCM_RATE_48000 &&
fp->rates != SNDRV_PCM_RATE_96000)
return -1;
......@@ -2702,10 +2845,10 @@ static int parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no)
struct usb_host_interface *alts;
struct usb_interface_descriptor *altsd;
int i, altno, err, stream;
int format;
int format = 0, num_channels = 0;
struct audioformat *fp = NULL;
unsigned char *fmt, *csep;
int num;
int num, protocol;
dev = chip->dev;
......@@ -2722,10 +2865,9 @@ static int parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no)
num = 4;
for (i = 0; i < num; i++) {
struct uac_as_header_descriptor_v1 *as;
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) ||
......@@ -2750,7 +2892,10 @@ static int parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no)
continue;
/* get audio formats */
as = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, AS_GENERAL);
switch (protocol) {
case UAC_VERSION_1: {
struct uac_as_header_descriptor_v1 *as =
snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, AS_GENERAL);
if (!as) {
snd_printk(KERN_ERR "%d:%u:%d : AS_GENERAL descriptor not found\n",
......@@ -2765,6 +2910,36 @@ static int parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no)
}
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, AS_GENERAL);
if (!as) {
snd_printk(KERN_ERR "%d:%u:%d : 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 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, FORMAT_TYPE);
......@@ -2773,7 +2948,8 @@ static int parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no)
dev->devnum, iface_no, altno);
continue;
}
if (fmt[0] < 8) {
if (((protocol == UAC_VERSION_1) && (fmt[0] < 8)) ||
((protocol == UAC_VERSION_2) && (fmt[0] != 6))) {
snd_printk(KERN_ERR "%d:%u:%d : invalid FORMAT_TYPE desc\n",
dev->devnum, iface_no, altno);
continue;
......@@ -2787,6 +2963,7 @@ static int parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no)
if (fmt[4] == 1 && fmt[5] == 2 && altno == 2 && num == 3 &&
fp && fp->altsetting == 1 && fp->channels == 1 &&
fp->format == SNDRV_PCM_FORMAT_S16_LE &&
protocol == UAC_VERSION_1 &&
le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) ==
fp->maxpacksize * 2)
continue;
......@@ -2815,6 +2992,8 @@ static int parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no)
fp->ep_attr = get_endpoint(alts, 0)->bmAttributes;
fp->datainterval = 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);
......@@ -2850,7 +3029,7 @@ static int parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no)
}
/* ok, let's parse further... */
if (parse_audio_format(chip, fp, format, fmt, stream) < 0) {
if (parse_audio_format(chip, fp, format, fmt, stream, alts) < 0) {
kfree(fp->rate_table);
kfree(fp);
continue;
......@@ -2958,22 +3137,26 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif)
{
struct usb_device *dev = chip->dev;
struct usb_host_interface *host_iface;
struct uac_ac_header_descriptor_v1 *h1;
struct usb_interface_descriptor *altsd;
void *control_header;
int i;
int i, protocol;
/* find audiocontrol interface */
host_iface = &usb_ifnum_to_if(dev, ctrlif)->altsetting[0];
control_header = snd_usb_find_csint_desc(host_iface->extra,
host_iface->extralen,
NULL, HEADER);
altsd = get_iface_desc(host_iface);
protocol = altsd->bInterfaceProtocol;
if (!control_header) {
snd_printk(KERN_ERR "cannot find HEADER\n");
return -EINVAL;
}
h1 = control_header;
switch (protocol) {
case UAC_VERSION_1: {
struct uac_ac_header_descriptor_v1 *h1 = control_header;
if (!h1->bInCollection) {
snd_printk(KERN_INFO "skipping empty audio interface (v1)\n");
......@@ -2988,6 +3171,49 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif)
for (i = 0; i < h1->bInCollection; i++)
snd_usb_create_stream(chip, ctrlif, h1->baInterfaceNr[i]);
break;
}
case UAC_VERSION_2: {
struct uac_clock_source_descriptor *cs;
struct usb_interface_assoc_descriptor *assoc =
usb_ifnum_to_if(dev, ctrlif)->intf_assoc;
if (!assoc) {
snd_printk(KERN_ERR "Audio class v2 interfaces need an interface association\n");
return -EINVAL;
}
/* FIXME: for now, we expect there is at least one clock source
* descriptor and we always take the first one.
* We should properly support devices with multiple clock sources,
* clock selectors and sample rate conversion units. */
cs = snd_usb_find_csint_desc(host_iface->extra, host_iface->extralen,
NULL, CLOCK_SOURCE);
if (!cs) {
snd_printk(KERN_ERR "CLOCK_SOURCE descriptor not found\n");
return -EINVAL;
}
chip->clock_id = cs->bClockID;
for (i = 0; i < assoc->bInterfaceCount; i++) {
int intf = assoc->bFirstInterface + i;
if (intf != ctrlif)
snd_usb_create_stream(chip, ctrlif, intf);
}
break;
}
default:
snd_printk(KERN_ERR "unknown protocol version 0x%02x\n", protocol);
return -EINVAL;
}
return 0;
}
......
......@@ -142,6 +142,9 @@ struct snd_usb_audio {
int num_interfaces;
int num_suspended_intf;
/* for audio class v2 */
int clock_id;
struct list_head pcm_list; /* list of pcm streams */
int pcm_devs;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment