From patchwork Thu Apr 25 21:51:15 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Wesley Cheng X-Patchwork-Id: 13643934 Received: from mx0a-0031df01.pphosted.com (mx0a-0031df01.pphosted.com [205.220.168.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B2DFF17AD97; Thu, 25 Apr 2024 21:52:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=205.220.168.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1714081936; cv=none; b=Vzp+nz3lFC5bJzGCuEZz2q/2nRKlWYufnp4DbJsGunK9PTaga/gCskxG7ScI2hjaXrpVrhw7XLSAVWWg3WZM708+SBbpttQdPZ9f+sI4YNWCPdGFGrWBa/xCzu70jr7UTYltEsreYJr5U1eYLE6E1o4V3gUoYvVTRFGeO4owBb0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1714081936; c=relaxed/simple; bh=N23dg8al6HbBLCda6qJ0oVDvGHdmFr2dXoi++liPoTE=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=mG/EmYduw0oW2H8KfpvKm1GE/1RsdPz7BGH5kIeCOcIwgIVPbl8pjxwjSYIhHyCONesDR1KXO5DT+LDynAF5OXrx5gHjzQZ8SxWGIYOwGAiehTnj66eUdozxal8snw+Ayy+VtmVsVJ/+yf/Cgt/U5Sx/hs8UqODcNlZ4cRQ1oeg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=quicinc.com; spf=pass smtp.mailfrom=quicinc.com; dkim=pass (2048-bit key) header.d=quicinc.com header.i=@quicinc.com header.b=mpPGirdK; arc=none smtp.client-ip=205.220.168.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=quicinc.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=quicinc.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=quicinc.com header.i=@quicinc.com header.b="mpPGirdK" Received: from pps.filterd (m0279867.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 43PLZKf2012743; Thu, 25 Apr 2024 21:51:47 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=quicinc.com; h= from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-type; s=qcppdkim1; bh=1+LksB4NEmAs1IJ5/76C 60ccidbJQS1B+QKm101TG6k=; b=mpPGirdKksLgBg1DvPkxCbtYWDaOSWYJZu40 3BKxbVPv+IkSS7sLssvRuUpw+D66heTdA8bszTFsHtStIhboxAUrCG0f7Kg74OKK 43YGeSWIUm3ariWqPzJXYr7IejZSPqOUaDf3uRVq6H0Wg5BXjhRM3sXlLfKElXPX r6zvLD3IxKjcx5j4bMTa8idvz3hRibaxSWyUyYud9ldnkXnX7bw1WPiGMLCRMDeg y4Dc+yPxMwrBIsm+KuhXp/ClwKPa9KMUfd67viUVoEDAKi3GDDeR4BfUSffr7rO7 LuMevOy/xW2dvZ19AjBXzZYPHr2ONlpPBxtMLS13D7Cit6NBPw== Received: from nalasppmta01.qualcomm.com (Global_NAT1.qualcomm.com [129.46.96.20]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 3xqenhuthr-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Thu, 25 Apr 2024 21:51:46 +0000 (GMT) Received: from nalasex01b.na.qualcomm.com (nalasex01b.na.qualcomm.com [10.47.209.197]) by NALASPPMTA01.qualcomm.com (8.17.1.5/8.17.1.5) with ESMTPS id 43PLpjb2011220 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Thu, 25 Apr 2024 21:51:45 GMT Received: from hu-wcheng-lv.qualcomm.com (10.49.16.6) by nalasex01b.na.qualcomm.com (10.47.209.197) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1544.9; Thu, 25 Apr 2024 14:51:45 -0700 From: Wesley Cheng To: , , , , , , , , , , , , , , CC: , , , , , , , Wesley Cheng Subject: [PATCH v20 31/41] ASoC: Introduce SND kcontrols to track USB offloading state Date: Thu, 25 Apr 2024 14:51:15 -0700 Message-ID: <20240425215125.29761-32-quic_wcheng@quicinc.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20240425215125.29761-1-quic_wcheng@quicinc.com> References: <20240425215125.29761-1-quic_wcheng@quicinc.com> Precedence: bulk X-Mailing-List: linux-usb@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-ClientProxiedBy: nalasex01a.na.qualcomm.com (10.47.209.196) To nalasex01b.na.qualcomm.com (10.47.209.197) X-QCInternal: smtphost X-Proofpoint-Virus-Version: vendor=nai engine=6200 definitions=5800 signatures=585085 X-Proofpoint-ORIG-GUID: NBwhXTXQNoZvzYAk_8dn9Tcsr2FTFHTf X-Proofpoint-GUID: NBwhXTXQNoZvzYAk_8dn9Tcsr2FTFHTf X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1011,Hydra:6.0.650,FMLib:17.11.176.26 definitions=2024-04-25_21,2024-04-25_01,2023-05-22_02 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 impostorscore=0 mlxscore=0 lowpriorityscore=0 bulkscore=0 malwarescore=0 adultscore=0 clxscore=1015 priorityscore=1501 phishscore=0 mlxlogscore=999 suspectscore=0 spamscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.19.0-2404010003 definitions=main-2404250159 Expose helpers in the SoC USB layer so components can update and keep track of the offloading sessions. This exposes a kcontrol to userspace, so that applications can be aware of what the current USB offloading status is. An example output using tinymix is: USB offloading idle: tinymix -D 0 get 'USB Offload Playback Route Status' -->-1, -1 (range -1->32) USB offloading active(USB card#1 pcm#0): tinymix -D 0 get 'USB Offload Playback Route Status' -->1, 0 (range -1->32) Signed-off-by: Wesley Cheng --- include/sound/soc-usb.h | 46 +++++++++++ sound/soc/soc-usb.c | 176 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 222 insertions(+) diff --git a/include/sound/soc-usb.h b/include/sound/soc-usb.h index 18cdc59df9aa..5e6076f65a41 100644 --- a/include/sound/soc-usb.h +++ b/include/sound/soc-usb.h @@ -9,9 +9,29 @@ enum snd_soc_usb_kctl { SND_SOC_USB_KCTL_CARD_ROUTE, SND_SOC_USB_KCTL_PCM_ROUTE, + SND_SOC_USB_KCTL_CARD_STATUS, + SND_SOC_USB_KCTL_PCM_STATUS, SND_SOC_USB_KCTL_MAX, }; +enum snd_soc_usb_dai_state { + SND_SOC_USB_IDLE, + SND_SOC_USB_PREPARED, + SND_SOC_USB_RUNNING, +}; + +/** + * struct snd_soc_usb_session + * @active_card_idx - active offloaded sound card + * @active_pcm_idx - active offloaded PCM device + * @state - USB BE DAI link PCM state + */ +struct snd_soc_usb_session { + int active_card_idx; + int active_pcm_idx; + enum snd_soc_usb_dai_state state; +}; + /** * struct snd_soc_usb_device * @card_idx - sound card index associated with USB device @@ -31,6 +51,7 @@ struct snd_soc_usb_device { * @list - list head for SND SOC struct list * @component - reference to ASoC component * @kctl - list of kcontrols created + * @active_list - active sessions * @num_supported_streams - number of supported concurrent sessions * @connection_status_cb - callback to notify connection events * @put_offload_dev - callback to select USB sound card/PCM device @@ -41,6 +62,7 @@ struct snd_soc_usb { struct list_head list; struct snd_soc_component *component; struct snd_kcontrol *kctl[SND_SOC_USB_KCTL_MAX]; + struct snd_soc_usb_session *active_list; unsigned int num_supported_streams; int (*connection_status_cb)(struct snd_soc_usb *usb, struct snd_soc_usb_device *sdev, bool connected); @@ -62,6 +84,11 @@ int snd_soc_usb_connect(struct device *usbdev, struct snd_soc_usb_device *sdev); int snd_soc_usb_disconnect(struct device *usbdev, struct snd_soc_usb_device *sdev); void *snd_soc_usb_find_priv_data(struct device *dev); +int snd_soc_usb_prepare_session(struct snd_soc_usb *usb, int card_idx, int pcm_idx); +int snd_soc_usb_shutdown_session(struct snd_soc_usb *usb, int session_id); +int snd_soc_usb_set_session_state(struct snd_soc_usb *usb, int session_id, + enum snd_soc_usb_dai_state state); + struct snd_soc_usb *snd_soc_usb_allocate_port(struct snd_soc_component *component, int num_streams, void *data); void snd_soc_usb_free_port(struct snd_soc_usb *usb); @@ -97,6 +124,25 @@ static inline void *snd_soc_usb_find_priv_data(struct device *dev) return NULL; } +static inline int snd_soc_usb_prepare_session(struct snd_soc_usb *usb, int card_idx, + int pcm_idx) +{ + return -EINVAL; +} + +static inline int snd_soc_usb_shutdown_session(struct snd_soc_usb *usb, + int session_id) +{ + return -EINVAL; +} + +static inline int snd_soc_usb_set_session_state(struct snd_soc_usb *usb, + int session_id, + enum snd_soc_usb_dai_state state) +{ + return -EINVAL; +} + static inline struct snd_soc_usb *snd_soc_usb_allocate_port( struct snd_soc_component *component, int num_streams, void *data) diff --git a/sound/soc/soc-usb.c b/sound/soc/soc-usb.c index ade09b416d45..e291f146a79d 100644 --- a/sound/soc/soc-usb.c +++ b/sound/soc/soc-usb.c @@ -42,6 +42,79 @@ static struct snd_soc_usb *snd_soc_find_usb_ctx(struct device_node *node) } /* SOC USB sound kcontrols */ +static int snd_soc_usb_get_offload_card_status(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct snd_soc_usb *ctx = snd_soc_find_usb_ctx(component->dev->of_node); + int control_idx = 0; + int card_idx; + int i; + + for (i = 0; i < ctx->num_supported_streams; i++) { + card_idx = -1; + + if (ctx->active_list[i].state == SND_SOC_USB_RUNNING) + card_idx = ctx->active_list[i].active_card_idx; + + ucontrol->value.integer.value[control_idx] = card_idx; + control_idx++; + } + + return 0; +} + +static int snd_soc_usb_offload_card_status_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct snd_soc_usb *ctx = snd_soc_find_usb_ctx(component->dev->of_node); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = ctx->num_supported_streams; + uinfo->value.integer.min = -1; + uinfo->value.integer.max = SNDRV_CARDS; + + return 0; +} + +static int snd_soc_usb_get_offload_pcm_status(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct snd_soc_usb *ctx = snd_soc_find_usb_ctx(component->dev->of_node); + int control_idx = 0; + int pcm_idx; + int i; + + for (i = 0; i < ctx->num_supported_streams; i++) { + pcm_idx = -1; + + if (ctx->active_list[i].state == SND_SOC_USB_RUNNING) + pcm_idx = ctx->active_list[i].active_pcm_idx; + + ucontrol->value.integer.value[control_idx] = pcm_idx; + control_idx++; + } + + return 0; +} + +static int snd_soc_usb_offload_pcm_status_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct snd_soc_usb *ctx = snd_soc_find_usb_ctx(component->dev->of_node); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = ctx->num_supported_streams; + uinfo->value.integer.min = -1; + /* Arbitrary max value, as there is no 'limit' on number of PCM devices */ + uinfo->value.integer.max = 0xff; + + return 0; +} + static int soc_usb_put_offload_pcm_dev(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -152,6 +225,22 @@ static const struct snd_kcontrol_new soc_usb_kcontrols[] = { .get = soc_usb_get_offload_pcm_dev, .put = soc_usb_put_offload_pcm_dev, }, + [SND_SOC_USB_KCTL_CARD_STATUS] = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .name = "USB Offload Playback Route Card Status", + .info = snd_soc_usb_offload_card_status_info, + .get = snd_soc_usb_get_offload_card_status, + .put = NULL, + }, + [SND_SOC_USB_KCTL_PCM_STATUS] = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .name = "USB Offload Playback Route PCM Status", + .info = snd_soc_usb_offload_pcm_status_info, + .get = snd_soc_usb_get_offload_pcm_status, + .put = NULL, + }, }; static int snd_soc_usb_control_remove(struct snd_soc_usb *usb) @@ -187,6 +276,85 @@ static int snd_soc_usb_control_init(struct snd_soc_usb *usb) return ret; } +/** + * snd_soc_usb_set_session_state() - Set the session state for a session + * @usb: SOC USB device + * @session_id: index to active_list + * @state: USB PCM device index + * + * Set the session state for an entry in active_list. This should be only + * called after snd_soc_usb_prepare_session. + * + * Returns 0 on success, negative on error. + * + */ +int snd_soc_usb_set_session_state(struct snd_soc_usb *usb, int session_id, + enum snd_soc_usb_dai_state state) +{ + if (session_id < 0 || session_id >= usb->num_supported_streams) + return -EINVAL; + + mutex_lock(&ctx_mutex); + if (usb->active_list[session_id].state == state) { + mutex_unlock(&ctx_mutex); + return 0; + } + + usb->active_list[session_id].state = state; + mutex_unlock(&ctx_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_usb_set_session_state); + +/** + * snd_soc_usb_prepare_session() - Find and prepare a session + * @usb: SOC USB device + * @card_idx: USB card index + * @pcm_idx: USB PCM device index + * + * Find an open active session slot on the SOC USB device. If all slots + * are busy, return an error. If not, claim the slot and place it into + * the SND_SOC_USB_PREPARED state. This should be called first before + * calling snd_soc_usb_set_session_state or snd_soc_usb_shutdown_session. + * + * Returns the session id (index) to active_list, negative on error. + * + */ +int snd_soc_usb_prepare_session(struct snd_soc_usb *usb, int card_idx, int pcm_idx) +{ + int i; + + mutex_lock(&ctx_mutex); + for (i = 0; i < usb->num_supported_streams; i++) { + if (usb->active_list[i].state == SND_SOC_USB_IDLE) { + usb->active_list[i].active_card_idx = card_idx; + usb->active_list[i].active_pcm_idx = pcm_idx; + usb->active_list[i].state = SND_SOC_USB_PREPARED; + mutex_unlock(&ctx_mutex); + return i; + } + } + mutex_unlock(&ctx_mutex); + + return -EBUSY; +} +EXPORT_SYMBOL_GPL(snd_soc_usb_prepare_session); + +/** + * snd_soc_usb_shutdown_session() - Set USB SOC to idle state + * @usb: SOC USB device + * @session_id: index to active_list + * + * Place the session specified by session_id into the idle/shutdown state. + * + */ +int snd_soc_usb_shutdown_session(struct snd_soc_usb *usb, int session_id) +{ + return snd_soc_usb_set_session_state(usb, session_id, SND_SOC_USB_IDLE); +} +EXPORT_SYMBOL_GPL(snd_soc_usb_shutdown_session); + /** * snd_soc_usb_get_components_tag() - Retrieve SOC USB component tag * @playback: direction of audio stream @@ -273,6 +441,13 @@ struct snd_soc_usb *snd_soc_usb_allocate_port(struct snd_soc_component *componen if (!usb) return ERR_PTR(-ENOMEM); + usb->active_list = kcalloc(num_streams, sizeof(struct snd_soc_usb_session), + GFP_KERNEL); + if (!usb->active_list) { + kfree(usb); + return ERR_PTR(-ENOMEM); + } + usb->component = component; usb->priv_data = data; usb->num_supported_streams = num_streams; @@ -291,6 +466,7 @@ EXPORT_SYMBOL_GPL(snd_soc_usb_allocate_port); void snd_soc_usb_free_port(struct snd_soc_usb *usb) { snd_soc_usb_remove_port(usb); + kfree(usb->active_list); kfree(usb); } EXPORT_SYMBOL_GPL(snd_soc_usb_free_port);