// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. */ #include #include #include #include #include "../usb/card.h" static DEFINE_MUTEX(ctx_mutex); static LIST_HEAD(usb_ctx_list); static struct device_node *snd_soc_find_phandle(struct device *dev) { struct device_node *node; node = of_parse_phandle(dev->of_node, "usb-soc-be", 0); if (!node) return ERR_PTR(-ENODEV); return node; } static struct snd_soc_usb *snd_soc_usb_ctx_lookup(struct device_node *node) { struct snd_soc_usb *ctx; if (!node) return NULL; list_for_each_entry(ctx, &usb_ctx_list, list) { if (ctx->component->dev->of_node == node) return ctx; } return NULL; } static struct snd_soc_usb *snd_soc_find_usb_ctx(struct device *dev) { struct snd_soc_usb *ctx; struct device_node *node; node = snd_soc_find_phandle(dev); if (!IS_ERR(node)) { ctx = snd_soc_usb_ctx_lookup(node); of_node_put(node); } else { ctx = snd_soc_usb_ctx_lookup(dev->of_node); } return ctx ? ctx : NULL; } /* SOC USB sound kcontrols */ /** * snd_soc_usb_setup_offload_jack() - Create USB offloading jack * @component: USB DPCM backend DAI component * @jack: jack structure to create * * Creates a jack device for notifying userspace of the availability * of an offload capable device. * * Returns 0 on success, negative on error. * */ int snd_soc_usb_setup_offload_jack(struct snd_soc_component *component, struct snd_soc_jack *jack) { int ret; ret = snd_soc_card_jack_new(component->card, "USB Offload Jack", SND_JACK_USB, jack); if (ret < 0) { dev_err(component->card->dev, "Unable to add USB offload jack: %d\n", ret); return ret; } ret = snd_soc_component_set_jack(component, jack, NULL); if (ret) { dev_err(component->card->dev, "Failed to set jack: %d\n", ret); return ret; } return 0; } EXPORT_SYMBOL_GPL(snd_soc_usb_setup_offload_jack); /** * snd_soc_usb_update_offload_route - Find active USB offload path * @dev: USB device to get offload status * @card: USB card index * @pcm: USB PCM device index * @direction: playback or capture direction * @path: pcm or card index * @route: pointer to route output array * * Fetch the current status for the USB SND card and PCM device indexes * specified. The "route" argument should be an array of integers being * used for a kcontrol output. The first element should have the selected * card index, and the second element should have the selected pcm device * index. */ int snd_soc_usb_update_offload_route(struct device *dev, int card, int pcm, int direction, enum snd_soc_usb_kctl path, long *route) { struct snd_soc_usb *ctx; int ret = -ENODEV; mutex_lock(&ctx_mutex); ctx = snd_soc_find_usb_ctx(dev); if (!ctx) goto exit; if (ctx->update_offload_route_info) ret = ctx->update_offload_route_info(ctx->component, card, pcm, direction, path, route); exit: mutex_unlock(&ctx_mutex); return ret; } EXPORT_SYMBOL_GPL(snd_soc_usb_update_offload_route); /** * snd_soc_usb_find_priv_data() - Retrieve private data stored * @usbdev: device reference * * Fetch the private data stored in the USB SND SoC structure. * */ void *snd_soc_usb_find_priv_data(struct device *usbdev) { struct snd_soc_usb *ctx; mutex_lock(&ctx_mutex); ctx = snd_soc_find_usb_ctx(usbdev); mutex_unlock(&ctx_mutex); return ctx ? ctx->priv_data : NULL; } EXPORT_SYMBOL_GPL(snd_soc_usb_find_priv_data); /** * snd_soc_usb_find_supported_format() - Check if audio format is supported * @card_idx: USB sound chip array index * @params: PCM parameters * @direction: capture or playback * * Ensure that a requested audio profile from the ASoC side is able to be * supported by the USB device. * * Return 0 on success, negative on error. * */ int snd_soc_usb_find_supported_format(int card_idx, struct snd_pcm_hw_params *params, int direction) { struct snd_usb_stream *as; as = snd_usb_find_suppported_substream(card_idx, params, direction); if (!as) return -EOPNOTSUPP; return 0; } EXPORT_SYMBOL_GPL(snd_soc_usb_find_supported_format); /** * snd_soc_usb_allocate_port() - allocate a SoC USB port for offloading support * @component: USB DPCM backend DAI component * @data: private data * * Allocate and initialize a SoC USB port. The SoC USB port is used to communicate * different USB audio devices attached, in order to start audio offloading handled * by an ASoC entity. USB device plug in/out events are signaled with a * notification, but don't directly impact the memory allocated for the SoC USB * port. * */ struct snd_soc_usb *snd_soc_usb_allocate_port(struct snd_soc_component *component, void *data) { struct snd_soc_usb *usb; usb = kzalloc(sizeof(*usb), GFP_KERNEL); if (!usb) return ERR_PTR(-ENOMEM); usb->component = component; usb->priv_data = data; return usb; } EXPORT_SYMBOL_GPL(snd_soc_usb_allocate_port); /** * snd_soc_usb_free_port() - free a SoC USB port used for offloading support * @usb: allocated SoC USB port * * Free and remove the SoC USB port from the available list of ports. This will * ensure that the communication between USB SND and ASoC is halted. * */ void snd_soc_usb_free_port(struct snd_soc_usb *usb) { snd_soc_usb_remove_port(usb); kfree(usb); } EXPORT_SYMBOL_GPL(snd_soc_usb_free_port); /** * snd_soc_usb_add_port() - Add a USB backend port * @usb: soc usb port to add * * Register a USB backend DAI link to the USB SoC framework. Memory is allocated * as part of the USB backend DAI link. * */ void snd_soc_usb_add_port(struct snd_soc_usb *usb) { mutex_lock(&ctx_mutex); list_add_tail(&usb->list, &usb_ctx_list); mutex_unlock(&ctx_mutex); snd_usb_rediscover_devices(); } EXPORT_SYMBOL_GPL(snd_soc_usb_add_port); /** * snd_soc_usb_remove_port() - Remove a USB backend port * @usb: soc usb port to remove * * Remove a USB backend DAI link from USB SoC. Memory is freed when USB backend * DAI is removed, or when snd_soc_usb_free_port() is called. * */ void snd_soc_usb_remove_port(struct snd_soc_usb *usb) { struct snd_soc_usb *ctx, *tmp; mutex_lock(&ctx_mutex); list_for_each_entry_safe(ctx, tmp, &usb_ctx_list, list) { if (ctx == usb) { list_del(&ctx->list); break; } } mutex_unlock(&ctx_mutex); } EXPORT_SYMBOL_GPL(snd_soc_usb_remove_port); /** * snd_soc_usb_connect() - Notification of USB device connection * @usbdev: USB bus device * @sdev: USB SND device to add * * Notify of a new USB SND device connection. The sdev->card_idx can be used to * handle how the DPCM backend selects, which device to enable USB offloading * on. * */ int snd_soc_usb_connect(struct device *usbdev, struct snd_soc_usb_device *sdev) { struct snd_soc_usb *ctx; if (!usbdev) return -ENODEV; mutex_lock(&ctx_mutex); ctx = snd_soc_find_usb_ctx(usbdev); if (!ctx) goto exit; if (ctx->connection_status_cb) ctx->connection_status_cb(ctx, sdev, true); exit: mutex_unlock(&ctx_mutex); return 0; } EXPORT_SYMBOL_GPL(snd_soc_usb_connect); /** * snd_soc_usb_disconnect() - Notification of USB device disconnection * @usbdev: USB bus device * @sdev: USB SND device to remove * * Notify of a new USB SND device disconnection to the USB backend. * */ int snd_soc_usb_disconnect(struct device *usbdev, struct snd_soc_usb_device *sdev) { struct snd_soc_usb *ctx; if (!usbdev) return -ENODEV; mutex_lock(&ctx_mutex); ctx = snd_soc_find_usb_ctx(usbdev); if (!ctx) goto exit; if (ctx->connection_status_cb) ctx->connection_status_cb(ctx, sdev, false); exit: mutex_unlock(&ctx_mutex); return 0; } EXPORT_SYMBOL_GPL(snd_soc_usb_disconnect); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("SoC USB driver for offloading");