[v9,29/34] ASoC: qcom: qdsp6: Add SND kcontrol to select offload device
Commit Message
Expose a kcontrol on the platform sound card, which will allow for
userspace to determine which USB card number and PCM device to offload.
This allows for userspace to potentially tag an alternate path for a
specific USB SND card and PCM device. Previously, control was absent, and
the offload path would be enabled on the last USB SND device which was
connected. This logic will continue to be applicable if no mixer input is
received for specific device selection.
An example to configure the offload device using tinymix:
tinymix -D 0 set 'Q6USB offload SND device select' 1 0
The above will set the Q6AFE device token to choose offload on card#1 and
pcm#0. Device selection is made possible by setting the Q6AFE device
token. The audio DSP utilizes this parameter, and will pass this field
back to the USB offload driver within the QMI stream requests.
Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com>
---
sound/soc/qcom/qdsp6/q6usb.c | 125 ++++++++++++++++++++++++++++++++++-
1 file changed, 122 insertions(+), 3 deletions(-)
Comments
On 10/17/23 15:01, Wesley Cheng wrote:
> Expose a kcontrol on the platform sound card, which will allow for
> userspace to determine which USB card number and PCM device to offload.
> This allows for userspace to potentially tag an alternate path for a
> specific USB SND card and PCM device. Previously, control was absent, and
> the offload path would be enabled on the last USB SND device which was
> connected. This logic will continue to be applicable if no mixer input is
> received for specific device selection.
>
> An example to configure the offload device using tinymix:
> tinymix -D 0 set 'Q6USB offload SND device select' 1 0
>
> The above will set the Q6AFE device token to choose offload on card#1 and
> pcm#0. Device selection is made possible by setting the Q6AFE device
> token. The audio DSP utilizes this parameter, and will pass this field
> back to the USB offload driver within the QMI stream requests.
This still begs the question on how userspace would figure what the
card1 is and which endpoint is used when PCM0 is opened?
Ideally userpace would not have to know anything about "Q6USB".
Presumably when other vendors expose their USB offload solution, we
would want a generic control name, no?
Jaroslav should chime in on this one :-)
> Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com>
> ---
> sound/soc/qcom/qdsp6/q6usb.c | 125 ++++++++++++++++++++++++++++++++++-
> 1 file changed, 122 insertions(+), 3 deletions(-)
>
> diff --git a/sound/soc/qcom/qdsp6/q6usb.c b/sound/soc/qcom/qdsp6/q6usb.c
> index d697cbe7f184..a95276b7d91d 100644
> --- a/sound/soc/qcom/qdsp6/q6usb.c
> +++ b/sound/soc/qcom/qdsp6/q6usb.c
> @@ -14,6 +14,7 @@
> #include <linux/dma-map-ops.h>
>
> #include <sound/pcm.h>
> +#include <sound/control.h>
> #include <sound/soc.h>
> #include <sound/soc-usb.h>
> #include <sound/pcm_params.h>
> @@ -35,9 +36,12 @@ struct q6usb_port_data {
> struct q6afe_usb_cfg usb_cfg;
> struct snd_soc_usb *usb;
> struct q6usb_offload priv;
> + struct mutex mutex;
missing comment on what this protects. "mutex" is really a poor
choice/name if I am honest.
> unsigned long available_card_slot;
> struct q6usb_status status[SNDRV_CARDS];
> - int active_idx;
> + bool idx_valid;
> + int sel_card_idx;
> + int sel_pcm_idx;
> };
> +/* Build a mixer control for a UAC connector control (jack-detect) */
> +static void q6usb_connector_control_init(struct snd_soc_component *component)
> +{
> + int ret;
> +
> + ret = snd_ctl_add(component->card->snd_card,
> + snd_ctl_new1(&q6usb_offload_dev_ctrl, component));
> + if (ret < 0)
> + return;
that error handling does not seem terribly useful...
> +}
> +
> static int q6usb_audio_ports_of_xlate_dai_name(struct snd_soc_component *component,
> const struct of_phandle_args *args,
> const char **dai_name)
> @@ -115,9 +227,11 @@ static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb,
>
> data = dev_get_drvdata(usb->component->dev);
>
> + mutex_lock(&data->mutex);
> if (connected) {
> /* We only track the latest USB headset plugged in */
> - data->active_idx = sdev->card_idx;
> + if (!data->idx_valid || data->sel_card_idx < 0)
> + data->sel_card_idx = sdev->card_idx;
>
> set_bit(sdev->card_idx, &data->available_card_slot);
> data->status[sdev->card_idx].num_pcm = sdev->num_playback;
> @@ -127,6 +241,7 @@ static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb,
> data->status[sdev->card_idx].num_pcm = 0;
> data->status[sdev->card_idx].chip_index = 0;
> }
> + mutex_unlock(&data->mutex);
>
> return 0;
> }
> @@ -135,6 +250,8 @@ static int q6usb_component_probe(struct snd_soc_component *component)
> {
> struct q6usb_port_data *data = dev_get_drvdata(component->dev);
>
> + q6usb_connector_control_init(component);
> +
> data->usb = snd_soc_usb_add_port(component->dev, &data->priv, q6usb_alsa_connection_cb);
> if (IS_ERR(data->usb)) {
> dev_err(component->dev, "failed to add usb port\n");
> @@ -189,6 +306,8 @@ static int q6usb_dai_dev_probe(struct platform_device *pdev)
>
> data->priv.domain = iommu_get_domain_for_dev(&pdev->dev);
>
> + mutex_init(&data->mutex);
> +
> data->priv.dev = dev;
> dev_set_drvdata(dev, data);
>
Hi Pierre,
On 10/17/2023 3:50 PM, Pierre-Louis Bossart wrote:
>
>
> On 10/17/23 15:01, Wesley Cheng wrote:
>> Expose a kcontrol on the platform sound card, which will allow for
>> userspace to determine which USB card number and PCM device to offload.
>> This allows for userspace to potentially tag an alternate path for a
>> specific USB SND card and PCM device. Previously, control was absent, and
>> the offload path would be enabled on the last USB SND device which was
>> connected. This logic will continue to be applicable if no mixer input is
>> received for specific device selection.
>>
>> An example to configure the offload device using tinymix:
>> tinymix -D 0 set 'Q6USB offload SND device select' 1 0
>>
>> The above will set the Q6AFE device token to choose offload on card#1 and
>> pcm#0. Device selection is made possible by setting the Q6AFE device
>> token. The audio DSP utilizes this parameter, and will pass this field
>> back to the USB offload driver within the QMI stream requests.
>
> This still begs the question on how userspace would figure what the
> card1 is and which endpoint is used when PCM0 is opened?
>
Assuming we have something maybe in the component string for card1, then
maybe that will help in this case as you mentioned earlier. Again, I
think my understanding of "endpoint" here is referring to the USB
endpoint. However, that isn't normally how we should think about it.
From the USB sound perspective, it would be the USB interface that is
selected, and the USB endpoints that that interface descriptor includes
(up to 2 usb eps).
> Ideally userpace would not have to know anything about "Q6USB".
> Presumably when other vendors expose their USB offload solution, we
> would want a generic control name, no?
>
Hmm...initially I was keeping the sound kcontrol implementation based on
a vendor specific approach. Although, it might be good to standardize
it a bit. I could see if we can add this to within soc-usb so whenever
we just create a soc usb device, it would also create and initialize the
kcontrols as well?
> Jaroslav should chime in on this one :-)
>
>
>> Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com>
>> ---
>> sound/soc/qcom/qdsp6/q6usb.c | 125 ++++++++++++++++++++++++++++++++++-
>> 1 file changed, 122 insertions(+), 3 deletions(-)
>>
>> diff --git a/sound/soc/qcom/qdsp6/q6usb.c b/sound/soc/qcom/qdsp6/q6usb.c
>> index d697cbe7f184..a95276b7d91d 100644
>> --- a/sound/soc/qcom/qdsp6/q6usb.c
>> +++ b/sound/soc/qcom/qdsp6/q6usb.c
>> @@ -14,6 +14,7 @@
>> #include <linux/dma-map-ops.h>
>>
>> #include <sound/pcm.h>
>> +#include <sound/control.h>
>> #include <sound/soc.h>
>> #include <sound/soc-usb.h>
>> #include <sound/pcm_params.h>
>> @@ -35,9 +36,12 @@ struct q6usb_port_data {
>> struct q6afe_usb_cfg usb_cfg;
>> struct snd_soc_usb *usb;
>> struct q6usb_offload priv;
>> + struct mutex mutex;
>
> missing comment on what this protects. "mutex" is really a poor
> choice/name if I am honest.
>
>> unsigned long available_card_slot;
>> struct q6usb_status status[SNDRV_CARDS];
>> - int active_idx;
>> + bool idx_valid;
>> + int sel_card_idx;
>> + int sel_pcm_idx;
>> };
>
>> +/* Build a mixer control for a UAC connector control (jack-detect) */
>> +static void q6usb_connector_control_init(struct snd_soc_component *component)
>> +{
>> + int ret;
>> +
>> + ret = snd_ctl_add(component->card->snd_card,
>> + snd_ctl_new1(&q6usb_offload_dev_ctrl, component));
>> + if (ret < 0)
>> + return;
>
> that error handling does not seem terribly useful...
>
Based on your earlier comment, this might change :)
Thanks
Wesley Cheng
@@ -14,6 +14,7 @@
#include <linux/dma-map-ops.h>
#include <sound/pcm.h>
+#include <sound/control.h>
#include <sound/soc.h>
#include <sound/soc-usb.h>
#include <sound/pcm_params.h>
@@ -35,9 +36,12 @@ struct q6usb_port_data {
struct q6afe_usb_cfg usb_cfg;
struct snd_soc_usb *usb;
struct q6usb_offload priv;
+ struct mutex mutex;
unsigned long available_card_slot;
struct q6usb_status status[SNDRV_CARDS];
- int active_idx;
+ bool idx_valid;
+ int sel_card_idx;
+ int sel_pcm_idx;
};
static const struct snd_soc_dapm_widget q6usb_dai_widgets[] = {
@@ -53,9 +57,34 @@ static int q6usb_hw_params(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct q6usb_port_data *data = dev_get_drvdata(dai->dev);
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ struct q6afe_port *q6usb_afe;
int direction = substream->stream;
+ int chip_idx;
+ int ret;
+
+ mutex_lock(&data->mutex);
+ chip_idx = data->status[data->sel_card_idx].chip_index;
+
+ ret = snd_soc_usb_find_format(chip_idx, params, direction);
+ if (ret < 0)
+ goto out;
+
+ q6usb_afe = q6afe_port_get_from_id(cpu_dai->dev, USB_RX);
+ if (IS_ERR(q6usb_afe))
+ goto out;
- return snd_soc_usb_find_format(data->active_idx, params, direction);
+ ret = afe_port_send_usb_dev_param(q6usb_afe, data->sel_card_idx,
+ data->sel_pcm_idx);
+ if (ret < 0)
+ goto out;
+
+ data->status[data->sel_card_idx].pcm_index = data->sel_pcm_idx;
+out:
+ mutex_unlock(&data->mutex);
+
+ return ret;
}
static const struct snd_soc_dai_ops q6usb_ops = {
@@ -86,6 +115,89 @@ static struct snd_soc_dai_driver q6usb_be_dais[] = {
},
};
+static int q6usb_get_offload_dev(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct q6usb_port_data *data = dev_get_drvdata(component->dev);
+ int pcm_idx;
+ int card_idx;
+
+ mutex_lock(&data->mutex);
+ if (!data->idx_valid) {
+ card_idx = -1;
+ pcm_idx = -1;
+ } else {
+ card_idx = data->sel_card_idx;
+ pcm_idx = data->sel_pcm_idx;
+ }
+
+ ucontrol->value.integer.value[0] = card_idx;
+ ucontrol->value.integer.value[1] = pcm_idx;
+ mutex_unlock(&data->mutex);
+
+ return 0;
+}
+
+static int q6usb_put_offload_dev(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct q6usb_port_data *data = dev_get_drvdata(component->dev);
+ int changed = 0;
+ int pcmidx;
+ int cardidx;
+
+ cardidx = ucontrol->value.integer.value[0];
+ pcmidx = ucontrol->value.integer.value[1];
+
+ mutex_lock(&data->mutex);
+ if ((cardidx >= 0 && test_bit(cardidx, &data->available_card_slot))) {
+ data->sel_card_idx = cardidx;
+ changed = 1;
+ }
+
+ if ((pcmidx >= 0 && pcmidx < data->status[cardidx].num_pcm)) {
+ data->sel_pcm_idx = pcmidx;
+ data->idx_valid = true;
+ changed = 1;
+ }
+ mutex_unlock(&data->mutex);
+
+ return changed;
+}
+
+static int q6usb_offload_dev_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = -1;
+ uinfo->value.integer.max = SNDRV_CARDS;
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new q6usb_offload_dev_ctrl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .name = "Q6USB offload SND device select",
+ .info = q6usb_offload_dev_info,
+ .get = q6usb_get_offload_dev,
+ .put = q6usb_put_offload_dev,
+};
+
+/* Build a mixer control for a UAC connector control (jack-detect) */
+static void q6usb_connector_control_init(struct snd_soc_component *component)
+{
+ int ret;
+
+ ret = snd_ctl_add(component->card->snd_card,
+ snd_ctl_new1(&q6usb_offload_dev_ctrl, component));
+ if (ret < 0)
+ return;
+}
+
static int q6usb_audio_ports_of_xlate_dai_name(struct snd_soc_component *component,
const struct of_phandle_args *args,
const char **dai_name)
@@ -115,9 +227,11 @@ static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb,
data = dev_get_drvdata(usb->component->dev);
+ mutex_lock(&data->mutex);
if (connected) {
/* We only track the latest USB headset plugged in */
- data->active_idx = sdev->card_idx;
+ if (!data->idx_valid || data->sel_card_idx < 0)
+ data->sel_card_idx = sdev->card_idx;
set_bit(sdev->card_idx, &data->available_card_slot);
data->status[sdev->card_idx].num_pcm = sdev->num_playback;
@@ -127,6 +241,7 @@ static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb,
data->status[sdev->card_idx].num_pcm = 0;
data->status[sdev->card_idx].chip_index = 0;
}
+ mutex_unlock(&data->mutex);
return 0;
}
@@ -135,6 +250,8 @@ static int q6usb_component_probe(struct snd_soc_component *component)
{
struct q6usb_port_data *data = dev_get_drvdata(component->dev);
+ q6usb_connector_control_init(component);
+
data->usb = snd_soc_usb_add_port(component->dev, &data->priv, q6usb_alsa_connection_cb);
if (IS_ERR(data->usb)) {
dev_err(component->dev, "failed to add usb port\n");
@@ -189,6 +306,8 @@ static int q6usb_dai_dev_probe(struct platform_device *pdev)
data->priv.domain = iommu_get_domain_for_dev(&pdev->dev);
+ mutex_init(&data->mutex);
+
data->priv.dev = dev;
dev_set_drvdata(dev, data);