[v4,06/32] ASoC: Add SOC USB APIs for adding an USB backend
Commit Message
Some platforms may have support for offloading USB audio devices to a
dedicated audio DSP. Introduce a set of APIs that allow for management of
USB sound card and PCM devices enumerated by the USB SND class driver.
This allows for the ASoC components to be aware of what USB devices are
available for offloading.
Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com>
---
include/sound/soc-usb.h | 35 ++++++++
sound/soc/Makefile | 2 +-
sound/soc/soc-usb.c | 180 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 216 insertions(+), 1 deletion(-)
create mode 100644 include/sound/soc-usb.h
create mode 100644 sound/soc/soc-usb.c
Comments
> +/**
> + * struct snd_soc_usb
> + * @list - list head for SND SOC struct list
> + * @dev - USB backend device reference
> + * @component - reference to DAPM component
ASoC component, not DAPM.
> + * @connection_status_cb - callback to notify connection events
> + * @priv_data - driver data
> + **/
> +struct snd_soc_usb {
> + struct list_head list;
> + struct device *dev;
> + struct snd_soc_component *component;
> + int (*connection_status_cb)(struct snd_soc_usb *usb, int card_idx,
> + int connected);
It's not clear what 'connected' really refers to, is this a boolean
really or is this a "connection_event?
> + void *priv_data;
> +};
> +
> +int snd_soc_usb_connect(struct device *usbdev, int card_idx);
> +int snd_soc_usb_disconnect(struct device *usbdev);
> +void snd_soc_usb_set_priv_data(struct device *dev, void *priv);
this function is not part of this patch, is this intentional to have a
get but not a set?
> +void *snd_soc_usb_get_priv_data(struct device *usbdev);
you are using 'usbdev' and 'dev' for the same type of parameters, why
not align on one set of definition with a consistent naming.
> +static struct snd_soc_usb *snd_soc_find_usb_ctx(struct device *dev)
> +{
> + struct device_node *node;
> + struct snd_soc_usb *ctx = NULL;
this init doesn't seem required?
> +
> + node = snd_soc_find_phandle(dev);
> + if (IS_ERR(node))
> + return NULL;
> +
> + mutex_lock(&ctx_mutex);
> + list_for_each_entry(ctx, &usb_ctx_list, list) {
> + if (ctx->dev->of_node == node) {
> + of_node_put(node);
> + mutex_unlock(&ctx_mutex);
> + return ctx;
> + }
> + }
> + of_node_put(node);
> + mutex_unlock(&ctx_mutex);
> +
> + return NULL;
> +}
> +
> +/**
> + * snd_soc_usb_get_priv_data() - Retrieve private data stored
> + * @dev: device reference
> + *
> + * Fetch the private data stored in the USB SND SOC structure.
> + *
> + */
> +void *snd_soc_usb_get_priv_data(struct device *dev)
> +{
> + struct snd_soc_usb *ctx;
> +
> + ctx = snd_soc_find_usb_ctx(dev);
so in this function you walk through the usb_ctx_list with locking...
> + if (!ctx) {
> + /* Check if backend device */
> + list_for_each_entry(ctx, &usb_ctx_list, list) {
... and here you walk again through the list without locking.
Something's weird here, if this was intentional you need to add comments.
> + if (dev->of_node == ctx->dev->of_node)
> + goto out;
> + }
> + ctx = NULL;
> + }
> +out:
> + return ctx ? ctx->priv_data : NULL;
> +}
> +EXPORT_SYMBOL_GPL(snd_soc_usb_get_priv_data);
> +
> +/**
> + * snd_soc_usb_add_port() - Add a USB backend port
> + * @dev: USB backend device
> + * @priv: private data
> + * @connection_cb: connection status callback
> + *
> + * Register a USB backend device to the SND USB SOC framework. Memory is
> + * allocated as part of the USB backend device.
> + *
> + */
> +struct snd_soc_usb *snd_soc_usb_add_port(struct device *dev, void *priv,
> + int (*connection_cb)(struct snd_soc_usb *usb, int card_idx,
> + int connected))
> +{
> + struct snd_soc_usb *usb;
> +
> + usb = devm_kzalloc(dev, sizeof(*usb), GFP_KERNEL);
> + if (!usb)
> + return ERR_PTR(-ENOMEM);
> +
> + usb->connection_status_cb = connection_cb;
> + usb->dev = dev;
> + usb->priv_data = priv;
back to my comment above, you don't seem to need the set_priv_data() ?
> +
> + mutex_lock(&ctx_mutex);
> + list_add_tail(&usb->list, &usb_ctx_list);
> + mutex_unlock(&ctx_mutex);
> +
> + return usb;
> +}
> +EXPORT_SYMBOL_GPL(snd_soc_usb_add_port);
> +/**
> + * snd_soc_usb_connect() - Notification of USB device connection
> + * @usbdev: USB bus device
> + * @card_idx: USB SND card instance
> + *
> + * Notify of a new USB SND device connection. The card_idx can be used to
> + * handle how the USB backend selects, which device to enable offloading on.
"USB backend" is confusing, not sure if this is the same concept as DPCM
"backend" or something else. Please try to avoid overloaded terms.
> + *
> + */
> +int snd_soc_usb_connect(struct device *usbdev, int card_idx)
> +{
> + struct snd_soc_usb *ctx;
> +
> + if (!usbdev)
> + return -ENODEV;
> +
> + ctx = snd_soc_find_usb_ctx(usbdev);
> + if (!ctx)
> + return -ENODEV;
> +
> + if (ctx->connection_status_cb)
> + ctx->connection_status_cb(ctx, card_idx, 1);
so either the 'connected' value is 1...
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(snd_soc_usb_connect);
> +
> +/**
> + * snd_soc_usb_disconnect() - Notification of USB device disconnection
> + * @usbdev: USB bus device
> + *
> + * Notify of a new USB SND device disconnection to the USB backend.
> + *
> + */
> +int snd_soc_usb_disconnect(struct device *usbdev)
> +{
> + struct snd_soc_usb *ctx;
> +
> + if (!usbdev)
> + return -ENODEV;
> +
> + ctx = snd_soc_find_usb_ctx(usbdev);
> + if (!ctx)
> + return -ENODEV;
> +
> + if (ctx->connection_status_cb)
> + ctx->connection_status_cb(ctx, -1, 0);
...and here it's zero.
should this 'connected' parameter be a boolean then with true/false
value, or do you want to add enums/defines for more flexibility down the
road?
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(snd_soc_usb_disconnect);
Hi Pierre,
On 7/25/2023 1:10 AM, Pierre-Louis Bossart wrote:
>
>> +/**
>> + * struct snd_soc_usb
>> + * @list - list head for SND SOC struct list
>> + * @dev - USB backend device reference
>> + * @component - reference to DAPM component
>
> ASoC component, not DAPM.
>
Thanks for the detailed review. Will fix this.
>> + * @connection_status_cb - callback to notify connection events
>> + * @priv_data - driver data
>> + **/
>> +struct snd_soc_usb {
>> + struct list_head list;
>> + struct device *dev;
>> + struct snd_soc_component *component;
>> + int (*connection_status_cb)(struct snd_soc_usb *usb, int card_idx,
>> + int connected);
>
> It's not clear what 'connected' really refers to, is this a boolean
> really or is this a "connection_event?
>
>
I'll change it to boolean for now, since its currently only
connected/disconnected. If more states are required, then we can add
that in the future.
>> + void *priv_data;
>> +};
>> +
>> +int snd_soc_usb_connect(struct device *usbdev, int card_idx);
>> +int snd_soc_usb_disconnect(struct device *usbdev);
>> +void snd_soc_usb_set_priv_data(struct device *dev, void *priv);
>
> this function is not part of this patch, is this intentional to have a
> get but not a set?
>
>> +void *snd_soc_usb_get_priv_data(struct device *usbdev);
>
> you are using 'usbdev' and 'dev' for the same type of parameters, why
> not align on one set of definition with a consistent naming.
>
>
I'll take a look at this and see what happened. I think Greg mentioned
something similar and I made the change to remove the set priv, and
moved it elsewhere.
>> +static struct snd_soc_usb *snd_soc_find_usb_ctx(struct device *dev)
>> +{
>> + struct device_node *node;
>> + struct snd_soc_usb *ctx = NULL;
>
> this init doesn't seem required?
>
Yes, not needed.
>> +
>> + node = snd_soc_find_phandle(dev);
>> + if (IS_ERR(node))
>> + return NULL;
>> +
>> + mutex_lock(&ctx_mutex);
>> + list_for_each_entry(ctx, &usb_ctx_list, list) {
>> + if (ctx->dev->of_node == node) {
>> + of_node_put(node);
>> + mutex_unlock(&ctx_mutex);
>> + return ctx;
>> + }
>> + }
>> + of_node_put(node);
>> + mutex_unlock(&ctx_mutex);
>> +
>> + return NULL;
>> +}
>> +
>> +/**
>> + * snd_soc_usb_get_priv_data() - Retrieve private data stored
>> + * @dev: device reference
>> + *
>> + * Fetch the private data stored in the USB SND SOC structure.
>> + *
>> + */
>> +void *snd_soc_usb_get_priv_data(struct device *dev)
>> +{
>> + struct snd_soc_usb *ctx;
>> +
>> + ctx = snd_soc_find_usb_ctx(dev);
>
> so in this function you walk through the usb_ctx_list with locking...
>
>> + if (!ctx) {
>> + /* Check if backend device */
>> + list_for_each_entry(ctx, &usb_ctx_list, list) {
>
> ... and here you walk again through the list without locking.
>
> Something's weird here, if this was intentional you need to add comments.
>
Yes, needs a lock here.
>> + if (dev->of_node == ctx->dev->of_node)
>> + goto out;
>> + }
>> + ctx = NULL;
>> + }
>> +out:
>> + return ctx ? ctx->priv_data : NULL;
>> +}
>> +EXPORT_SYMBOL_GPL(snd_soc_usb_get_priv_data);
>> +
>> +/**
>> + * snd_soc_usb_add_port() - Add a USB backend port
>> + * @dev: USB backend device
>> + * @priv: private data
>> + * @connection_cb: connection status callback
>> + *
>> + * Register a USB backend device to the SND USB SOC framework. Memory is
>> + * allocated as part of the USB backend device.
>> + *
>> + */
>> +struct snd_soc_usb *snd_soc_usb_add_port(struct device *dev, void *priv,
>> + int (*connection_cb)(struct snd_soc_usb *usb, int card_idx,
>> + int connected))
>> +{
>> + struct snd_soc_usb *usb;
>> +
>> + usb = devm_kzalloc(dev, sizeof(*usb), GFP_KERNEL);
>> + if (!usb)
>> + return ERR_PTR(-ENOMEM);
>> +
>> + usb->connection_status_cb = connection_cb;
>> + usb->dev = dev;
>> + usb->priv_data = priv;
>
> back to my comment above, you don't seem to need the set_priv_data() ?
>
And yes, this is where I moved the priv data setting.
>> +
>> + mutex_lock(&ctx_mutex);
>> + list_add_tail(&usb->list, &usb_ctx_list);
>> + mutex_unlock(&ctx_mutex);
>> +
>> + return usb;
>> +}
>> +EXPORT_SYMBOL_GPL(snd_soc_usb_add_port);
>
>> +/**
>> + * snd_soc_usb_connect() - Notification of USB device connection
>> + * @usbdev: USB bus device
>> + * @card_idx: USB SND card instance
>> + *
>> + * Notify of a new USB SND device connection. The card_idx can be used to
>> + * handle how the USB backend selects, which device to enable offloading on.
>
> "USB backend" is confusing, not sure if this is the same concept as DPCM
> "backend" or something else. Please try to avoid overloaded terms.
>
Sure, I meant DPCM backend.
>> + *
>> + */
>> +int snd_soc_usb_connect(struct device *usbdev, int card_idx)
>> +{
>> + struct snd_soc_usb *ctx;
>> +
>> + if (!usbdev)
>> + return -ENODEV;
>> +
>> + ctx = snd_soc_find_usb_ctx(usbdev);
>> + if (!ctx)
>> + return -ENODEV;
>> +
>> + if (ctx->connection_status_cb)
>> + ctx->connection_status_cb(ctx, card_idx, 1);
>
> so either the 'connected' value is 1...
>> +
>> + return 0;
>> +}
>> +EXPORT_SYMBOL_GPL(snd_soc_usb_connect);
>> +
>> +/**
>> + * snd_soc_usb_disconnect() - Notification of USB device disconnection
>> + * @usbdev: USB bus device
>> + *
>> + * Notify of a new USB SND device disconnection to the USB backend.
>> + *
>> + */
>> +int snd_soc_usb_disconnect(struct device *usbdev)
>> +{
>> + struct snd_soc_usb *ctx;
>> +
>> + if (!usbdev)
>> + return -ENODEV;
>> +
>> + ctx = snd_soc_find_usb_ctx(usbdev);
>> + if (!ctx)
>> + return -ENODEV;
>> +
>> + if (ctx->connection_status_cb)
>> + ctx->connection_status_cb(ctx, -1, 0);
>
> ...and here it's zero.
>
> should this 'connected' parameter be a boolean then with true/false
> value, or do you want to add enums/defines for more flexibility down the
> road?
>
As mentioned above, will switch to boolean for now. I don't see a need
to have other enums.
Thanks
Wesley Cheng
new file mode 100644
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef __LINUX_SND_SOC_USB_H
+#define __LINUX_SND_SOC_USB_H
+
+/**
+ * struct snd_soc_usb
+ * @list - list head for SND SOC struct list
+ * @dev - USB backend device reference
+ * @component - reference to DAPM component
+ * @connection_status_cb - callback to notify connection events
+ * @priv_data - driver data
+ **/
+struct snd_soc_usb {
+ struct list_head list;
+ struct device *dev;
+ struct snd_soc_component *component;
+ int (*connection_status_cb)(struct snd_soc_usb *usb, int card_idx,
+ int connected);
+ void *priv_data;
+};
+
+int snd_soc_usb_connect(struct device *usbdev, int card_idx);
+int snd_soc_usb_disconnect(struct device *usbdev);
+void snd_soc_usb_set_priv_data(struct device *dev, void *priv);
+void *snd_soc_usb_get_priv_data(struct device *usbdev);
+
+struct snd_soc_usb *snd_soc_usb_add_port(struct device *dev, void *priv,
+ int (*connection_cb)(struct snd_soc_usb *usb, int card_idx,
+ int connected));
+int snd_soc_usb_remove_port(struct device *dev);
+#endif
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
-snd-soc-core-objs := soc-core.o soc-dapm.o soc-jack.o soc-utils.o soc-dai.o soc-component.o
+snd-soc-core-objs := soc-core.o soc-dapm.o soc-jack.o soc-usb.o soc-utils.o soc-dai.o soc-component.o
snd-soc-core-objs += soc-pcm.o soc-devres.o soc-ops.o soc-link.o soc-card.o
snd-soc-core-$(CONFIG_SND_SOC_COMPRESS) += soc-compress.o
new file mode 100644
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+#include <linux/of.h>
+#include <linux/usb.h>
+#include <sound/soc.h>
+#include <sound/soc-usb.h>
+#include "../usb/card.h"
+
+static DEFINE_MUTEX(ctx_mutex);
+static LIST_HEAD(usb_ctx_list);
+
+static struct device_node *snd_soc_find_phandle(struct device *dev)
+{
+ struct device_node *node;
+
+ node = of_parse_phandle(dev->of_node, "usb-soc-be", 0);
+ if (!node)
+ return ERR_PTR(-ENODEV);
+
+ return node;
+}
+
+static struct snd_soc_usb *snd_soc_find_usb_ctx(struct device *dev)
+{
+ struct device_node *node;
+ struct snd_soc_usb *ctx = NULL;
+
+ node = snd_soc_find_phandle(dev);
+ if (IS_ERR(node))
+ return NULL;
+
+ mutex_lock(&ctx_mutex);
+ list_for_each_entry(ctx, &usb_ctx_list, list) {
+ if (ctx->dev->of_node == node) {
+ of_node_put(node);
+ mutex_unlock(&ctx_mutex);
+ return ctx;
+ }
+ }
+ of_node_put(node);
+ mutex_unlock(&ctx_mutex);
+
+ return NULL;
+}
+
+/**
+ * snd_soc_usb_get_priv_data() - Retrieve private data stored
+ * @dev: device reference
+ *
+ * Fetch the private data stored in the USB SND SOC structure.
+ *
+ */
+void *snd_soc_usb_get_priv_data(struct device *dev)
+{
+ struct snd_soc_usb *ctx;
+
+ ctx = snd_soc_find_usb_ctx(dev);
+ if (!ctx) {
+ /* Check if backend device */
+ list_for_each_entry(ctx, &usb_ctx_list, list) {
+ if (dev->of_node == ctx->dev->of_node)
+ goto out;
+ }
+ ctx = NULL;
+ }
+out:
+ return ctx ? ctx->priv_data : NULL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_usb_get_priv_data);
+
+/**
+ * snd_soc_usb_add_port() - Add a USB backend port
+ * @dev: USB backend device
+ * @priv: private data
+ * @connection_cb: connection status callback
+ *
+ * Register a USB backend device to the SND USB SOC framework. Memory is
+ * allocated as part of the USB backend device.
+ *
+ */
+struct snd_soc_usb *snd_soc_usb_add_port(struct device *dev, void *priv,
+ int (*connection_cb)(struct snd_soc_usb *usb, int card_idx,
+ int connected))
+{
+ struct snd_soc_usb *usb;
+
+ usb = devm_kzalloc(dev, sizeof(*usb), GFP_KERNEL);
+ if (!usb)
+ return ERR_PTR(-ENOMEM);
+
+ usb->connection_status_cb = connection_cb;
+ usb->dev = dev;
+ usb->priv_data = priv;
+
+ mutex_lock(&ctx_mutex);
+ list_add_tail(&usb->list, &usb_ctx_list);
+ mutex_unlock(&ctx_mutex);
+
+ return usb;
+}
+EXPORT_SYMBOL_GPL(snd_soc_usb_add_port);
+
+/**
+ * snd_soc_usb_remove_port() - Remove a USB backend port
+ * @dev: USB backend device
+ *
+ * Remove a USB backend device from USB SND SOC. Memory is freed when USB
+ * backend is removed.
+ *
+ */
+int snd_soc_usb_remove_port(struct device *dev)
+{
+ struct snd_soc_usb *ctx, *tmp;
+
+ mutex_lock(&ctx_mutex);
+ list_for_each_entry_safe(ctx, tmp, &usb_ctx_list, list) {
+ if (ctx->dev == dev) {
+ list_del(&ctx->list);
+ break;
+ }
+ }
+ mutex_unlock(&ctx_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_usb_remove_port);
+
+/**
+ * snd_soc_usb_connect() - Notification of USB device connection
+ * @usbdev: USB bus device
+ * @card_idx: USB SND card instance
+ *
+ * Notify of a new USB SND device connection. The card_idx can be used to
+ * handle how the USB backend selects, which device to enable offloading on.
+ *
+ */
+int snd_soc_usb_connect(struct device *usbdev, int card_idx)
+{
+ struct snd_soc_usb *ctx;
+
+ if (!usbdev)
+ return -ENODEV;
+
+ ctx = snd_soc_find_usb_ctx(usbdev);
+ if (!ctx)
+ return -ENODEV;
+
+ if (ctx->connection_status_cb)
+ ctx->connection_status_cb(ctx, card_idx, 1);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_usb_connect);
+
+/**
+ * snd_soc_usb_disconnect() - Notification of USB device disconnection
+ * @usbdev: USB bus device
+ *
+ * Notify of a new USB SND device disconnection to the USB backend.
+ *
+ */
+int snd_soc_usb_disconnect(struct device *usbdev)
+{
+ struct snd_soc_usb *ctx;
+
+ if (!usbdev)
+ return -ENODEV;
+
+ ctx = snd_soc_find_usb_ctx(usbdev);
+ if (!ctx)
+ return -ENODEV;
+
+ if (ctx->connection_status_cb)
+ ctx->connection_status_cb(ctx, -1, 0);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_usb_disconnect);