// SPDX-License-Identifier: GPL-2.01/*2* Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved.3*/4#include <linux/of.h>5#include <linux/usb.h>67#include <sound/jack.h>8#include <sound/soc-usb.h>910#include "../usb/card.h"1112static DEFINE_MUTEX(ctx_mutex);13static LIST_HEAD(usb_ctx_list);1415static struct device_node *snd_soc_find_phandle(struct device *dev)16{17struct device_node *node;1819node = of_parse_phandle(dev->of_node, "usb-soc-be", 0);20if (!node)21return ERR_PTR(-ENODEV);2223return node;24}2526static struct snd_soc_usb *snd_soc_usb_ctx_lookup(struct device_node *node)27{28struct snd_soc_usb *ctx;2930if (!node)31return NULL;3233list_for_each_entry(ctx, &usb_ctx_list, list) {34if (ctx->component->dev->of_node == node)35return ctx;36}3738return NULL;39}4041static struct snd_soc_usb *snd_soc_find_usb_ctx(struct device *dev)42{43struct snd_soc_usb *ctx;44struct device_node *node;4546node = snd_soc_find_phandle(dev);47if (!IS_ERR(node)) {48ctx = snd_soc_usb_ctx_lookup(node);49of_node_put(node);50} else {51ctx = snd_soc_usb_ctx_lookup(dev->of_node);52}5354return ctx ? ctx : NULL;55}5657/* SOC USB sound kcontrols */58/**59* snd_soc_usb_setup_offload_jack() - Create USB offloading jack60* @component: USB DPCM backend DAI component61* @jack: jack structure to create62*63* Creates a jack device for notifying userspace of the availability64* of an offload capable device.65*66* Returns 0 on success, negative on error.67*68*/69int snd_soc_usb_setup_offload_jack(struct snd_soc_component *component,70struct snd_soc_jack *jack)71{72int ret;7374ret = snd_soc_card_jack_new(component->card, "USB Offload Jack",75SND_JACK_USB, jack);76if (ret < 0) {77dev_err(component->card->dev, "Unable to add USB offload jack: %d\n",78ret);79return ret;80}8182ret = snd_soc_component_set_jack(component, jack, NULL);83if (ret) {84dev_err(component->card->dev, "Failed to set jack: %d\n", ret);85return ret;86}8788return 0;89}90EXPORT_SYMBOL_GPL(snd_soc_usb_setup_offload_jack);9192/**93* snd_soc_usb_update_offload_route - Find active USB offload path94* @dev: USB device to get offload status95* @card: USB card index96* @pcm: USB PCM device index97* @direction: playback or capture direction98* @path: pcm or card index99* @route: pointer to route output array100*101* Fetch the current status for the USB SND card and PCM device indexes102* specified. The "route" argument should be an array of integers being103* used for a kcontrol output. The first element should have the selected104* card index, and the second element should have the selected pcm device105* index.106*/107int snd_soc_usb_update_offload_route(struct device *dev, int card, int pcm,108int direction, enum snd_soc_usb_kctl path,109long *route)110{111struct snd_soc_usb *ctx;112int ret = -ENODEV;113114mutex_lock(&ctx_mutex);115ctx = snd_soc_find_usb_ctx(dev);116if (!ctx)117goto exit;118119if (ctx->update_offload_route_info)120ret = ctx->update_offload_route_info(ctx->component, card, pcm,121direction, path, route);122exit:123mutex_unlock(&ctx_mutex);124125return ret;126}127EXPORT_SYMBOL_GPL(snd_soc_usb_update_offload_route);128129/**130* snd_soc_usb_find_priv_data() - Retrieve private data stored131* @usbdev: device reference132*133* Fetch the private data stored in the USB SND SoC structure.134*135*/136void *snd_soc_usb_find_priv_data(struct device *usbdev)137{138struct snd_soc_usb *ctx;139140mutex_lock(&ctx_mutex);141ctx = snd_soc_find_usb_ctx(usbdev);142mutex_unlock(&ctx_mutex);143144return ctx ? ctx->priv_data : NULL;145}146EXPORT_SYMBOL_GPL(snd_soc_usb_find_priv_data);147148/**149* snd_soc_usb_find_supported_format() - Check if audio format is supported150* @card_idx: USB sound chip array index151* @params: PCM parameters152* @direction: capture or playback153*154* Ensure that a requested audio profile from the ASoC side is able to be155* supported by the USB device.156*157* Return 0 on success, negative on error.158*159*/160int snd_soc_usb_find_supported_format(int card_idx,161struct snd_pcm_hw_params *params,162int direction)163{164struct snd_usb_stream *as;165166as = snd_usb_find_suppported_substream(card_idx, params, direction);167if (!as)168return -EOPNOTSUPP;169170return 0;171}172EXPORT_SYMBOL_GPL(snd_soc_usb_find_supported_format);173174/**175* snd_soc_usb_allocate_port() - allocate a SoC USB port for offloading support176* @component: USB DPCM backend DAI component177* @data: private data178*179* Allocate and initialize a SoC USB port. The SoC USB port is used to communicate180* different USB audio devices attached, in order to start audio offloading handled181* by an ASoC entity. USB device plug in/out events are signaled with a182* notification, but don't directly impact the memory allocated for the SoC USB183* port.184*185*/186struct snd_soc_usb *snd_soc_usb_allocate_port(struct snd_soc_component *component,187void *data)188{189struct snd_soc_usb *usb;190191usb = kzalloc(sizeof(*usb), GFP_KERNEL);192if (!usb)193return ERR_PTR(-ENOMEM);194195usb->component = component;196usb->priv_data = data;197198return usb;199}200EXPORT_SYMBOL_GPL(snd_soc_usb_allocate_port);201202/**203* snd_soc_usb_free_port() - free a SoC USB port used for offloading support204* @usb: allocated SoC USB port205*206* Free and remove the SoC USB port from the available list of ports. This will207* ensure that the communication between USB SND and ASoC is halted.208*209*/210void snd_soc_usb_free_port(struct snd_soc_usb *usb)211{212snd_soc_usb_remove_port(usb);213kfree(usb);214}215EXPORT_SYMBOL_GPL(snd_soc_usb_free_port);216217/**218* snd_soc_usb_add_port() - Add a USB backend port219* @usb: soc usb port to add220*221* Register a USB backend DAI link to the USB SoC framework. Memory is allocated222* as part of the USB backend DAI link.223*224*/225void snd_soc_usb_add_port(struct snd_soc_usb *usb)226{227mutex_lock(&ctx_mutex);228list_add_tail(&usb->list, &usb_ctx_list);229mutex_unlock(&ctx_mutex);230231snd_usb_rediscover_devices();232}233EXPORT_SYMBOL_GPL(snd_soc_usb_add_port);234235/**236* snd_soc_usb_remove_port() - Remove a USB backend port237* @usb: soc usb port to remove238*239* Remove a USB backend DAI link from USB SoC. Memory is freed when USB backend240* DAI is removed, or when snd_soc_usb_free_port() is called.241*242*/243void snd_soc_usb_remove_port(struct snd_soc_usb *usb)244{245struct snd_soc_usb *ctx, *tmp;246247mutex_lock(&ctx_mutex);248list_for_each_entry_safe(ctx, tmp, &usb_ctx_list, list) {249if (ctx == usb) {250list_del(&ctx->list);251break;252}253}254mutex_unlock(&ctx_mutex);255}256EXPORT_SYMBOL_GPL(snd_soc_usb_remove_port);257258/**259* snd_soc_usb_connect() - Notification of USB device connection260* @usbdev: USB bus device261* @sdev: USB SND device to add262*263* Notify of a new USB SND device connection. The sdev->card_idx can be used to264* handle how the DPCM backend selects, which device to enable USB offloading265* on.266*267*/268int snd_soc_usb_connect(struct device *usbdev, struct snd_soc_usb_device *sdev)269{270struct snd_soc_usb *ctx;271272if (!usbdev)273return -ENODEV;274275mutex_lock(&ctx_mutex);276ctx = snd_soc_find_usb_ctx(usbdev);277if (!ctx)278goto exit;279280if (ctx->connection_status_cb)281ctx->connection_status_cb(ctx, sdev, true);282283exit:284mutex_unlock(&ctx_mutex);285286return 0;287}288EXPORT_SYMBOL_GPL(snd_soc_usb_connect);289290/**291* snd_soc_usb_disconnect() - Notification of USB device disconnection292* @usbdev: USB bus device293* @sdev: USB SND device to remove294*295* Notify of a new USB SND device disconnection to the USB backend.296*297*/298int snd_soc_usb_disconnect(struct device *usbdev, struct snd_soc_usb_device *sdev)299{300struct snd_soc_usb *ctx;301302if (!usbdev)303return -ENODEV;304305mutex_lock(&ctx_mutex);306ctx = snd_soc_find_usb_ctx(usbdev);307if (!ctx)308goto exit;309310if (ctx->connection_status_cb)311ctx->connection_status_cb(ctx, sdev, false);312313exit:314mutex_unlock(&ctx_mutex);315316return 0;317}318EXPORT_SYMBOL_GPL(snd_soc_usb_disconnect);319320MODULE_LICENSE("GPL");321MODULE_DESCRIPTION("SoC USB driver for offloading");322323324