diff mbox series

[v26,23/33] ASoC: qcom: qdsp6: Fetch USB offload mapped card and PCM device

Message ID 20240829194105.1504814-24-quic_wcheng@quicinc.com
State Superseded
Headers show
Series Introduce QC USB SND audio offloading support | expand

Commit Message

Wesley Cheng Aug. 29, 2024, 7:40 p.m. UTC
The USB SND path may need to know how the USB offload path is routed, so
that applications can open the proper sound card and PCM device.  The
implementation for the QC ASoC design has a "USB Mixer" kcontrol for each
possible FE (Q6ASM) DAI, which can be utilized to know which front end link
is enabled.

When an application/userspace queries for the mapped offload devices, the
logic will lookup the USB mixer status though the following path:

MultiMedia* <-> MM_DL* <-> USB Mixer*

The "USB Mixer" is a DAPM widget, and the q6routing entity will set the
DAPM connect status accordingly if the USB mixer is enabled.  If enabled,
the Q6USB backend link can fetch the PCM device number from the FE DAI
link (Multimedia*).  With respects to the card number, that is
straightforward, as the ASoC components have direct references to the ASoC
platform sound card.

An example output can be shown below:

Number of controls: 9
name                                    value
Capture Channel Map                     0, 0 (range 0->36)
Playback Channel Map                    0, 0 (range 0->36)
Headset Capture Switch                  On
Headset Capture Volume                  1 (range 0->4)
Sidetone Playback Switch                On
Sidetone Playback Volume                4096 (range 0->8192)
Headset Playback Switch                 On
Headset Playback Volume                 20, 20 (range 0->24)
USB Offload Playback Route PCM#0        0, 1 (range -1->255)

