diff mbox series

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

Message ID 20240213005422.3121-42-quic_wcheng@quicinc.com (mailing list archive)
State Superseded
Headers show
Series Introduce QC USB SND audio offloading support | expand

Commit Message

Wesley Cheng Feb. 13, 2024, 12:54 a.m. UTC
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@quicinc.com>
---
 include/sound/soc-usb.h |  46 ++++++++++++
 sound/soc/soc-usb.c     | 150 +++++++++++++++++++++++++++++++++++++++-
 2 files changed, 194 insertions(+), 2 deletions(-)

Comments

Takashi Iwai Feb. 13, 2024, 12:10 p.m. UTC | #1
On Tue, 13 Feb 2024 01:54:13 +0100,
Wesley Cheng wrote:
> 
> 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]

Ah, I didn't notice until now that the second value is the PCM index.

> +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;

Then it's bogus to set SNDRV_CARDS as max.  The PCM index number is
independent from the card number.  In theory, it can be even more than
the card max (very unlikely, though).

Wouldn't it be more intuitive to provide two different controls, one
for card number and one for PCM index number?


thanks,

Takashi
Wesley Cheng Feb. 13, 2024, 10:58 p.m. UTC | #2
Hi Takashi,

On 2/13/2024 4:10 AM, Takashi Iwai wrote:
> On Tue, 13 Feb 2024 01:54:13 +0100,
> Wesley Cheng wrote:
>>
>> 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]
> 
> Ah, I didn't notice until now that the second value is the PCM index.
> 
>> +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;
> 
> Then it's bogus to set SNDRV_CARDS as max.  The PCM index number is
> independent from the card number.  In theory, it can be even more than
> the card max (very unlikely, though).
> 

I don't think its technically capped anywhere :).  I just used 
SNDRV_CARDS to cap the sound card number.  If I split this as a separate 
entity, then I'll need to change the max value for the PCM dev.

> Wouldn't it be more intuitive to provide two different controls, one
> for card number and one for PCM index number?
> 

Sure, I can split this up.

Thanks
Wesley Cheng
diff mbox series

Patch

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);