From patchwork Sun Jul 11 09:53:48 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Pavel Hofman X-Patchwork-Id: 473319 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.6 required=3.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id B82B7C07E96 for ; Sun, 11 Jul 2021 09:54:06 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 8A2E861279 for ; Sun, 11 Jul 2021 09:54:06 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231987AbhGKJ4v (ORCPT ); Sun, 11 Jul 2021 05:56:51 -0400 Received: from cable.insite.cz ([84.242.75.189]:47211 "EHLO cable.insite.cz" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229688AbhGKJ4t (ORCPT ); Sun, 11 Jul 2021 05:56:49 -0400 Received: from localhost (localhost [127.0.0.1]) by cable.insite.cz (Postfix) with ESMTP id AF8DAA1A3D407; Sun, 11 Jul 2021 11:54:01 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=ivitera.com; s=mail; t=1625997241; bh=vJlus4ofuCFwPzbuWq2f1FcNK+j5C7YAGdAZi7zMfgE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=GCmJcJI1ozXfUNdIuF4yRW1+2TU2n7NtzcYl1xxnpaqTzrF1tSLavu2y9QA5WDXaP GnDcEiBKMzkP47FCzIhQqDbpPFuZtdPtc4IDLatZgKuMYE6jiTHSfK784oX5++kq/z WddkKNw0liLnSFa9ikQt3lNuqAC3eZctdHY4U8Fc= Received: from cable.insite.cz ([84.242.75.189]) by localhost (server.insite.cz [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id FMjKKHqZyw7C; Sun, 11 Jul 2021 11:53:56 +0200 (CEST) Received: from precision.doma (ip28.insite.cz [81.0.237.28]) (Authenticated sender: pavel) by cable.insite.cz (Postfix) with ESMTPSA id 70247A1A3D403; Sun, 11 Jul 2021 11:53:56 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=ivitera.com; s=mail; t=1625997236; bh=vJlus4ofuCFwPzbuWq2f1FcNK+j5C7YAGdAZi7zMfgE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=N1WjCi3V+kpotew//UH8LVyhnTAIi/JHUhDZvPRBaD4XcifBTV5xB26OWrzJEn/c7 h6xQauBSZa9xpL5Mmb+2qX9rp7ZZJJ2mMtg/luejsCjutk56rvfA8eDRLNs46hZiHY 5vw9T1JjDoyA8iS93azi7P1HANPND0aPGjyt64kU= From: Pavel Hofman To: linux-usb@vger.kernel.org Cc: Pavel Hofman , Ruslan Bilovol , Felipe Balbi , Jerome Brunet Subject: [PATCH v2 1/4] usb: audio-v2: add ability to define feature unit descriptor Date: Sun, 11 Jul 2021 11:53:48 +0200 Message-Id: <20210711095351.16877-2-pavel.hofman@ivitera.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210711095351.16877-1-pavel.hofman@ivitera.com> References: <20210711095351.16877-1-pavel.hofman@ivitera.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org From: Ruslan Bilovol Similar to UAC1 spec, UAC2 feature unit descriptor has variable size. Current audio-v2 feature unit descriptor structure is used for parsing descriptors, but can't be used to define your own descriptor. Add a new macro similar to what audio v1 already has. Signed-off-by: Ruslan Bilovol Signed-off-by: Pavel Hofman --- include/linux/usb/audio-v2.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/include/linux/usb/audio-v2.h b/include/linux/usb/audio-v2.h index ead8c9a47c6a..8fc2abd7aecb 100644 --- a/include/linux/usb/audio-v2.h +++ b/include/linux/usb/audio-v2.h @@ -156,6 +156,20 @@ struct uac2_feature_unit_descriptor { __u8 bmaControls[]; /* variable length */ } __attribute__((packed)); +#define UAC2_DT_FEATURE_UNIT_SIZE(ch) (6 + ((ch) + 1) * 4) + +/* As above, but more useful for defining your own descriptors: */ +#define DECLARE_UAC2_FEATURE_UNIT_DESCRIPTOR(ch) \ +struct uac2_feature_unit_descriptor_##ch { \ + __u8 bLength; \ + __u8 bDescriptorType; \ + __u8 bDescriptorSubtype; \ + __u8 bUnitID; \ + __u8 bSourceID; \ + __le32 bmaControls[ch + 1]; \ + __u8 iFeature; \ +} __packed + /* 4.7.2.10 Effect Unit Descriptor */ struct uac2_effect_unit_descriptor { From patchwork Sun Jul 11 09:53:49 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Pavel Hofman X-Patchwork-Id: 473318 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.8 required=3.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,UNWANTED_LANGUAGE_BODY, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 4E84FC07E96 for ; Sun, 11 Jul 2021 09:54:09 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 3657C61249 for ; Sun, 11 Jul 2021 09:54:09 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232010AbhGKJ4y (ORCPT ); Sun, 11 Jul 2021 05:56:54 -0400 Received: from cable.insite.cz ([84.242.75.189]:53309 "EHLO cable.insite.cz" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231928AbhGKJ4v (ORCPT ); Sun, 11 Jul 2021 05:56:51 -0400 Received: from localhost (localhost [127.0.0.1]) by cable.insite.cz (Postfix) with ESMTP id BD34DA1A3D402; Sun, 11 Jul 2021 11:54:02 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=ivitera.com; s=mail; t=1625997242; bh=8YkhOcaqkeUQJzRE3yOHz0lGUUAJjjvd2n3ISWQTj70=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=J1PHYRJSHp4w5fOoBzAO7tI15UmJShdlYPTnrb0yCbSFrmjSRvqKGlVAF7OulmrSc DT3U0YPEn22n+xhMcviDdnFzwxR/vJRw0IFGnFOAereTBPbatuPn1A1olk1s0xkgE6 EhFpiX+kZUrTd82EwQvgN+SMAaiXwoLbrZxTDzQo= Received: from cable.insite.cz ([84.242.75.189]) by localhost (server.insite.cz [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id xKCjhzTqJsyh; Sun, 11 Jul 2021 11:53:57 +0200 (CEST) Received: from precision.doma (ip28.insite.cz [81.0.237.28]) (Authenticated sender: pavel) by cable.insite.cz (Postfix) with ESMTPSA id BC975A1A3D404; Sun, 11 Jul 2021 11:53:56 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=ivitera.com; s=mail; t=1625997236; bh=8YkhOcaqkeUQJzRE3yOHz0lGUUAJjjvd2n3ISWQTj70=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=WX73evr79oU7FwsuMmXHkLadMa51KuemxwSSYqMXbS/1vmyv9Jnqd6s+lcfEfZr18 C1wR7gDbjvy7FWn5HKfoD1M9GVQt96io1ODDXhive4C/6E652TIISa29rdJ/KRZMf/ AFhsep2rZmpb1pN9gHhr4eWZb9zhorXXGsKt8Cu0= From: Pavel Hofman To: linux-usb@vger.kernel.org Cc: Pavel Hofman , Ruslan Bilovol , Felipe Balbi , Jerome Brunet Subject: [PATCH v2 2/4] usb: gadget: u_audio: add bi-directional volume and mute support Date: Sun, 11 Jul 2021 11:53:49 +0200 Message-Id: <20210711095351.16877-3-pavel.hofman@ivitera.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210711095351.16877-1-pavel.hofman@ivitera.com> References: <20210711095351.16877-1-pavel.hofman@ivitera.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org From: Ruslan Bilovol USB Audio Class 1/2 have ability to change device's volume and mute by USB Host through class-specific control requests. Device also can notify Host about volume/mute change on its side through optional interrupt endpoint. This patch adds Volume and Mute ALSA controls which can be used by user to send and receive notifications to/from the USB Host about Volume and Mute change. These params come from f_uac* so volume and mute controls will be created only if the function support and enable each explicitly Signed-off-by: Ruslan Bilovol Signed-off-by: Pavel Hofman --- drivers/usb/gadget/function/u_audio.c | 369 +++++++++++++++++++++++++- drivers/usb/gadget/function/u_audio.h | 22 ++ 2 files changed, 380 insertions(+), 11 deletions(-) diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c index 018dd0978995..f6b5b9547236 100644 --- a/drivers/usb/gadget/function/u_audio.c +++ b/drivers/usb/gadget/function/u_audio.c @@ -12,11 +12,14 @@ * Jaswinder Singh (jaswinder.singh@linaro.org) */ +#include #include #include #include #include #include +#include +#include #include "u_audio.h" @@ -24,6 +27,12 @@ #define PRD_SIZE_MAX PAGE_SIZE #define MIN_PERIODS 4 +enum { + UAC_FBACK_CTRL, + UAC_MUTE_CTRL, + UAC_VOLUME_CTRL, +}; + /* Runtime data params for one stream */ struct uac_rtd_params { struct snd_uac_chip *uac; /* parent chip */ @@ -43,6 +52,17 @@ struct uac_rtd_params { struct usb_request *req_fback; /* Feedback endpoint request */ bool fb_ep_enabled; /* if the ep is enabled */ + + /* Volume/Mute controls and their state */ + int fu_id; /* Feature Unit ID */ + struct snd_kcontrol *snd_kctl_volume; + struct snd_kcontrol *snd_kctl_mute; + s16 volume_min, volume_max, volume_res; + s16 volume; + int mute; + + spinlock_t lock; /* lock for control transfers */ + }; struct snd_uac_chip { @@ -597,6 +617,103 @@ void u_audio_stop_playback(struct g_audio *audio_dev) } EXPORT_SYMBOL_GPL(u_audio_stop_playback); +int u_audio_get_volume(struct g_audio *audio_dev, int playback, s16 *val) +{ + struct snd_uac_chip *uac = audio_dev->uac; + struct uac_rtd_params *prm; + unsigned long flags; + + if (playback) + prm = &uac->p_prm; + else + prm = &uac->c_prm; + + spin_lock_irqsave(&prm->lock, flags); + *val = prm->volume; + spin_unlock_irqrestore(&prm->lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(u_audio_get_volume); + +int u_audio_set_volume(struct g_audio *audio_dev, int playback, s16 val) +{ + struct snd_uac_chip *uac = audio_dev->uac; + struct uac_rtd_params *prm; + unsigned long flags; + int change = 0; + + if (playback) + prm = &uac->p_prm; + else + prm = &uac->c_prm; + + spin_lock_irqsave(&prm->lock, flags); + val = clamp(val, prm->volume_min, prm->volume_max); + if (prm->volume != val) { + prm->volume = val; + change = 1; + } + spin_unlock_irqrestore(&prm->lock, flags); + + if (change) + snd_ctl_notify(uac->card, SNDRV_CTL_EVENT_MASK_VALUE, + &prm->snd_kctl_volume->id); + + return 0; +} +EXPORT_SYMBOL_GPL(u_audio_set_volume); + +int u_audio_get_mute(struct g_audio *audio_dev, int playback, int *val) +{ + struct snd_uac_chip *uac = audio_dev->uac; + struct uac_rtd_params *prm; + unsigned long flags; + + if (playback) + prm = &uac->p_prm; + else + prm = &uac->c_prm; + + spin_lock_irqsave(&prm->lock, flags); + *val = prm->mute; + spin_unlock_irqrestore(&prm->lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(u_audio_get_mute); + +int u_audio_set_mute(struct g_audio *audio_dev, int playback, int val) +{ + struct snd_uac_chip *uac = audio_dev->uac; + struct uac_rtd_params *prm; + unsigned long flags; + int change = 0; + int mute; + + if (playback) + prm = &uac->p_prm; + else + prm = &uac->c_prm; + + mute = val ? 1 : 0; + + spin_lock_irqsave(&prm->lock, flags); + if (prm->mute != mute) { + prm->mute = mute; + change = 1; + } + spin_unlock_irqrestore(&prm->lock, flags); + + if (change) + snd_ctl_notify(uac->card, SNDRV_CTL_EVENT_MASK_VALUE, + &prm->snd_kctl_mute->id); + + return 0; +} +EXPORT_SYMBOL_GPL(u_audio_set_mute); + + static int u_audio_pitch_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { @@ -656,14 +773,158 @@ static int u_audio_pitch_put(struct snd_kcontrol *kcontrol, return change; } -static const struct snd_kcontrol_new u_audio_controls[] = { +static int u_audio_mute_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) { - .iface = SNDRV_CTL_ELEM_IFACE_PCM, - .name = "Capture Pitch 1000000", - .info = u_audio_pitch_info, - .get = u_audio_pitch_get, - .put = u_audio_pitch_put, -}, + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + uinfo->value.integer.step = 1; + + return 0; +} + +static int u_audio_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&prm->lock, flags); + ucontrol->value.integer.value[0] = !prm->mute; + spin_unlock_irqrestore(&prm->lock, flags); + + return 0; +} + +static int u_audio_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + struct snd_uac_chip *uac = prm->uac; + struct g_audio *audio_dev = uac->audio_dev; + unsigned int val; + unsigned long flags; + int change = 0; + + val = !ucontrol->value.integer.value[0]; + + spin_lock_irqsave(&prm->lock, flags); + if (val != prm->mute) { + prm->mute = val; + change = 1; + } + spin_unlock_irqrestore(&prm->lock, flags); + + if (change && audio_dev->notify) + audio_dev->notify(audio_dev, prm->fu_id, UAC_FU_MUTE); + + return change; +} + +/* + * TLV callback for mixer volume controls + */ +static int u_audio_volume_tlv(struct snd_kcontrol *kcontrol, int op_flag, + unsigned int size, unsigned int __user *_tlv) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + DECLARE_TLV_DB_MINMAX(scale, 0, 0); + + if (size < sizeof(scale)) + return -ENOMEM; + + /* UAC volume resolution is 1/256 dB, TLV is 1/100 dB */ + scale[2] = (prm->volume_min * 100) / 256; + scale[3] = (prm->volume_max * 100) / 256; + if (copy_to_user(_tlv, scale, sizeof(scale))) + return -EFAULT; + + return 0; +} + +static int u_audio_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = + (prm->volume_max - prm->volume_min + prm->volume_res - 1) + / prm->volume_res; + uinfo->value.integer.step = 1; + + return 0; +} + +static int u_audio_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&prm->lock, flags); + ucontrol->value.integer.value[0] = + (prm->volume - prm->volume_min) / prm->volume_res; + spin_unlock_irqrestore(&prm->lock, flags); + + return 0; +} + +static int u_audio_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + struct snd_uac_chip *uac = prm->uac; + struct g_audio *audio_dev = uac->audio_dev; + unsigned int val; + s16 volume; + unsigned long flags; + int change = 0; + + val = ucontrol->value.integer.value[0]; + + spin_lock_irqsave(&prm->lock, flags); + volume = (val * prm->volume_res) + prm->volume_min; + volume = clamp(volume, prm->volume_min, prm->volume_max); + if (volume != prm->volume) { + prm->volume = volume; + change = 1; + } + spin_unlock_irqrestore(&prm->lock, flags); + + if (change && audio_dev->notify) + audio_dev->notify(audio_dev, prm->fu_id, UAC_FU_VOLUME); + + return change; +} + + +static struct snd_kcontrol_new u_audio_controls[] = { + [UAC_FBACK_CTRL] { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "Capture Pitch 1000000", + .info = u_audio_pitch_info, + .get = u_audio_pitch_get, + .put = u_audio_pitch_put, + }, + [UAC_MUTE_CTRL] { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", /* will be filled later */ + .info = u_audio_mute_info, + .get = u_audio_mute_get, + .put = u_audio_mute_put, + }, + [UAC_VOLUME_CTRL] { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", /* will be filled later */ + .info = u_audio_volume_info, + .get = u_audio_volume_get, + .put = u_audio_volume_put, + }, }; int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, @@ -675,7 +936,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, struct snd_kcontrol *kctl; struct uac_params *params; int p_chmask, c_chmask; - int err; + int i, err; if (!g_audio) return -EINVAL; @@ -693,7 +954,8 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, if (c_chmask) { struct uac_rtd_params *prm = &uac->c_prm; - uac->c_prm.uac = uac; + spin_lock_init(&prm->lock); + uac->c_prm.uac = uac; prm->max_psize = g_audio->out_ep_maxpsize; prm->reqs = kcalloc(params->req_number, @@ -716,6 +978,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, if (p_chmask) { struct uac_rtd_params *prm = &uac->p_prm; + spin_lock_init(&prm->lock); uac->p_prm.uac = uac; prm->max_psize = g_audio->in_ep_maxpsize; @@ -760,10 +1023,18 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac_pcm_ops); snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac_pcm_ops); - if (c_chmask && g_audio->in_ep_fback) { + /* + * Create mixer and controls + * Create only if it's required on USB side + */ + if ((c_chmask && g_audio->in_ep_fback) + || (p_chmask && params->p_fu.id) + || (c_chmask && params->c_fu.id)) strscpy(card->mixername, card_name, sizeof(card->driver)); - kctl = snd_ctl_new1(&u_audio_controls[0], &uac->c_prm); + if (c_chmask && g_audio->in_ep_fback) { + kctl = snd_ctl_new1(&u_audio_controls[UAC_FBACK_CTRL], + &uac->c_prm); if (!kctl) { err = -ENOMEM; goto snd_fail; @@ -777,6 +1048,82 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, goto snd_fail; } + for (i = 0; i <= SNDRV_PCM_STREAM_LAST; i++) { + struct uac_rtd_params *prm; + struct uac_fu_params *fu; + char ctrl_name[24]; + char *direction; + + if (!pcm->streams[i].substream_count) + continue; + + if (i == SNDRV_PCM_STREAM_PLAYBACK) { + prm = &uac->p_prm; + fu = ¶ms->p_fu; + direction = "Playback"; + } else { + prm = &uac->c_prm; + fu = ¶ms->c_fu; + direction = "Capture"; + } + + prm->fu_id = fu->id; + + if (fu->mute_present) { + snprintf(ctrl_name, sizeof(ctrl_name), + "PCM %s Switch", direction); + + u_audio_controls[UAC_MUTE_CTRL].name = ctrl_name; + + kctl = snd_ctl_new1(&u_audio_controls[UAC_MUTE_CTRL], + prm); + if (!kctl) { + err = -ENOMEM; + goto snd_fail; + } + + kctl->id.device = pcm->device; + kctl->id.subdevice = i; + + err = snd_ctl_add(card, kctl); + if (err < 0) + goto snd_fail; + prm->snd_kctl_mute = kctl; + prm->mute = 0; + } + + if (fu->volume_present) { + snprintf(ctrl_name, sizeof(ctrl_name), + "PCM %s Volume", direction); + + u_audio_controls[UAC_VOLUME_CTRL].name = ctrl_name; + + kctl = snd_ctl_new1(&u_audio_controls[UAC_VOLUME_CTRL], + prm); + if (!kctl) { + err = -ENOMEM; + goto snd_fail; + } + + kctl->id.device = pcm->device; + kctl->id.subdevice = i; + + + kctl->tlv.c = u_audio_volume_tlv; + kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; + + err = snd_ctl_add(card, kctl); + if (err < 0) + goto snd_fail; + prm->snd_kctl_volume = kctl; + prm->volume = fu->volume_max; + prm->volume_max = fu->volume_max; + prm->volume_min = fu->volume_min; + prm->volume_res = fu->volume_res; + } + } + strscpy(card->driver, card_name, sizeof(card->driver)); strscpy(card->shortname, card_name, sizeof(card->shortname)); sprintf(card->longname, "%s %i", card_name, card->dev->id); diff --git a/drivers/usb/gadget/function/u_audio.h b/drivers/usb/gadget/function/u_audio.h index a218cdf771fe..001a79a46022 100644 --- a/drivers/usb/gadget/function/u_audio.h +++ b/drivers/usb/gadget/function/u_audio.h @@ -19,16 +19,30 @@ */ #define FBACK_SLOW_MAX 250 +/* Feature Unit parameters */ +struct uac_fu_params { + int id; /* Feature Unit ID */ + + bool mute_present; /* mute control enable */ + + bool volume_present; /* volume control enable */ + s16 volume_min; /* min volume in 1/256 dB */ + s16 volume_max; /* max volume in 1/256 dB */ + s16 volume_res; /* volume resolution in 1/256 dB */ +}; + struct uac_params { /* playback */ int p_chmask; /* channel mask */ int p_srate; /* rate in Hz */ int p_ssize; /* sample size */ + struct uac_fu_params p_fu; /* Feature Unit parameters */ /* capture */ int c_chmask; /* channel mask */ int c_srate; /* rate in Hz */ int c_ssize; /* sample size */ + struct uac_fu_params c_fu; /* Feature Unit parameters */ int req_number; /* number of preallocated requests */ int fb_max; /* upper frequency drift feedback limit per-mil */ @@ -49,6 +63,9 @@ struct g_audio { /* Max packet size for all out_ep possible speeds */ unsigned int out_ep_maxpsize; + /* Notify UAC driver about control change */ + int (*notify)(struct g_audio *g_audio, int unit_id, int cs); + /* The ALSA Sound Card it represents on the USB-Client side */ struct snd_uac_chip *uac; @@ -94,4 +111,9 @@ void u_audio_stop_capture(struct g_audio *g_audio); int u_audio_start_playback(struct g_audio *g_audio); void u_audio_stop_playback(struct g_audio *g_audio); +int u_audio_get_volume(struct g_audio *g_audio, int playback, s16 *val); +int u_audio_set_volume(struct g_audio *g_audio, int playback, s16 val); +int u_audio_get_mute(struct g_audio *g_audio, int playback, int *val); +int u_audio_set_mute(struct g_audio *g_audio, int playback, int val); + #endif /* __U_AUDIO_H */ From patchwork Sun Jul 11 09:53:50 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Pavel Hofman X-Patchwork-Id: 473317 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.6 required=3.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 32650C07E9E for ; Sun, 11 Jul 2021 09:54:11 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 1D58B61351 for ; Sun, 11 Jul 2021 09:54:11 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232038AbhGKJ4z (ORCPT ); Sun, 11 Jul 2021 05:56:55 -0400 Received: from cable.insite.cz ([84.242.75.189]:55908 "EHLO cable.insite.cz" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229688AbhGKJ4w (ORCPT ); Sun, 11 Jul 2021 05:56:52 -0400 Received: from localhost (localhost [127.0.0.1]) by cable.insite.cz (Postfix) with ESMTP id 8F5BBA1A3D403; Sun, 11 Jul 2021 11:54:04 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=ivitera.com; s=mail; t=1625997244; bh=2kDczKluZc6HMBHx4qi1A3f0ESxHRHOdQp+qxeXzzD4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=E67i5BnWuSSMwivB8JSWfPZeCKlrrtLqrfp9+Az8LVUXEnU9J0HZy4k/8FS0nKKFQ KVX6fl6jK128gLllBxCPnnZUvBQaLyFe0Q6hHxRRqBqKn3fR8IVqYeSvV8Y5xhMomO B5m/v25PtpZbGFeurbdddoPLwngW3Q+eYxE3TQDk= Received: from cable.insite.cz ([84.242.75.189]) by localhost (server.insite.cz [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id iwhuZgWoVjTM; Sun, 11 Jul 2021 11:53:57 +0200 (CEST) Received: from precision.doma (ip28.insite.cz [81.0.237.28]) (Authenticated sender: pavel) by cable.insite.cz (Postfix) with ESMTPSA id 16AF4A1A3D405; Sun, 11 Jul 2021 11:53:57 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=ivitera.com; s=mail; t=1625997237; bh=2kDczKluZc6HMBHx4qi1A3f0ESxHRHOdQp+qxeXzzD4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=HkUcDBd8di/5obMTWxvAPah2NVKvICma8Phi3WMgQb+lAdMkJP1YSx9MN3IFB7g3U +8XotjiG195g6kb+r9N7qMAKTq3OVaKTq5nkupjHZYHZAL7CePyW4VOz32upR51Wv/ qxzfDEl2W4+qZ+Lmg0P1TB6QGgvsz9cX0fbkfnjk= From: Pavel Hofman To: linux-usb@vger.kernel.org Cc: Pavel Hofman , Ruslan Bilovol , Felipe Balbi , Jerome Brunet Subject: [PATCH v2 3/4] usb: gadget: f_uac2: add volume and mute support Date: Sun, 11 Jul 2021 11:53:50 +0200 Message-Id: <20210711095351.16877-4-pavel.hofman@ivitera.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210711095351.16877-1-pavel.hofman@ivitera.com> References: <20210711095351.16877-1-pavel.hofman@ivitera.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org From: Ruslan Bilovol This adds bi-directional (host->device, device->host) volume/mute support to the f_uac2 driver by adding Feature Units and interrupt endpoint. Currently only master channel is supported. Volume and mute are configurable through configfs, by default volume has -100..0 dB range with 1 dB step. Similar to existing flexible endpoints configuration, Feature Unit won't be added to the topology if both mute and volume are not enabled, also interrupt endpoint isn't added to the device if no feature unit is present Signed-off-by: Ruslan Bilovol Signed-off-by: Pavel Hofman --- .../ABI/testing/configfs-usb-gadget-uac2 | 10 + Documentation/usb/gadget-testing.rst | 12 +- drivers/usb/gadget/function/f_uac2.c | 659 ++++++++++++++++-- drivers/usb/gadget/function/u_uac2.h | 23 +- 4 files changed, 641 insertions(+), 63 deletions(-) diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac2 b/Documentation/ABI/testing/configfs-usb-gadget-uac2 index 26fb8e9b4e61..cfd160ff8b56 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-uac2 +++ b/Documentation/ABI/testing/configfs-usb-gadget-uac2 @@ -9,8 +9,18 @@ Description: c_srate capture sampling rate c_ssize capture sample size (bytes) c_sync capture synchronization type (async/adaptive) + c_mute_present capture mute control enable + c_volume_present capture volume control enable + c_volume_min capture volume control min value (in 1/256 dB) + c_volume_max capture volume control max value (in 1/256 dB) + c_volume_res capture volume control resolution (in 1/256 dB) fb_max maximum extra bandwidth in async mode p_chmask playback channel mask p_srate playback sampling rate p_ssize playback sample size (bytes) + p_mute_present playback mute control enable + p_volume_present playback volume control enable + p_volume_min playback volume control min value (in 1/256 dB) + p_volume_max playback volume control max value (in 1/256 dB) + p_volume_res playback volume control resolution (in 1/256 dB) ========= ============================ diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst index 9d6276f82774..272e51f17f54 100644 --- a/Documentation/usb/gadget-testing.rst +++ b/Documentation/usb/gadget-testing.rst @@ -729,10 +729,20 @@ The uac2 function provides these attributes in its function directory: c_srate capture sampling rate c_ssize capture sample size (bytes) c_sync capture synchronization type (async/adaptive) - fb_max maximum extra bandwidth in async mode + c_mute_present capture mute control enable + c_volume_present capture volume control enable + c_volume_min capture volume control min value (in 1/256 dB) + c_volume_max capture volume control max value (in 1/256 dB) + c_volume_res capture volume control resolution (in 1/256 dB) + fb_max maximum extra bandwidth in async mode p_chmask playback channel mask p_srate playback sampling rate p_ssize playback sample size (bytes) + p_mute_present playback mute control enable + p_volume_present playback volume control enable + p_volume_min playback volume control min value (in 1/256 dB) + p_volume_max playback volume control max value (in 1/256 dB) + p_volume_res playback volume control resolution (in 1/256 dB) req_number the number of pre-allocated request for both capture and playback =============== ==================================================== diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c index ae29ff2b2b68..0e5eef8d5456 100644 --- a/drivers/usb/gadget/function/f_uac2.c +++ b/drivers/usb/gadget/function/f_uac2.c @@ -5,6 +5,9 @@ * Copyright (C) 2011 * Yadwinder Singh (yadi.brar01@gmail.com) * Jaswinder Singh (jaswinder.singh@linaro.org) + * + * Copyright (C) 2020 + * Ruslan Bilovol (ruslan.bilovol@gmail.com) */ #include @@ -19,14 +22,16 @@ /* * The driver implements a simple UAC_2 topology. - * USB-OUT -> IT_1 -> OT_3 -> ALSA_Capture - * ALSA_Playback -> IT_2 -> OT_4 -> USB-IN + * USB-OUT -> IT_1 -> FU -> OT_3 -> ALSA_Capture + * ALSA_Playback -> IT_2 -> FU -> OT_4 -> USB-IN * Capture and Playback sampling rates are independently * controlled by two clock sources : * CLK_5 := c_srate, and CLK_6 := p_srate */ #define USB_OUT_CLK_ID (out_clk_src_desc.bClockID) #define USB_IN_CLK_ID (in_clk_src_desc.bClockID) +#define USB_OUT_FU_ID (out_feature_unit_desc->bUnitID) +#define USB_IN_FU_ID (in_feature_unit_desc->bUnitID) #define CONTROL_ABSENT 0 #define CONTROL_RDONLY 1 @@ -34,6 +39,8 @@ #define CLK_FREQ_CTRL 0 #define CLK_VLD_CTRL 2 +#define FU_MUTE_CTRL 0 +#define FU_VOL_CTRL 2 #define COPY_CTRL 0 #define CONN_CTRL 2 @@ -44,12 +51,24 @@ #define EPIN_EN(_opts) ((_opts)->p_chmask != 0) #define EPOUT_EN(_opts) ((_opts)->c_chmask != 0) +#define FUIN_EN(_opts) (EPIN_EN(_opts) \ + && ((_opts)->p_mute_present \ + || (_opts)->p_volume_present)) +#define FUOUT_EN(_opts) (EPOUT_EN(_opts) \ + && ((_opts)->c_mute_present \ + || (_opts)->c_volume_present)) #define EPOUT_FBACK_IN_EN(_opts) ((_opts)->c_sync == USB_ENDPOINT_SYNC_ASYNC) struct f_uac2 { struct g_audio g_audio; u8 ac_intf, as_in_intf, as_out_intf; u8 ac_alt, as_in_alt, as_out_alt; /* needed for get_alt() */ + + struct usb_ctrlrequest setup_cr; /* will be used in data stage */ + + /* Interrupt IN endpoint of AC interface */ + struct usb_ep *int_ep; + atomic_t int_count; }; static inline struct f_uac2 *func_to_uac2(struct usb_function *f) @@ -63,6 +82,8 @@ struct f_uac2_opts *g_audio_to_uac2_opts(struct g_audio *agdev) return container_of(agdev->func.fi, struct f_uac2_opts, func_inst); } +static int afunc_notify(struct g_audio *agdev, int unit_id, int cs); + /* --------- USB Function Interface ------------- */ enum { @@ -74,6 +95,8 @@ enum { STR_IO_IT, STR_USB_OT, STR_IO_OT, + STR_FU_IN, + STR_FU_OUT, STR_AS_OUT_ALT0, STR_AS_OUT_ALT1, STR_AS_IN_ALT0, @@ -92,6 +115,8 @@ static struct usb_string strings_fn[] = { [STR_IO_IT].s = "USBD Out", [STR_USB_OT].s = "USBH In", [STR_IO_OT].s = "USBD In", + [STR_FU_IN].s = "Capture Volume", + [STR_FU_OUT].s = "Playback Volume", [STR_AS_OUT_ALT0].s = "Playback Inactive", [STR_AS_OUT_ALT1].s = "Playback Active", [STR_AS_IN_ALT0].s = "Capture Inactive", @@ -126,7 +151,7 @@ static struct usb_interface_descriptor std_ac_if_desc = { .bDescriptorType = USB_DT_INTERFACE, .bAlternateSetting = 0, - .bNumEndpoints = 0, + /* .bNumEndpoints = DYNAMIC */ .bInterfaceClass = USB_CLASS_AUDIO, .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, .bInterfaceProtocol = UAC_VERSION_2, @@ -212,6 +237,9 @@ static struct uac2_output_terminal_descriptor io_out_ot_desc = { .bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL), }; +static struct uac2_feature_unit_descriptor *in_feature_unit_desc; +static struct uac2_feature_unit_descriptor *out_feature_unit_desc; + static struct uac2_ac_header_descriptor ac_hdr_desc = { .bLength = sizeof ac_hdr_desc, .bDescriptorType = USB_DT_CS_INTERFACE, @@ -223,6 +251,36 @@ static struct uac2_ac_header_descriptor ac_hdr_desc = { .bmControls = 0, }; +/* AC IN Interrupt Endpoint */ +static struct usb_endpoint_descriptor fs_ep_int_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(6), + .bInterval = 1, +}; + +static struct usb_endpoint_descriptor hs_ep_int_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(6), + .bInterval = 4, +}; + +static struct usb_endpoint_descriptor ss_ep_int_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(6), + .bInterval = 4, +}; + /* Audio Streaming OUT Interface - Alt0 */ static struct usb_interface_descriptor std_as_out_if0_desc = { .bLength = sizeof std_as_out_if0_desc, @@ -452,10 +510,14 @@ static struct usb_descriptor_header *fs_audio_desc[] = { (struct usb_descriptor_header *)&in_clk_src_desc, (struct usb_descriptor_header *)&out_clk_src_desc, (struct usb_descriptor_header *)&usb_out_it_desc, + (struct usb_descriptor_header *)&out_feature_unit_desc, (struct usb_descriptor_header *)&io_in_it_desc, (struct usb_descriptor_header *)&usb_in_ot_desc, + (struct usb_descriptor_header *)&in_feature_unit_desc, (struct usb_descriptor_header *)&io_out_ot_desc, + (struct usb_descriptor_header *)&fs_ep_int_desc, + (struct usb_descriptor_header *)&std_as_out_if0_desc, (struct usb_descriptor_header *)&std_as_out_if1_desc, @@ -483,10 +545,14 @@ static struct usb_descriptor_header *hs_audio_desc[] = { (struct usb_descriptor_header *)&in_clk_src_desc, (struct usb_descriptor_header *)&out_clk_src_desc, (struct usb_descriptor_header *)&usb_out_it_desc, + (struct usb_descriptor_header *)&out_feature_unit_desc, (struct usb_descriptor_header *)&io_in_it_desc, (struct usb_descriptor_header *)&usb_in_ot_desc, + (struct usb_descriptor_header *)&in_feature_unit_desc, (struct usb_descriptor_header *)&io_out_ot_desc, + (struct usb_descriptor_header *)&hs_ep_int_desc, + (struct usb_descriptor_header *)&std_as_out_if0_desc, (struct usb_descriptor_header *)&std_as_out_if1_desc, @@ -514,10 +580,14 @@ static struct usb_descriptor_header *ss_audio_desc[] = { (struct usb_descriptor_header *)&in_clk_src_desc, (struct usb_descriptor_header *)&out_clk_src_desc, (struct usb_descriptor_header *)&usb_out_it_desc, + (struct usb_descriptor_header *)&out_feature_unit_desc, (struct usb_descriptor_header *)&io_in_it_desc, (struct usb_descriptor_header *)&usb_in_ot_desc, + (struct usb_descriptor_header *)&in_feature_unit_desc, (struct usb_descriptor_header *)&io_out_ot_desc, + (struct usb_descriptor_header *)&fs_ep_int_desc, + (struct usb_descriptor_header *)&std_as_out_if0_desc, (struct usb_descriptor_header *)&std_as_out_if1_desc, @@ -539,6 +609,17 @@ static struct usb_descriptor_header *ss_audio_desc[] = { NULL, }; +struct cntrl_cur_lay2 { + __le16 wCUR; +}; + +struct cntrl_range_lay2 { + __le16 wNumSubRanges; + __le16 wMIN; + __le16 wMAX; + __le16 wRES; +} __packed; + struct cntrl_cur_lay3 { __le32 dCUR; }; @@ -595,6 +676,26 @@ static int set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, return 0; } +static struct uac2_feature_unit_descriptor *build_fu_desc(int chmask) +{ + struct uac2_feature_unit_descriptor *fu_desc; + int channels = num_channels(chmask); + int fu_desc_size = UAC2_DT_FEATURE_UNIT_SIZE(channels); + + fu_desc = kzalloc(fu_desc_size, GFP_KERNEL); + if (!fu_desc) + return NULL; + + fu_desc->bLength = fu_desc_size; + fu_desc->bDescriptorType = USB_DT_CS_INTERFACE; + + fu_desc->bDescriptorSubtype = UAC_FEATURE_UNIT; + + /* bUnitID, bSourceID and bmaControls will be defined later */ + + return fu_desc; +} + /* Use macro to overcome line length limitation */ #define USBDHDR(p) (struct usb_descriptor_header *)(p) @@ -607,6 +708,7 @@ static void setup_headers(struct f_uac2_opts *opts, struct usb_endpoint_descriptor *epout_desc; struct usb_endpoint_descriptor *epin_desc; struct usb_endpoint_descriptor *epin_fback_desc; + struct usb_endpoint_descriptor *ep_int_desc; int i; switch (speed) { @@ -614,11 +716,13 @@ static void setup_headers(struct f_uac2_opts *opts, epout_desc = &fs_epout_desc; epin_desc = &fs_epin_desc; epin_fback_desc = &fs_epin_fback_desc; + ep_int_desc = &fs_ep_int_desc; break; case USB_SPEED_HIGH: epout_desc = &hs_epout_desc; epin_desc = &hs_epin_desc; epin_fback_desc = &hs_epin_fback_desc; + ep_int_desc = &hs_ep_int_desc; break; default: epout_desc = &ss_epout_desc; @@ -626,6 +730,7 @@ static void setup_headers(struct f_uac2_opts *opts, epout_desc_comp = &ss_epout_desc_comp; epin_desc_comp = &ss_epin_desc_comp; epin_fback_desc = &ss_epin_fback_desc; + ep_int_desc = &ss_ep_int_desc; } i = 0; @@ -637,13 +742,27 @@ static void setup_headers(struct f_uac2_opts *opts, if (EPOUT_EN(opts)) { headers[i++] = USBDHDR(&out_clk_src_desc); headers[i++] = USBDHDR(&usb_out_it_desc); - } + + if (FUOUT_EN(opts)) + headers[i++] = USBDHDR(out_feature_unit_desc); + } + if (EPIN_EN(opts)) { headers[i++] = USBDHDR(&io_in_it_desc); + + if (FUIN_EN(opts)) + headers[i++] = USBDHDR(in_feature_unit_desc); + headers[i++] = USBDHDR(&usb_in_ot_desc); } - if (EPOUT_EN(opts)) { + + if (EPOUT_EN(opts)) headers[i++] = USBDHDR(&io_out_ot_desc); + + if (FUOUT_EN(opts) || FUIN_EN(opts)) + headers[i++] = USBDHDR(ep_int_desc); + + if (EPOUT_EN(opts)) { headers[i++] = USBDHDR(&std_as_out_if0_desc); headers[i++] = USBDHDR(&std_as_out_if1_desc); headers[i++] = USBDHDR(&as_out_hdr_desc); @@ -657,6 +776,7 @@ static void setup_headers(struct f_uac2_opts *opts, if (EPOUT_FBACK_IN_EN(opts)) headers[i++] = USBDHDR(epin_fback_desc); } + if (EPIN_EN(opts)) { headers[i++] = USBDHDR(&std_as_in_if0_desc); headers[i++] = USBDHDR(&std_as_in_if1_desc); @@ -684,17 +804,35 @@ static void setup_descriptor(struct f_uac2_opts *opts) io_out_ot_desc.bTerminalID = i++; if (EPIN_EN(opts)) usb_in_ot_desc.bTerminalID = i++; + if (FUOUT_EN(opts)) + out_feature_unit_desc->bUnitID = i++; + if (FUIN_EN(opts)) + in_feature_unit_desc->bUnitID = i++; if (EPOUT_EN(opts)) out_clk_src_desc.bClockID = i++; if (EPIN_EN(opts)) in_clk_src_desc.bClockID = i++; usb_out_it_desc.bCSourceID = out_clk_src_desc.bClockID; - usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID; + + if (FUIN_EN(opts)) { + usb_in_ot_desc.bSourceID = in_feature_unit_desc->bUnitID; + in_feature_unit_desc->bSourceID = io_in_it_desc.bTerminalID; + } else { + usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID; + } + usb_in_ot_desc.bCSourceID = in_clk_src_desc.bClockID; io_in_it_desc.bCSourceID = in_clk_src_desc.bClockID; io_out_ot_desc.bCSourceID = out_clk_src_desc.bClockID; - io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID; + + if (FUOUT_EN(opts)) { + io_out_ot_desc.bSourceID = out_feature_unit_desc->bUnitID; + out_feature_unit_desc->bSourceID = usb_out_it_desc.bTerminalID; + } else { + io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID; + } + as_out_hdr_desc.bTerminalLink = usb_out_it_desc.bTerminalID; as_in_hdr_desc.bTerminalLink = usb_in_ot_desc.bTerminalID; @@ -706,6 +844,10 @@ static void setup_descriptor(struct f_uac2_opts *opts) len += sizeof(in_clk_src_desc); len += sizeof(usb_in_ot_desc); + + if (FUIN_EN(opts)) + len += in_feature_unit_desc->bLength; + len += sizeof(io_in_it_desc); ac_hdr_desc.wTotalLength = cpu_to_le16(len); iad_desc.bInterfaceCount++; @@ -715,6 +857,10 @@ static void setup_descriptor(struct f_uac2_opts *opts) len += sizeof(out_clk_src_desc); len += sizeof(usb_out_it_desc); + + if (FUOUT_EN(opts)) + len += out_feature_unit_desc->bLength; + len += sizeof(io_out_ot_desc); ac_hdr_desc.wTotalLength = cpu_to_le16(len); iad_desc.bInterfaceCount++; @@ -752,6 +898,28 @@ static int afunc_validate_opts(struct g_audio *agdev, struct device *dev) return -EINVAL; } + if (opts->p_volume_max <= opts->p_volume_min) { + dev_err(dev, "Error: incorrect playback volume max/min\n"); + return -EINVAL; + } else if (opts->c_volume_max <= opts->c_volume_min) { + dev_err(dev, "Error: incorrect capture volume max/min\n"); + return -EINVAL; + } else if (opts->p_volume_res <= 0) { + dev_err(dev, "Error: negative/zero playback volume resolution\n"); + return -EINVAL; + } else if (opts->c_volume_res <= 0) { + dev_err(dev, "Error: negative/zero capture volume resolution\n"); + return -EINVAL; + } + + if ((opts->p_volume_max - opts->p_volume_min) % opts->p_volume_res) { + dev_err(dev, "Error: incorrect playback volume resolution\n"); + return -EINVAL; + } else if ((opts->c_volume_max - opts->c_volume_min) % opts->c_volume_res) { + dev_err(dev, "Error: incorrect capture volume resolution\n"); + return -EINVAL; + } + return 0; } @@ -774,6 +942,23 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) us = usb_gstrings_attach(cdev, fn_strings, ARRAY_SIZE(strings_fn)); if (IS_ERR(us)) return PTR_ERR(us); + + dev_err(dev, "%s:%d FUOUT_EN result: %d!\n", __func__, __LINE__, FUOUT_EN(uac2_opts)); + dev_err(dev, "%s:%d FUIN_EN result: %d!\n", __func__, __LINE__, FUIN_EN(uac2_opts)); + + if (FUOUT_EN(uac2_opts)) { + out_feature_unit_desc = build_fu_desc(uac2_opts->c_chmask); + if (!out_feature_unit_desc) + return -ENOMEM; + } + if (FUIN_EN(uac2_opts)) { + in_feature_unit_desc = build_fu_desc(uac2_opts->p_chmask); + if (!in_feature_unit_desc) { + ret = -ENOMEM; + goto err_free_fu; + } + } + iad_desc.iFunction = us[STR_ASSOC].id; std_ac_if_desc.iInterface = us[STR_IF_CTRL].id; in_clk_src_desc.iClockSource = us[STR_CLKSRC_IN].id; @@ -787,6 +972,21 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) std_as_in_if0_desc.iInterface = us[STR_AS_IN_ALT0].id; std_as_in_if1_desc.iInterface = us[STR_AS_IN_ALT1].id; + if (FUOUT_EN(uac2_opts)) { + u8 *i_feature = (u8 *)out_feature_unit_desc; + + i_feature = (u8 *)out_feature_unit_desc + + out_feature_unit_desc->bLength - 1; + *i_feature = us[STR_FU_OUT].id; + } + if (FUIN_EN(uac2_opts)) { + u8 *i_feature = (u8 *)in_feature_unit_desc; + + i_feature = (u8 *)in_feature_unit_desc + + in_feature_unit_desc->bLength - 1; + *i_feature = us[STR_FU_IN].id; + } + /* Initialize the configurable parameters */ usb_out_it_desc.bNrChannels = num_channels(uac2_opts->c_chmask); @@ -801,6 +1001,26 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) as_out_fmt1_desc.bBitResolution = uac2_opts->c_ssize * 8; as_in_fmt1_desc.bSubslotSize = uac2_opts->p_ssize; as_in_fmt1_desc.bBitResolution = uac2_opts->p_ssize * 8; + if (FUOUT_EN(uac2_opts)) { + __le32 *bma = (__le32 *)&out_feature_unit_desc->bmaControls[0]; + u32 control = 0; + + if (uac2_opts->c_mute_present) + control |= CONTROL_RDWR << FU_MUTE_CTRL; + if (uac2_opts->c_volume_present) + control |= CONTROL_RDWR << FU_VOL_CTRL; + *bma = cpu_to_le32(control); + } + if (FUIN_EN(uac2_opts)) { + __le32 *bma = (__le32 *)&in_feature_unit_desc->bmaControls[0]; + u32 control = 0; + + if (uac2_opts->p_mute_present) + control |= CONTROL_RDWR << FU_MUTE_CTRL; + if (uac2_opts->p_volume_present) + control |= CONTROL_RDWR << FU_VOL_CTRL; + *bma = cpu_to_le32(control); + } snprintf(clksrc_in, sizeof(clksrc_in), "%uHz", uac2_opts->p_srate); snprintf(clksrc_out, sizeof(clksrc_out), "%uHz", uac2_opts->c_srate); @@ -808,7 +1028,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) ret = usb_interface_id(cfg, fn); if (ret < 0) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); - return ret; + goto err_free_fu; } iad_desc.bFirstInterface = ret; @@ -820,7 +1040,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) ret = usb_interface_id(cfg, fn); if (ret < 0) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); - return ret; + goto err_free_fu; } std_as_out_if0_desc.bInterfaceNumber = ret; std_as_out_if1_desc.bInterfaceNumber = ret; @@ -849,7 +1069,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) ret = usb_interface_id(cfg, fn); if (ret < 0) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); - return ret; + goto err_free_fu; } std_as_in_if0_desc.bInterfaceNumber = ret; std_as_in_if1_desc.bInterfaceNumber = ret; @@ -857,6 +1077,17 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) uac2->as_in_alt = 0; } + if (FUOUT_EN(uac2_opts) || FUIN_EN(uac2_opts)) { + uac2->int_ep = usb_ep_autoconfig(gadget, &fs_ep_int_desc); + if (!uac2->int_ep) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + ret = -ENODEV; + goto err_free_fu; + } + + std_ac_if_desc.bNumEndpoints = 1; + } + /* Calculate wMaxPacketSize according to audio bandwidth */ ret = set_ep_max_packet_size(uac2_opts, &fs_epin_desc, USB_SPEED_FULL, true); @@ -904,7 +1135,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc); if (!agdev->out_ep) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); - return -ENODEV; + ret = -ENODEV; + goto err_free_fu; } if (EPOUT_FBACK_IN_EN(uac2_opts)) { agdev->in_ep_fback = usb_ep_autoconfig(gadget, @@ -912,7 +1144,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) if (!agdev->in_ep_fback) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); - return -ENODEV; + ret = -ENODEV; + goto err_free_fu; } } } @@ -921,7 +1154,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) agdev->in_ep = usb_ep_autoconfig(gadget, &fs_epin_desc); if (!agdev->in_ep) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); - return -ENODEV; + ret = -ENODEV; + goto err_free_fu; } } @@ -937,38 +1171,137 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) agdev->out_ep_maxpsize = max_t(u16, agdev->out_ep_maxpsize, le16_to_cpu(ss_epout_desc.wMaxPacketSize)); + // HS and SS endpoint addresses are copied from autoconfigured FS descriptors + hs_ep_int_desc.bEndpointAddress = fs_ep_int_desc.bEndpointAddress; hs_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress; hs_epin_fback_desc.bEndpointAddress = fs_epin_fback_desc.bEndpointAddress; hs_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress; ss_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress; ss_epin_fback_desc.bEndpointAddress = fs_epin_fback_desc.bEndpointAddress; ss_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress; + ss_ep_int_desc.bEndpointAddress = fs_ep_int_desc.bEndpointAddress; setup_descriptor(uac2_opts); ret = usb_assign_descriptors(fn, fs_audio_desc, hs_audio_desc, ss_audio_desc, ss_audio_desc); if (ret) - return ret; + goto err_free_fu; agdev->gadget = gadget; agdev->params.p_chmask = uac2_opts->p_chmask; agdev->params.p_srate = uac2_opts->p_srate; agdev->params.p_ssize = uac2_opts->p_ssize; + if (FUIN_EN(uac2_opts)) { + agdev->params.p_fu.id = USB_IN_FU_ID; + agdev->params.p_fu.mute_present = uac2_opts->p_mute_present; + agdev->params.p_fu.volume_present = uac2_opts->p_volume_present; + agdev->params.p_fu.volume_min = uac2_opts->p_volume_min; + agdev->params.p_fu.volume_max = uac2_opts->p_volume_max; + agdev->params.p_fu.volume_res = uac2_opts->p_volume_res; + } agdev->params.c_chmask = uac2_opts->c_chmask; agdev->params.c_srate = uac2_opts->c_srate; agdev->params.c_ssize = uac2_opts->c_ssize; + if (FUOUT_EN(uac2_opts)) { + agdev->params.c_fu.id = USB_OUT_FU_ID; + agdev->params.c_fu.mute_present = uac2_opts->c_mute_present; + agdev->params.c_fu.volume_present = uac2_opts->c_volume_present; + agdev->params.c_fu.volume_min = uac2_opts->c_volume_min; + agdev->params.c_fu.volume_max = uac2_opts->c_volume_max; + agdev->params.c_fu.volume_res = uac2_opts->c_volume_res; + } agdev->params.req_number = uac2_opts->req_number; agdev->params.fb_max = uac2_opts->fb_max; + + if (FUOUT_EN(uac2_opts) || FUIN_EN(uac2_opts)) + agdev->notify = afunc_notify; + ret = g_audio_setup(agdev, "UAC2 PCM", "UAC2_Gadget"); if (ret) goto err_free_descs; + return 0; err_free_descs: usb_free_all_descriptors(fn); agdev->gadget = NULL; +err_free_fu: + kfree(out_feature_unit_desc); + out_feature_unit_desc = NULL; + kfree(in_feature_unit_desc); + in_feature_unit_desc = NULL; + return ret; +} + +static void +afunc_notify_complete(struct usb_ep *_ep, struct usb_request *req) +{ + struct g_audio *agdev = req->context; + struct f_uac2 *uac2 = func_to_uac2(&agdev->func); + + atomic_dec(&uac2->int_count); + kfree(req->buf); + usb_ep_free_request(_ep, req); +} + +static int +afunc_notify(struct g_audio *agdev, int unit_id, int cs) +{ + struct f_uac2 *uac2 = func_to_uac2(&agdev->func); + struct usb_request *req; + struct uac2_interrupt_data_msg *msg; + u16 w_index, w_value; + int ret; + + if (!uac2->int_ep->enabled) + return 0; + + if (atomic_inc_return(&uac2->int_count) > UAC2_DEF_INT_REQ_NUM) { + atomic_dec(&uac2->int_count); + return 0; + } + + req = usb_ep_alloc_request(uac2->int_ep, GFP_ATOMIC); + if (req == NULL) { + ret = -ENOMEM; + goto err_dec_int_count; + } + + msg = kzalloc(sizeof(*msg), GFP_ATOMIC); + if (msg == NULL) { + ret = -ENOMEM; + goto err_free_request; + } + + w_index = unit_id << 8 | uac2->ac_intf; + w_value = cs << 8; + + msg->bInfo = 0; /* Non-vendor, interface interrupt */ + msg->bAttribute = UAC2_CS_CUR; + msg->wIndex = cpu_to_le16(w_index); + msg->wValue = cpu_to_le16(w_value); + + req->length = sizeof(*msg); + req->buf = msg; + req->context = agdev; + req->complete = afunc_notify_complete; + + ret = usb_ep_queue(uac2->int_ep, req, GFP_ATOMIC); + + if (ret) + goto err_free_msg; + + return 0; + +err_free_msg: + kfree(msg); +err_free_request: + usb_ep_free_request(uac2->int_ep, req); +err_dec_int_count: + atomic_dec(&uac2->int_count); + return ret; } @@ -977,6 +1310,7 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt) { struct usb_composite_dev *cdev = fn->config->cdev; struct f_uac2 *uac2 = func_to_uac2(fn); + struct g_audio *agdev = func_to_g_audio(fn); struct usb_gadget *gadget = cdev->gadget; struct device *dev = &gadget->dev; int ret = 0; @@ -993,6 +1327,14 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt) dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return -EINVAL; } + + /* restart interrupt endpoint */ + if (uac2->int_ep) { + usb_ep_disable(uac2->int_ep); + config_ep_by_speed(gadget, &agdev->func, uac2->int_ep); + usb_ep_enable(uac2->int_ep); + } + return 0; } @@ -1047,6 +1389,8 @@ afunc_disable(struct usb_function *fn) uac2->as_out_alt = 0; u_audio_stop_capture(&uac2->g_audio); u_audio_stop_playback(&uac2->g_audio); + if (uac2->int_ep) + usb_ep_disable(uac2->int_ep); } static int @@ -1054,7 +1398,7 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) { struct usb_request *req = fn->config->cdev->req; struct g_audio *agdev = func_to_g_audio(fn); - struct f_uac2_opts *opts; + struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev); u16 w_length = le16_to_cpu(cr->wLength); u16 w_index = le16_to_cpu(cr->wIndex); u16 w_value = le16_to_cpu(cr->wValue); @@ -1063,28 +1407,64 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) int value = -EOPNOTSUPP; int p_srate, c_srate; - opts = g_audio_to_uac2_opts(agdev); p_srate = opts->p_srate; c_srate = opts->c_srate; - if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) { - struct cntrl_cur_lay3 c; - memset(&c, 0, sizeof(struct cntrl_cur_lay3)); + if ((entity_id == USB_IN_CLK_ID) || (entity_id == USB_OUT_CLK_ID)) { + if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) { + struct cntrl_cur_lay3 c; + + memset(&c, 0, sizeof(struct cntrl_cur_lay3)); + + if (entity_id == USB_IN_CLK_ID) + c.dCUR = cpu_to_le32(p_srate); + else if (entity_id == USB_OUT_CLK_ID) + c.dCUR = cpu_to_le32(c_srate); + + value = min_t(unsigned int, w_length, sizeof(c)); + memcpy(req->buf, &c, value); + } else if (control_selector == UAC2_CS_CONTROL_CLOCK_VALID) { + *(u8 *)req->buf = 1; + value = min_t(unsigned int, w_length, 1); + } else { + dev_err(&agdev->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } + } else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; - if (entity_id == USB_IN_CLK_ID) - c.dCUR = cpu_to_le32(p_srate); - else if (entity_id == USB_OUT_CLK_ID) - c.dCUR = cpu_to_le32(c_srate); + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; - value = min_t(unsigned, w_length, sizeof c); - memcpy(req->buf, &c, value); - } else if (control_selector == UAC2_CS_CONTROL_CLOCK_VALID) { - *(u8 *)req->buf = 1; - value = min_t(unsigned, w_length, 1); + if (control_selector == UAC_FU_MUTE) { + unsigned int mute; + + u_audio_get_mute(agdev, is_playback, &mute); + + *(u8 *)req->buf = mute; + value = min_t(unsigned int, w_length, 1); + } else if (control_selector == UAC_FU_VOLUME) { + struct cntrl_cur_lay2 c; + s16 volume; + + memset(&c, 0, sizeof(struct cntrl_cur_lay2)); + + u_audio_get_volume(agdev, is_playback, &volume); + c.wCUR = cpu_to_le16(volume); + + value = min_t(unsigned int, w_length, sizeof(c)); + memcpy(req->buf, &c, value); + } else { + dev_err(&agdev->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } } else { dev_err(&agdev->gadget->dev, - "%s:%d control_selector=%d TODO!\n", - __func__, __LINE__, control_selector); + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); } return value; @@ -1095,38 +1475,77 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr) { struct usb_request *req = fn->config->cdev->req; struct g_audio *agdev = func_to_g_audio(fn); - struct f_uac2_opts *opts; + struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev); u16 w_length = le16_to_cpu(cr->wLength); u16 w_index = le16_to_cpu(cr->wIndex); u16 w_value = le16_to_cpu(cr->wValue); u8 entity_id = (w_index >> 8) & 0xff; u8 control_selector = w_value >> 8; - struct cntrl_range_lay3 r; int value = -EOPNOTSUPP; int p_srate, c_srate; - opts = g_audio_to_uac2_opts(agdev); p_srate = opts->p_srate; c_srate = opts->c_srate; - if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) { - if (entity_id == USB_IN_CLK_ID) - r.dMIN = cpu_to_le32(p_srate); - else if (entity_id == USB_OUT_CLK_ID) - r.dMIN = cpu_to_le32(c_srate); - else - return -EOPNOTSUPP; + if ((entity_id == USB_IN_CLK_ID) || (entity_id == USB_OUT_CLK_ID)) { + if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) { + struct cntrl_range_lay3 r; + + if (entity_id == USB_IN_CLK_ID) + r.dMIN = cpu_to_le32(p_srate); + else if (entity_id == USB_OUT_CLK_ID) + r.dMIN = cpu_to_le32(c_srate); + else + return -EOPNOTSUPP; - r.dMAX = r.dMIN; - r.dRES = 0; - r.wNumSubRanges = cpu_to_le16(1); + r.dMAX = r.dMIN; + r.dRES = 0; + r.wNumSubRanges = cpu_to_le16(1); - value = min_t(unsigned, w_length, sizeof r); - memcpy(req->buf, &r, value); + value = min_t(unsigned int, w_length, sizeof(r)); + memcpy(req->buf, &r, value); + } else { + dev_err(&agdev->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } + } else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; + + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; + + if (control_selector == UAC_FU_VOLUME) { + struct cntrl_range_lay2 r; + s16 max_db, min_db, res_db; + + if (is_playback) { + max_db = opts->p_volume_max; + min_db = opts->p_volume_min; + res_db = opts->p_volume_res; + } else { + max_db = opts->c_volume_max; + min_db = opts->c_volume_min; + res_db = opts->c_volume_res; + } + + r.wMAX = cpu_to_le16(max_db); + r.wMIN = cpu_to_le16(min_db); + r.wRES = cpu_to_le16(res_db); + r.wNumSubRanges = cpu_to_le16(1); + + value = min_t(unsigned int, w_length, sizeof(r)); + memcpy(req->buf, &r, value); + } else { + dev_err(&agdev->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } } else { dev_err(&agdev->gadget->dev, - "%s:%d control_selector=%d TODO!\n", - __func__, __LINE__, control_selector); + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); } return value; @@ -1143,16 +1562,82 @@ ac_rq_in(struct usb_function *fn, const struct usb_ctrlrequest *cr) return -EOPNOTSUPP; } +static void +out_rq_cur_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct g_audio *agdev = req->context; + struct usb_composite_dev *cdev = agdev->func.config->cdev; + struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev); + struct f_uac2 *uac2 = func_to_uac2(&agdev->func); + struct usb_ctrlrequest *cr = &uac2->setup_cr; + u16 w_index = le16_to_cpu(cr->wIndex); + u16 w_value = le16_to_cpu(cr->wValue); + u8 entity_id = (w_index >> 8) & 0xff; + u8 control_selector = w_value >> 8; + + if (req->status != 0) { + dev_dbg(&cdev->gadget->dev, "completion err %d\n", req->status); + return; + } + + if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; + + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; + + if (control_selector == UAC_FU_MUTE) { + u8 mute = *(u8 *)req->buf; + + u_audio_set_mute(agdev, is_playback, mute); + + return; + } else if (control_selector == UAC_FU_VOLUME) { + struct cntrl_cur_lay2 *c = req->buf; + s16 volume; + + volume = le16_to_cpu(c->wCUR); + u_audio_set_volume(agdev, is_playback, volume); + + return; + } else { + dev_err(&agdev->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + usb_ep_set_halt(ep); + } + } +} + static int out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) { + struct usb_request *req = fn->config->cdev->req; + struct g_audio *agdev = func_to_g_audio(fn); + struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev); + struct f_uac2 *uac2 = func_to_uac2(fn); u16 w_length = le16_to_cpu(cr->wLength); + u16 w_index = le16_to_cpu(cr->wIndex); u16 w_value = le16_to_cpu(cr->wValue); + u8 entity_id = (w_index >> 8) & 0xff; u8 control_selector = w_value >> 8; - if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) - return w_length; + if ((entity_id == USB_IN_CLK_ID) || (entity_id == USB_OUT_CLK_ID)) { + if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) + return w_length; + } else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + memcpy(&uac2->setup_cr, cr, sizeof(*cr)); + req->context = agdev; + req->complete = out_rq_cur_complete; + return w_length; + } else { + dev_err(&agdev->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); + } return -EOPNOTSUPP; } @@ -1228,7 +1713,15 @@ static struct configfs_item_operations f_uac2_item_ops = { .release = f_uac2_attr_release, }; -#define UAC2_ATTRIBUTE(name) \ +#define uac2_kstrtou32 kstrtou32 +#define uac2_kstrtos16 kstrtos16 +#define uac2_kstrtobool(s, base, res) kstrtobool((s), (res)) + +static const char *u32_fmt = "%u\n"; +static const char *s16_fmt = "%hd\n"; +static const char *bool_fmt = "%u\n"; + +#define UAC2_ATTRIBUTE(type, name) \ static ssize_t f_uac2_opts_##name##_show(struct config_item *item, \ char *page) \ { \ @@ -1236,7 +1729,7 @@ static ssize_t f_uac2_opts_##name##_show(struct config_item *item, \ int result; \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%u\n", opts->name); \ + result = sprintf(page, type##_fmt, opts->name); \ mutex_unlock(&opts->lock); \ \ return result; \ @@ -1247,7 +1740,7 @@ static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \ { \ struct f_uac2_opts *opts = to_f_uac2_opts(item); \ int ret; \ - u32 num; \ + type num; \ \ mutex_lock(&opts->lock); \ if (opts->refcnt) { \ @@ -1255,7 +1748,7 @@ static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \ goto end; \ } \ \ - ret = kstrtou32(page, 0, &num); \ + ret = uac2_kstrto##type(page, 0, &num); \ if (ret) \ goto end; \ \ @@ -1325,15 +1818,27 @@ end: \ \ CONFIGFS_ATTR(f_uac2_opts_, name) -UAC2_ATTRIBUTE(p_chmask); -UAC2_ATTRIBUTE(p_srate); -UAC2_ATTRIBUTE(p_ssize); -UAC2_ATTRIBUTE(c_chmask); -UAC2_ATTRIBUTE(c_srate); +UAC2_ATTRIBUTE(u32, p_chmask); +UAC2_ATTRIBUTE(u32, p_srate); +UAC2_ATTRIBUTE(u32, p_ssize); +UAC2_ATTRIBUTE(u32, c_chmask); +UAC2_ATTRIBUTE(u32, c_srate); UAC2_ATTRIBUTE_SYNC(c_sync); -UAC2_ATTRIBUTE(c_ssize); -UAC2_ATTRIBUTE(req_number); -UAC2_ATTRIBUTE(fb_max); +UAC2_ATTRIBUTE(u32, c_ssize); +UAC2_ATTRIBUTE(u32, req_number); + +UAC2_ATTRIBUTE(bool, p_mute_present); +UAC2_ATTRIBUTE(bool, p_volume_present); +UAC2_ATTRIBUTE(s16, p_volume_min); +UAC2_ATTRIBUTE(s16, p_volume_max); +UAC2_ATTRIBUTE(s16, p_volume_res); + +UAC2_ATTRIBUTE(bool, c_mute_present); +UAC2_ATTRIBUTE(bool, c_volume_present); +UAC2_ATTRIBUTE(s16, c_volume_min); +UAC2_ATTRIBUTE(s16, c_volume_max); +UAC2_ATTRIBUTE(s16, c_volume_res); +UAC2_ATTRIBUTE(u32, fb_max); static struct configfs_attribute *f_uac2_attrs[] = { &f_uac2_opts_attr_p_chmask, @@ -1345,6 +1850,19 @@ static struct configfs_attribute *f_uac2_attrs[] = { &f_uac2_opts_attr_c_sync, &f_uac2_opts_attr_req_number, &f_uac2_opts_attr_fb_max, + + &f_uac2_opts_attr_p_mute_present, + &f_uac2_opts_attr_p_volume_present, + &f_uac2_opts_attr_p_volume_min, + &f_uac2_opts_attr_p_volume_max, + &f_uac2_opts_attr_p_volume_res, + + &f_uac2_opts_attr_c_mute_present, + &f_uac2_opts_attr_c_volume_present, + &f_uac2_opts_attr_c_volume_min, + &f_uac2_opts_attr_c_volume_max, + &f_uac2_opts_attr_c_volume_res, + NULL, }; @@ -1383,6 +1901,19 @@ static struct usb_function_instance *afunc_alloc_inst(void) opts->c_srate = UAC2_DEF_CSRATE; opts->c_ssize = UAC2_DEF_CSSIZE; opts->c_sync = UAC2_DEF_CSYNC; + + opts->p_mute_present = UAC2_DEF_MUTE_PRESENT; + opts->p_volume_present = UAC2_DEF_VOLUME_PRESENT; + opts->p_volume_min = UAC2_DEF_MIN_DB; + opts->p_volume_max = UAC2_DEF_MAX_DB; + opts->p_volume_res = UAC2_DEF_RES_DB; + + opts->c_mute_present = UAC2_DEF_MUTE_PRESENT; + opts->c_volume_present = UAC2_DEF_VOLUME_PRESENT; + opts->c_volume_min = UAC2_DEF_MIN_DB; + opts->c_volume_max = UAC2_DEF_MAX_DB; + opts->c_volume_res = UAC2_DEF_RES_DB; + opts->req_number = UAC2_DEF_REQ_NUM; opts->fb_max = UAC2_DEF_FB_MAX; return &opts->func_inst; @@ -1409,6 +1940,11 @@ static void afunc_unbind(struct usb_configuration *c, struct usb_function *f) usb_free_all_descriptors(f); agdev->gadget = NULL; + + kfree(out_feature_unit_desc); + out_feature_unit_desc = NULL; + kfree(in_feature_unit_desc); + in_feature_unit_desc = NULL; } static struct usb_function *afunc_alloc(struct usb_function_instance *fi) @@ -1441,3 +1977,4 @@ DECLARE_USB_FUNCTION_INIT(uac2, afunc_alloc_inst, afunc_alloc); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Yadwinder Singh"); MODULE_AUTHOR("Jaswinder Singh"); +MODULE_AUTHOR("Ruslan Bilovol"); diff --git a/drivers/usb/gadget/function/u_uac2.h b/drivers/usb/gadget/function/u_uac2.h index 179d3ef6a195..a73b35774c44 100644 --- a/drivers/usb/gadget/function/u_uac2.h +++ b/drivers/usb/gadget/function/u_uac2.h @@ -22,8 +22,16 @@ #define UAC2_DEF_CSRATE 64000 #define UAC2_DEF_CSSIZE 2 #define UAC2_DEF_CSYNC USB_ENDPOINT_SYNC_ASYNC + +#define UAC2_DEF_MUTE_PRESENT 1 +#define UAC2_DEF_VOLUME_PRESENT 1 +#define UAC2_DEF_MIN_DB (-100*256) /* -100 dB */ +#define UAC2_DEF_MAX_DB 0 /* 0 dB */ +#define UAC2_DEF_RES_DB (1*256) /* 1 dB */ + #define UAC2_DEF_REQ_NUM 2 #define UAC2_DEF_FB_MAX 5 +#define UAC2_DEF_INT_REQ_NUM 10 struct f_uac2_opts { struct usb_function_instance func_inst; @@ -34,9 +42,22 @@ struct f_uac2_opts { int c_srate; int c_ssize; int c_sync; + + bool p_mute_present; + bool p_volume_present; + s16 p_volume_min; + s16 p_volume_max; + s16 p_volume_res; + + bool c_mute_present; + bool c_volume_present; + s16 c_volume_min; + s16 c_volume_max; + s16 c_volume_res; + int req_number; int fb_max; - bool bound; + bool bound; struct mutex lock; int refcnt; From patchwork Sun Jul 11 09:53:51 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Pavel Hofman X-Patchwork-Id: 472788 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-13.8 required=3.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,UNWANTED_LANGUAGE_BODY, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 318A8C07E9B for ; Sun, 11 Jul 2021 09:54:11 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 0ABE56124B for ; Sun, 11 Jul 2021 09:54:11 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232024AbhGKJ4y (ORCPT ); Sun, 11 Jul 2021 05:56:54 -0400 Received: from cable.insite.cz ([84.242.75.189]:55845 "EHLO cable.insite.cz" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232001AbhGKJ4x (ORCPT ); Sun, 11 Jul 2021 05:56:53 -0400 Received: from localhost (localhost [127.0.0.1]) by cable.insite.cz (Postfix) with ESMTP id 0B884A1A3D409; Sun, 11 Jul 2021 11:54:05 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=ivitera.com; s=mail; t=1625997245; bh=q1GGITaZdPaBggBanl6OIEvhzg2gpJZOvp4Mtwuv63E=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=VXp271MC+iDZ9+x1HTrA/zYa7iMVKV0Zhm1bk0CClvAUC8QjOQ+TZKLb0oJThOLPl FhrHdjoYSuAL19YJqF2y6NxYkibREGeaD8sH0CWW71c67ckHPBIGisgzMMXBjtsqGq aVXeHFWZro4FVdZrWRdQyEyslud9VA4GzAuz56/U= Received: from cable.insite.cz ([84.242.75.189]) by localhost (server.insite.cz [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id sg20Y6EnfTpW; Sun, 11 Jul 2021 11:53:58 +0200 (CEST) Received: from precision.doma (ip28.insite.cz [81.0.237.28]) (Authenticated sender: pavel) by cable.insite.cz (Postfix) with ESMTPSA id B9ADCA1A3D406; Sun, 11 Jul 2021 11:53:57 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=ivitera.com; s=mail; t=1625997237; bh=q1GGITaZdPaBggBanl6OIEvhzg2gpJZOvp4Mtwuv63E=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=IVeWNwDPXYPGc/5PN53qnCCeDLmAl1Ml3SfUuFoPjzUP0EixkSYdIJnAYbHOztSJi 8GiuHc1j+K8Uib06pLaOy4yoOLIUleDO8d0O/i7fHhQtDeekvOFQlF4/+Y097tCWTl 8gSVeBcIfOApwswDDWOWNxR5WJFtZJQWGhwOItVc= From: Pavel Hofman To: linux-usb@vger.kernel.org Cc: Pavel Hofman , Ruslan Bilovol , Felipe Balbi , Jerome Brunet Subject: [PATCH v2 4/4] usb: gadget: f_uac1: add volume and mute support Date: Sun, 11 Jul 2021 11:53:51 +0200 Message-Id: <20210711095351.16877-5-pavel.hofman@ivitera.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210711095351.16877-1-pavel.hofman@ivitera.com> References: <20210711095351.16877-1-pavel.hofman@ivitera.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org From: Ruslan Bilovol This adds bi-directional (host->device, device->host) volume/mute support to the f_uac1 driver by adding Feature Units and interrupt endpoint. Currently only master channel is supported. Volume and mute are configurable through configfs, by default volume has -100..0 dB range with 1 dB step. Similar to existing flexible endpoints configuration, Feature Unit won't be added to the topology if both mute and volume are not enabled, also interrupt endpoint isn't added to the device if no feature unit is present Signed-off-by: Ruslan Bilovol Signed-off-by: Pavel Hofman --- .../ABI/testing/configfs-usb-gadget-uac1 | 10 + Documentation/usb/gadget-testing.rst | 26 +- drivers/usb/gadget/function/f_uac1.c | 674 +++++++++++++++++- drivers/usb/gadget/function/u_uac1.h | 20 + 4 files changed, 700 insertions(+), 30 deletions(-) diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac1 b/Documentation/ABI/testing/configfs-usb-gadget-uac1 index dc23fd776943..dd647d44d975 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-uac1 +++ b/Documentation/ABI/testing/configfs-usb-gadget-uac1 @@ -8,9 +8,19 @@ Description: c_chmask capture channel mask c_srate capture sampling rate c_ssize capture sample size (bytes) + c_mute_present capture mute control enable + c_volume_present capture volume control enable + c_volume_min capture volume control min value (in 1/256 dB) + c_volume_max capture volume control max value (in 1/256 dB) + c_volume_res capture volume control resolution (in 1/256 dB) p_chmask playback channel mask p_srate playback sampling rate p_ssize playback sample size (bytes) + p_mute_present playback mute control enable + p_volume_present playback volume control enable + p_volume_min playback volume control min value (in 1/256 dB) + p_volume_max playback volume control max value (in 1/256 dB) + p_volume_res playback volume control resolution (in 1/256 dB) req_number the number of pre-allocated request for both capture and playback ========== =================================== diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst index 272e51f17f54..d6253f1a32a1 100644 --- a/Documentation/usb/gadget-testing.rst +++ b/Documentation/usb/gadget-testing.rst @@ -915,14 +915,24 @@ The function name to use when creating the function directory is "uac1". The uac1 function provides these attributes in its function directory: ========== ==================================================== - c_chmask capture channel mask - c_srate capture sampling rate - c_ssize capture sample size (bytes) - p_chmask playback channel mask - p_srate playback sampling rate - p_ssize playback sample size (bytes) - req_number the number of pre-allocated request for both capture - and playback + c_chmask capture channel mask + c_srate capture sampling rate + c_ssize capture sample size (bytes) + c_mute_present capture mute control enable + c_volume_present capture volume control enable + c_volume_min capture volume control min value (in 1/256 dB) + c_volume_max capture volume control max value (in 1/256 dB) + c_volume_res capture volume control resolution (in 1/256 dB) + p_chmask playback channel mask + p_srate playback sampling rate + p_ssize playback sample size (bytes) + p_mute_present playback mute control enable + p_volume_present playback volume control enable + p_volume_min playback volume control min value (in 1/256 dB) + p_volume_max playback volume control max value (in 1/256 dB) + p_volume_res playback volume control resolution (in 1/256 dB) + req_number the number of pre-allocated request for both capture + and playback ========== ==================================================== The attributes have sane default values. diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c index d04707580068..3b3db1a8df75 100644 --- a/drivers/usb/gadget/function/f_uac1.c +++ b/drivers/usb/gadget/function/f_uac1.c @@ -22,13 +22,26 @@ /* UAC1 spec: 3.7.2.3 Audio Channel Cluster Format */ #define UAC1_CHANNEL_MASK 0x0FFF +#define USB_OUT_FU_ID (out_feature_unit_desc->bUnitID) +#define USB_IN_FU_ID (in_feature_unit_desc->bUnitID) + #define EPIN_EN(_opts) ((_opts)->p_chmask != 0) #define EPOUT_EN(_opts) ((_opts)->c_chmask != 0) +#define FUIN_EN(_opts) ((_opts)->p_mute_present \ + || (_opts)->p_volume_present) +#define FUOUT_EN(_opts) ((_opts)->c_mute_present \ + || (_opts)->c_volume_present) struct f_uac1 { struct g_audio g_audio; u8 ac_intf, as_in_intf, as_out_intf; u8 ac_alt, as_in_alt, as_out_alt; /* needed for get_alt() */ + + struct usb_ctrlrequest setup_cr; /* will be used in data stage */ + + /* Interrupt IN endpoint of AC interface */ + struct usb_ep *int_ep; + atomic_t int_count; }; static inline struct f_uac1 *func_to_uac1(struct usb_function *f) @@ -58,7 +71,7 @@ static inline struct f_uac1_opts *g_audio_to_uac1_opts(struct g_audio *audio) static struct usb_interface_descriptor ac_interface_desc = { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, - .bNumEndpoints = 0, + /* .bNumEndpoints = DYNAMIC */ .bInterfaceClass = USB_CLASS_AUDIO, .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, }; @@ -106,6 +119,19 @@ static struct uac1_output_terminal_descriptor usb_in_ot_desc = { /* .bSourceID = DYNAMIC */ }; +static struct uac_feature_unit_descriptor *in_feature_unit_desc; +static struct uac_feature_unit_descriptor *out_feature_unit_desc; + +/* AC IN Interrupt Endpoint */ +static struct usb_endpoint_descriptor ac_int_ep_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(2), + .bInterval = 4, +}; + /* B.4.1 Standard AS Interface Descriptor */ static struct usb_interface_descriptor as_out_interface_alt_0_desc = { .bLength = USB_DT_INTERFACE_SIZE, @@ -232,8 +258,13 @@ static struct usb_descriptor_header *f_audio_desc[] = { (struct usb_descriptor_header *)&usb_out_it_desc, (struct usb_descriptor_header *)&io_out_ot_desc, + (struct usb_descriptor_header *)&out_feature_unit_desc, + (struct usb_descriptor_header *)&io_in_it_desc, (struct usb_descriptor_header *)&usb_in_ot_desc, + (struct usb_descriptor_header *)&in_feature_unit_desc, + + (struct usb_descriptor_header *)&ac_int_ep_desc, (struct usb_descriptor_header *)&as_out_interface_alt_0_desc, (struct usb_descriptor_header *)&as_out_interface_alt_1_desc, @@ -263,6 +294,8 @@ enum { STR_IO_IN_IT, STR_IO_IN_IT_CH_NAMES, STR_USB_IN_OT, + STR_FU_IN, + STR_FU_OUT, STR_AS_OUT_IF_ALT0, STR_AS_OUT_IF_ALT1, STR_AS_IN_IF_ALT0, @@ -277,6 +310,8 @@ static struct usb_string strings_uac1[] = { [STR_IO_IN_IT].s = "Capture Input terminal", [STR_IO_IN_IT_CH_NAMES].s = "Capture Channels", [STR_USB_IN_OT].s = "Capture Output terminal", + [STR_FU_IN].s = "Capture Volume", + [STR_FU_OUT].s = "Playback Volume", [STR_AS_OUT_IF_ALT0].s = "Playback Inactive", [STR_AS_OUT_IF_ALT1].s = "Playback Active", [STR_AS_IN_IF_ALT0].s = "Capture Inactive", @@ -298,6 +333,376 @@ static struct usb_gadget_strings *uac1_strings[] = { * This function is an ALSA sound card following USB Audio Class Spec 1.0. */ +static void audio_notify_complete(struct usb_ep *_ep, struct usb_request *req) +{ + struct g_audio *audio = req->context; + struct f_uac1 *uac1 = func_to_uac1(&audio->func); + + atomic_dec(&uac1->int_count); + kfree(req->buf); + usb_ep_free_request(_ep, req); +} + +static int audio_notify(struct g_audio *audio, int unit_id, int cs) +{ + struct f_uac1 *uac1 = func_to_uac1(&audio->func); + struct usb_request *req; + struct uac1_status_word *msg; + int ret; + + if (!uac1->int_ep->enabled) + return 0; + + if (atomic_inc_return(&uac1->int_count) > UAC1_DEF_INT_REQ_NUM) { + atomic_dec(&uac1->int_count); + return 0; + } + + req = usb_ep_alloc_request(uac1->int_ep, GFP_ATOMIC); + if (req == NULL) { + ret = -ENOMEM; + goto err_dec_int_count; + } + + msg = kmalloc(sizeof(*msg), GFP_ATOMIC); + if (msg == NULL) { + ret = -ENOMEM; + goto err_free_request; + } + + msg->bStatusType = UAC1_STATUS_TYPE_IRQ_PENDING + | UAC1_STATUS_TYPE_ORIG_AUDIO_CONTROL_IF; + msg->bOriginator = unit_id; + + req->length = sizeof(*msg); + req->buf = msg; + req->context = audio; + req->complete = audio_notify_complete; + + ret = usb_ep_queue(uac1->int_ep, req, GFP_ATOMIC); + + if (ret) + goto err_free_msg; + + return 0; + +err_free_msg: + kfree(msg); +err_free_request: + usb_ep_free_request(uac1->int_ep, req); +err_dec_int_count: + atomic_dec(&uac1->int_count); + + return ret; +} + +static int +in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + struct usb_request *req = fn->config->cdev->req; + struct g_audio *audio = func_to_g_audio(fn); + struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio); + u16 w_length = le16_to_cpu(cr->wLength); + u16 w_index = le16_to_cpu(cr->wIndex); + u16 w_value = le16_to_cpu(cr->wValue); + u8 entity_id = (w_index >> 8) & 0xff; + u8 control_selector = w_value >> 8; + int value = -EOPNOTSUPP; + + if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; + + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; + + if (control_selector == UAC_FU_MUTE) { + unsigned int mute; + + u_audio_get_mute(audio, is_playback, &mute); + + *(u8 *)req->buf = mute; + value = min_t(unsigned int, w_length, 1); + } else if (control_selector == UAC_FU_VOLUME) { + __le16 c; + s16 volume; + + u_audio_get_volume(audio, is_playback, &volume); + + c = cpu_to_le16(volume); + + value = min_t(unsigned int, w_length, sizeof(c)); + memcpy(req->buf, &c, value); + } else { + dev_err(&audio->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } + } else { + dev_err(&audio->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); + } + + return value; +} + +static int +in_rq_min(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + struct usb_request *req = fn->config->cdev->req; + struct g_audio *audio = func_to_g_audio(fn); + struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio); + u16 w_length = le16_to_cpu(cr->wLength); + u16 w_index = le16_to_cpu(cr->wIndex); + u16 w_value = le16_to_cpu(cr->wValue); + u8 entity_id = (w_index >> 8) & 0xff; + u8 control_selector = w_value >> 8; + int value = -EOPNOTSUPP; + + if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; + + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; + + if (control_selector == UAC_FU_VOLUME) { + __le16 r; + s16 min_db; + + if (is_playback) + min_db = opts->p_volume_min; + else + min_db = opts->c_volume_min; + + r = cpu_to_le16(min_db); + + value = min_t(unsigned int, w_length, sizeof(r)); + memcpy(req->buf, &r, value); + } else { + dev_err(&audio->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } + } else { + dev_err(&audio->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); + } + + return value; +} + +static int +in_rq_max(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + struct usb_request *req = fn->config->cdev->req; + struct g_audio *audio = func_to_g_audio(fn); + struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio); + u16 w_length = le16_to_cpu(cr->wLength); + u16 w_index = le16_to_cpu(cr->wIndex); + u16 w_value = le16_to_cpu(cr->wValue); + u8 entity_id = (w_index >> 8) & 0xff; + u8 control_selector = w_value >> 8; + int value = -EOPNOTSUPP; + + if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; + + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; + + if (control_selector == UAC_FU_VOLUME) { + __le16 r; + s16 max_db; + + if (is_playback) + max_db = opts->p_volume_max; + else + max_db = opts->c_volume_max; + + r = cpu_to_le16(max_db); + + value = min_t(unsigned int, w_length, sizeof(r)); + memcpy(req->buf, &r, value); + } else { + dev_err(&audio->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } + } else { + dev_err(&audio->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); + } + + return value; +} + +static int +in_rq_res(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + struct usb_request *req = fn->config->cdev->req; + struct g_audio *audio = func_to_g_audio(fn); + struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio); + u16 w_length = le16_to_cpu(cr->wLength); + u16 w_index = le16_to_cpu(cr->wIndex); + u16 w_value = le16_to_cpu(cr->wValue); + u8 entity_id = (w_index >> 8) & 0xff; + u8 control_selector = w_value >> 8; + int value = -EOPNOTSUPP; + + if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; + + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; + + if (control_selector == UAC_FU_VOLUME) { + __le16 r; + s16 res_db; + + if (is_playback) + res_db = opts->p_volume_res; + else + res_db = opts->c_volume_res; + + r = cpu_to_le16(res_db); + + value = min_t(unsigned int, w_length, sizeof(r)); + memcpy(req->buf, &r, value); + } else { + dev_err(&audio->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } + } else { + dev_err(&audio->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); + } + + return value; +} + +static void +out_rq_cur_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct g_audio *audio = req->context; + struct usb_composite_dev *cdev = audio->func.config->cdev; + struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio); + struct f_uac1 *uac1 = func_to_uac1(&audio->func); + struct usb_ctrlrequest *cr = &uac1->setup_cr; + u16 w_index = le16_to_cpu(cr->wIndex); + u16 w_value = le16_to_cpu(cr->wValue); + u8 entity_id = (w_index >> 8) & 0xff; + u8 control_selector = w_value >> 8; + + if (req->status != 0) { + dev_dbg(&cdev->gadget->dev, "completion err %d\n", req->status); + return; + } + + if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; + + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; + + if (control_selector == UAC_FU_MUTE) { + u8 mute = *(u8 *)req->buf; + + u_audio_set_mute(audio, is_playback, mute); + + return; + } else if (control_selector == UAC_FU_VOLUME) { + __le16 *c = req->buf; + s16 volume; + + volume = le16_to_cpu(*c); + u_audio_set_volume(audio, is_playback, volume); + + return; + } else { + dev_err(&audio->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + usb_ep_set_halt(ep); + } + } else { + dev_err(&audio->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); + usb_ep_set_halt(ep); + + } +} + +static int +out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + struct usb_request *req = fn->config->cdev->req; + struct g_audio *audio = func_to_g_audio(fn); + struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio); + struct f_uac1 *uac1 = func_to_uac1(&audio->func); + u16 w_length = le16_to_cpu(cr->wLength); + u16 w_index = le16_to_cpu(cr->wIndex); + u16 w_value = le16_to_cpu(cr->wValue); + u8 entity_id = (w_index >> 8) & 0xff; + u8 control_selector = w_value >> 8; + + if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + memcpy(&uac1->setup_cr, cr, sizeof(*cr)); + req->context = audio; + req->complete = out_rq_cur_complete; + + return w_length; + } else { + dev_err(&audio->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); + } + return -EOPNOTSUPP; +} + +static int ac_rq_in(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + int value = -EOPNOTSUPP; + u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", + ctrl->bRequest, w_value, len, ep); + + switch (ctrl->bRequest) { + case UAC_GET_CUR: + return in_rq_cur(f, ctrl); + case UAC_GET_MIN: + return in_rq_min(f, ctrl); + case UAC_GET_MAX: + return in_rq_max(f, ctrl); + case UAC_GET_RES: + return in_rq_res(f, ctrl); + case UAC_GET_MEM: + break; + case UAC_GET_STAT: + value = len; + break; + default: + break; + } + + return value; +} + static int audio_set_endpoint_req(struct usb_function *f, const struct usb_ctrlrequest *ctrl) { @@ -383,7 +788,13 @@ f_audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: value = audio_get_endpoint_req(f, ctrl); break; - + case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE: + if (ctrl->bRequest == UAC_SET_CUR) + value = out_rq_cur(f, ctrl); + break; + case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE: + value = ac_rq_in(f, ctrl); + break; default: ERROR(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", ctrl->bRequestType, ctrl->bRequest, @@ -411,6 +822,7 @@ static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt) struct usb_composite_dev *cdev = f->config->cdev; struct usb_gadget *gadget = cdev->gadget; struct device *dev = &gadget->dev; + struct g_audio *audio = func_to_g_audio(f); struct f_uac1 *uac1 = func_to_uac1(f); int ret = 0; @@ -426,6 +838,14 @@ static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt) dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return -EINVAL; } + + /* restart interrupt endpoint */ + if (uac1->int_ep) { + usb_ep_disable(uac1->int_ep); + config_ep_by_speed(gadget, &audio->func, uac1->int_ep); + usb_ep_enable(uac1->int_ep); + } + return 0; } @@ -481,10 +901,33 @@ static void f_audio_disable(struct usb_function *f) u_audio_stop_playback(&uac1->g_audio); u_audio_stop_capture(&uac1->g_audio); + if (uac1->int_ep) + usb_ep_disable(uac1->int_ep); } /*-------------------------------------------------------------------------*/ +static struct uac_feature_unit_descriptor *build_fu_desc(int chmask) +{ + struct uac_feature_unit_descriptor *fu_desc; + int channels = num_channels(chmask); + int fu_desc_size = UAC_DT_FEATURE_UNIT_SIZE(channels); + + fu_desc = kzalloc(fu_desc_size, GFP_KERNEL); + if (!fu_desc) + return NULL; + + fu_desc->bLength = fu_desc_size; + fu_desc->bDescriptorType = USB_DT_CS_INTERFACE; + + fu_desc->bDescriptorSubtype = UAC_FEATURE_UNIT; + fu_desc->bControlSize = 2; + /* bUnitID, bSourceID and bmaControls will be defined later */ + + return fu_desc; +} + +/* B.3.2 Class-Specific AC Interface Descriptor */ static struct uac1_ac_header_descriptor *build_ac_header_desc(struct f_uac1_opts *opts) { @@ -530,9 +973,23 @@ static void setup_descriptor(struct f_uac1_opts *opts) io_out_ot_desc.bTerminalID = i++; if (EPIN_EN(opts)) usb_in_ot_desc.bTerminalID = i++; - - usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID; - io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID; + if (FUOUT_EN(opts)) + out_feature_unit_desc->bUnitID = i++; + if (FUIN_EN(opts)) + in_feature_unit_desc->bUnitID = i++; + + if (FUIN_EN(opts)) { + usb_in_ot_desc.bSourceID = in_feature_unit_desc->bUnitID; + in_feature_unit_desc->bSourceID = io_in_it_desc.bTerminalID; + } else { + usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID; + } + if (FUOUT_EN(opts)) { + io_out_ot_desc.bSourceID = out_feature_unit_desc->bUnitID; + out_feature_unit_desc->bSourceID = usb_out_it_desc.bTerminalID; + } else { + io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID; + } as_out_header_desc.bTerminalLink = usb_out_it_desc.bTerminalID; as_in_header_desc.bTerminalLink = usb_in_ot_desc.bTerminalID; @@ -544,6 +1001,8 @@ static void setup_descriptor(struct f_uac1_opts *opts) len += sizeof(usb_in_ot_desc); len += sizeof(io_in_it_desc); + if (FUIN_EN(opts)) + len += in_feature_unit_desc->bLength; ac_header_desc->wTotalLength = cpu_to_le16(len); } if (EPOUT_EN(opts)) { @@ -551,6 +1010,8 @@ static void setup_descriptor(struct f_uac1_opts *opts) len += sizeof(usb_out_it_desc); len += sizeof(io_out_ot_desc); + if (FUOUT_EN(opts)) + len += out_feature_unit_desc->bLength; ac_header_desc->wTotalLength = cpu_to_le16(len); } @@ -561,13 +1022,20 @@ static void setup_descriptor(struct f_uac1_opts *opts) if (EPOUT_EN(opts)) { f_audio_desc[i++] = USBDHDR(&usb_out_it_desc); f_audio_desc[i++] = USBDHDR(&io_out_ot_desc); + if (FUOUT_EN(opts)) + f_audio_desc[i++] = USBDHDR(out_feature_unit_desc); } if (EPIN_EN(opts)) { f_audio_desc[i++] = USBDHDR(&io_in_it_desc); f_audio_desc[i++] = USBDHDR(&usb_in_ot_desc); + if (FUIN_EN(opts)) + f_audio_desc[i++] = USBDHDR(in_feature_unit_desc); } + if (FUOUT_EN(opts) || FUIN_EN(opts)) + f_audio_desc[i++] = USBDHDR(&ac_int_ep_desc); + if (EPOUT_EN(opts)) { f_audio_desc[i++] = USBDHDR(&as_out_interface_alt_0_desc); f_audio_desc[i++] = USBDHDR(&as_out_interface_alt_1_desc); @@ -614,6 +1082,28 @@ static int f_audio_validate_opts(struct g_audio *audio, struct device *dev) return -EINVAL; } + if (opts->p_volume_max <= opts->p_volume_min) { + dev_err(dev, "Error: incorrect playback volume max/min\n"); + return -EINVAL; + } else if (opts->c_volume_max <= opts->c_volume_min) { + dev_err(dev, "Error: incorrect capture volume max/min\n"); + return -EINVAL; + } else if (opts->p_volume_res <= 0) { + dev_err(dev, "Error: negative/zero playback volume resolution\n"); + return -EINVAL; + } else if (opts->c_volume_res <= 0) { + dev_err(dev, "Error: negative/zero capture volume resolution\n"); + return -EINVAL; + } + + if ((opts->p_volume_max - opts->p_volume_min) % opts->p_volume_res) { + dev_err(dev, "Error: incorrect playback volume resolution\n"); + return -EINVAL; + } else if ((opts->c_volume_max - opts->c_volume_min) % opts->c_volume_res) { + dev_err(dev, "Error: incorrect capture volume resolution\n"); + return -EINVAL; + } + return 0; } @@ -647,6 +1137,21 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) if (!ac_header_desc) return -ENOMEM; + if (FUOUT_EN(audio_opts)) { + out_feature_unit_desc = build_fu_desc(audio_opts->c_chmask); + if (!out_feature_unit_desc) { + status = -ENOMEM; + goto fail; + } + } + if (FUIN_EN(audio_opts)) { + in_feature_unit_desc = build_fu_desc(audio_opts->p_chmask); + if (!in_feature_unit_desc) { + status = -ENOMEM; + goto err_free_fu; + } + } + ac_interface_desc.iInterface = us[STR_AC_IF].id; usb_out_it_desc.iTerminal = us[STR_USB_OUT_IT].id; usb_out_it_desc.iChannelNames = us[STR_USB_OUT_IT_CH_NAMES].id; @@ -659,6 +1164,21 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) as_in_interface_alt_0_desc.iInterface = us[STR_AS_IN_IF_ALT0].id; as_in_interface_alt_1_desc.iInterface = us[STR_AS_IN_IF_ALT1].id; + if (FUOUT_EN(audio_opts)) { + u8 *i_feature; + + i_feature = (u8 *)out_feature_unit_desc + + out_feature_unit_desc->bLength - 1; + *i_feature = us[STR_FU_OUT].id; + } + if (FUIN_EN(audio_opts)) { + u8 *i_feature; + + i_feature = (u8 *)in_feature_unit_desc + + in_feature_unit_desc->bLength - 1; + *i_feature = us[STR_FU_IN].id; + } + /* Set channel numbers */ usb_out_it_desc.bNrChannels = num_channels(audio_opts->c_chmask); usb_out_it_desc.wChannelConfig = cpu_to_le16(audio_opts->c_chmask); @@ -671,6 +1191,27 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) as_in_type_i_desc.bSubframeSize = audio_opts->p_ssize; as_in_type_i_desc.bBitResolution = audio_opts->p_ssize * 8; + if (FUOUT_EN(audio_opts)) { + __le16 *bma = (__le16 *)&out_feature_unit_desc->bmaControls[0]; + u32 control = 0; + + if (audio_opts->c_mute_present) + control |= UAC_FU_MUTE; + if (audio_opts->c_volume_present) + control |= UAC_FU_VOLUME; + *bma = cpu_to_le16(control); + } + if (FUIN_EN(audio_opts)) { + __le16 *bma = (__le16 *)&in_feature_unit_desc->bmaControls[0]; + u32 control = 0; + + if (audio_opts->p_mute_present) + control |= UAC_FU_MUTE; + if (audio_opts->p_volume_present) + control |= UAC_FU_VOLUME; + *bma = cpu_to_le16(control); + } + /* Set sample rates */ rate = audio_opts->c_srate; sam_freq = as_out_type_i_desc.tSamFreq[0]; @@ -682,7 +1223,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) /* allocate instance-specific interface IDs, and patch descriptors */ status = usb_interface_id(c, f); if (status < 0) - goto fail; + goto err_free_fu; ac_interface_desc.bInterfaceNumber = status; uac1->ac_intf = status; uac1->ac_alt = 0; @@ -692,7 +1233,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) if (EPOUT_EN(audio_opts)) { status = usb_interface_id(c, f); if (status < 0) - goto fail; + goto err_free_fu; as_out_interface_alt_0_desc.bInterfaceNumber = status; as_out_interface_alt_1_desc.bInterfaceNumber = status; ac_header_desc->baInterfaceNr[ba_iface_id++] = status; @@ -703,7 +1244,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) if (EPIN_EN(audio_opts)) { status = usb_interface_id(c, f); if (status < 0) - goto fail; + goto err_free_fu; as_in_interface_alt_0_desc.bInterfaceNumber = status; as_in_interface_alt_1_desc.bInterfaceNumber = status; ac_header_desc->baInterfaceNr[ba_iface_id++] = status; @@ -715,11 +1256,24 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) status = -ENODEV; + ac_interface_desc.bNumEndpoints = 0; + + /* allocate AC interrupt endpoint */ + if (FUOUT_EN(audio_opts) || FUIN_EN(audio_opts)) { + ep = usb_ep_autoconfig(cdev->gadget, &ac_int_ep_desc); + if (!ep) + goto err_free_fu; + uac1->int_ep = ep; + uac1->int_ep->desc = &ac_int_ep_desc; + + ac_interface_desc.bNumEndpoints = 1; + } + /* allocate instance-specific endpoints */ if (EPOUT_EN(audio_opts)) { ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc); if (!ep) - goto fail; + goto err_free_fu; audio->out_ep = ep; audio->out_ep->desc = &as_out_ep_desc; } @@ -727,7 +1281,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) if (EPIN_EN(audio_opts)) { ep = usb_ep_autoconfig(cdev->gadget, &as_in_ep_desc); if (!ep) - goto fail; + goto err_free_fu; audio->in_ep = ep; audio->in_ep->desc = &as_in_ep_desc; } @@ -738,17 +1292,37 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL, NULL); if (status) - goto fail; + goto err_free_fu; audio->out_ep_maxpsize = le16_to_cpu(as_out_ep_desc.wMaxPacketSize); audio->in_ep_maxpsize = le16_to_cpu(as_in_ep_desc.wMaxPacketSize); audio->params.c_chmask = audio_opts->c_chmask; audio->params.c_srate = audio_opts->c_srate; audio->params.c_ssize = audio_opts->c_ssize; + if (FUIN_EN(audio_opts)) { + audio->params.p_fu.id = USB_IN_FU_ID; + audio->params.p_fu.mute_present = audio_opts->p_mute_present; + audio->params.p_fu.volume_present = + audio_opts->p_volume_present; + audio->params.p_fu.volume_min = audio_opts->p_volume_min; + audio->params.p_fu.volume_max = audio_opts->p_volume_max; + audio->params.p_fu.volume_res = audio_opts->p_volume_res; + } audio->params.p_chmask = audio_opts->p_chmask; audio->params.p_srate = audio_opts->p_srate; audio->params.p_ssize = audio_opts->p_ssize; + if (FUOUT_EN(audio_opts)) { + audio->params.c_fu.id = USB_OUT_FU_ID; + audio->params.c_fu.mute_present = audio_opts->c_mute_present; + audio->params.c_fu.volume_present = + audio_opts->c_volume_present; + audio->params.c_fu.volume_min = audio_opts->c_volume_min; + audio->params.c_fu.volume_max = audio_opts->c_volume_max; + audio->params.c_fu.volume_res = audio_opts->c_volume_res; + } audio->params.req_number = audio_opts->req_number; + if (FUOUT_EN(audio_opts) || FUIN_EN(audio_opts)) + audio->notify = audio_notify; status = g_audio_setup(audio, "UAC1_PCM", "UAC1_Gadget"); if (status) @@ -758,6 +1332,11 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) err_card_register: usb_free_all_descriptors(f); +err_free_fu: + kfree(out_feature_unit_desc); + out_feature_unit_desc = NULL; + kfree(in_feature_unit_desc); + in_feature_unit_desc = NULL; fail: kfree(ac_header_desc); ac_header_desc = NULL; @@ -783,7 +1362,15 @@ static struct configfs_item_operations f_uac1_item_ops = { .release = f_uac1_attr_release, }; -#define UAC1_ATTRIBUTE(name) \ +#define uac1_kstrtou32 kstrtou32 +#define uac1_kstrtos16 kstrtos16 +#define uac1_kstrtobool(s, base, res) kstrtobool((s), (res)) + +static const char *u32_fmt = "%u\n"; +static const char *s16_fmt = "%hd\n"; +static const char *bool_fmt = "%u\n"; + +#define UAC1_ATTRIBUTE(type, name) \ static ssize_t f_uac1_opts_##name##_show( \ struct config_item *item, \ char *page) \ @@ -792,7 +1379,7 @@ static ssize_t f_uac1_opts_##name##_show( \ int result; \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%u\n", opts->name); \ + result = sprintf(page, type##_fmt, opts->name); \ mutex_unlock(&opts->lock); \ \ return result; \ @@ -804,7 +1391,7 @@ static ssize_t f_uac1_opts_##name##_store( \ { \ struct f_uac1_opts *opts = to_f_uac1_opts(item); \ int ret; \ - u32 num; \ + type num; \ \ mutex_lock(&opts->lock); \ if (opts->refcnt) { \ @@ -812,7 +1399,7 @@ static ssize_t f_uac1_opts_##name##_store( \ goto end; \ } \ \ - ret = kstrtou32(page, 0, &num); \ + ret = uac1_kstrto##type(page, 0, &num); \ if (ret) \ goto end; \ \ @@ -826,13 +1413,25 @@ end: \ \ CONFIGFS_ATTR(f_uac1_opts_, name) -UAC1_ATTRIBUTE(c_chmask); -UAC1_ATTRIBUTE(c_srate); -UAC1_ATTRIBUTE(c_ssize); -UAC1_ATTRIBUTE(p_chmask); -UAC1_ATTRIBUTE(p_srate); -UAC1_ATTRIBUTE(p_ssize); -UAC1_ATTRIBUTE(req_number); +UAC1_ATTRIBUTE(u32, c_chmask); +UAC1_ATTRIBUTE(u32, c_srate); +UAC1_ATTRIBUTE(u32, c_ssize); +UAC1_ATTRIBUTE(u32, p_chmask); +UAC1_ATTRIBUTE(u32, p_srate); +UAC1_ATTRIBUTE(u32, p_ssize); +UAC1_ATTRIBUTE(u32, req_number); + +UAC1_ATTRIBUTE(bool, p_mute_present); +UAC1_ATTRIBUTE(bool, p_volume_present); +UAC1_ATTRIBUTE(s16, p_volume_min); +UAC1_ATTRIBUTE(s16, p_volume_max); +UAC1_ATTRIBUTE(s16, p_volume_res); + +UAC1_ATTRIBUTE(bool, c_mute_present); +UAC1_ATTRIBUTE(bool, c_volume_present); +UAC1_ATTRIBUTE(s16, c_volume_min); +UAC1_ATTRIBUTE(s16, c_volume_max); +UAC1_ATTRIBUTE(s16, c_volume_res); static struct configfs_attribute *f_uac1_attrs[] = { &f_uac1_opts_attr_c_chmask, @@ -842,6 +1441,19 @@ static struct configfs_attribute *f_uac1_attrs[] = { &f_uac1_opts_attr_p_srate, &f_uac1_opts_attr_p_ssize, &f_uac1_opts_attr_req_number, + + &f_uac1_opts_attr_p_mute_present, + &f_uac1_opts_attr_p_volume_present, + &f_uac1_opts_attr_p_volume_min, + &f_uac1_opts_attr_p_volume_max, + &f_uac1_opts_attr_p_volume_res, + + &f_uac1_opts_attr_c_mute_present, + &f_uac1_opts_attr_c_volume_present, + &f_uac1_opts_attr_c_volume_min, + &f_uac1_opts_attr_c_volume_max, + &f_uac1_opts_attr_c_volume_res, + NULL, }; @@ -879,6 +1491,19 @@ static struct usb_function_instance *f_audio_alloc_inst(void) opts->p_chmask = UAC1_DEF_PCHMASK; opts->p_srate = UAC1_DEF_PSRATE; opts->p_ssize = UAC1_DEF_PSSIZE; + + opts->p_mute_present = UAC1_DEF_MUTE_PRESENT; + opts->p_volume_present = UAC1_DEF_VOLUME_PRESENT; + opts->p_volume_min = UAC1_DEF_MIN_DB; + opts->p_volume_max = UAC1_DEF_MAX_DB; + opts->p_volume_res = UAC1_DEF_RES_DB; + + opts->c_mute_present = UAC1_DEF_MUTE_PRESENT; + opts->c_volume_present = UAC1_DEF_VOLUME_PRESENT; + opts->c_volume_min = UAC1_DEF_MIN_DB; + opts->c_volume_max = UAC1_DEF_MAX_DB; + opts->c_volume_res = UAC1_DEF_RES_DB; + opts->req_number = UAC1_DEF_REQ_NUM; return &opts->func_inst; } @@ -903,6 +1528,11 @@ static void f_audio_unbind(struct usb_configuration *c, struct usb_function *f) g_audio_cleanup(audio); usb_free_all_descriptors(f); + kfree(out_feature_unit_desc); + out_feature_unit_desc = NULL; + kfree(in_feature_unit_desc); + in_feature_unit_desc = NULL; + kfree(ac_header_desc); ac_header_desc = NULL; diff --git a/drivers/usb/gadget/function/u_uac1.h b/drivers/usb/gadget/function/u_uac1.h index 39c0e29e1b46..589fae861141 100644 --- a/drivers/usb/gadget/function/u_uac1.h +++ b/drivers/usb/gadget/function/u_uac1.h @@ -18,6 +18,13 @@ #define UAC1_DEF_PSRATE 48000 #define UAC1_DEF_PSSIZE 2 #define UAC1_DEF_REQ_NUM 2 +#define UAC1_DEF_INT_REQ_NUM 10 + +#define UAC1_DEF_MUTE_PRESENT 1 +#define UAC1_DEF_VOLUME_PRESENT 1 +#define UAC1_DEF_MIN_DB (-100*256) /* -100 dB */ +#define UAC1_DEF_MAX_DB 0 /* 0 dB */ +#define UAC1_DEF_RES_DB (1*256) /* 1 dB */ struct f_uac1_opts { @@ -28,6 +35,19 @@ struct f_uac1_opts { int p_chmask; int p_srate; int p_ssize; + + bool p_mute_present; + bool p_volume_present; + s16 p_volume_min; + s16 p_volume_max; + s16 p_volume_res; + + bool c_mute_present; + bool c_volume_present; + s16 c_volume_min; + s16 c_volume_max; + s16 c_volume_res; + int req_number; unsigned bound:1;