Received: by 10.192.165.156 with SMTP id m28csp1203580imm; Fri, 13 Apr 2018 15:26:32 -0700 (PDT) X-Google-Smtp-Source: AIpwx4+l4GMUtbK5awXmNz664VRrj6fySh3Oy36YbzXQ9RByyH77hwU7D5T7De/08gFlxZAebiFa X-Received: by 10.98.100.198 with SMTP id y189mr5196590pfb.245.1523658391996; Fri, 13 Apr 2018 15:26:31 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1523658391; cv=none; d=google.com; s=arc-20160816; b=GUNERqdqKSuSWb76hStpTNXknzw5I8pLJ9EolaU/salacH0vcLoPDF5yPfWZNit5eC aS8R8AYi/l8rrSCDgfCYDotbMoY4VaUE2QPi3wXwF7ebbmM7jAAlYmmBXlMg2zcsLUzR 4ZekEv0G0C3hZmWbB7dkF0+6ZA1wqgu28wV7nuyBcRsdE6gzN3kLhboZV4ABZ0QjS8VF uqXW8DVTdLstiNQeVEsAhEVMGKULX/HliHFhABDgdmXeukrf/EFv3FsyG4uWKm+StFIH N8XNIuzHM+uNkMAw6E7y8MYhN7OZSUyaL0adbLlc+uXNZtjpPdulIjXUXz0ZXsNo48e/ C/QA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature:arc-authentication-results; bh=/xV069D5F3+/1hhAuH2OU4twEah/nX1uIBIWRlTshHg=; b=HHbR3xBwAfI1WDYNU0a+Wfh7OWooLkxFEViGXFs14889MtVFrVzr0YH7atb/Kiq1li 12+n/E8ymwjL7XVvuKXpMVokJc2gxsj7G1iSJBZrWrocqoAAJXJcFjAyEQveDWY89n/S 6zgpHqCmlp7yF+5iwKS8f69bgTUtTKjngCCc4DzzDAnKEIAFoSo2OctvGPBTNuwGwXlc p/Hv/OvwW9tLwlhGdTeYi/VrSfr3Mzqy6fIZE34+00vM6sa+ncYIopzE2rkOfxkB4wkX AbMAcf3LQyp9+7wmK60xBWLFmdiO0W1Xl9tAmJbfhaP4hBdlq2ciZMPAp/nb/V2r9X64 Z+rg== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@gmail.com header.s=20161025 header.b=EjPu/xko; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id z8si3307941pgo.611.2018.04.13.15.26.17; Fri, 13 Apr 2018 15:26:31 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@gmail.com header.s=20161025 header.b=EjPu/xko; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752854AbeDMWZG (ORCPT + 99 others); Fri, 13 Apr 2018 18:25:06 -0400 Received: from mail-lf0-f67.google.com ([209.85.215.67]:45270 "EHLO mail-lf0-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752775AbeDMWYs (ORCPT ); Fri, 13 Apr 2018 18:24:48 -0400 Received: by mail-lf0-f67.google.com with SMTP id q5-v6so14552982lff.12 for ; Fri, 13 Apr 2018 15:24:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=/xV069D5F3+/1hhAuH2OU4twEah/nX1uIBIWRlTshHg=; b=EjPu/xko3Pf9KlieezkNa2nda/hyaSYKpChQqRr1gSdoW/ary75E1xV70MdTpQsOmJ 2qhnFfvlcVeJiMvdqoxHXMY/xestHrbpr9QMFJZRTpF0yVLmDhFFp3nA4aes62BOXHnO 0H6zS9xf8JLV4GAaFEzYRQDlalcRLUP5FhqzUUU0pU7Mjrm+ZXOHdvnObq/YLqQWIUyt 7JCcBthYsw8srN7olgMahqewdJc2vnbh5EA8slFwcQlkOacEQb9E6NNS7aXBbU3/J4SA P6rylvP7DGr4o37G9dNngIkS4AcXomg69X3fIwjKYqgOzWnofBnOTGSQFGkW0JzC5ww8 VygA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=/xV069D5F3+/1hhAuH2OU4twEah/nX1uIBIWRlTshHg=; b=kY+EF4lV9/39uzBnZXTtelz81WJetOHBCv5NIkUBKXTHqLNxi6kzdT7hvPPB0Xd1jd zj4eIcw1wD3AzYvOhGijl95leAGtiDkOm6L5X42RHgIQKoG6GBFDg1zntvwtf731kMg0 3L+o8CYHYBwQUdBx55BW4HU5DYpImafSIpQx3/k+uwe6g9ACS/ybGwaa6SUDtOPq4ZG1 686KMDeg+B3Xc2RPhGGVll3BLVMbzqgIZOec7/Tr8Mg5QQL4cFfVH3llqfpE+srU1uUU 0GPZ7s70HtMInQSX45lTR7kGOpO4qt4TG4Vvlk3Iyadbgo66ZLPqh00pNDQPDXOuM5lB q8hg== X-Gm-Message-State: ALQs6tAJIHdeyRk7biGF90n/AUKQykyk0IQkI8f8rl1piUJkFlCPNUfG CQcKJKfGu9pKOzASaEMNNzA= X-Received: by 2002:a19:b588:: with SMTP id g8-v6mr9470103lfk.90.1523658286701; Fri, 13 Apr 2018 15:24:46 -0700 (PDT) Received: from localhost ([62.216.62.149]) by smtp.gmail.com with ESMTPSA id v84-v6sm149977lfi.69.2018.04.13.15.24.45 (version=TLS1_2 cipher=AES128-SHA bits=128/128); Fri, 13 Apr 2018 15:24:46 -0700 (PDT) From: Ruslan Bilovol To: Takashi Iwai Cc: Jorge , Greg Kroah-Hartman , alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org Subject: [PATCH 4/4] ALSA: usb: add UAC3 BADD profiles support Date: Sat, 14 Apr 2018 01:24:26 +0300 Message-Id: <1523658266-2259-5-git-send-email-ruslan.bilovol@gmail.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1523658266-2259-1-git-send-email-ruslan.bilovol@gmail.com> References: <1523658266-2259-1-git-send-email-ruslan.bilovol@gmail.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org 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 --- sound/usb/card.c | 14 +++ sound/usb/clock.c | 9 +- sound/usb/mixer.c | 313 +++++++++++++++++++++++++++++++++++++++++++++++-- sound/usb/mixer_maps.c | 65 ++++++++++ sound/usb/stream.c | 83 +++++++++++-- sound/usb/usbaudio.h | 2 + 6 files changed, 466 insertions(+), 20 deletions(-) diff --git a/sound/usb/card.c b/sound/usb/card.c index 4d866bd..47ebc50 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 301ad61..e5c3b0d 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,6 +1331,76 @@ static struct usb_feature_control_info *get_feature_control_info(int control) return NULL; } +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) +{ + struct usb_feature_control_info *ctl_info; + unsigned int len = 0; + struct snd_kcontrol *kctl; + struct usb_mixer_elem_info *cval; + const struct usbmix_name_map *map; + + map = find_map(badd_map, unitid, control); + if (!map) + return; + + cval = kzalloc(sizeof(*cval), GFP_KERNEL); + if (!cval) + return; + snd_usb_mixer_elem_init_std(&cval->head, mixer, unitid); + cval->control = control; + cval->cmask = ctl_mask; + + ctl_info = get_feature_control_info(control); + if (!ctl_info) { + kfree(cval); + return; + } + cval->val_type = ctl_info->type; + + if (ctl_mask == 0) { + cval->channels = 1; /* master channel */ + } else { + int i, c = 0; + + for (i = 0; i < 2; i++) + if (ctl_mask & (1 << i)) + c++; + cval->channels = c; + } + + kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval); + + if (!kctl) { + usb_audio_err(mixer->chip, "cannot malloc kcontrol\n"); + kfree(cval); + return; + } + kctl->private_free = snd_usb_mixer_elem_free; + len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); + + append_ctl_name(kctl, control == UAC_FU_MUTE ? " Switch" : " Volume"); + + /* get min/max values */ + get_min_max_with_quirks(cval, 0, kctl); + + if (control == UAC_FU_VOLUME) { + check_mapped_dB(map, cval); + if (cval->dBmin < cval->dBmax || !cval->initialized) { + kctl->tlv.c = snd_usb_mixer_vol_tlv; + kctl->vd[0].access |= + SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; + } + } + + 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, @@ -1353,7 +1421,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, return; } - map = find_map(state, unitid, control); + map = find_map(state->map, unitid, control); if (check_ignored_ctl(map)) return; @@ -1806,7 +1874,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; @@ -2105,7 +2173,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); @@ -2308,7 +2376,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; @@ -2495,6 +2563,226 @@ static int snd_usb_mixer_dev_free(struct snd_device *device) } /* + * 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; + 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; + + if (intf != ctrlif) { + struct usb_interface *iface; + struct usb_host_interface *alts; + struct usb_interface_descriptor *altsd; + unsigned int maxpacksize; + char dir_in; + int chmask, num; + + 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) { + mixer->ignore_ctl_error = map->ignore_ctl_error; + break; + } + } + + if (!map->id) + return -EINVAL; + + switch (badd_profile) { + default: + return -EINVAL; + case UAC3_FUNCTION_SUBCLASS_GENERIC_IO: + /* + * BAIF, BAOF or combination of both + * IN: Mono or Stereo cfg, Mono alt possible + * OUT: Mono or Stereo cfg, Mono alt possible + */ + /* c_chmask := DYNAMIC */ + /* p_chmask := DYNAMIC */ + if (!c_chmask && !p_chmask) { + usb_audio_err(mixer->chip, + "BADD GENERIC_IO profile: no channels?\n"); + return -EINVAL; + } + break; + case UAC3_FUNCTION_SUBCLASS_HEADPHONE: + /* BAOF; Stereo only cfg, Mono alt possible */ + if (p_chmask != 3) + usb_audio_warn(mixer->chip, + "BADD HEADPHONE p_chmask mismatch: expected 3 actual %d\n", + p_chmask); + p_chmask = 3; + break; + case UAC3_FUNCTION_SUBCLASS_SPEAKER: + /* BAOF; Mono or Stereo cfg, Mono alt possible */ + /* p_chmask := DYNAMIC */ + if (!p_chmask) { + usb_audio_err(mixer->chip, + "BADD SPEAKER profile: no playback channels?\n"); + return -EINVAL; + } + break; + case UAC3_FUNCTION_SUBCLASS_MICROPHONE: + /* BAIF; Mono or Stereo cfg, Mono alt possible */ + /* c_chmask := DYNAMIC */ + if (!c_chmask) { + usb_audio_err(mixer->chip, + "BADD MICROPHONE profile: no capture channels?\n"); + return -EINVAL; + } + break; + case UAC3_FUNCTION_SUBCLASS_HEADSET: + /* + * BAIOF + * IN: Mono only + * OUT: Mono or Stereo cfg, Mono alt possible + */ + if (c_chmask != 1) + usb_audio_warn(mixer->chip, + "BADD HEADSET c_chmask mismatch: expected 1 actual %d\n", + c_chmask); + c_chmask = 1; + st_chmask = 1; + /* p_chmask := DYNAMIC */ + if (!p_chmask) { + usb_audio_err(mixer->chip, + "BADD HEADSET profile: no playback channels?\n"); + return -EINVAL; + } + break; + case UAC3_FUNCTION_SUBCLASS_HEADSET_ADAPTER: + /* BAIOF; IN: Mono only; OUT: Stereo only, Mono alt possible */ + if (c_chmask != 1) + usb_audio_warn(mixer->chip, + "BADD HEADSET_ADAPTER c_chmask mismatch: expected 1 actual %d\n", + c_chmask); + if (p_chmask != 3) + usb_audio_warn(mixer->chip, + "BADD HEADSET_ADAPTER p_chmask mismatch: expected 3 actual %d\n", + p_chmask); + c_chmask = 1; + st_chmask = 1; + p_chmask = 3; + break; + case UAC3_FUNCTION_SUBCLASS_SPEAKERPHONE: + /* BAIF + BAOF; IN: Mono only; OUT: Mono only */ + if (c_chmask != 1) + usb_audio_warn(mixer->chip, + "BADD SPEAKERPHONE c_chmask mismatch: expected 1 actual %d\n", + c_chmask); + if (p_chmask != 1) + usb_audio_warn(mixer->chip, + "BADD SPEAKERPHONE p_chmask mismatch: expected 1 actual %d\n", + p_chmask); + c_chmask = 1; + p_chmask = 1; + break; + } + + /* 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 * * walk through all UAC_OUTPUT_TERMINAL descriptors to search for mixers @@ -2838,9 +3126,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; + } snd_usb_mixer_apply_create_quirk(mixer); diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index 9038b2e..13f03c8 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -482,3 +482,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, "Side Tone 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 586d664..ea5a13e0 100644 --- a/sound/usb/stream.c +++ b/sound/usb/stream.c @@ -798,15 +798,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) { @@ -922,18 +974,31 @@ static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip, 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, - UAC_VERSION_3, - iface_no); fp->clock = clock; fp->chmap = chmap; INIT_LIST_HEAD(&fp->list); - /* 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 4d5c89a..1bb5e2c 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; -- 1.9.1