blob: 26baa66d29a87b2d0d4d77a6f272a1850b7ed2e3 [file] [log] [blame]
// 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(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");