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
snd_soc_find_phandle(struct device * dev)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
snd_soc_usb_ctx_lookup(struct device_node * node)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
snd_soc_find_usb_ctx(struct device * dev)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 */
snd_soc_usb_setup_offload_jack(struct snd_soc_component * component,struct snd_soc_jack * jack)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 */
snd_soc_usb_update_offload_route(struct device * dev,int card,int pcm,int direction,enum snd_soc_usb_kctl path,long * route)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 */
snd_soc_usb_find_priv_data(struct device * usbdev)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 */
snd_soc_usb_find_supported_format(int card_idx,struct snd_pcm_hw_params * params,int direction)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 */
snd_soc_usb_allocate_port(struct snd_soc_component * component,void * data)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 */
snd_soc_usb_free_port(struct snd_soc_usb * usb)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 */
snd_soc_usb_add_port(struct snd_soc_usb * usb)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 */
snd_soc_usb_remove_port(struct snd_soc_usb * usb)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 */
snd_soc_usb_connect(struct device * usbdev,struct snd_soc_usb_device * sdev)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 */
snd_soc_usb_disconnect(struct device * usbdev,struct snd_soc_usb_device * sdev)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