[alsa-devel] [PATCH v2 0/7] USB Audio Device Class 3.0 BADD profiles support
This patchset adds BADD profiles support from the USB Audio Device Class 3.0 spec [1].
BADD profile support is defined as mandatory feature of UAC3-compliant device, it should be implemented as a separate USB configuration.
Notable issue with BADD configuration is that it misses class-specific descriptors (and it's mandatory as per spec), so host should guess them from BADD profile number and parameters of endpoints (type, number of endpoints and max packet size)
This patchset adds support of all known/existing BADD profiles from the UAC3 specification.
First 5 patches are refactoring and improvements, and last 2 patches actually implement UAC3 BADD profiles support.
It's an alternative implementation comparing to [2], and doesn't build usb descriptors on the host but instead initializes alsa-usb structures with known parameters, so we don't need to keep whole class-specific descriptors in the driver since we anyway need to have BADD-specific logic.
I've picked one Jorge's UAC1 patch-improvement and updated it to v4.17 wich contais recently introduced header's sanity checks.
Remaining part is to add interrupt endpoint support so we will be able to detect jack insertion in the Headset Adapter profile.
This has been tested on ARM and x86-64 machines with custom UAC3 gadget which I'll post later to linux-usb
Comments and testing are welcome.
v2: - split refactoring patch to more atomic and bisectable changes as suggested by Takashi - renamed "Side Tone"->"Sidetone" as suggested by Andrew - revorked main BADD patch to have table lookup for checking BADD channels validity and to reduce identation as suggested by Takashi - also removed code duplication in BADD patch by reusing common part of build_feature_ctl() func
v1: http://mailman.alsa-project.org/pipermail/alsa-devel/2018-April/134412.html
[1] http://www.usb.org/developers/docs/devclass_docs/USB_Audio_v3.0.zip [2] https://www.spinics.net/lists/alsa-devel/msg71614.html
Jorge Sanjuan (1): ALSA: usb: Only get AudioControl header for UAC1 class.
Ruslan Bilovol (6): ALSA: usb: stream: move audioformat alloc/init into separate function ALSA: usb: stream: refactor uac1/2 audio interface parsing ALSA: usb: stream: refactor uac3 audio interface parsing ALSA: usb: mixer: make string parsing independent of mixer_build state include: usb: audio-v3: add BADD-specific values ALSA: usb: add UAC3 BADD profiles support
include/linux/usb/audio-v3.h | 26 ++ sound/usb/card.c | 53 ++-- sound/usb/clock.c | 9 +- sound/usb/mixer.c | 351 +++++++++++++++++++--- sound/usb/mixer_maps.c | 65 ++++ sound/usb/stream.c | 687 +++++++++++++++++++++++++------------------ sound/usb/usbaudio.h | 2 + 7 files changed, 849 insertions(+), 344 deletions(-)
Offload snd_usb_parse_audio_interface() function which became quite long after adding UAC3 spec support.
Move audioformat allocation and initialization into separate function, this will make easier future refactoring. Attributes left in the original func because it'll be used for UAC3 BADD profiles suport in the future
There is no functional change.
Signed-off-by: Ruslan Bilovol ruslan.bilovol@gmail.com --- sound/usb/stream.c | 52 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 16 deletions(-)
diff --git a/sound/usb/stream.c b/sound/usb/stream.c index 956be9f..8ec0a52 100644 --- a/sound/usb/stream.c +++ b/sound/usb/stream.c @@ -626,6 +626,37 @@ static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip, return NULL; }
+static struct audioformat * +audio_format_alloc_init(struct snd_usb_audio *chip, + struct usb_host_interface *alts, + int protocol, int iface_no, int altset_idx, + int altno, int num_channels, int clock) +{ + struct audioformat *fp; + + fp = kzalloc(sizeof(*fp), GFP_KERNEL); + if (!fp) + return NULL; + + fp->iface = iface_no; + fp->altsetting = altno; + fp->altset_idx = altset_idx; + fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress; + fp->ep_attr = get_endpoint(alts, 0)->bmAttributes; + fp->datainterval = snd_usb_parse_datainterval(chip, alts); + fp->protocol = protocol; + fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); + fp->channels = num_channels; + if (snd_usb_get_speed(chip->dev) == USB_SPEED_HIGH) + fp->maxpacksize = (((fp->maxpacksize >> 11) & 3) + 1) + * (fp->maxpacksize & 0x7ff); + fp->clock = clock; + INIT_LIST_HEAD(&fp->list); + + return fp; +} + + int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) { struct usb_device *dev; @@ -928,25 +959,14 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) continue; }
- fp = kzalloc(sizeof(*fp), GFP_KERNEL); + fp = audio_format_alloc_init(chip, alts, protocol, iface_no, i, + altno, num_channels, clock); if (!fp) 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->protocol = protocol; - fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); - 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 = parse_uac_endpoint_attributes(chip, alts, protocol, iface_no); - fp->clock = clock; - INIT_LIST_HEAD(&fp->list); + fp->attributes = parse_uac_endpoint_attributes(chip, alts, + protocol, + iface_no);
/* some quirks for attributes here */ snd_usb_audioformat_attributes_quirk(chip, fp, stream);
Offload snd_usb_parse_audio_interface() function which became quite long after adding UAC3 spec support.
Move class-specific parts of uac1/2 parsing to separate function which now produce audioformat structure that is ready to be fed to snd_usb_add_audio_stream().
This also broke Blue Microphones workaround (which relies on audioformat decoded from previous altsetting) into two parts: prepare quirk flag analyzing previous altsetting then use it with current altsetting.
Signed-off-by: Ruslan Bilovol ruslan.bilovol@gmail.com --- sound/usb/stream.c | 333 +++++++++++++++++++++++++++++------------------------ 1 file changed, 185 insertions(+), 148 deletions(-)
diff --git a/sound/usb/stream.c b/sound/usb/stream.c index 8ec0a52..3369226 100644 --- a/sound/usb/stream.c +++ b/sound/usb/stream.c @@ -656,6 +656,156 @@ static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip, return fp; }
+static struct audioformat * +snd_usb_get_audioformat_uac12(struct snd_usb_audio *chip, + struct usb_host_interface *alts, + int protocol, int iface_no, int altset_idx, + int altno, int stream, int bm_quirk) +{ + struct usb_device *dev = chip->dev; + struct uac_format_type_i_continuous_descriptor *fmt; + unsigned int num_channels = 0, chconfig = 0; + struct audioformat *fp; + int clock = 0; + u64 format; + + /* get audio formats */ + if (protocol == UAC_VERSION_1) { + struct uac1_as_header_descriptor *as = + snd_usb_find_csint_desc(alts->extra, alts->extralen, + NULL, UAC_AS_GENERAL); + struct uac_input_terminal_descriptor *iterm; + + if (!as) { + dev_err(&dev->dev, + "%u:%d : UAC_AS_GENERAL descriptor not found\n", + iface_no, altno); + return NULL; + } + + if (as->bLength < sizeof(*as)) { + dev_err(&dev->dev, + "%u:%d : invalid UAC_AS_GENERAL desc\n", + iface_no, altno); + return NULL; + } + + format = le16_to_cpu(as->wFormatTag); /* remember the format value */ + + iterm = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf, + as->bTerminalLink); + if (iterm) { + num_channels = iterm->bNrChannels; + chconfig = le16_to_cpu(iterm->wChannelConfig); + } + } else { /* UAC_VERSION_2 */ + struct uac2_input_terminal_descriptor *input_term; + struct uac2_output_terminal_descriptor *output_term; + struct uac2_as_header_descriptor *as = + snd_usb_find_csint_desc(alts->extra, alts->extralen, + NULL, UAC_AS_GENERAL); + + if (!as) { + dev_err(&dev->dev, + "%u:%d : UAC_AS_GENERAL descriptor not found\n", + iface_no, altno); + return NULL; + } + + if (as->bLength < sizeof(*as)) { + dev_err(&dev->dev, + "%u:%d : invalid UAC_AS_GENERAL desc\n", + iface_no, altno); + return NULL; + } + + num_channels = as->bNrChannels; + format = le32_to_cpu(as->bmFormats); + chconfig = le32_to_cpu(as->bmChannelConfig); + + /* + * lookup the terminal associated to this interface + * to extract the clock + */ + input_term = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf, + as->bTerminalLink); + if (input_term) { + clock = input_term->bCSourceID; + if (!chconfig && (num_channels == input_term->bNrChannels)) + chconfig = le32_to_cpu(input_term->bmChannelConfig); + goto found_clock; + } + + output_term = snd_usb_find_output_terminal_descriptor(chip->ctrl_intf, + as->bTerminalLink); + if (output_term) { + clock = output_term->bCSourceID; + goto found_clock; + } + + dev_err(&dev->dev, + "%u:%d : bogus bTerminalLink %d\n", + iface_no, altno, as->bTerminalLink); + return NULL; + } + +found_clock: + /* get format type */ + fmt = snd_usb_find_csint_desc(alts->extra, alts->extralen, + NULL, UAC_FORMAT_TYPE); + if (!fmt) { + dev_err(&dev->dev, + "%u:%d : no UAC_FORMAT_TYPE desc\n", + iface_no, altno); + return NULL; + } + if (((protocol == UAC_VERSION_1) && (fmt->bLength < 8)) + || ((protocol == UAC_VERSION_2) && + (fmt->bLength < 6))) { + dev_err(&dev->dev, + "%u:%d : invalid UAC_FORMAT_TYPE desc\n", + iface_no, altno); + return NULL; + } + + /* + * 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. + * + * Part 2: analyze quirk flag and format + */ + if (bm_quirk && fmt->bNrChannels == 1 && fmt->bSubframeSize == 2) + return NULL; + + fp = audio_format_alloc_init(chip, alts, protocol, iface_no, + altset_idx, altno, num_channels, clock); + if (!fp) + return ERR_PTR(-ENOMEM); + + fp->attributes = parse_uac_endpoint_attributes(chip, alts, protocol, + iface_no); + + /* some quirks for attributes here */ + snd_usb_audioformat_attributes_quirk(chip, fp, stream); + + /* ok, let's parse further... */ + if (snd_usb_parse_audio_format(chip, fp, format, + fmt, stream) < 0) { + kfree(fp->rate_table); + kfree(fp); + return NULL; + } + + /* Create chmap */ + if (fp->channels != num_channels) + chconfig = 0; + + fp->chmap = convert_chmap(fp->channels, chconfig, protocol); + + return fp; +}
int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) { @@ -663,14 +813,13 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) struct usb_interface *iface; struct usb_host_interface *alts; struct usb_interface_descriptor *altsd; + struct uac3_as_header_descriptor *as = NULL; int i, altno, err, stream; u64 format = 0; unsigned int num_channels = 0; struct audioformat *fp = NULL; int num, protocol, clock = 0; - struct uac_format_type_i_continuous_descriptor *fmt = NULL; struct snd_pcm_chmap_elem *chmap_v3 = NULL; - unsigned int chconfig;
dev = chip->dev;
@@ -719,98 +868,41 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) protocol <= 2) protocol = UAC_VERSION_1;
- chconfig = 0; - /* get audio formats */ switch (protocol) { default: dev_dbg(&dev->dev, "%u:%d: unknown interface protocol %#02x, assuming v1\n", iface_no, altno, protocol); protocol = UAC_VERSION_1; /* fall through */ - - case UAC_VERSION_1: { - struct uac1_as_header_descriptor *as = - snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL); - struct uac_input_terminal_descriptor *iterm; - - if (!as) { - dev_err(&dev->dev, - "%u:%d : UAC_AS_GENERAL descriptor not found\n", - iface_no, altno); - continue; - } - - if (as->bLength < sizeof(*as)) { - dev_err(&dev->dev, - "%u:%d : invalid UAC_AS_GENERAL desc\n", - iface_no, altno); - continue; - } - - format = le16_to_cpu(as->wFormatTag); /* remember the format value */ - - iterm = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf, - as->bTerminalLink); - if (iterm) { - num_channels = iterm->bNrChannels; - chconfig = le16_to_cpu(iterm->wChannelConfig); - } - - break; - } - + case UAC_VERSION_1: + /* fall through */ case UAC_VERSION_2: { - struct uac2_input_terminal_descriptor *input_term; - struct uac2_output_terminal_descriptor *output_term; - struct uac2_as_header_descriptor *as = - snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL); - - if (!as) { - dev_err(&dev->dev, - "%u:%d : UAC_AS_GENERAL descriptor not found\n", - iface_no, altno); - continue; - } - - if (as->bLength < sizeof(*as)) { - dev_err(&dev->dev, - "%u:%d : invalid UAC_AS_GENERAL desc\n", - iface_no, altno); - continue; - } + int bm_quirk = 0;
- num_channels = as->bNrChannels; - format = le32_to_cpu(as->bmFormats); - chconfig = le32_to_cpu(as->bmChannelConfig); - - /* lookup the terminal associated to this interface - * to extract the clock */ - input_term = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf, - as->bTerminalLink); - if (input_term) { - clock = input_term->bCSourceID; - if (!chconfig && (num_channels == input_term->bNrChannels)) - chconfig = le32_to_cpu(input_term->bmChannelConfig); - break; - } - - output_term = snd_usb_find_output_terminal_descriptor(chip->ctrl_intf, - as->bTerminalLink); - if (output_term) { - clock = output_term->bCSourceID; - break; - } + /* + * 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. + * + * Part 1: prepare quirk flag + */ + if (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) + bm_quirk = 1;
- dev_err(&dev->dev, - "%u:%d : bogus bTerminalLink %d\n", - iface_no, altno, as->bTerminalLink); - continue; + fp = snd_usb_get_audioformat_uac12(chip, alts, protocol, + iface_no, i, altno, + stream, bm_quirk); + break; } - case UAC_VERSION_3: { struct uac3_input_terminal_descriptor *input_term; struct uac3_output_terminal_descriptor *output_term; - struct uac3_as_header_descriptor *as; struct uac3_cluster_header_descriptor *cluster; struct uac3_hc_descriptor_header hc_header; u16 cluster_id, wLength; @@ -923,40 +1015,12 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) }
if (protocol == UAC_VERSION_1 || protocol == UAC_VERSION_2) { - /* get format type */ - fmt = snd_usb_find_csint_desc(alts->extra, - alts->extralen, - NULL, UAC_FORMAT_TYPE); - if (!fmt) { - dev_err(&dev->dev, - "%u:%d : no UAC_FORMAT_TYPE desc\n", - iface_no, altno); - continue; - } - if (((protocol == UAC_VERSION_1) && (fmt->bLength < 8)) - || ((protocol == UAC_VERSION_2) && - (fmt->bLength < 6))) { - dev_err(&dev->dev, - "%u:%d : invalid UAC_FORMAT_TYPE desc\n", - iface_no, altno); + if (!fp) continue; - } + else if (IS_ERR(fp)) + return PTR_ERR(fp);
- /* - * 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->bNrChannels == 1 && - fmt->bSubframeSize == 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; + goto skip_uac3; }
fp = audio_format_alloc_init(chip, alts, protocol, iface_no, i, @@ -967,45 +1031,18 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) fp->attributes = parse_uac_endpoint_attributes(chip, alts, protocol, iface_no); - - /* some quirks for attributes here */ - snd_usb_audioformat_attributes_quirk(chip, fp, stream); + fp->chmap = chmap_v3;
/* ok, let's parse further... */ - if (protocol == UAC_VERSION_1 || protocol == UAC_VERSION_2) { - if (snd_usb_parse_audio_format(chip, fp, format, - fmt, stream) < 0) { - kfree(fp->rate_table); - kfree(fp); - fp = NULL; - continue; - } - } else { - struct uac3_as_header_descriptor *as; - - as = snd_usb_find_csint_desc(alts->extra, - alts->extralen, - NULL, UAC_AS_GENERAL); - - if (snd_usb_parse_audio_format_v3(chip, fp, as, - stream) < 0) { - kfree(fp->rate_table); - kfree(fp); - fp = NULL; - continue; - } + if (snd_usb_parse_audio_format_v3(chip, fp, as, + stream) < 0) { + kfree(fp->rate_table); + kfree(fp); + fp = NULL; + continue; }
- /* Create chmap */ - if (fp->channels != num_channels) - chconfig = 0; - - if (protocol == UAC_VERSION_3) - fp->chmap = chmap_v3; - else - fp->chmap = convert_chmap(fp->channels, chconfig, - protocol); - +skip_uac3: dev_dbg(&dev->dev, "%u:%d: add audio endpoint %#x\n", iface_no, altno, fp->endpoint); err = snd_usb_add_audio_stream(chip, stream, fp); if (err < 0) {
Offload snd_usb_parse_audio_interface() function which became quite long after adding UAC3 spec support.
Move class-specific parts of uac3 parsing to separate function which now produce audioformat structure that is ready to be fed to snd_usb_add_audio_stream().
Signed-off-by: Ruslan Bilovol ruslan.bilovol@gmail.com --- sound/usb/stream.c | 289 +++++++++++++++++++++++++++-------------------------- 1 file changed, 146 insertions(+), 143 deletions(-)
diff --git a/sound/usb/stream.c b/sound/usb/stream.c index 3369226..764be07 100644 --- a/sound/usb/stream.c +++ b/sound/usb/stream.c @@ -807,19 +807,154 @@ static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip, return fp; }
+static struct audioformat * +snd_usb_get_audioformat_uac3(struct snd_usb_audio *chip, + struct usb_host_interface *alts, + int iface_no, int altset_idx, + int altno, int stream) +{ + struct usb_device *dev = chip->dev; + struct uac3_input_terminal_descriptor *input_term; + struct uac3_output_terminal_descriptor *output_term; + struct uac3_cluster_header_descriptor *cluster; + struct uac3_as_header_descriptor *as; + struct uac3_hc_descriptor_header hc_header; + struct snd_pcm_chmap_elem *chmap; + unsigned int num_channels; + struct audioformat *fp; + u16 cluster_id, wLength; + int clock = 0; + int err; + + as = snd_usb_find_csint_desc(alts->extra, alts->extralen, + NULL, UAC_AS_GENERAL); + if (!as) { + dev_err(&dev->dev, + "%u:%d : UAC_AS_GENERAL descriptor not found\n", + iface_no, altno); + return NULL; + } + + if (as->bLength < sizeof(*as)) { + dev_err(&dev->dev, + "%u:%d : invalid UAC_AS_GENERAL desc\n", + iface_no, altno); + return NULL; + } + + cluster_id = le16_to_cpu(as->wClusterDescrID); + if (!cluster_id) { + dev_err(&dev->dev, + "%u:%d : no cluster descriptor\n", + iface_no, altno); + return NULL; + } + + /* + * Get number of channels and channel map through + * High Capability Cluster Descriptor + * + * First step: get High Capability header and + * read size of Cluster Descriptor + */ + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), + UAC3_CS_REQ_HIGH_CAPABILITY_DESCRIPTOR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + cluster_id, + snd_usb_ctrl_intf(chip), + &hc_header, sizeof(hc_header)); + if (err < 0) + return ERR_PTR(err); + else if (err != sizeof(hc_header)) { + dev_err(&dev->dev, + "%u:%d : can't get High Capability descriptor\n", + iface_no, altno); + return ERR_PTR(-EIO); + } + + /* + * Second step: allocate needed amount of memory + * and request Cluster Descriptor + */ + wLength = le16_to_cpu(hc_header.wLength); + cluster = kzalloc(wLength, GFP_KERNEL); + if (!cluster) + return ERR_PTR(-ENOMEM); + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), + UAC3_CS_REQ_HIGH_CAPABILITY_DESCRIPTOR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + cluster_id, + snd_usb_ctrl_intf(chip), + cluster, wLength); + if (err < 0) { + kfree(cluster); + return ERR_PTR(err); + } else if (err != wLength) { + dev_err(&dev->dev, + "%u:%d : can't get Cluster Descriptor\n", + iface_no, altno); + kfree(cluster); + return ERR_PTR(-EIO); + } + + num_channels = cluster->bNrChannels; + chmap = convert_chmap_v3(cluster); + kfree(cluster); + + /* + * lookup the terminal associated to this interface + * to extract the clock + */ + input_term = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf, + as->bTerminalLink); + if (input_term) { + clock = input_term->bCSourceID; + goto found_clock; + } + + output_term = snd_usb_find_output_terminal_descriptor(chip->ctrl_intf, + as->bTerminalLink); + if (output_term) { + clock = output_term->bCSourceID; + goto found_clock; + } + + dev_err(&dev->dev, "%u:%d : bogus bTerminalLink %d\n", + iface_no, altno, as->bTerminalLink); + return NULL; + +found_clock: + fp = audio_format_alloc_init(chip, alts, UAC_VERSION_3, iface_no, + altset_idx, altno, num_channels, clock); + if (!fp) + return ERR_PTR(-ENOMEM); + + fp->attributes = parse_uac_endpoint_attributes(chip, alts, + UAC_VERSION_3, + iface_no); + fp->chmap = chmap; + + /* ok, let's parse further... */ + if (snd_usb_parse_audio_format_v3(chip, fp, as, stream) < 0) { + kfree(fp->rate_table); + kfree(fp); + return NULL; + } + + return fp; +} + int snd_usb_parse_audio_interface(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; - struct uac3_as_header_descriptor *as = NULL; int i, altno, err, stream; - u64 format = 0; - unsigned int num_channels = 0; struct audioformat *fp = NULL; - int num, protocol, clock = 0; - struct snd_pcm_chmap_elem *chmap_v3 = NULL; + int num, protocol;
dev = chip->dev;
@@ -900,149 +1035,17 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) stream, bm_quirk); break; } - case UAC_VERSION_3: { - struct uac3_input_terminal_descriptor *input_term; - struct uac3_output_terminal_descriptor *output_term; - struct uac3_cluster_header_descriptor *cluster; - struct uac3_hc_descriptor_header hc_header; - u16 cluster_id, wLength; - - as = snd_usb_find_csint_desc(alts->extra, - alts->extralen, - NULL, UAC_AS_GENERAL); - - if (!as) { - dev_err(&dev->dev, - "%u:%d : UAC_AS_GENERAL descriptor not found\n", - iface_no, altno); - continue; - } - - if (as->bLength < sizeof(*as)) { - dev_err(&dev->dev, - "%u:%d : invalid UAC_AS_GENERAL desc\n", - iface_no, altno); - continue; - } - - cluster_id = le16_to_cpu(as->wClusterDescrID); - if (!cluster_id) { - dev_err(&dev->dev, - "%u:%d : no cluster descriptor\n", - iface_no, altno); - continue; - } - - /* - * Get number of channels and channel map through - * High Capability Cluster Descriptor - * - * First step: get High Capability header and - * read size of Cluster Descriptor - */ - err = snd_usb_ctl_msg(chip->dev, - usb_rcvctrlpipe(chip->dev, 0), - UAC3_CS_REQ_HIGH_CAPABILITY_DESCRIPTOR, - USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, - cluster_id, - snd_usb_ctrl_intf(chip), - &hc_header, sizeof(hc_header)); - if (err < 0) - return err; - else if (err != sizeof(hc_header)) { - dev_err(&dev->dev, - "%u:%d : can't get High Capability descriptor\n", - iface_no, altno); - return -EIO; - } - - /* - * Second step: allocate needed amount of memory - * and request Cluster Descriptor - */ - wLength = le16_to_cpu(hc_header.wLength); - cluster = kzalloc(wLength, GFP_KERNEL); - if (!cluster) - return -ENOMEM; - err = snd_usb_ctl_msg(chip->dev, - usb_rcvctrlpipe(chip->dev, 0), - UAC3_CS_REQ_HIGH_CAPABILITY_DESCRIPTOR, - USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, - cluster_id, - snd_usb_ctrl_intf(chip), - cluster, wLength); - if (err < 0) { - kfree(cluster); - return err; - } else if (err != wLength) { - dev_err(&dev->dev, - "%u:%d : can't get Cluster Descriptor\n", - iface_no, altno); - kfree(cluster); - return -EIO; - } - - num_channels = cluster->bNrChannels; - chmap_v3 = convert_chmap_v3(cluster); - - kfree(cluster); - - format = le64_to_cpu(as->bmFormats); - - /* lookup the terminal associated to this interface - * to extract the clock */ - input_term = snd_usb_find_input_terminal_descriptor( - chip->ctrl_intf, - as->bTerminalLink); - - if (input_term) { - clock = input_term->bCSourceID; - break; - } - - output_term = snd_usb_find_output_terminal_descriptor(chip->ctrl_intf, - as->bTerminalLink); - if (output_term) { - clock = output_term->bCSourceID; - break; - } - - dev_err(&dev->dev, - "%u:%d : bogus bTerminalLink %d\n", - iface_no, altno, as->bTerminalLink); - continue; - } - } - - if (protocol == UAC_VERSION_1 || protocol == UAC_VERSION_2) { - if (!fp) - continue; - else if (IS_ERR(fp)) - return PTR_ERR(fp); - - goto skip_uac3; + case UAC_VERSION_3: + fp = snd_usb_get_audioformat_uac3(chip, alts, + iface_no, i, altno, stream); + break; }
- fp = audio_format_alloc_init(chip, alts, protocol, iface_no, i, - altno, num_channels, clock); if (!fp) - return -ENOMEM; - - fp->attributes = parse_uac_endpoint_attributes(chip, alts, - protocol, - iface_no); - fp->chmap = chmap_v3; - - /* ok, let's parse further... */ - if (snd_usb_parse_audio_format_v3(chip, fp, as, - stream) < 0) { - kfree(fp->rate_table); - kfree(fp); - fp = NULL; continue; - } + else if (IS_ERR(fp)) + return PTR_ERR(fp);
-skip_uac3: dev_dbg(&dev->dev, "%u:%d: add audio endpoint %#x\n", iface_no, altno, fp->endpoint); err = snd_usb_add_audio_stream(chip, stream, fp); if (err < 0) {
From: Jorge Sanjuan jorge.sanjuan@codethink.co.uk
The control header needs to be read from buffer at this point only in the case of UAC1 protocol. Move it inside the switch case as other protocols such as the Basic Audio Device spec will have an empty buffer that is latter filled as inferred.
Signed-off-by: Jorge Sanjuan jorge.sanjuan@codethink.co.uk [Ruslan: updated with recently added sanity checks] Signed-off-by: Ruslan Bilovol ruslan.bilovol@gmail.com --- sound/usb/card.c | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-)
diff --git a/sound/usb/card.c b/sound/usb/card.c index 36c289b..0d7a5d7 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -221,32 +221,13 @@ 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 usb_interface_descriptor *altsd; - void *control_header; int i, protocol; - int rest_bytes;
/* 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, UAC_HEADER); altsd = get_iface_desc(host_iface); protocol = altsd->bInterfaceProtocol;
- if (!control_header) { - dev_err(&dev->dev, "cannot find UAC_HEADER\n"); - return -EINVAL; - } - - rest_bytes = (void *)(host_iface->extra + host_iface->extralen) - - control_header; - - /* just to be sure -- this shouldn't hit at all */ - if (rest_bytes <= 0) { - dev_err(&dev->dev, "invalid control header\n"); - return -EINVAL; - } - switch (protocol) { default: dev_warn(&dev->dev, @@ -255,7 +236,25 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif) /* fall through */
case UAC_VERSION_1: { - struct uac1_ac_header_descriptor *h1 = control_header; + struct uac1_ac_header_descriptor *h1; + int rest_bytes; + + h1 = snd_usb_find_csint_desc(host_iface->extra, + host_iface->extralen, + NULL, UAC_HEADER); + if (!h1) { + dev_err(&dev->dev, "cannot find UAC_HEADER\n"); + return -EINVAL; + } + + rest_bytes = (void *)(host_iface->extra + + host_iface->extralen) - (void *)h1; + + /* just to be sure -- this shouldn't hit at all */ + if (rest_bytes <= 0) { + dev_err(&dev->dev, "invalid control header\n"); + return -EINVAL; + }
if (rest_bytes < sizeof(*h1)) { dev_err(&dev->dev, "too short v1 buffer descriptor\n");
Functions like snd_usb_copy_string_desc() or get_term_name() don't actually need mixer_build state but can use snd_usb_audio structure instead to get usb device.
This patch has no functional change but prepares to future UAC3 BADD profiles support which don't have class-specific descriptors so won't have mixer_build state.
Signed-off-by: Ruslan Bilovol ruslan.bilovol@gmail.com --- sound/usb/mixer.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-)
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index bb203b3..e280354 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -201,10 +201,10 @@ static void *find_audio_control_unit(struct mixer_build *state, /* * copy a string with the given id */ -static int snd_usb_copy_string_desc(struct mixer_build *state, +static int snd_usb_copy_string_desc(struct snd_usb_audio *chip, int index, char *buf, int maxlen) { - int len = usb_string(state->chip->dev, index, buf, maxlen - 1); + int len = usb_string(chip->dev, index, buf, maxlen - 1);
if (len < 0) return 0; @@ -658,14 +658,14 @@ int snd_usb_mixer_add_control(struct usb_mixer_elem_list *list, { 0 }, };
-static int get_term_name(struct mixer_build *state, struct usb_audio_term *iterm, +static int get_term_name(struct snd_usb_audio *chip, struct usb_audio_term *iterm, unsigned char *name, int maxlen, int term_only) { struct iterm_name_combo *names; int len;
if (iterm->name) { - len = snd_usb_copy_string_desc(state, iterm->name, + len = snd_usb_copy_string_desc(chip, iterm->name, name, maxlen); if (len) return len; @@ -1407,7 +1407,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); mapped_name = len != 0; if (!len && nameid) - len = snd_usb_copy_string_desc(state, nameid, + len = snd_usb_copy_string_desc(state->chip, nameid, kctl->id.name, sizeof(kctl->id.name));
switch (control) { @@ -1422,10 +1422,10 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, * - otherwise, anonymous name. */ if (!len) { - len = get_term_name(state, iterm, kctl->id.name, + len = get_term_name(state->chip, iterm, kctl->id.name, sizeof(kctl->id.name), 1); if (!len) - len = get_term_name(state, &state->oterm, + len = get_term_name(state->chip, &state->oterm, kctl->id.name, sizeof(kctl->id.name), 1); if (!len) @@ -1498,7 +1498,7 @@ static void get_connector_control_name(struct mixer_build *state, struct usb_audio_term *term, bool is_input, char *name, int name_size) { - int name_len = get_term_name(state, term, name, name_size, 0); + int name_len = get_term_name(state->chip, term, name, name_size, 0);
if (name_len == 0) strlcpy(name, "Unknown", name_size); @@ -1597,7 +1597,7 @@ static int parse_clock_source_unit(struct mixer_build *state, int unitid, }
kctl->private_free = snd_usb_mixer_elem_free; - ret = snd_usb_copy_string_desc(state, hdr->iClockSource, + ret = snd_usb_copy_string_desc(state->chip, hdr->iClockSource, name, sizeof(name)); if (ret > 0) snprintf(kctl->id.name, sizeof(kctl->id.name), @@ -1840,7 +1840,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state,
len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); if (!len) - len = get_term_name(state, iterm, kctl->id.name, + len = get_term_name(state->chip, iterm, kctl->id.name, sizeof(kctl->id.name), 0); if (!len) len = sprintf(kctl->id.name, "Mixer Source %d", in_ch + 1); @@ -2154,7 +2154,8 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, nameid = uac_processing_unit_iProcessing(desc, state->mixer->protocol); len = 0; if (nameid) - len = snd_usb_copy_string_desc(state, nameid, + len = snd_usb_copy_string_desc(state->chip, + nameid, kctl->id.name, sizeof(kctl->id.name)); if (!len) @@ -2350,7 +2351,8 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, len = check_mapped_selector_name(state, unitid, i, namelist[i], MAX_ITEM_NAME_LEN); if (! len && check_input_term(state, desc->baSourceID[i], &iterm) >= 0) - len = get_term_name(state, &iterm, namelist[i], MAX_ITEM_NAME_LEN, 0); + len = get_term_name(state->chip, &iterm, namelist[i], + MAX_ITEM_NAME_LEN, 0); if (! len) sprintf(namelist[i], "Input %u", i); } @@ -2372,12 +2374,12 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, /* if iSelector is given, use it */ nameid = uac_selector_unit_iSelector(desc); if (nameid) - len = snd_usb_copy_string_desc(state, nameid, + len = snd_usb_copy_string_desc(state->chip, nameid, kctl->id.name, sizeof(kctl->id.name)); /* ... or pick up the terminal name at next */ if (!len) - len = get_term_name(state, &state->oterm, + len = get_term_name(state->chip, &state->oterm, kctl->id.name, sizeof(kctl->id.name), 0); /* ... or use the fixed string "USB" as the last resort */ if (!len)
Add BADD-specific predefined values to audio-v3 so usb-audio in ALSA and UAC3 gadget can use them
Signed-off-by: Ruslan Bilovol ruslan.bilovol@gmail.com --- include/linux/usb/audio-v3.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+)
diff --git a/include/linux/usb/audio-v3.h b/include/linux/usb/audio-v3.h index a8959aa..38add1d 100644 --- a/include/linux/usb/audio-v3.h +++ b/include/linux/usb/audio-v3.h @@ -392,4 +392,30 @@ struct uac3_interrupt_data_msg { #define UAC3_AC_ACTIVE_INTERFACE_CONTROL 0x01 #define UAC3_AC_POWER_DOMAIN_CONTROL 0x02
+/* BADD predefined Unit/Terminal values */ +#define UAC3_BADD_IT_ID1 1 /* Input Terminal ID1: bTerminalID = 1 */ +#define UAC3_BADD_FU_ID2 2 /* Feature Unit ID2: bUnitID = 2 */ +#define UAC3_BADD_OT_ID3 3 /* Output Terminal ID3: bTerminalID = 3 */ +#define UAC3_BADD_IT_ID4 4 /* Input Terminal ID4: bTerminalID = 4 */ +#define UAC3_BADD_FU_ID5 5 /* Feature Unit ID5: bUnitID = 5 */ +#define UAC3_BADD_OT_ID6 6 /* Output Terminal ID6: bTerminalID = 6 */ +#define UAC3_BADD_FU_ID7 7 /* Feature Unit ID7: bUnitID = 7 */ +#define UAC3_BADD_MU_ID8 8 /* Mixer Unit ID8: bUnitID = 8 */ +#define UAC3_BADD_CS_ID9 9 /* Clock Source Entity ID9: bClockID = 9 */ +#define UAC3_BADD_PD_ID10 10 /* Power Domain ID10: bPowerDomainID = 10 */ +#define UAC3_BADD_PD_ID11 11 /* Power Domain ID11: bPowerDomainID = 11 */ + +/* BADD wMaxPacketSize of AS endpoints */ +#define UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_16 0x0060 +#define UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_16 0x0062 +#define UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_24 0x0090 +#define UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_24 0x0093 +#define UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_16 0x00C0 +#define UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_16 0x00C4 +#define UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_24 0x0120 +#define UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_24 0x0126 + +/* BADD sample rate is always fixed to 48kHz */ +#define UAC3_BADD_SAMPLING_RATE 48000 + #endif /* __LINUX_USB_AUDIO_V3_H */
Recently released USB Audio Class 3.0 specification contains BADD (Basic Audio Device Definition) document which describes pre-defined UAC3 configurations.
BADD support is mandatory for UAC3 devices, it should be implemented as a separate USB device configuration. As per BADD document, class-specific descriptors shall not be included in the Device’s Configuration descriptor ("inferred"), but host can guess them from BADD profile number, number of endpoints and their max packed sizes.
This patch adds support of all BADD profiles from the spec
Signed-off-by: Ruslan Bilovol ruslan.bilovol@gmail.com --- sound/usb/card.c | 14 +++ sound/usb/clock.c | 9 +- sound/usb/mixer.c | 327 ++++++++++++++++++++++++++++++++++++++++++++----- sound/usb/mixer_maps.c | 65 ++++++++++ sound/usb/stream.c | 83 +++++++++++-- sound/usb/usbaudio.h | 2 + 6 files changed, 459 insertions(+), 41 deletions(-)
diff --git a/sound/usb/card.c b/sound/usb/card.c index 0d7a5d7..f6c3c1c 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -307,6 +307,20 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif) return -EINVAL; }
+ if (protocol == UAC_VERSION_3) { + int badd = assoc->bFunctionSubClass; + + if (badd != UAC3_FUNCTION_SUBCLASS_FULL_ADC_3_0 && + (badd < UAC3_FUNCTION_SUBCLASS_GENERIC_IO || + badd > UAC3_FUNCTION_SUBCLASS_SPEAKERPHONE)) { + dev_err(&dev->dev, + "Unsupported UAC3 BADD profile\n"); + return -EINVAL; + } + + chip->badd_profile = badd; + } + for (i = 0; i < assoc->bInterfaceCount; i++) { int intf = assoc->bFirstInterface + i;
diff --git a/sound/usb/clock.c b/sound/usb/clock.c index 0b030d8..17673f3 100644 --- a/sound/usb/clock.c +++ b/sound/usb/clock.c @@ -587,8 +587,15 @@ int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface, default: return set_sample_rate_v1(chip, iface, alts, fmt, rate);
- case UAC_VERSION_2: case UAC_VERSION_3: + if (chip->badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) { + if (rate != UAC3_BADD_SAMPLING_RATE) + return -ENXIO; + else + return 0; + } + /* fall through */ + case UAC_VERSION_2: return set_sample_rate_v2v3(chip, iface, alts, fmt, rate); } } diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index e280354..d98bc3f 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -112,14 +112,12 @@ enum { #include "mixer_maps.c"
static const struct usbmix_name_map * -find_map(struct mixer_build *state, int unitid, int control) +find_map(const struct usbmix_name_map *p, int unitid, int control) { - const struct usbmix_name_map *p = state->map; - if (!p) return NULL;
- for (p = state->map; p->id; p++) { + for (; p->id; p++) { if (p->id == unitid && (!control || !p->control || control == p->control)) return p; @@ -1333,16 +1331,16 @@ static struct usb_feature_control_info *get_feature_control_info(int control) return NULL; }
-static void build_feature_ctl(struct mixer_build *state, void *raw_desc, - unsigned int ctl_mask, int control, - struct usb_audio_term *iterm, int unitid, - int readonly_mask) +static void __build_feature_ctl(struct usb_mixer_interface *mixer, + const struct usbmix_name_map *imap, + unsigned int ctl_mask, int control, + struct usb_audio_term *iterm, + struct usb_audio_term *oterm, + int unitid, int nameid, int readonly_mask) { - struct uac_feature_unit_descriptor *desc = raw_desc; struct usb_feature_control_info *ctl_info; unsigned int len = 0; int mapped_name = 0; - int nameid = uac_feature_unit_iFeature(desc); struct snd_kcontrol *kctl; struct usb_mixer_elem_info *cval; const struct usbmix_name_map *map; @@ -1353,14 +1351,14 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, return; }
- map = find_map(state, unitid, control); + map = find_map(imap, unitid, control); if (check_ignored_ctl(map)) return;
cval = kzalloc(sizeof(*cval), GFP_KERNEL); if (!cval) return; - snd_usb_mixer_elem_init_std(&cval->head, state->mixer, unitid); + snd_usb_mixer_elem_init_std(&cval->head, mixer, unitid); cval->control = control; cval->cmask = ctl_mask;
@@ -1369,7 +1367,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, kfree(cval); return; } - if (state->mixer->protocol == UAC_VERSION_1) + if (mixer->protocol == UAC_VERSION_1) cval->val_type = ctl_info->type; else /* UAC_VERSION_2 */ cval->val_type = ctl_info->type_uac2 >= 0 ? @@ -1398,7 +1396,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval);
if (!kctl) { - usb_audio_err(state->chip, "cannot malloc kcontrol\n"); + usb_audio_err(mixer->chip, "cannot malloc kcontrol\n"); kfree(cval); return; } @@ -1407,7 +1405,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); mapped_name = len != 0; if (!len && nameid) - len = snd_usb_copy_string_desc(state->chip, nameid, + len = snd_usb_copy_string_desc(mixer->chip, nameid, kctl->id.name, sizeof(kctl->id.name));
switch (control) { @@ -1422,10 +1420,12 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, * - otherwise, anonymous name. */ if (!len) { - len = get_term_name(state->chip, iterm, kctl->id.name, - sizeof(kctl->id.name), 1); - if (!len) - len = get_term_name(state->chip, &state->oterm, + if (iterm) + len = get_term_name(mixer->chip, iterm, + kctl->id.name, + sizeof(kctl->id.name), 1); + if (!len && oterm) + len = get_term_name(mixer->chip, oterm, kctl->id.name, sizeof(kctl->id.name), 1); if (!len) @@ -1434,15 +1434,15 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, }
if (!mapped_name) - check_no_speaker_on_headset(kctl, state->mixer->chip->card); + check_no_speaker_on_headset(kctl, mixer->chip->card);
/* * determine the stream direction: * if the connected output is USB stream, then it's likely a * capture stream. otherwise it should be playback (hopefully :) */ - if (!mapped_name && !(state->oterm.type >> 16)) { - if ((state->oterm.type & 0xff00) == 0x0100) + if (!mapped_name && oterm && !(oterm->type >> 16)) { + if ((oterm->type & 0xff00) == 0x0100) append_ctl_name(kctl, " Capture"); else append_ctl_name(kctl, " Playback"); @@ -1470,7 +1470,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, } }
- snd_usb_mixer_fu_apply_quirk(state->mixer, cval, unitid, kctl); + snd_usb_mixer_fu_apply_quirk(mixer, cval, unitid, kctl);
range = (cval->max - cval->min) / cval->res; /* @@ -1479,21 +1479,41 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, * devices. It will definitively catch all buggy Logitech devices. */ if (range > 384) { - usb_audio_warn(state->chip, + usb_audio_warn(mixer->chip, "Warning! Unlikely big volume range (=%u), cval->res is probably wrong.", range); - usb_audio_warn(state->chip, + usb_audio_warn(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d", cval->head.id, kctl->id.name, cval->channels, cval->min, cval->max, cval->res); }
- usb_audio_dbg(state->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n", + usb_audio_dbg(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n", cval->head.id, kctl->id.name, cval->channels, cval->min, cval->max, cval->res); snd_usb_mixer_add_control(&cval->head, kctl); }
+static void build_feature_ctl(struct mixer_build *state, void *raw_desc, + unsigned int ctl_mask, int control, + struct usb_audio_term *iterm, int unitid, + int readonly_mask) +{ + struct uac_feature_unit_descriptor *desc = raw_desc; + int nameid = uac_feature_unit_iFeature(desc); + + __build_feature_ctl(state->mixer, state->map, ctl_mask, control, + iterm, &state->oterm, unitid, nameid, readonly_mask); +} + +static void build_feature_ctl_badd(struct usb_mixer_interface *mixer, + unsigned int ctl_mask, int control, int unitid, + const struct usbmix_name_map *badd_map) +{ + __build_feature_ctl(mixer, badd_map, ctl_mask, control, + NULL, NULL, unitid, 0, 0); +} + static void get_connector_control_name(struct mixer_build *state, struct usb_audio_term *term, bool is_input, char *name, int name_size) @@ -1807,7 +1827,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state, struct snd_kcontrol *kctl; const struct usbmix_name_map *map;
- map = find_map(state, unitid, 0); + map = find_map(state->map, unitid, 0); if (check_ignored_ctl(map)) return;
@@ -2106,7 +2126,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid,
if (!(controls[valinfo->control / 8] & (1 << ((valinfo->control % 8) - 1)))) continue; - map = find_map(state, unitid, valinfo->control); + map = find_map(state->map, unitid, valinfo->control); if (check_ignored_ctl(map)) continue; cval = kzalloc(sizeof(*cval), GFP_KERNEL); @@ -2310,7 +2330,7 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, if (desc->bNrInPins == 1) /* only one ? nonsense! */ return 0;
- map = find_map(state, unitid, 0); + map = find_map(state->map, unitid, 0); if (check_ignored_ctl(map)) return 0;
@@ -2497,6 +2517,246 @@ static int snd_usb_mixer_dev_free(struct snd_device *device) return 0; }
+/* UAC3 predefined channels configuration */ +struct uac3_badd_profile { + int subclass; + const char *name; + int c_chmask; /* capture channels mask */ + int p_chmask; /* playback channels mask */ + int st_chmask; /* side tone mixing channel mask */ +}; + +static struct uac3_badd_profile uac3_badd_profiles[] = { + { + /* + * BAIF, BAOF or combination of both + * IN: Mono or Stereo cfg, Mono alt possible + * OUT: Mono or Stereo cfg, Mono alt possible + */ + .subclass = UAC3_FUNCTION_SUBCLASS_GENERIC_IO, + .name = "GENERIC IO", + .c_chmask = -1, /* dynamic channels */ + .p_chmask = -1, /* dynamic channels */ + }, + { + /* BAOF; Stereo only cfg, Mono alt possible */ + .subclass = UAC3_FUNCTION_SUBCLASS_HEADPHONE, + .name = "HEADPHONE", + .p_chmask = 3, + }, + { + /* BAOF; Mono or Stereo cfg, Mono alt possible */ + .subclass = UAC3_FUNCTION_SUBCLASS_SPEAKER, + .name = "SPEAKER", + .p_chmask = -1, /* dynamic channels */ + }, + { + /* BAIF; Mono or Stereo cfg, Mono alt possible */ + .subclass = UAC3_FUNCTION_SUBCLASS_MICROPHONE, + .name = "MICROPHONE", + .c_chmask = -1, /* dynamic channels */ + }, + { + /* + * BAIOF topology + * IN: Mono only + * OUT: Mono or Stereo cfg, Mono alt possible + */ + .subclass = UAC3_FUNCTION_SUBCLASS_HEADSET, + .name = "HEADSET", + .c_chmask = 1, + .p_chmask = -1, /* dynamic channels */ + .st_chmask = 1, + }, + { + /* BAIOF; IN: Mono only; OUT: Stereo only, Mono alt possible */ + .subclass = UAC3_FUNCTION_SUBCLASS_HEADSET_ADAPTER, + .name = "HEADSET ADAPTER", + .c_chmask = 1, + .p_chmask = 3, + .st_chmask = 1, + }, + { + /* BAIF + BAOF; IN: Mono only; OUT: Mono only */ + .subclass = UAC3_FUNCTION_SUBCLASS_SPEAKERPHONE, + .name = "SPEAKERPHONE", + .c_chmask = 1, + .p_chmask = 1, + }, + { 0 } /* terminator */ +}; + +static bool uac3_badd_func_has_valid_channels(struct usb_mixer_interface *mixer, + struct uac3_badd_profile *f, + int c_chmask, int p_chmask) +{ + /* + * If both playback/capture channels are dynamic, make sure + * at least one channel is present + */ + if (f->c_chmask < 0 && f->p_chmask < 0) { + if (!c_chmask && !p_chmask) { + usb_audio_warn(mixer->chip, "BAAD %s: no channels?", + f->name); + return false; + } + return true; + } + + if ((f->c_chmask < 0 && !c_chmask) || + (f->c_chmask >= 0 && f->c_chmask != c_chmask)) { + usb_audio_warn(mixer->chip, "BAAD %s c_chmask mismatch", + f->name); + return false; + } + if ((f->p_chmask < 0 && !p_chmask) || + (f->p_chmask >= 0 && f->p_chmask != p_chmask)) { + usb_audio_warn(mixer->chip, "BAAD %s p_chmask mismatch", + f->name); + return false; + } + return true; +} + +/* + * create mixer controls for UAC3 BADD profiles + * + * UAC3 BADD device doesn't contain CS descriptors thus we will guess everything + * + * BADD device may contain Mixer Unit, which doesn't have any controls, skip it + */ +static int snd_usb_mixer_controls_badd(struct usb_mixer_interface *mixer, + int ctrlif) +{ + struct usb_device *dev = mixer->chip->dev; + struct usb_interface_assoc_descriptor *assoc; + int badd_profile = mixer->chip->badd_profile; + struct uac3_badd_profile *f; + const struct usbmix_ctl_map *map; + int p_chmask = 0, c_chmask = 0, st_chmask = 0; + int i; + + assoc = usb_ifnum_to_if(dev, ctrlif)->intf_assoc; + + /* Detect BADD capture/playback channels from AS EP descriptors */ + for (i = 0; i < assoc->bInterfaceCount; i++) { + int intf = assoc->bFirstInterface + i; + + struct usb_interface *iface; + struct usb_host_interface *alts; + struct usb_interface_descriptor *altsd; + unsigned int maxpacksize; + char dir_in; + int chmask, num; + + if (intf == ctrlif) + continue; + + iface = usb_ifnum_to_if(dev, intf); + num = iface->num_altsetting; + + if (num < 2) + return -EINVAL; + + /* + * The number of Channels in an AudioStreaming interface + * and the audio sample bit resolution (16 bits or 24 + * bits) can be derived from the wMaxPacketSize field in + * the Standard AS Audio Data Endpoint descriptor in + * Alternate Setting 1 + */ + alts = &iface->altsetting[1]; + altsd = get_iface_desc(alts); + + if (altsd->bNumEndpoints < 1) + return -EINVAL; + + /* check direction */ + dir_in = (get_endpoint(alts, 0)->bEndpointAddress & USB_DIR_IN); + maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); + + switch (maxpacksize) { + default: + usb_audio_err(mixer->chip, + "incorrect wMaxPacketSize 0x%x for BADD profile\n", + maxpacksize); + return -EINVAL; + case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_16: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_16: + case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_24: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_24: + chmask = 1; + break; + case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_16: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_16: + case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_24: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_24: + chmask = 3; + break; + } + + if (dir_in) + c_chmask = chmask; + else + p_chmask = chmask; + } + + usb_audio_dbg(mixer->chip, + "UAC3 BADD profile 0x%x: detected c_chmask=%d p_chmask=%d\n", + badd_profile, c_chmask, p_chmask); + + /* check the mapping table */ + for (map = uac3_badd_usbmix_ctl_maps; map->id; map++) { + if (map->id == badd_profile) + break; + } + + if (!map->id) + return -EINVAL; + + for (f = uac3_badd_profiles; f->name; f++) { + if (badd_profile == f->subclass) + break; + } + if (!f->name) + return -EINVAL; + if (!uac3_badd_func_has_valid_channels(mixer, f, c_chmask, p_chmask)) + return -EINVAL; + st_chmask = f->st_chmask; + + /* Playback */ + if (p_chmask) { + /* Master channel, always writable */ + build_feature_ctl_badd(mixer, 0, UAC_FU_MUTE, + UAC3_BADD_FU_ID2, map->map); + /* Mono/Stereo volume channels, always writable */ + build_feature_ctl_badd(mixer, p_chmask, UAC_FU_VOLUME, + UAC3_BADD_FU_ID2, map->map); + } + + /* Capture */ + if (c_chmask) { + /* Master channel, always writable */ + build_feature_ctl_badd(mixer, 0, UAC_FU_MUTE, + UAC3_BADD_FU_ID5, map->map); + /* Mono/Stereo volume channels, always writable */ + build_feature_ctl_badd(mixer, c_chmask, UAC_FU_VOLUME, + UAC3_BADD_FU_ID5, map->map); + } + + /* Side tone-mixing */ + if (st_chmask) { + /* Master channel, always writable */ + build_feature_ctl_badd(mixer, 0, UAC_FU_MUTE, + UAC3_BADD_FU_ID7, map->map); + /* Mono volume channel, always writable */ + build_feature_ctl_badd(mixer, 1, UAC_FU_VOLUME, + UAC3_BADD_FU_ID7, map->map); + } + + return 0; +} + /* * create mixer controls * @@ -2883,9 +3143,14 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, break; }
- if ((err = snd_usb_mixer_controls(mixer)) < 0 || - (err = snd_usb_mixer_status_create(mixer)) < 0) + if (mixer->protocol == UAC_VERSION_3 && + chip->badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) { + if ((err = snd_usb_mixer_controls_badd(mixer, ctrlif)) < 0) + goto _error; + } else if ((err = snd_usb_mixer_controls(mixer)) < 0 || + (err = snd_usb_mixer_status_create(mixer)) < 0) { goto _error; + } err = create_keep_iface_ctl(mixer); if (err < 0) goto _error; diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index eaa03ac..71069e1 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -485,3 +485,68 @@ struct usbmix_ctl_map { { 0 } /* terminator */ };
+/* + * Control map entries for UAC3 BADD profiles + */ + +static struct usbmix_name_map uac3_badd_generic_io_map[] = { + { UAC3_BADD_FU_ID2, "Generic Out Playback" }, + { UAC3_BADD_FU_ID5, "Generic In Capture" }, + { 0 } /* terminator */ +}; +static struct usbmix_name_map uac3_badd_headphone_map[] = { + { UAC3_BADD_FU_ID2, "Headphone Playback" }, + { 0 } /* terminator */ +}; +static struct usbmix_name_map uac3_badd_speaker_map[] = { + { UAC3_BADD_FU_ID2, "Speaker Playback" }, + { 0 } /* terminator */ +}; +static struct usbmix_name_map uac3_badd_microphone_map[] = { + { UAC3_BADD_FU_ID5, "Mic Capture" }, + { 0 } /* terminator */ +}; +/* Covers also 'headset adapter' profile */ +static struct usbmix_name_map uac3_badd_headset_map[] = { + { UAC3_BADD_FU_ID2, "Headset Playback" }, + { UAC3_BADD_FU_ID5, "Headset Capture" }, + { UAC3_BADD_FU_ID7, "Sidetone Mixing" }, + { 0 } /* terminator */ +}; +static struct usbmix_name_map uac3_badd_speakerphone_map[] = { + { UAC3_BADD_FU_ID2, "Speaker Playback" }, + { UAC3_BADD_FU_ID5, "Mic Capture" }, + { 0 } /* terminator */ +}; + +static struct usbmix_ctl_map uac3_badd_usbmix_ctl_maps[] = { + { + .id = UAC3_FUNCTION_SUBCLASS_GENERIC_IO, + .map = uac3_badd_generic_io_map, + }, + { + .id = UAC3_FUNCTION_SUBCLASS_HEADPHONE, + .map = uac3_badd_headphone_map, + }, + { + .id = UAC3_FUNCTION_SUBCLASS_SPEAKER, + .map = uac3_badd_speaker_map, + }, + { + .id = UAC3_FUNCTION_SUBCLASS_MICROPHONE, + .map = uac3_badd_microphone_map, + }, + { + .id = UAC3_FUNCTION_SUBCLASS_HEADSET, + .map = uac3_badd_headset_map, + }, + { + .id = UAC3_FUNCTION_SUBCLASS_HEADSET_ADAPTER, + .map = uac3_badd_headset_map, + }, + { + .id = UAC3_FUNCTION_SUBCLASS_SPEAKERPHONE, + .map = uac3_badd_speakerphone_map, + }, + { 0 } /* terminator */ +}; diff --git a/sound/usb/stream.c b/sound/usb/stream.c index 764be07..de8bbb3 100644 --- a/sound/usb/stream.c +++ b/sound/usb/stream.c @@ -817,15 +817,67 @@ static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip, struct uac3_input_terminal_descriptor *input_term; struct uac3_output_terminal_descriptor *output_term; struct uac3_cluster_header_descriptor *cluster; - struct uac3_as_header_descriptor *as; + struct uac3_as_header_descriptor *as = NULL; struct uac3_hc_descriptor_header hc_header; struct snd_pcm_chmap_elem *chmap; + unsigned char badd_profile; + u64 badd_formats = 0; unsigned int num_channels; struct audioformat *fp; u16 cluster_id, wLength; int clock = 0; int err;
+ badd_profile = chip->badd_profile; + + if (badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) { + unsigned int maxpacksize = + le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); + + switch (maxpacksize) { + default: + dev_err(&dev->dev, + "%u:%d : incorrect wMaxPacketSize for BADD profile\n", + iface_no, altno); + return NULL; + case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_16: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_16: + badd_formats = SNDRV_PCM_FMTBIT_S16_LE; + num_channels = 1; + break; + case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_24: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_24: + badd_formats = SNDRV_PCM_FMTBIT_S24_3LE; + num_channels = 1; + break; + case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_16: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_16: + badd_formats = SNDRV_PCM_FMTBIT_S16_LE; + num_channels = 2; + break; + case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_24: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_24: + badd_formats = SNDRV_PCM_FMTBIT_S24_3LE; + num_channels = 2; + break; + } + + chmap = kzalloc(sizeof(*chmap), GFP_KERNEL); + if (!chmap) + return ERR_PTR(-ENOMEM); + + if (num_channels == 1) { + chmap->map[0] = SNDRV_CHMAP_MONO; + } else { + chmap->map[0] = SNDRV_CHMAP_FL; + chmap->map[1] = SNDRV_CHMAP_FR; + } + + chmap->channels = num_channels; + clock = UAC3_BADD_CS_ID9; + goto found_clock; + } + as = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL); if (!as) { @@ -931,16 +983,29 @@ static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip, if (!fp) return ERR_PTR(-ENOMEM);
- fp->attributes = parse_uac_endpoint_attributes(chip, alts, - UAC_VERSION_3, - iface_no); fp->chmap = chmap;
- /* ok, let's parse further... */ - if (snd_usb_parse_audio_format_v3(chip, fp, as, stream) < 0) { - kfree(fp->rate_table); - kfree(fp); - return NULL; + if (badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) { + fp->attributes = 0; /* No attributes */ + + fp->fmt_type = UAC_FORMAT_TYPE_I; + fp->formats = badd_formats; + + fp->nr_rates = 0; /* SNDRV_PCM_RATE_CONTINUOUS */ + fp->rate_min = UAC3_BADD_SAMPLING_RATE; + fp->rate_max = UAC3_BADD_SAMPLING_RATE; + fp->rates = SNDRV_PCM_RATE_CONTINUOUS; + + } else { + fp->attributes = parse_uac_endpoint_attributes(chip, alts, + UAC_VERSION_3, + iface_no); + /* ok, let's parse further... */ + if (snd_usb_parse_audio_format_v3(chip, fp, as, stream) < 0) { + kfree(fp->rate_table); + kfree(fp); + return NULL; + } }
return fp; diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 1cb6b3e..7b28cbd 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -49,6 +49,8 @@ struct snd_usb_audio { int num_suspended_intf; int sample_rate_read_error;
+ int badd_profile; /* UAC3 BADD profile */ + struct list_head pcm_list; /* list of pcm streams */ struct list_head ep_list; /* list of audio-related endpoints */ int pcm_devs;
On 04/05/18 02:24, Ruslan Bilovol wrote:
Recently released USB Audio Class 3.0 specification contains BADD (Basic Audio Device Definition) document which describes pre-defined UAC3 configurations.
BADD support is mandatory for UAC3 devices, it should be implemented as a separate USB device configuration. As per BADD document, class-specific descriptors shall not be included in the Device’s Configuration descriptor ("inferred"), but host can guess them from BADD profile number, number of endpoints and their max packed sizes.
This patch adds support of all BADD profiles from the spec
Signed-off-by: Ruslan Bilovol ruslan.bilovol@gmail.com
Tested-by: Jorge Sanjuan jorge.sanjuan@codethink.co.uk
sound/usb/card.c | 14 +++ sound/usb/clock.c | 9 +- sound/usb/mixer.c | 327 ++++++++++++++++++++++++++++++++++++++++++++----- sound/usb/mixer_maps.c | 65 ++++++++++ sound/usb/stream.c | 83 +++++++++++-- sound/usb/usbaudio.h | 2 + 6 files changed, 459 insertions(+), 41 deletions(-)
diff --git a/sound/usb/card.c b/sound/usb/card.c index 0d7a5d7..f6c3c1c 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -307,6 +307,20 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif) return -EINVAL; }
if (protocol == UAC_VERSION_3) {
int badd = assoc->bFunctionSubClass;
if (badd != UAC3_FUNCTION_SUBCLASS_FULL_ADC_3_0 &&
(badd < UAC3_FUNCTION_SUBCLASS_GENERIC_IO ||
badd > UAC3_FUNCTION_SUBCLASS_SPEAKERPHONE)) {
dev_err(&dev->dev,
"Unsupported UAC3 BADD profile\n");
return -EINVAL;
}
chip->badd_profile = badd;
}
- for (i = 0; i < assoc->bInterfaceCount; i++) { int intf = assoc->bFirstInterface + i;
diff --git a/sound/usb/clock.c b/sound/usb/clock.c index 0b030d8..17673f3 100644 --- a/sound/usb/clock.c +++ b/sound/usb/clock.c @@ -587,8 +587,15 @@ int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface, default: return set_sample_rate_v1(chip, iface, alts, fmt, rate);
- case UAC_VERSION_2: case UAC_VERSION_3:
if (chip->badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) {
if (rate != UAC3_BADD_SAMPLING_RATE)
return -ENXIO;
else
return 0;
}
- /* fall through */
- case UAC_VERSION_2: return set_sample_rate_v2v3(chip, iface, alts, fmt, rate); } }
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index e280354..d98bc3f 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -112,14 +112,12 @@ enum { #include "mixer_maps.c"
static const struct usbmix_name_map * -find_map(struct mixer_build *state, int unitid, int control) +find_map(const struct usbmix_name_map *p, int unitid, int control) {
const struct usbmix_name_map *p = state->map;
if (!p) return NULL;
for (p = state->map; p->id; p++) {
- for (; p->id; p++) { if (p->id == unitid && (!control || !p->control || control == p->control)) return p;
@@ -1333,16 +1331,16 @@ static struct usb_feature_control_info *get_feature_control_info(int control) return NULL; }
-static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
unsigned int ctl_mask, int control,
struct usb_audio_term *iterm, int unitid,
int readonly_mask)
+static void __build_feature_ctl(struct usb_mixer_interface *mixer,
const struct usbmix_name_map *imap,
unsigned int ctl_mask, int control,
struct usb_audio_term *iterm,
struct usb_audio_term *oterm,
{int unitid, int nameid, int readonly_mask)
- struct uac_feature_unit_descriptor *desc = raw_desc; struct usb_feature_control_info *ctl_info; unsigned int len = 0; int mapped_name = 0;
- int nameid = uac_feature_unit_iFeature(desc); struct snd_kcontrol *kctl; struct usb_mixer_elem_info *cval; const struct usbmix_name_map *map;
@@ -1353,14 +1351,14 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, return; }
- map = find_map(state, unitid, control);
map = find_map(imap, unitid, control); if (check_ignored_ctl(map)) return;
cval = kzalloc(sizeof(*cval), GFP_KERNEL); if (!cval) return;
- snd_usb_mixer_elem_init_std(&cval->head, state->mixer, unitid);
- snd_usb_mixer_elem_init_std(&cval->head, mixer, unitid); cval->control = control; cval->cmask = ctl_mask;
@@ -1369,7 +1367,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, kfree(cval); return; }
- if (state->mixer->protocol == UAC_VERSION_1)
- if (mixer->protocol == UAC_VERSION_1) cval->val_type = ctl_info->type; else /* UAC_VERSION_2 */ cval->val_type = ctl_info->type_uac2 >= 0 ?
@@ -1398,7 +1396,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval);
if (!kctl) {
usb_audio_err(state->chip, "cannot malloc kcontrol\n");
kfree(cval); return; }usb_audio_err(mixer->chip, "cannot malloc kcontrol\n");
@@ -1407,7 +1405,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); mapped_name = len != 0; if (!len && nameid)
len = snd_usb_copy_string_desc(state->chip, nameid,
len = snd_usb_copy_string_desc(mixer->chip, nameid, kctl->id.name, sizeof(kctl->id.name));
switch (control) {
@@ -1422,10 +1420,12 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, * - otherwise, anonymous name. */ if (!len) {
len = get_term_name(state->chip, iterm, kctl->id.name,
sizeof(kctl->id.name), 1);
if (!len)
len = get_term_name(state->chip, &state->oterm,
if (iterm)
len = get_term_name(mixer->chip, iterm,
kctl->id.name,
sizeof(kctl->id.name), 1);
if (!len && oterm)
len = get_term_name(mixer->chip, oterm, kctl->id.name, sizeof(kctl->id.name), 1); if (!len)
@@ -1434,15 +1434,15 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, }
if (!mapped_name)
check_no_speaker_on_headset(kctl, state->mixer->chip->card);
check_no_speaker_on_headset(kctl, mixer->chip->card);
/*
- determine the stream direction:
- if the connected output is USB stream, then it's likely a
- capture stream. otherwise it should be playback (hopefully :)
*/
if (!mapped_name && !(state->oterm.type >> 16)) {
if ((state->oterm.type & 0xff00) == 0x0100)
if (!mapped_name && oterm && !(oterm->type >> 16)) {
if ((oterm->type & 0xff00) == 0x0100) append_ctl_name(kctl, " Capture"); else append_ctl_name(kctl, " Playback");
@@ -1470,7 +1470,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, } }
- snd_usb_mixer_fu_apply_quirk(state->mixer, cval, unitid, kctl);
snd_usb_mixer_fu_apply_quirk(mixer, cval, unitid, kctl);
range = (cval->max - cval->min) / cval->res; /*
@@ -1479,21 +1479,41 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, * devices. It will definitively catch all buggy Logitech devices. */ if (range > 384) {
usb_audio_warn(state->chip,
usb_audio_warn(mixer->chip, "Warning! Unlikely big volume range (=%u), cval->res is probably wrong.", range);
usb_audio_warn(state->chip,
}usb_audio_warn(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d", cval->head.id, kctl->id.name, cval->channels, cval->min, cval->max, cval->res);
- usb_audio_dbg(state->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n",
- usb_audio_dbg(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n", cval->head.id, kctl->id.name, cval->channels, cval->min, cval->max, cval->res); snd_usb_mixer_add_control(&cval->head, kctl); }
+static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
unsigned int ctl_mask, int control,
struct usb_audio_term *iterm, int unitid,
int readonly_mask)
+{
- struct uac_feature_unit_descriptor *desc = raw_desc;
- int nameid = uac_feature_unit_iFeature(desc);
- __build_feature_ctl(state->mixer, state->map, ctl_mask, control,
iterm, &state->oterm, unitid, nameid, readonly_mask);
+}
+static void build_feature_ctl_badd(struct usb_mixer_interface *mixer,
unsigned int ctl_mask, int control, int unitid,
const struct usbmix_name_map *badd_map)
+{
- __build_feature_ctl(mixer, badd_map, ctl_mask, control,
NULL, NULL, unitid, 0, 0);
+}
- static void get_connector_control_name(struct mixer_build *state, struct usb_audio_term *term, bool is_input, char *name, int name_size)
@@ -1807,7 +1827,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state, struct snd_kcontrol *kctl; const struct usbmix_name_map *map;
- map = find_map(state, unitid, 0);
- map = find_map(state->map, unitid, 0); if (check_ignored_ctl(map)) return;
@@ -2106,7 +2126,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid,
if (!(controls[valinfo->control / 8] & (1 << ((valinfo->control % 8) - 1)))) continue;
map = find_map(state, unitid, valinfo->control);
if (check_ignored_ctl(map)) continue; cval = kzalloc(sizeof(*cval), GFP_KERNEL);map = find_map(state->map, unitid, valinfo->control);
@@ -2310,7 +2330,7 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, if (desc->bNrInPins == 1) /* only one ? nonsense! */ return 0;
- map = find_map(state, unitid, 0);
- map = find_map(state->map, unitid, 0); if (check_ignored_ctl(map)) return 0;
@@ -2497,6 +2517,246 @@ static int snd_usb_mixer_dev_free(struct snd_device *device) return 0; }
+/* UAC3 predefined channels configuration */ +struct uac3_badd_profile {
- int subclass;
- const char *name;
- int c_chmask; /* capture channels mask */
- int p_chmask; /* playback channels mask */
- int st_chmask; /* side tone mixing channel mask */
+};
+static struct uac3_badd_profile uac3_badd_profiles[] = {
- {
/*
* BAIF, BAOF or combination of both
* IN: Mono or Stereo cfg, Mono alt possible
* OUT: Mono or Stereo cfg, Mono alt possible
*/
.subclass = UAC3_FUNCTION_SUBCLASS_GENERIC_IO,
.name = "GENERIC IO",
.c_chmask = -1, /* dynamic channels */
.p_chmask = -1, /* dynamic channels */
- },
- {
/* BAOF; Stereo only cfg, Mono alt possible */
.subclass = UAC3_FUNCTION_SUBCLASS_HEADPHONE,
.name = "HEADPHONE",
.p_chmask = 3,
- },
- {
/* BAOF; Mono or Stereo cfg, Mono alt possible */
.subclass = UAC3_FUNCTION_SUBCLASS_SPEAKER,
.name = "SPEAKER",
.p_chmask = -1, /* dynamic channels */
- },
- {
/* BAIF; Mono or Stereo cfg, Mono alt possible */
.subclass = UAC3_FUNCTION_SUBCLASS_MICROPHONE,
.name = "MICROPHONE",
.c_chmask = -1, /* dynamic channels */
- },
- {
/*
* BAIOF topology
* IN: Mono only
* OUT: Mono or Stereo cfg, Mono alt possible
*/
.subclass = UAC3_FUNCTION_SUBCLASS_HEADSET,
.name = "HEADSET",
.c_chmask = 1,
.p_chmask = -1, /* dynamic channels */
.st_chmask = 1,
- },
- {
/* BAIOF; IN: Mono only; OUT: Stereo only, Mono alt possible */
.subclass = UAC3_FUNCTION_SUBCLASS_HEADSET_ADAPTER,
.name = "HEADSET ADAPTER",
.c_chmask = 1,
.p_chmask = 3,
.st_chmask = 1,
- },
- {
/* BAIF + BAOF; IN: Mono only; OUT: Mono only */
.subclass = UAC3_FUNCTION_SUBCLASS_SPEAKERPHONE,
.name = "SPEAKERPHONE",
.c_chmask = 1,
.p_chmask = 1,
- },
- { 0 } /* terminator */
+};
+static bool uac3_badd_func_has_valid_channels(struct usb_mixer_interface *mixer,
struct uac3_badd_profile *f,
int c_chmask, int p_chmask)
+{
- /*
* If both playback/capture channels are dynamic, make sure
* at least one channel is present
*/
- if (f->c_chmask < 0 && f->p_chmask < 0) {
if (!c_chmask && !p_chmask) {
usb_audio_warn(mixer->chip, "BAAD %s: no channels?",
f->name);
return false;
}
return true;
- }
- if ((f->c_chmask < 0 && !c_chmask) ||
(f->c_chmask >= 0 && f->c_chmask != c_chmask)) {
usb_audio_warn(mixer->chip, "BAAD %s c_chmask mismatch",
f->name);
return false;
- }
- if ((f->p_chmask < 0 && !p_chmask) ||
(f->p_chmask >= 0 && f->p_chmask != p_chmask)) {
usb_audio_warn(mixer->chip, "BAAD %s p_chmask mismatch",
f->name);
return false;
- }
- return true;
+}
+/*
- create mixer controls for UAC3 BADD profiles
- UAC3 BADD device doesn't contain CS descriptors thus we will guess everything
- BADD device may contain Mixer Unit, which doesn't have any controls, skip it
- */
+static int snd_usb_mixer_controls_badd(struct usb_mixer_interface *mixer,
int ctrlif)
+{
- struct usb_device *dev = mixer->chip->dev;
- struct usb_interface_assoc_descriptor *assoc;
- int badd_profile = mixer->chip->badd_profile;
- struct uac3_badd_profile *f;
- const struct usbmix_ctl_map *map;
- int p_chmask = 0, c_chmask = 0, st_chmask = 0;
- int i;
- assoc = usb_ifnum_to_if(dev, ctrlif)->intf_assoc;
- /* Detect BADD capture/playback channels from AS EP descriptors */
- for (i = 0; i < assoc->bInterfaceCount; i++) {
int intf = assoc->bFirstInterface + i;
struct usb_interface *iface;
struct usb_host_interface *alts;
struct usb_interface_descriptor *altsd;
unsigned int maxpacksize;
char dir_in;
int chmask, num;
if (intf == ctrlif)
continue;
iface = usb_ifnum_to_if(dev, intf);
num = iface->num_altsetting;
if (num < 2)
return -EINVAL;
/*
* The number of Channels in an AudioStreaming interface
* and the audio sample bit resolution (16 bits or 24
* bits) can be derived from the wMaxPacketSize field in
* the Standard AS Audio Data Endpoint descriptor in
* Alternate Setting 1
*/
alts = &iface->altsetting[1];
altsd = get_iface_desc(alts);
if (altsd->bNumEndpoints < 1)
return -EINVAL;
/* check direction */
dir_in = (get_endpoint(alts, 0)->bEndpointAddress & USB_DIR_IN);
maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
switch (maxpacksize) {
default:
usb_audio_err(mixer->chip,
"incorrect wMaxPacketSize 0x%x for BADD profile\n",
maxpacksize);
return -EINVAL;
case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_16:
case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_16:
case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_24:
case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_24:
chmask = 1;
break;
case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_16:
case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_16:
case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_24:
case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_24:
chmask = 3;
break;
}
if (dir_in)
c_chmask = chmask;
else
p_chmask = chmask;
- }
- usb_audio_dbg(mixer->chip,
"UAC3 BADD profile 0x%x: detected c_chmask=%d p_chmask=%d\n",
badd_profile, c_chmask, p_chmask);
- /* check the mapping table */
- for (map = uac3_badd_usbmix_ctl_maps; map->id; map++) {
if (map->id == badd_profile)
break;
- }
- if (!map->id)
return -EINVAL;
- for (f = uac3_badd_profiles; f->name; f++) {
if (badd_profile == f->subclass)
break;
- }
- if (!f->name)
return -EINVAL;
- if (!uac3_badd_func_has_valid_channels(mixer, f, c_chmask, p_chmask))
return -EINVAL;
- st_chmask = f->st_chmask;
- /* Playback */
- if (p_chmask) {
/* Master channel, always writable */
build_feature_ctl_badd(mixer, 0, UAC_FU_MUTE,
UAC3_BADD_FU_ID2, map->map);
/* Mono/Stereo volume channels, always writable */
build_feature_ctl_badd(mixer, p_chmask, UAC_FU_VOLUME,
UAC3_BADD_FU_ID2, map->map);
- }
- /* Capture */
- if (c_chmask) {
/* Master channel, always writable */
build_feature_ctl_badd(mixer, 0, UAC_FU_MUTE,
UAC3_BADD_FU_ID5, map->map);
/* Mono/Stereo volume channels, always writable */
build_feature_ctl_badd(mixer, c_chmask, UAC_FU_VOLUME,
UAC3_BADD_FU_ID5, map->map);
- }
- /* Side tone-mixing */
- if (st_chmask) {
/* Master channel, always writable */
build_feature_ctl_badd(mixer, 0, UAC_FU_MUTE,
UAC3_BADD_FU_ID7, map->map);
/* Mono volume channel, always writable */
build_feature_ctl_badd(mixer, 1, UAC_FU_VOLUME,
UAC3_BADD_FU_ID7, map->map);
- }
- return 0;
+}
- /*
- create mixer controls
@@ -2883,9 +3143,14 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, break; }
- if ((err = snd_usb_mixer_controls(mixer)) < 0 ||
(err = snd_usb_mixer_status_create(mixer)) < 0)
- if (mixer->protocol == UAC_VERSION_3 &&
chip->badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) {
if ((err = snd_usb_mixer_controls_badd(mixer, ctrlif)) < 0)
goto _error;
- } else if ((err = snd_usb_mixer_controls(mixer)) < 0 ||
goto _error;(err = snd_usb_mixer_status_create(mixer)) < 0) {
- } err = create_keep_iface_ctl(mixer); if (err < 0) goto _error;
diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index eaa03ac..71069e1 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -485,3 +485,68 @@ struct usbmix_ctl_map { { 0 } /* terminator */ };
+/*
- Control map entries for UAC3 BADD profiles
- */
+static struct usbmix_name_map uac3_badd_generic_io_map[] = {
- { UAC3_BADD_FU_ID2, "Generic Out Playback" },
- { UAC3_BADD_FU_ID5, "Generic In Capture" },
- { 0 } /* terminator */
+}; +static struct usbmix_name_map uac3_badd_headphone_map[] = {
- { UAC3_BADD_FU_ID2, "Headphone Playback" },
- { 0 } /* terminator */
+}; +static struct usbmix_name_map uac3_badd_speaker_map[] = {
- { UAC3_BADD_FU_ID2, "Speaker Playback" },
- { 0 } /* terminator */
+}; +static struct usbmix_name_map uac3_badd_microphone_map[] = {
- { UAC3_BADD_FU_ID5, "Mic Capture" },
- { 0 } /* terminator */
+}; +/* Covers also 'headset adapter' profile */ +static struct usbmix_name_map uac3_badd_headset_map[] = {
- { UAC3_BADD_FU_ID2, "Headset Playback" },
- { UAC3_BADD_FU_ID5, "Headset Capture" },
- { UAC3_BADD_FU_ID7, "Sidetone Mixing" },
- { 0 } /* terminator */
+}; +static struct usbmix_name_map uac3_badd_speakerphone_map[] = {
- { UAC3_BADD_FU_ID2, "Speaker Playback" },
- { UAC3_BADD_FU_ID5, "Mic Capture" },
- { 0 } /* terminator */
+};
+static struct usbmix_ctl_map uac3_badd_usbmix_ctl_maps[] = {
- {
.id = UAC3_FUNCTION_SUBCLASS_GENERIC_IO,
.map = uac3_badd_generic_io_map,
- },
- {
.id = UAC3_FUNCTION_SUBCLASS_HEADPHONE,
.map = uac3_badd_headphone_map,
- },
- {
.id = UAC3_FUNCTION_SUBCLASS_SPEAKER,
.map = uac3_badd_speaker_map,
- },
- {
.id = UAC3_FUNCTION_SUBCLASS_MICROPHONE,
.map = uac3_badd_microphone_map,
- },
- {
.id = UAC3_FUNCTION_SUBCLASS_HEADSET,
.map = uac3_badd_headset_map,
- },
- {
.id = UAC3_FUNCTION_SUBCLASS_HEADSET_ADAPTER,
.map = uac3_badd_headset_map,
- },
- {
.id = UAC3_FUNCTION_SUBCLASS_SPEAKERPHONE,
.map = uac3_badd_speakerphone_map,
- },
- { 0 } /* terminator */
+}; diff --git a/sound/usb/stream.c b/sound/usb/stream.c index 764be07..de8bbb3 100644 --- a/sound/usb/stream.c +++ b/sound/usb/stream.c @@ -817,15 +817,67 @@ static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip, struct uac3_input_terminal_descriptor *input_term; struct uac3_output_terminal_descriptor *output_term; struct uac3_cluster_header_descriptor *cluster;
- struct uac3_as_header_descriptor *as;
struct uac3_as_header_descriptor *as = NULL; struct uac3_hc_descriptor_header hc_header; struct snd_pcm_chmap_elem *chmap;
unsigned char badd_profile;
u64 badd_formats = 0; unsigned int num_channels; struct audioformat *fp; u16 cluster_id, wLength; int clock = 0; int err;
badd_profile = chip->badd_profile;
if (badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) {
unsigned int maxpacksize =
le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
switch (maxpacksize) {
default:
dev_err(&dev->dev,
"%u:%d : incorrect wMaxPacketSize for BADD profile\n",
iface_no, altno);
return NULL;
case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_16:
case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_16:
badd_formats = SNDRV_PCM_FMTBIT_S16_LE;
num_channels = 1;
break;
case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_24:
case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_24:
badd_formats = SNDRV_PCM_FMTBIT_S24_3LE;
num_channels = 1;
break;
case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_16:
case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_16:
badd_formats = SNDRV_PCM_FMTBIT_S16_LE;
num_channels = 2;
break;
case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_24:
case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_24:
badd_formats = SNDRV_PCM_FMTBIT_S24_3LE;
num_channels = 2;
break;
}
chmap = kzalloc(sizeof(*chmap), GFP_KERNEL);
if (!chmap)
return ERR_PTR(-ENOMEM);
if (num_channels == 1) {
chmap->map[0] = SNDRV_CHMAP_MONO;
} else {
chmap->map[0] = SNDRV_CHMAP_FL;
chmap->map[1] = SNDRV_CHMAP_FR;
}
chmap->channels = num_channels;
clock = UAC3_BADD_CS_ID9;
goto found_clock;
}
as = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL); if (!as) {
@@ -931,16 +983,29 @@ static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip, if (!fp) return ERR_PTR(-ENOMEM);
fp->attributes = parse_uac_endpoint_attributes(chip, alts,
UAC_VERSION_3,
iface_no);
fp->chmap = chmap;
/* ok, let's parse further... */
if (snd_usb_parse_audio_format_v3(chip, fp, as, stream) < 0) {
kfree(fp->rate_table);
kfree(fp);
return NULL;
if (badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) {
fp->attributes = 0; /* No attributes */
fp->fmt_type = UAC_FORMAT_TYPE_I;
fp->formats = badd_formats;
fp->nr_rates = 0; /* SNDRV_PCM_RATE_CONTINUOUS */
fp->rate_min = UAC3_BADD_SAMPLING_RATE;
fp->rate_max = UAC3_BADD_SAMPLING_RATE;
fp->rates = SNDRV_PCM_RATE_CONTINUOUS;
} else {
fp->attributes = parse_uac_endpoint_attributes(chip, alts,
UAC_VERSION_3,
iface_no);
/* ok, let's parse further... */
if (snd_usb_parse_audio_format_v3(chip, fp, as, stream) < 0) {
kfree(fp->rate_table);
kfree(fp);
return NULL;
}
}
return fp;
diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 1cb6b3e..7b28cbd 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -49,6 +49,8 @@ struct snd_usb_audio { int num_suspended_intf; int sample_rate_read_error;
- int badd_profile; /* UAC3 BADD profile */
- struct list_head pcm_list; /* list of pcm streams */ struct list_head ep_list; /* list of audio-related endpoints */ int pcm_devs;
On Fri, 11 May 2018 17:36:36 +0200, Jorge wrote:
On 04/05/18 02:24, Ruslan Bilovol wrote:
Recently released USB Audio Class 3.0 specification contains BADD (Basic Audio Device Definition) document which describes pre-defined UAC3 configurations.
BADD support is mandatory for UAC3 devices, it should be implemented as a separate USB device configuration. As per BADD document, class-specific descriptors shall not be included in the Device’s Configuration descriptor ("inferred"), but host can guess them from BADD profile number, number of endpoints and their max packed sizes.
This patch adds support of all BADD profiles from the spec
Signed-off-by: Ruslan Bilovol ruslan.bilovol@gmail.com
Tested-by: Jorge Sanjuan jorge.sanjuan@codethink.co.uk
OK, I'll queue this one to for-next branch. Thanks!
Takashi
On Fri, 04 May 2018 03:23:57 +0200, Ruslan Bilovol wrote:
This patchset adds BADD profiles support from the USB Audio Device Class 3.0 spec [1].
BADD profile support is defined as mandatory feature of UAC3-compliant device, it should be implemented as a separate USB configuration.
Notable issue with BADD configuration is that it misses class-specific descriptors (and it's mandatory as per spec), so host should guess them from BADD profile number and parameters of endpoints (type, number of endpoints and max packet size)
This patchset adds support of all known/existing BADD profiles from the UAC3 specification.
First 5 patches are refactoring and improvements, and last 2 patches actually implement UAC3 BADD profiles support.
OK, these 5 look like a nice cleanup, so I'll apply them right now.
It's an alternative implementation comparing to [2], and doesn't build usb descriptors on the host but instead initializes alsa-usb structures with known parameters, so we don't need to keep whole class-specific descriptors in the driver since we anyway need to have BADD-specific logic.
For this essential one, I'd like agreements from you guys before taking it.
Through my quick glance over the code, it looks good enough, but obviously I haven't tested it at all, so it's no right judge.
As I'm going to be off in the next week from tomorrow, I'll be less responsive, but don't mind and go ahead for discussions :)
thanks,
Takashi
I've picked one Jorge's UAC1 patch-improvement and updated it to v4.17 wich contais recently introduced header's sanity checks.
Remaining part is to add interrupt endpoint support so we will be able to detect jack insertion in the Headset Adapter profile.
This has been tested on ARM and x86-64 machines with custom UAC3 gadget which I'll post later to linux-usb
Comments and testing are welcome.
v2:
- split refactoring patch to more atomic and bisectable changes as suggested by Takashi
- renamed "Side Tone"->"Sidetone" as suggested by Andrew
- revorked main BADD patch to have table lookup for checking BADD channels validity and to reduce identation as suggested by Takashi
- also removed code duplication in BADD patch by reusing common part of build_feature_ctl() func
v1: http://mailman.alsa-project.org/pipermail/alsa-devel/2018-April/134412.html
[1] http://www.usb.org/developers/docs/devclass_docs/USB_Audio_v3.0.zip [2] https://www.spinics.net/lists/alsa-devel/msg71614.html
Jorge Sanjuan (1): ALSA: usb: Only get AudioControl header for UAC1 class.
Ruslan Bilovol (6): ALSA: usb: stream: move audioformat alloc/init into separate function ALSA: usb: stream: refactor uac1/2 audio interface parsing ALSA: usb: stream: refactor uac3 audio interface parsing ALSA: usb: mixer: make string parsing independent of mixer_build state include: usb: audio-v3: add BADD-specific values ALSA: usb: add UAC3 BADD profiles support
include/linux/usb/audio-v3.h | 26 ++ sound/usb/card.c | 53 ++-- sound/usb/clock.c | 9 +- sound/usb/mixer.c | 351 +++++++++++++++++++--- sound/usb/mixer_maps.c | 65 ++++ sound/usb/stream.c | 687 +++++++++++++++++++++++++------------------ sound/usb/usbaudio.h | 2 + 7 files changed, 849 insertions(+), 344 deletions(-)
-- 1.9.1
participants (3)
-
Jorge
-
Ruslan Bilovol
-
Takashi Iwai