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 13 static DEFINE_MUTEX(ctx_mutex); 14 static LIST_HEAD(usb_ctx_list); 15 16 static 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 27 static 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 42 static 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 */ 70 int 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 } 91 EXPORT_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 */ 108 int 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); 123 exit: 124 mutex_unlock(&ctx_mutex); 125 126 return ret; 127 } 128 EXPORT_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 */ 137 void *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 } 147 EXPORT_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 */ 161 int 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 } 173 EXPORT_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 */ 187 struct 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 } 201 EXPORT_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 */ 211 void snd_soc_usb_free_port(struct snd_soc_usb *usb) 212 { 213 snd_soc_usb_remove_port(usb); 214 kfree(usb); 215 } 216 EXPORT_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 */ 226 void 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 } 234 EXPORT_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 */ 244 void 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 } 257 EXPORT_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 */ 269 int 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 284 exit: 285 mutex_unlock(&ctx_mutex); 286 287 return 0; 288 } 289 EXPORT_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 */ 299 int 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 314 exit: 315 mutex_unlock(&ctx_mutex); 316 317 return 0; 318 } 319 EXPORT_SYMBOL_GPL(snd_soc_usb_disconnect); 320 321 MODULE_LICENSE("GPL"); 322 MODULE_DESCRIPTION("SoC USB driver for offloading"); 323