linux/sound/soc/soc-usb.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved.
   4 */
   5#include <linux/of.h>
   6#include <linux/usb.h>
   7
   8#include <sound/jack.h>
   9#include <sound/soc-usb.h>
  10
  11#include "../usb/card.h"
  12
  13static DEFINE_MUTEX(ctx_mutex);
  14static LIST_HEAD(usb_ctx_list);
  15
  16static struct device_node *snd_soc_find_phandle(struct device *dev)
  17{
  18        struct device_node *node;
  19
  20        node = of_parse_phandle(dev->of_node, "usb-soc-be", 0);
  21        if (!node)
  22                return ERR_PTR(-ENODEV);
  23
  24        return node;
  25}
  26
  27static struct snd_soc_usb *snd_soc_usb_ctx_lookup(struct device_node *node)
  28{
  29        struct snd_soc_usb *ctx;
  30
  31        if (!node)
  32                return NULL;
  33
  34        list_for_each_entry(ctx, &usb_ctx_list, list) {
  35                if (ctx->component->dev->of_node == node)
  36                        return ctx;
  37        }
  38
  39        return NULL;
  40}
  41
  42static struct snd_soc_usb *snd_soc_find_usb_ctx(struct device *dev)
  43{
  44        struct snd_soc_usb *ctx;
  45        struct device_node *node;
  46
  47        node = snd_soc_find_phandle(dev);
  48        if (!IS_ERR(node)) {
  49                ctx = snd_soc_usb_ctx_lookup(node);
  50                of_node_put(node);
  51        } else {
  52                ctx = snd_soc_usb_ctx_lookup(dev->of_node);
  53        }
  54
  55        return ctx ? ctx : NULL;
  56}
  57
  58/* SOC USB sound kcontrols */
  59/**
  60 * snd_soc_usb_setup_offload_jack() - Create USB offloading jack
  61 * @component: USB DPCM backend DAI component
  62 * @jack: jack structure to create
  63 *
  64 * Creates a jack device for notifying userspace of the availability
  65 * of an offload capable device.
  66 *
  67 * Returns 0 on success, negative on error.
  68 *
  69 */
  70int snd_soc_usb_setup_offload_jack(struct snd_soc_component *component,
  71                                   struct snd_soc_jack *jack)
  72{
  73        int ret;
  74
  75        ret = snd_soc_card_jack_new(component->card, "USB Offload Jack",
  76                                    SND_JACK_USB, jack);
  77        if (ret < 0) {
  78                dev_err(component->card->dev, "Unable to add USB offload jack: %d\n",
  79                        ret);
  80                return ret;
  81        }
  82
  83        ret = snd_soc_component_set_jack(component, jack, NULL);
  84        if (ret) {
  85                dev_err(component->card->dev, "Failed to set jack: %d\n", ret);
  86                return ret;
  87        }
  88
  89        return 0;
  90}
  91EXPORT_SYMBOL_GPL(snd_soc_usb_setup_offload_jack);
  92
  93/**
  94 * snd_soc_usb_update_offload_route - Find active USB offload path
  95 * @dev: USB device to get offload status
  96 * @card: USB card index
  97 * @pcm: USB PCM device index
  98 * @direction: playback or capture direction
  99 * @path: pcm or card index
 100 * @route: pointer to route output array
 101 *
 102 * Fetch the current status for the USB SND card and PCM device indexes
 103 * specified.  The "route" argument should be an array of integers being
 104 * used for a kcontrol output.  The first element should have the selected
 105 * card index, and the second element should have the selected pcm device
 106 * index.
 107 */
 108int snd_soc_usb_update_offload_route(struct device *dev, int card, int pcm,
 109                                     int direction, enum snd_soc_usb_kctl path,
 110                                     long *route)
 111{
 112        struct snd_soc_usb *ctx;
 113        int ret = -ENODEV;
 114
 115        mutex_lock(&ctx_mutex);
 116        ctx = snd_soc_find_usb_ctx(dev);
 117        if (!ctx)
 118                goto exit;
 119
 120        if (ctx->update_offload_route_info)
 121                ret = ctx->update_offload_route_info(ctx->component, card, pcm,
 122                                                     direction, path, route);
 123exit:
 124        mutex_unlock(&ctx_mutex);
 125
 126        return ret;
 127}
 128EXPORT_SYMBOL_GPL(snd_soc_usb_update_offload_route);
 129
 130/**
 131 * snd_soc_usb_find_priv_data() - Retrieve private data stored
 132 * @usbdev: device reference
 133 *
 134 * Fetch the private data stored in the USB SND SoC structure.
 135 *
 136 */
 137void *snd_soc_usb_find_priv_data(struct device *usbdev)
 138{
 139        struct snd_soc_usb *ctx;
 140
 141        mutex_lock(&ctx_mutex);
 142        ctx = snd_soc_find_usb_ctx(usbdev);
 143        mutex_unlock(&ctx_mutex);
 144
 145        return ctx ? ctx->priv_data : NULL;
 146}
 147EXPORT_SYMBOL_GPL(snd_soc_usb_find_priv_data);
 148
 149/**
 150 * snd_soc_usb_find_supported_format() - Check if audio format is supported
 151 * @card_idx: USB sound chip array index
 152 * @params: PCM parameters
 153 * @direction: capture or playback
 154 *
 155 * Ensure that a requested audio profile from the ASoC side is able to be
 156 * supported by the USB device.
 157 *
 158 * Return 0 on success, negative on error.
 159 *
 160 */
 161int snd_soc_usb_find_supported_format(int card_idx,
 162                                      struct snd_pcm_hw_params *params,
 163                                      int direction)
 164{
 165        struct snd_usb_stream *as;
 166
 167        as = snd_usb_find_suppported_substream(card_idx, params, direction);
 168        if (!as)
 169                return -EOPNOTSUPP;
 170
 171        return 0;
 172}
 173EXPORT_SYMBOL_GPL(snd_soc_usb_find_supported_format);
 174
 175/**
 176 * snd_soc_usb_allocate_port() - allocate a SoC USB port for offloading support
 177 * @component: USB DPCM backend DAI component
 178 * @data: private data
 179 *
 180 * Allocate and initialize a SoC USB port.  The SoC USB port is used to communicate
 181 * different USB audio devices attached, in order to start audio offloading handled
 182 * by an ASoC entity.  USB device plug in/out events are signaled with a
 183 * notification, but don't directly impact the memory allocated for the SoC USB
 184 * port.
 185 *
 186 */
 187struct snd_soc_usb *snd_soc_usb_allocate_port(struct snd_soc_component *component,
 188                                              void *data)
 189{
 190        struct snd_soc_usb *usb;
 191
 192        usb = kzalloc(sizeof(*usb), GFP_KERNEL);
 193        if (!usb)
 194                return ERR_PTR(-ENOMEM);
 195
 196        usb->component = component;
 197        usb->priv_data = data;
 198
 199        return usb;
 200}
 201EXPORT_SYMBOL_GPL(snd_soc_usb_allocate_port);
 202
 203/**
 204 * snd_soc_usb_free_port() - free a SoC USB port used for offloading support
 205 * @usb: allocated SoC USB port
 206 *
 207 * Free and remove the SoC USB port from the available list of ports.  This will
 208 * ensure that the communication between USB SND and ASoC is halted.
 209 *
 210 */
 211void snd_soc_usb_free_port(struct snd_soc_usb *usb)
 212{
 213        snd_soc_usb_remove_port(usb);
 214        kfree(usb);
 215}
 216EXPORT_SYMBOL_GPL(snd_soc_usb_free_port);
 217
 218/**
 219 * snd_soc_usb_add_port() - Add a USB backend port
 220 * @usb: soc usb port to add
 221 *
 222 * Register a USB backend DAI link to the USB SoC framework.  Memory is allocated
 223 * as part of the USB backend DAI link.
 224 *
 225 */
 226void snd_soc_usb_add_port(struct snd_soc_usb *usb)
 227{
 228        mutex_lock(&ctx_mutex);
 229        list_add_tail(&usb->list, &usb_ctx_list);
 230        mutex_unlock(&ctx_mutex);
 231
 232        snd_usb_rediscover_devices();
 233}
 234EXPORT_SYMBOL_GPL(snd_soc_usb_add_port);
 235
 236/**
 237 * snd_soc_usb_remove_port() - Remove a USB backend port
 238 * @usb: soc usb port to remove
 239 *
 240 * Remove a USB backend DAI link from USB SoC.  Memory is freed when USB backend
 241 * DAI is removed, or when snd_soc_usb_free_port() is called.
 242 *
 243 */
 244void snd_soc_usb_remove_port(struct snd_soc_usb *usb)
 245{
 246        struct snd_soc_usb *ctx, *tmp;
 247
 248        mutex_lock(&ctx_mutex);
 249        list_for_each_entry_safe(ctx, tmp, &usb_ctx_list, list) {
 250                if (ctx == usb) {
 251                        list_del(&ctx->list);
 252                        break;
 253                }
 254        }
 255        mutex_unlock(&ctx_mutex);
 256}
 257EXPORT_SYMBOL_GPL(snd_soc_usb_remove_port);
 258
 259/**
 260 * snd_soc_usb_connect() - Notification of USB device connection
 261 * @usbdev: USB bus device
 262 * @sdev: USB SND device to add
 263 *
 264 * Notify of a new USB SND device connection.  The sdev->card_idx can be used to
 265 * handle how the DPCM backend selects, which device to enable USB offloading
 266 * on.
 267 *
 268 */
 269int snd_soc_usb_connect(struct device *usbdev, struct snd_soc_usb_device *sdev)
 270{
 271        struct snd_soc_usb *ctx;
 272
 273        if (!usbdev)
 274                return -ENODEV;
 275
 276        mutex_lock(&ctx_mutex);
 277        ctx = snd_soc_find_usb_ctx(usbdev);
 278        if (!ctx)
 279                goto exit;
 280
 281        if (ctx->connection_status_cb)
 282                ctx->connection_status_cb(ctx, sdev, true);
 283
 284exit:
 285        mutex_unlock(&ctx_mutex);
 286
 287        return 0;
 288}
 289EXPORT_SYMBOL_GPL(snd_soc_usb_connect);
 290
 291/**
 292 * snd_soc_usb_disconnect() - Notification of USB device disconnection
 293 * @usbdev: USB bus device
 294 * @sdev: USB SND device to remove
 295 *
 296 * Notify of a new USB SND device disconnection to the USB backend.
 297 *
 298 */
 299int snd_soc_usb_disconnect(struct device *usbdev, struct snd_soc_usb_device *sdev)
 300{
 301        struct snd_soc_usb *ctx;
 302
 303        if (!usbdev)
 304                return -ENODEV;
 305
 306        mutex_lock(&ctx_mutex);
 307        ctx = snd_soc_find_usb_ctx(usbdev);
 308        if (!ctx)
 309                goto exit;
 310
 311        if (ctx->connection_status_cb)
 312                ctx->connection_status_cb(ctx, sdev, false);
 313
 314exit:
 315        mutex_unlock(&ctx_mutex);
 316
 317        return 0;
 318}
 319EXPORT_SYMBOL_GPL(snd_soc_usb_disconnect);
 320
 321MODULE_LICENSE("GPL");
 322MODULE_DESCRIPTION("SoC USB driver for offloading");
 323