[PATCH v15 41/50] ASoC: Add SND kcontrol for fetching USB offload status

From: Wesley Cheng
Date: Mon Feb 12 2024 - 20:34:00 EST


Add a kcontrol to the platform sound card to fetch the current offload
status. This can allow for userspace to ensure/check which USB SND
resources are actually busy versus having to attempt opening the USB SND
devices, which will result in an error if offloading is active.

An example of fetching the USB offloading status would look like:
tinymix -D 0 get 'USB Offload Playback Route Status'
-1, -1 (range -1->32) --> [Offload is idle]

tinymix -D 0 get 'USB Offload Playback Route Status'
1, 0 (range -1->32) --> [Offload active on card#1 pcm#0]

Signed-off-by: Wesley Cheng <quic_wcheng@xxxxxxxxxxx>
---
include/sound/soc-usb.h | 46 ++++++++++++
sound/soc/soc-usb.c | 150 +++++++++++++++++++++++++++++++++++++++-
2 files changed, 194 insertions(+), 2 deletions(-)

diff --git a/include/sound/soc-usb.h b/include/sound/soc-usb.h
index 7b0531f975c2..41e59892c360 100644
--- a/include/sound/soc-usb.h
+++ b/include/sound/soc-usb.h
@@ -6,6 +6,24 @@
#ifndef __LINUX_SND_SOC_USB_H
#define __LINUX_SND_SOC_USB_H

+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
@@ -25,6 +43,8 @@ struct snd_soc_usb_device {
* @list - list head for SND SOC struct list
* @dev - USB backend device reference
* @component - reference to ASoC component
+ * @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
* @get_offload_dev - callback to fetch selected USB sound card/PCM device
@@ -33,6 +53,8 @@ struct snd_soc_usb_device {
struct snd_soc_usb {
struct list_head list;
struct snd_soc_component *component;
+ 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);
int (*put_offload_dev)(struct snd_kcontrol *kcontrol,
@@ -51,6 +73,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_supported_streams, void *data);
void snd_soc_usb_free_port(struct snd_soc_usb *usb);
@@ -86,6 +113,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_supported_streams, void *data)
diff --git a/sound/soc/soc-usb.c b/sound/soc/soc-usb.c
index a55d1c509297..d3183c0d3844 100644
--- a/sound/soc/soc-usb.c
+++ b/sound/soc/soc-usb.c
@@ -42,11 +42,62 @@ 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_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 card_idx;
+ int i;
+
+ for (i = 0; i < ctx->num_supported_streams; i++) {
+ card_idx = -1;
+ pcm_idx = -1;
+
+ if (ctx->active_list[i].state == SND_SOC_USB_RUNNING) {
+ card_idx = ctx->active_list[i].active_card_idx;
+ pcm_idx = ctx->active_list[i].active_pcm_idx;
+ }
+
+ ucontrol->value.integer.value[control_idx] = card_idx;
+ control_idx++;
+ ucontrol->value.integer.value[control_idx] = pcm_idx;
+ control_idx++;
+ }
+
+ return 0;
+}
+
+static int snd_soc_usb_offload_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 = 2*ctx->num_supported_streams;
+ uinfo->value.integer.min = -1;
+ uinfo->value.integer.max = SNDRV_CARDS;
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new soc_usb_status_ctrl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .name = "USB Offload Playback Route Status",
+ .info = snd_soc_usb_offload_status_info,
+ .get = snd_soc_usb_get_offload_status,
+ .put = NULL,
+};
+
static int soc_usb_put_offload_dev(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_usb_find_priv_data(component->dev);
+ struct snd_soc_usb *ctx = snd_soc_find_usb_ctx(component->dev->of_node);
int ret = 0;

mutex_lock(&ctx_mutex);
@@ -61,7 +112,7 @@ static int soc_usb_get_offload_dev(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_usb_find_priv_data(component->dev);
+ struct snd_soc_usb *ctx = snd_soc_find_usb_ctx(component->dev->of_node);
int ret = 0;

mutex_lock(&ctx_mutex);
@@ -95,10 +146,96 @@ static const struct snd_kcontrol_new soc_usb_dev_ctrl = {

static int snd_soc_usb_control_init(struct snd_soc_component *component)
{
+ int ret;
+
+ ret = snd_ctl_add(component->card->snd_card,
+ snd_ctl_new1(&soc_usb_status_ctrl, component));
+ if (ret < 0)
+ return ret;
+
return snd_ctl_add(component->card->snd_card,
snd_ctl_new1(&soc_usb_dev_ctrl, component));
}

+/**
+ * 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
@@ -185,8 +322,16 @@ 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;

return usb;
}
@@ -202,6 +347,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);