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