[v4,10/32] ASoC: qcom: Add USB backend ASoC driver for Q6
Commit Message
Create a USB BE component that will register a new USB port to the ASoC USB
framework. This will handle determination on if the requested audio
profile is supported by the USB device currently selected.
Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com>
---
include/sound/q6usboffload.h | 20 ++++
sound/soc/qcom/Kconfig | 4 +
sound/soc/qcom/qdsp6/Makefile | 1 +
sound/soc/qcom/qdsp6/q6usb.c | 208 ++++++++++++++++++++++++++++++++++
4 files changed, 233 insertions(+)
create mode 100644 include/sound/q6usboffload.h
create mode 100644 sound/soc/qcom/qdsp6/q6usb.c
Comments
> +struct q6usb_port_data {
> + struct q6afe_usb_cfg usb_cfg;
> + struct snd_soc_usb *usb;
> + struct q6usb_offload priv;
> + int active_idx;
what is an 'active_idx' ?
> +static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb, int card_idx,
> + int connected)
> +{
> + struct snd_soc_dapm_context *dapm;
> + struct q6usb_port_data *data;
> +
> + dapm = snd_soc_component_get_dapm(usb->component);
> + data = dev_get_drvdata(usb->component->dev);
shouldn't you test that 'dapm' and 'data' are not NULL ?
> +
> + if (connected) {
this goes back to my earlier comment that you treat 'connected' as a
boolean.
> + snd_soc_dapm_enable_pin(dapm, "USB_RX_BE");
> + /* We only track the latest USB headset plugged in */
> + data->active_idx = card_idx;
> + } else {
> + snd_soc_dapm_disable_pin(dapm, "USB_RX_BE");
> + }
> + snd_soc_dapm_sync(dapm);
> +
> + return 0;
> +}
> +
> +static int q6usb_component_probe(struct snd_soc_component *component)
> +{
> + struct q6usb_port_data *data = dev_get_drvdata(component->dev);
> + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
> +
> + snd_soc_dapm_disable_pin(dapm, "USB_RX_BE");
> + snd_soc_dapm_sync(dapm);
> +
> + 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");
> + return -ENODEV;
> + }
> +
> + data->usb->component = component;
> +
> + return 0;
> +}
> +
> +static const struct snd_soc_component_driver q6usb_dai_component = {
> + .probe = q6usb_component_probe,
erm, if you have a .probe that adds a port, don't you need a remove that
removes the same port, and sets the pin state as well?
> + .name = "q6usb-dai-component",
> + .dapm_widgets = q6usb_dai_widgets,
> + .num_dapm_widgets = ARRAY_SIZE(q6usb_dai_widgets),
> + .dapm_routes = q6usb_dapm_routes,
> + .num_dapm_routes = ARRAY_SIZE(q6usb_dapm_routes),
> + .of_xlate_dai_name = q6usb_audio_ports_of_xlate_dai_name,
> +};
> +
> +static int q6usb_dai_dev_probe(struct platform_device *pdev)
> +{
> + struct device_node *node = pdev->dev.of_node;
> + struct q6usb_port_data *data;
> + struct device *dev = &pdev->dev;
> + struct of_phandle_args args;
> + int ret;
> +
> + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
> + if (!data)
> + return -ENOMEM;
> +
> + ret = of_property_read_u32(node, "qcom,usb-audio-intr-num",
> + &data->priv.intr_num);
> + if (ret) {
> + dev_err(&pdev->dev, "failed to read intr num.\n");
> + return ret;
> + }
> +
> + ret = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args);
> + if (ret < 0)
> + data->priv.sid = -1;
> + else
> + data->priv.sid = args.args[0] & SID_MASK;
> +
> + data->priv.domain = iommu_get_domain_for_dev(&pdev->dev);
> +
> + data->priv.dev = dev;
> + dev_set_drvdata(dev, data);
> +
> + ret = devm_snd_soc_register_component(dev, &q6usb_dai_component,
> + q6usb_be_dais, ARRAY_SIZE(q6usb_be_dais));
> + if (ret < 0)
> + return ret;
> +
> + return 0;
return devm_snd_soc_register_component
> +}
> +
> +static int q6usb_dai_dev_remove(struct platform_device *pdev)
> +{
> + snd_soc_usb_remove_port(&pdev->dev);
that seems wrong, the port is added in the component probe, not the
platform device probe.
> +
> + return 0;
> +}> +
Hi Pierre,
On 7/25/2023 1:45 AM, Pierre-Louis Bossart wrote:
>
>> +struct q6usb_port_data {
>> + struct q6afe_usb_cfg usb_cfg;
>> + struct snd_soc_usb *usb;
>> + struct q6usb_offload priv;
>> + int active_idx;
>
> what is an 'active_idx' ?
>
>
active_idx carries the USB sound card we're going to be offloading.
>> +static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb, int card_idx,
>> + int connected)
>> +{
>> + struct snd_soc_dapm_context *dapm;
>> + struct q6usb_port_data *data;
>> +
>> + dapm = snd_soc_component_get_dapm(usb->component);
>> + data = dev_get_drvdata(usb->component->dev);
>
> shouldn't you test that 'dapm' and 'data' are not NULL ?
>
q6usb_component_probe() would be the one that registers to SOC USB to
add this callback. At that time, the component's dev and dapm
references should be populated, so that should ensure that those are
valid. However, we could see that usb->component to be NULL, as that
assignment happens after adding the port. Instead I will add a check
for usb->component before attempting to access the dapm/data params.
Another thing I will modify is to add a component removal callback,
which will remove the SOC USB port. That will ensure that no
connection_cb() events are issued, so we don't run into any NULL pointer
issues during the remove path.
>> +
>> + if (connected) {
>
> this goes back to my earlier comment that you treat 'connected' as a
> boolean.
>
Done, changed to boolean.
>> + snd_soc_dapm_enable_pin(dapm, "USB_RX_BE");
>> + /* We only track the latest USB headset plugged in */
>> + data->active_idx = card_idx;
>> + } else {
>> + snd_soc_dapm_disable_pin(dapm, "USB_RX_BE");
>> + }
>> + snd_soc_dapm_sync(dapm);
>> +
>> + return 0;
>> +}
>> +
>> +static int q6usb_component_probe(struct snd_soc_component *component)
>> +{
>> + struct q6usb_port_data *data = dev_get_drvdata(component->dev);
>> + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
>> +
>> + snd_soc_dapm_disable_pin(dapm, "USB_RX_BE");
>> + snd_soc_dapm_sync(dapm);
>> +
>> + 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");
>> + return -ENODEV;
>> + }
>> +
>> + data->usb->component = component;
>> +
>> + return 0;
>> +}
>> +
>> +static const struct snd_soc_component_driver q6usb_dai_component = {
>> + .probe = q6usb_component_probe,
>
> erm, if you have a .probe that adds a port, don't you need a remove that
> removes the same port, and sets the pin state as well?
>
Will add this as mentioned above.
>> + .name = "q6usb-dai-component",
>> + .dapm_widgets = q6usb_dai_widgets,
>> + .num_dapm_widgets = ARRAY_SIZE(q6usb_dai_widgets),
>> + .dapm_routes = q6usb_dapm_routes,
>> + .num_dapm_routes = ARRAY_SIZE(q6usb_dapm_routes),
>> + .of_xlate_dai_name = q6usb_audio_ports_of_xlate_dai_name,
>> +};
>> +
>> +static int q6usb_dai_dev_probe(struct platform_device *pdev)
>> +{
>> + struct device_node *node = pdev->dev.of_node;
>> + struct q6usb_port_data *data;
>> + struct device *dev = &pdev->dev;
>> + struct of_phandle_args args;
>> + int ret;
>> +
>> + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
>> + if (!data)
>> + return -ENOMEM;
>> +
>> + ret = of_property_read_u32(node, "qcom,usb-audio-intr-num",
>> + &data->priv.intr_num);
>> + if (ret) {
>> + dev_err(&pdev->dev, "failed to read intr num.\n");
>> + return ret;
>> + }
>> +
>> + ret = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args);
>> + if (ret < 0)
>> + data->priv.sid = -1;
>> + else
>> + data->priv.sid = args.args[0] & SID_MASK;
>> +
>> + data->priv.domain = iommu_get_domain_for_dev(&pdev->dev);
>> +
>> + data->priv.dev = dev;
>> + dev_set_drvdata(dev, data);
>> +
>> + ret = devm_snd_soc_register_component(dev, &q6usb_dai_component,
>> + q6usb_be_dais, ARRAY_SIZE(q6usb_be_dais));
>> + if (ret < 0)
>> + return ret;
>> +
>> + return 0;
>
> return devm_snd_soc_register_component
>
>> +}
>> +
>> +static int q6usb_dai_dev_remove(struct platform_device *pdev)
>> +{
>> + snd_soc_usb_remove_port(&pdev->dev);
>
> that seems wrong, the port is added in the component probe, not the
> platform device probe.
>
Will fix this.
Thanks
Wesley Cheng
new file mode 100644
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * linux/sound/q6usboffload.h -- QDSP6 USB offload
+ *
+ * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+/**
+ * struct q6usb_offload
+ * @dev - dev handle to usb be
+ * @sid - streamID for iommu
+ * @intr_num - usb interrupter number
+ * @domain - allocated iommu domain
+ **/
+struct q6usb_offload {
+ struct device *dev;
+ long long sid;
+ u32 intr_num;
+ struct iommu_domain *domain;
+};
@@ -114,6 +114,9 @@ config SND_SOC_QDSP6_APM
config SND_SOC_QDSP6_PRM_LPASS_CLOCKS
tristate
+config SND_SOC_QDSP6_USB
+ tristate
+
config SND_SOC_QDSP6_PRM
tristate
select SND_SOC_QDSP6_PRM_LPASS_CLOCKS
@@ -134,6 +137,7 @@ config SND_SOC_QDSP6
select SND_SOC_TOPOLOGY
select SND_SOC_QDSP6_APM
select SND_SOC_QDSP6_PRM
+ select SND_SOC_QDSP6_USB
help
To add support for MSM QDSP6 Soc Audio.
This will enable sound soc platform specific
@@ -17,3 +17,4 @@ obj-$(CONFIG_SND_SOC_QDSP6_APM_DAI) += q6apm-dai.o
obj-$(CONFIG_SND_SOC_QDSP6_APM_LPASS_DAI) += q6apm-lpass-dais.o
obj-$(CONFIG_SND_SOC_QDSP6_PRM) += q6prm.o
obj-$(CONFIG_SND_SOC_QDSP6_PRM_LPASS_CLOCKS) += q6prm-clocks.o
+obj-$(CONFIG_SND_SOC_QDSP6_USB) += q6usb.o
new file mode 100644
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/iommu.h>
+#include <linux/dma-mapping.h>
+#include <linux/dma-map-ops.h>
+
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-usb.h>
+#include <sound/pcm_params.h>
+#include <sound/asound.h>
+#include <sound/q6usboffload.h>
+
+#include "q6dsp-lpass-ports.h"
+#include "q6afe.h"
+
+#define SID_MASK 0xF
+
+struct q6usb_port_data {
+ struct q6afe_usb_cfg usb_cfg;
+ struct snd_soc_usb *usb;
+ struct q6usb_offload priv;
+ int active_idx;
+};
+
+static const struct snd_soc_dapm_widget q6usb_dai_widgets[] = {
+ SND_SOC_DAPM_HP("USB_RX_BE", NULL),
+};
+
+static const struct snd_soc_dapm_route q6usb_dapm_routes[] = {
+ {"USB Playback", NULL, "USB_RX_BE"},
+};
+
+static int q6usb_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ return 0;
+}
+static const struct snd_soc_dai_ops q6usb_ops = {
+ .hw_params = q6usb_hw_params,
+};
+
+static struct snd_soc_dai_driver q6usb_be_dais[] = {
+ {
+ .playback = {
+ .stream_name = "USB BE RX",
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |
+ SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE |
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE |
+ SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE,
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_max = 192000,
+ .rate_min = 8000,
+ },
+ .id = USB_RX,
+ .name = "USB_RX_BE",
+ .ops = &q6usb_ops,
+ },
+};
+
+static int q6usb_audio_ports_of_xlate_dai_name(struct snd_soc_component *component,
+ const struct of_phandle_args *args,
+ const char **dai_name)
+{
+ int id = args->args[0];
+ int ret = -EINVAL;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(q6usb_be_dais); i++) {
+ if (q6usb_be_dais[i].id == id) {
+ *dai_name = q6usb_be_dais[i].name;
+ ret = 0;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb, int card_idx,
+ int connected)
+{
+ struct snd_soc_dapm_context *dapm;
+ struct q6usb_port_data *data;
+
+ dapm = snd_soc_component_get_dapm(usb->component);
+ data = dev_get_drvdata(usb->component->dev);
+
+ if (connected) {
+ snd_soc_dapm_enable_pin(dapm, "USB_RX_BE");
+ /* We only track the latest USB headset plugged in */
+ data->active_idx = card_idx;
+ } else {
+ snd_soc_dapm_disable_pin(dapm, "USB_RX_BE");
+ }
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+static int q6usb_component_probe(struct snd_soc_component *component)
+{
+ struct q6usb_port_data *data = dev_get_drvdata(component->dev);
+ struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
+
+ snd_soc_dapm_disable_pin(dapm, "USB_RX_BE");
+ snd_soc_dapm_sync(dapm);
+
+ 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");
+ return -ENODEV;
+ }
+
+ data->usb->component = component;
+
+ return 0;
+}
+
+static const struct snd_soc_component_driver q6usb_dai_component = {
+ .probe = q6usb_component_probe,
+ .name = "q6usb-dai-component",
+ .dapm_widgets = q6usb_dai_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(q6usb_dai_widgets),
+ .dapm_routes = q6usb_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(q6usb_dapm_routes),
+ .of_xlate_dai_name = q6usb_audio_ports_of_xlate_dai_name,
+};
+
+static int q6usb_dai_dev_probe(struct platform_device *pdev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct q6usb_port_data *data;
+ struct device *dev = &pdev->dev;
+ struct of_phandle_args args;
+ int ret;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ ret = of_property_read_u32(node, "qcom,usb-audio-intr-num",
+ &data->priv.intr_num);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to read intr num.\n");
+ return ret;
+ }
+
+ ret = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args);
+ if (ret < 0)
+ data->priv.sid = -1;
+ else
+ data->priv.sid = args.args[0] & SID_MASK;
+
+ data->priv.domain = iommu_get_domain_for_dev(&pdev->dev);
+
+ data->priv.dev = dev;
+ dev_set_drvdata(dev, data);
+
+ ret = devm_snd_soc_register_component(dev, &q6usb_dai_component,
+ q6usb_be_dais, ARRAY_SIZE(q6usb_be_dais));
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int q6usb_dai_dev_remove(struct platform_device *pdev)
+{
+ snd_soc_usb_remove_port(&pdev->dev);
+
+ return 0;
+}
+
+static const struct of_device_id q6usb_dai_device_id[] = {
+ { .compatible = "qcom,q6usb" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, q6usb_dai_device_id);
+
+static struct platform_driver q6usb_dai_platform_driver = {
+ .driver = {
+ .name = "q6usb-dai",
+ .of_match_table = of_match_ptr(q6usb_dai_device_id),
+ },
+ .probe = q6usb_dai_dev_probe,
+ .remove = q6usb_dai_dev_remove,
+};
+module_platform_driver(q6usb_dai_platform_driver);
+
+MODULE_DESCRIPTION("Q6 USB backend dai driver");
+MODULE_LICENSE("GPL");