| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved.
- */
- #include <linux/of.h>
- #include <linux/usb.h>
- #include <sound/jack.h>
- #include <sound/soc-usb.h>
- #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_obj(*usb);
- 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");
|