The "USB Offload Playback Route PCM#*" kcontrol will signify the
corresponding card and pcm device it is offload to. (card#0 pcm - device#1)
If the USB SND device supports multiple audio interfaces, then it will
contain several PCM streams, hence in those situations, it is expected
that there will be multiple playback route kcontrols created.

Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com>
---
 sound/soc/qcom/qdsp6/q6usb.c | 104 +++++++++++++++++++++++++++++++++++
 1 file changed, 104 insertions(+)

Comments

Pierre-Louis Bossart Aug. 30, 2024, 9:34 a.m. UTC | #1
On 8/29/24 21:40, Wesley Cheng wrote:
> The USB SND path may need to know how the USB offload path is routed, so
> that applications can open the proper sound card and PCM device.  The
> implementation for the QC ASoC design has a "USB Mixer" kcontrol for each
> possible FE (Q6ASM) DAI, which can be utilized to know which front end link
> is enabled.
> 
> When an application/userspace queries for the mapped offload devices, the
> logic will lookup the USB mixer status though the following path:
> 
> MultiMedia* <-> MM_DL* <-> USB Mixer*
> 
> The "USB Mixer" is a DAPM widget, and the q6routing entity will set the
> DAPM connect status accordingly if the USB mixer is enabled.  If enabled,
> the Q6USB backend link can fetch the PCM device number from the FE DAI
> link (Multimedia*).  With respects to the card number, that is
> straightforward, as the ASoC components have direct references to the ASoC
> platform sound card.
> 
> An example output can be shown below:
> 
> Number of controls: 9
> name                                    value
> Capture Channel Map                     0, 0 (range 0->36)
> Playback Channel Map                    0, 0 (range 0->36)
> Headset Capture Switch                  On
> Headset Capture Volume                  1 (range 0->4)
> Sidetone Playback Switch                On
> Sidetone Playback Volume                4096 (range 0->8192)
> Headset Playback Switch                 On
> Headset Playback Volume                 20, 20 (range 0->24)
> USB Offload Playback Route PCM#0        0, 1 (range -1->255)
> 
> The "USB Offload Playback Route PCM#*" kcontrol will signify the
> corresponding card and pcm device it is offload to. (card#0 pcm - device#1)
> If the USB SND device supports multiple audio interfaces, then it will
> contain several PCM streams, hence in those situations, it is expected
> that there will be multiple playback route kcontrols created.
> 
> Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com>
> ---
>  sound/soc/qcom/qdsp6/q6usb.c | 104 +++++++++++++++++++++++++++++++++++
>  1 file changed, 104 insertions(+)
> 
> diff --git a/sound/soc/qcom/qdsp6/q6usb.c b/sound/soc/qcom/qdsp6/q6usb.c
> index 10337d70eb27..c2fc0dedf430 100644
> --- a/sound/soc/qcom/qdsp6/q6usb.c
> +++ b/sound/soc/qcom/qdsp6/q6usb.c
> @@ -132,6 +132,109 @@ static int q6usb_audio_ports_of_xlate_dai_name(struct snd_soc_component *compone
>  	return ret;
>  }
>  
> +static int q6usb_get_pcm_id_from_widget(struct snd_soc_dapm_widget *w)
> +{
> +	struct snd_soc_pcm_runtime *rtd;
> +	struct snd_soc_dai *dai;
> +
> +	for_each_card_rtds(w->dapm->card, rtd) {
> +		dai = snd_soc_rtd_to_cpu(rtd, 0);
> +		/*
> +		 * Only look for playback widget. RTD number carries the assigned
> +		 * PCM index.
> +		 */
> +		if (dai->stream[0].widget == w)
> +			return rtd->num;
> +	}
> +
> +	return -1;
> +}
> +
> +static int q6usb_usb_mixer_enabled(struct snd_soc_dapm_widget *w)
> +{
> +	struct snd_soc_dapm_path *p;
> +
> +	/* Checks to ensure USB path is enabled/connected */
> +	snd_soc_dapm_widget_for_each_sink_path(w, p)
> +		if (!strcmp(p->sink->name, "USB Mixer") && p->connect)
> +			return 1;
> +
> +	return 0;
> +}
> +
> +static int q6usb_get_pcm_id(struct snd_soc_component *component)
> +{
> +	struct snd_soc_dapm_widget *w;
> +	struct snd_soc_dapm_path *p;
> +	int pidx;
> +
> +	/*
> +	 * Traverse widgets to find corresponding FE widget.  The DAI links are
> +	 * built like the following:
> +	 *    MultiMedia* <-> MM_DL* <-> USB Mixer*
> +	 */
> +	for_each_card_widgets(component->card, w) {
> +		if (!strncmp(w->name, "MultiMedia", 10)) {
> +			/*
> +			 * Look up all paths associated with the FE widget to see if
> +			 * the USB BE is enabled.  The sink widget is responsible to
> +			 * link with the USB mixers.
> +			 */
> +			snd_soc_dapm_widget_for_each_sink_path(w, p) {
> +				if (q6usb_usb_mixer_enabled(p->sink)) {
> +					pidx = q6usb_get_pcm_id_from_widget(w);
> +					return pidx;
> +				}
> +			}

Humm, there should be a note that the design assumes that the USB
offload path exposes a single PCM per endpoints - same as the
non-offloaded path. If the ASoC card has multiple PCMs for each
endpoint, possibly with different processing on each PCM, then the
mapping would fail.

The other question is whether you need to walk in the DAPM graph, in
theory DPCM has helpers to find which FEs are connected to which BE.
Wesley Cheng Sept. 3, 2024, 9:49 p.m. UTC | #2
Hi Pierre,

On 8/30/2024 2:34 AM, Pierre-Louis Bossart wrote:
>
> On 8/29/24 21:40, Wesley Cheng wrote:
>> The USB SND path may need to know how the USB offload path is routed, so
>> that applications can open the proper sound card and PCM device.  The
>> implementation for the QC ASoC design has a "USB Mixer" kcontrol for each
>> possible FE (Q6ASM) DAI, which can be utilized to know which front end link
>> is enabled.
>>
>> When an application/userspace queries for the mapped offload devices, the
>> logic will lookup the USB mixer status though the following path:
>>
>> MultiMedia* <-> MM_DL* <-> USB Mixer*
>>
>> The "USB Mixer" is a DAPM widget, and the q6routing entity will set the
>> DAPM connect status accordingly if the USB mixer is enabled.  If enabled,
>> the Q6USB backend link can fetch the PCM device number from the FE DAI
>> link (Multimedia*).  With respects to the card number, that is
>> straightforward, as the ASoC components have direct references to the ASoC
>> platform sound card.
>>
>> An example output can be shown below:
>>
>> Number of controls: 9
>> name                                    value
>> Capture Channel Map                     0, 0 (range 0->36)
>> Playback Channel Map                    0, 0 (range 0->36)
>> Headset Capture Switch                  On
>> Headset Capture Volume                  1 (range 0->4)
>> Sidetone Playback Switch                On
>> Sidetone Playback Volume                4096 (range 0->8192)
>> Headset Playback Switch                 On
>> Headset Playback Volume                 20, 20 (range 0->24)
>> USB Offload Playback Route PCM#0        0, 1 (range -1->255)
>>
>> The "USB Offload Playback Route PCM#*" kcontrol will signify the
>> corresponding card and pcm device it is offload to. (card#0 pcm - device#1)
>> If the USB SND device supports multiple audio interfaces, then it will
>> contain several PCM streams, hence in those situations, it is expected
>> that there will be multiple playback route kcontrols created.
>>
>> Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com>
>> ---
>>  sound/soc/qcom/qdsp6/q6usb.c | 104 +++++++++++++++++++++++++++++++++++
>>  1 file changed, 104 insertions(+)
>>
>> diff --git a/sound/soc/qcom/qdsp6/q6usb.c b/sound/soc/qcom/qdsp6/q6usb.c
>> index 10337d70eb27..c2fc0dedf430 100644
>> --- a/sound/soc/qcom/qdsp6/q6usb.c
>> +++ b/sound/soc/qcom/qdsp6/q6usb.c
>> @@ -132,6 +132,109 @@ static int q6usb_audio_ports_of_xlate_dai_name(struct snd_soc_component *compone
>>  	return ret;
>>  }
>>  
>> +static int q6usb_get_pcm_id_from_widget(struct snd_soc_dapm_widget *w)
>> +{
>> +	struct snd_soc_pcm_runtime *rtd;
>> +	struct snd_soc_dai *dai;
>> +
>> +	for_each_card_rtds(w->dapm->card, rtd) {
>> +		dai = snd_soc_rtd_to_cpu(rtd, 0);
>> +		/*
>> +		 * Only look for playback widget. RTD number carries the assigned
>> +		 * PCM index.
>> +		 */
>> +		if (dai->stream[0].widget == w)
>> +			return rtd->num;
>> +	}
>> +
>> +	return -1;
>> +}
>> +
>> +static int q6usb_usb_mixer_enabled(struct snd_soc_dapm_widget *w)
>> +{
>> +	struct snd_soc_dapm_path *p;
>> +
>> +	/* Checks to ensure USB path is enabled/connected */
>> +	snd_soc_dapm_widget_for_each_sink_path(w, p)
>> +		if (!strcmp(p->sink->name, "USB Mixer") && p->connect)
>> +			return 1;
>> +
>> +	return 0;
>> +}
>> +
>> +static int q6usb_get_pcm_id(struct snd_soc_component *component)
>> +{
>> +	struct snd_soc_dapm_widget *w;
>> +	struct snd_soc_dapm_path *p;
>> +	int pidx;
>> +
>> +	/*
>> +	 * Traverse widgets to find corresponding FE widget.  The DAI links are
>> +	 * built like the following:
>> +	 *    MultiMedia* <-> MM_DL* <-> USB Mixer*
>> +	 */
>> +	for_each_card_widgets(component->card, w) {
>> +		if (!strncmp(w->name, "MultiMedia", 10)) {
>> +			/*
>> +			 * Look up all paths associated with the FE widget to see if
>> +			 * the USB BE is enabled.  The sink widget is responsible to
>> +			 * link with the USB mixers.
>> +			 */
>> +			snd_soc_dapm_widget_for_each_sink_path(w, p) {
>> +				if (q6usb_usb_mixer_enabled(p->sink)) {
>> +					pidx = q6usb_get_pcm_id_from_widget(w);
>> +					return pidx;
>> +				}
>> +			}
> Humm, there should be a note that the design assumes that the USB
> offload path exposes a single PCM per endpoints - same as the
> non-offloaded path. If the ASoC card has multiple PCMs for each
> endpoint, possibly with different processing on each PCM, then the
> mapping would fail.
Sure I'll add a note within the comments on the above.
> The other question is whether you need to walk in the DAPM graph, in
> theory DPCM has helpers to find which FEs are connected to which BE.

Hmm...Yes, I've tried to see if I could utilize some of the helpers that were present, but none of them was able to fetch the DAPM widget that was associated with the FE, hence why I had to implement the lookup that would work for our current design.

Thanks

Wesley Cheng
diff mbox series

Patch

diff --git a/sound/soc/qcom/qdsp6/q6usb.c b/sound/soc/qcom/qdsp6/q6usb.c
index 10337d70eb27..c2fc0dedf430 100644
--- a/sound/soc/qcom/qdsp6/q6usb.c
+++ b/sound/soc/qcom/qdsp6/q6usb.c
@@ -132,6 +132,109 @@  static int q6usb_audio_ports_of_xlate_dai_name(struct snd_soc_component *compone
 	return ret;
 }
 
+static int q6usb_get_pcm_id_from_widget(struct snd_soc_dapm_widget *w)
+{
+	struct snd_soc_pcm_runtime *rtd;
+	struct snd_soc_dai *dai;
+
+	for_each_card_rtds(w->dapm->card, rtd) {
+		dai = snd_soc_rtd_to_cpu(rtd, 0);
+		/*
+		 * Only look for playback widget. RTD number carries the assigned
+		 * PCM index.
+		 */
+		if (dai->stream[0].widget == w)
+			return rtd->num;
+	}
+
+	return -1;
+}
+
+static int q6usb_usb_mixer_enabled(struct snd_soc_dapm_widget *w)
+{
+	struct snd_soc_dapm_path *p;
+
+	/* Checks to ensure USB path is enabled/connected */
+	snd_soc_dapm_widget_for_each_sink_path(w, p)
+		if (!strcmp(p->sink->name, "USB Mixer") && p->connect)
+			return 1;
+
+	return 0;
+}
+
+static int q6usb_get_pcm_id(struct snd_soc_component *component)
+{
+	struct snd_soc_dapm_widget *w;
+	struct snd_soc_dapm_path *p;
+	int pidx;
+
+	/*
+	 * Traverse widgets to find corresponding FE widget.  The DAI links are
+	 * built like the following:
+	 *    MultiMedia* <-> MM_DL* <-> USB Mixer*
+	 */
+	for_each_card_widgets(component->card, w) {
+		if (!strncmp(w->name, "MultiMedia", 10)) {
+			/*
+			 * Look up all paths associated with the FE widget to see if
+			 * the USB BE is enabled.  The sink widget is responsible to
+			 * link with the USB mixers.
+			 */
+			snd_soc_dapm_widget_for_each_sink_path(w, p) {
+				if (q6usb_usb_mixer_enabled(p->sink)) {
+					pidx = q6usb_get_pcm_id_from_widget(w);
+					return pidx;
+				}
+			}
+		}
+	}
+
+	return -1;
+}
+
+static int q6usb_update_offload_route(struct snd_soc_component *component, int card,
+				      int pcm, int direction, long *route)
+{
+	struct q6usb_port_data *data = dev_get_drvdata(component->dev);
+	struct snd_soc_usb_device *sdev;
+	int ret = 0;
+	int cidx = -1;
+	int pidx = -1;
+
+	mutex_lock(&data->mutex);
+
+	if (list_empty(&data->devices) ||
+	    direction == SNDRV_PCM_STREAM_CAPTURE) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	sdev = list_last_entry(&data->devices, struct snd_soc_usb_device, list);
+
+	/*
+	 * Will always look for last PCM device discovered/probed as the
+	 * active offload index.
+	 */
+	if (card == sdev->card_idx &&
+	    pcm == sdev->ppcm_idx[sdev->num_playback - 1]) {
+		cidx = component->card->snd_card->number;
+		pidx = q6usb_get_pcm_id(component);
+	}
+
+	if (cidx < 0 || pidx < 0) {
+		cidx = -1;
+		pidx = -1;
+	}
+
+out:
+	route[0] = cidx;
+	route[1] = pidx;
+
+	mutex_unlock(&data->mutex);
+
+	return ret;
+}
+
 static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb,
 				    struct snd_soc_usb_device *sdev, bool connected)
 {
@@ -202,6 +305,7 @@  static int q6usb_component_probe(struct snd_soc_component *component)
 		return -ENOMEM;
 
 	usb->connection_status_cb = q6usb_alsa_connection_cb;
+	usb->update_offload_route_info = q6usb_update_offload_route;
 
 	snd_soc_usb_add_port(usb);
 	data->usb = usb;