xref: /linux/sound/soc/soc-usb.c (revision c0c9379f235df33a12ceae94370ad80c5278324d)
1dba7759aSWesley Cheng // SPDX-License-Identifier: GPL-2.0
2dba7759aSWesley Cheng /*
3dba7759aSWesley Cheng  * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved.
4dba7759aSWesley Cheng  */
5dba7759aSWesley Cheng #include <linux/of.h>
6dba7759aSWesley Cheng #include <linux/usb.h>
70bb5f361SWesley Cheng 
80bb5f361SWesley Cheng #include <sound/jack.h>
9dba7759aSWesley Cheng #include <sound/soc-usb.h>
100bb5f361SWesley Cheng 
11dba7759aSWesley Cheng #include "../usb/card.h"
12dba7759aSWesley Cheng 
13dba7759aSWesley Cheng static DEFINE_MUTEX(ctx_mutex);
14dba7759aSWesley Cheng static LIST_HEAD(usb_ctx_list);
15dba7759aSWesley Cheng 
snd_soc_find_phandle(struct device * dev)16dba7759aSWesley Cheng static struct device_node *snd_soc_find_phandle(struct device *dev)
17dba7759aSWesley Cheng {
18dba7759aSWesley Cheng 	struct device_node *node;
19dba7759aSWesley Cheng 
20dba7759aSWesley Cheng 	node = of_parse_phandle(dev->of_node, "usb-soc-be", 0);
21dba7759aSWesley Cheng 	if (!node)
22dba7759aSWesley Cheng 		return ERR_PTR(-ENODEV);
23dba7759aSWesley Cheng 
24dba7759aSWesley Cheng 	return node;
25dba7759aSWesley Cheng }
26dba7759aSWesley Cheng 
snd_soc_usb_ctx_lookup(struct device_node * node)27dba7759aSWesley Cheng static struct snd_soc_usb *snd_soc_usb_ctx_lookup(struct device_node *node)
28dba7759aSWesley Cheng {
29dba7759aSWesley Cheng 	struct snd_soc_usb *ctx;
30dba7759aSWesley Cheng 
31dba7759aSWesley Cheng 	if (!node)
32dba7759aSWesley Cheng 		return NULL;
33dba7759aSWesley Cheng 
34dba7759aSWesley Cheng 	list_for_each_entry(ctx, &usb_ctx_list, list) {
35dba7759aSWesley Cheng 		if (ctx->component->dev->of_node == node)
36dba7759aSWesley Cheng 			return ctx;
37dba7759aSWesley Cheng 	}
38dba7759aSWesley Cheng 
39dba7759aSWesley Cheng 	return NULL;
40dba7759aSWesley Cheng }
41dba7759aSWesley Cheng 
snd_soc_find_usb_ctx(struct device * dev)42dba7759aSWesley Cheng static struct snd_soc_usb *snd_soc_find_usb_ctx(struct device *dev)
43dba7759aSWesley Cheng {
44dba7759aSWesley Cheng 	struct snd_soc_usb *ctx;
45dba7759aSWesley Cheng 	struct device_node *node;
46dba7759aSWesley Cheng 
47dba7759aSWesley Cheng 	node = snd_soc_find_phandle(dev);
48dba7759aSWesley Cheng 	if (!IS_ERR(node)) {
49dba7759aSWesley Cheng 		ctx = snd_soc_usb_ctx_lookup(node);
50dba7759aSWesley Cheng 		of_node_put(node);
51dba7759aSWesley Cheng 	} else {
52dba7759aSWesley Cheng 		ctx = snd_soc_usb_ctx_lookup(dev->of_node);
53dba7759aSWesley Cheng 	}
54dba7759aSWesley Cheng 
55dba7759aSWesley Cheng 	return ctx ? ctx : NULL;
56dba7759aSWesley Cheng }
57dba7759aSWesley Cheng 
580bb5f361SWesley Cheng /* SOC USB sound kcontrols */
590bb5f361SWesley Cheng /**
600bb5f361SWesley Cheng  * snd_soc_usb_setup_offload_jack() - Create USB offloading jack
610bb5f361SWesley Cheng  * @component: USB DPCM backend DAI component
620bb5f361SWesley Cheng  * @jack: jack structure to create
630bb5f361SWesley Cheng  *
640bb5f361SWesley Cheng  * Creates a jack device for notifying userspace of the availability
650bb5f361SWesley Cheng  * of an offload capable device.
660bb5f361SWesley Cheng  *
670bb5f361SWesley Cheng  * Returns 0 on success, negative on error.
680bb5f361SWesley Cheng  *
690bb5f361SWesley Cheng  */
snd_soc_usb_setup_offload_jack(struct snd_soc_component * component,struct snd_soc_jack * jack)700bb5f361SWesley Cheng int snd_soc_usb_setup_offload_jack(struct snd_soc_component *component,
710bb5f361SWesley Cheng 				   struct snd_soc_jack *jack)
720bb5f361SWesley Cheng {
730bb5f361SWesley Cheng 	int ret;
740bb5f361SWesley Cheng 
750bb5f361SWesley Cheng 	ret = snd_soc_card_jack_new(component->card, "USB Offload Jack",
760bb5f361SWesley Cheng 				    SND_JACK_USB, jack);
770bb5f361SWesley Cheng 	if (ret < 0) {
780bb5f361SWesley Cheng 		dev_err(component->card->dev, "Unable to add USB offload jack: %d\n",
790bb5f361SWesley Cheng 			ret);
800bb5f361SWesley Cheng 		return ret;
810bb5f361SWesley Cheng 	}
820bb5f361SWesley Cheng 
830bb5f361SWesley Cheng 	ret = snd_soc_component_set_jack(component, jack, NULL);
840bb5f361SWesley Cheng 	if (ret) {
850bb5f361SWesley Cheng 		dev_err(component->card->dev, "Failed to set jack: %d\n", ret);
860bb5f361SWesley Cheng 		return ret;
870bb5f361SWesley Cheng 	}
880bb5f361SWesley Cheng 
890bb5f361SWesley Cheng 	return 0;
900bb5f361SWesley Cheng }
910bb5f361SWesley Cheng EXPORT_SYMBOL_GPL(snd_soc_usb_setup_offload_jack);
920bb5f361SWesley Cheng 
93dba7759aSWesley Cheng /**
94234ed325SWesley Cheng  * snd_soc_usb_update_offload_route - Find active USB offload path
95234ed325SWesley Cheng  * @dev: USB device to get offload status
96234ed325SWesley Cheng  * @card: USB card index
97234ed325SWesley Cheng  * @pcm: USB PCM device index
98234ed325SWesley Cheng  * @direction: playback or capture direction
99234ed325SWesley Cheng  * @path: pcm or card index
100234ed325SWesley Cheng  * @route: pointer to route output array
101234ed325SWesley Cheng  *
102234ed325SWesley Cheng  * Fetch the current status for the USB SND card and PCM device indexes
103234ed325SWesley Cheng  * specified.  The "route" argument should be an array of integers being
104234ed325SWesley Cheng  * used for a kcontrol output.  The first element should have the selected
105234ed325SWesley Cheng  * card index, and the second element should have the selected pcm device
106234ed325SWesley Cheng  * index.
107234ed325SWesley Cheng  */
snd_soc_usb_update_offload_route(struct device * dev,int card,int pcm,int direction,enum snd_soc_usb_kctl path,long * route)108234ed325SWesley Cheng int snd_soc_usb_update_offload_route(struct device *dev, int card, int pcm,
109234ed325SWesley Cheng 				     int direction, enum snd_soc_usb_kctl path,
110234ed325SWesley Cheng 				     long *route)
111234ed325SWesley Cheng {
112234ed325SWesley Cheng 	struct snd_soc_usb *ctx;
113234ed325SWesley Cheng 	int ret = -ENODEV;
114234ed325SWesley Cheng 
115234ed325SWesley Cheng 	mutex_lock(&ctx_mutex);
116234ed325SWesley Cheng 	ctx = snd_soc_find_usb_ctx(dev);
117234ed325SWesley Cheng 	if (!ctx)
118234ed325SWesley Cheng 		goto exit;
119234ed325SWesley Cheng 
120234ed325SWesley Cheng 	if (ctx->update_offload_route_info)
121234ed325SWesley Cheng 		ret = ctx->update_offload_route_info(ctx->component, card, pcm,
122234ed325SWesley Cheng 						     direction, path, route);
123234ed325SWesley Cheng exit:
124234ed325SWesley Cheng 	mutex_unlock(&ctx_mutex);
125234ed325SWesley Cheng 
126234ed325SWesley Cheng 	return ret;
127234ed325SWesley Cheng }
128234ed325SWesley Cheng EXPORT_SYMBOL_GPL(snd_soc_usb_update_offload_route);
129234ed325SWesley Cheng 
130234ed325SWesley Cheng /**
131dba7759aSWesley Cheng  * snd_soc_usb_find_priv_data() - Retrieve private data stored
132dba7759aSWesley Cheng  * @usbdev: device reference
133dba7759aSWesley Cheng  *
134dba7759aSWesley Cheng  * Fetch the private data stored in the USB SND SoC structure.
135dba7759aSWesley Cheng  *
136dba7759aSWesley Cheng  */
snd_soc_usb_find_priv_data(struct device * usbdev)137dba7759aSWesley Cheng void *snd_soc_usb_find_priv_data(struct device *usbdev)
138dba7759aSWesley Cheng {
139dba7759aSWesley Cheng 	struct snd_soc_usb *ctx;
140dba7759aSWesley Cheng 
141dba7759aSWesley Cheng 	mutex_lock(&ctx_mutex);
142dba7759aSWesley Cheng 	ctx = snd_soc_find_usb_ctx(usbdev);
143dba7759aSWesley Cheng 	mutex_unlock(&ctx_mutex);
144dba7759aSWesley Cheng 
145dba7759aSWesley Cheng 	return ctx ? ctx->priv_data : NULL;
146dba7759aSWesley Cheng }
147dba7759aSWesley Cheng EXPORT_SYMBOL_GPL(snd_soc_usb_find_priv_data);
148dba7759aSWesley Cheng 
149dba7759aSWesley Cheng /**
15000f5d6bfSWesley Cheng  * snd_soc_usb_find_supported_format() - Check if audio format is supported
15100f5d6bfSWesley Cheng  * @card_idx: USB sound chip array index
15200f5d6bfSWesley Cheng  * @params: PCM parameters
15300f5d6bfSWesley Cheng  * @direction: capture or playback
15400f5d6bfSWesley Cheng  *
15500f5d6bfSWesley Cheng  * Ensure that a requested audio profile from the ASoC side is able to be
15600f5d6bfSWesley Cheng  * supported by the USB device.
15700f5d6bfSWesley Cheng  *
15800f5d6bfSWesley Cheng  * Return 0 on success, negative on error.
15900f5d6bfSWesley Cheng  *
16000f5d6bfSWesley Cheng  */
snd_soc_usb_find_supported_format(int card_idx,struct snd_pcm_hw_params * params,int direction)16100f5d6bfSWesley Cheng int snd_soc_usb_find_supported_format(int card_idx,
16200f5d6bfSWesley Cheng 				      struct snd_pcm_hw_params *params,
16300f5d6bfSWesley Cheng 				      int direction)
16400f5d6bfSWesley Cheng {
16500f5d6bfSWesley Cheng 	struct snd_usb_stream *as;
16600f5d6bfSWesley Cheng 
16700f5d6bfSWesley Cheng 	as = snd_usb_find_suppported_substream(card_idx, params, direction);
16800f5d6bfSWesley Cheng 	if (!as)
16900f5d6bfSWesley Cheng 		return -EOPNOTSUPP;
17000f5d6bfSWesley Cheng 
17100f5d6bfSWesley Cheng 	return 0;
17200f5d6bfSWesley Cheng }
17300f5d6bfSWesley Cheng EXPORT_SYMBOL_GPL(snd_soc_usb_find_supported_format);
17400f5d6bfSWesley Cheng 
17500f5d6bfSWesley Cheng /**
176dba7759aSWesley Cheng  * snd_soc_usb_allocate_port() - allocate a SoC USB port for offloading support
177dba7759aSWesley Cheng  * @component: USB DPCM backend DAI component
178dba7759aSWesley Cheng  * @data: private data
179dba7759aSWesley Cheng  *
180dba7759aSWesley Cheng  * Allocate and initialize a SoC USB port.  The SoC USB port is used to communicate
181dba7759aSWesley Cheng  * different USB audio devices attached, in order to start audio offloading handled
182dba7759aSWesley Cheng  * by an ASoC entity.  USB device plug in/out events are signaled with a
183dba7759aSWesley Cheng  * notification, but don't directly impact the memory allocated for the SoC USB
184dba7759aSWesley Cheng  * port.
185dba7759aSWesley Cheng  *
186dba7759aSWesley Cheng  */
snd_soc_usb_allocate_port(struct snd_soc_component * component,void * data)187dba7759aSWesley Cheng struct snd_soc_usb *snd_soc_usb_allocate_port(struct snd_soc_component *component,
188dba7759aSWesley Cheng 					      void *data)
189dba7759aSWesley Cheng {
190dba7759aSWesley Cheng 	struct snd_soc_usb *usb;
191dba7759aSWesley Cheng 
192dba7759aSWesley Cheng 	usb = kzalloc(sizeof(*usb), GFP_KERNEL);
193dba7759aSWesley Cheng 	if (!usb)
194dba7759aSWesley Cheng 		return ERR_PTR(-ENOMEM);
195dba7759aSWesley Cheng 
196dba7759aSWesley Cheng 	usb->component = component;
197dba7759aSWesley Cheng 	usb->priv_data = data;
198dba7759aSWesley Cheng 
199dba7759aSWesley Cheng 	return usb;
200dba7759aSWesley Cheng }
201dba7759aSWesley Cheng EXPORT_SYMBOL_GPL(snd_soc_usb_allocate_port);
202dba7759aSWesley Cheng 
203dba7759aSWesley Cheng /**
204dba7759aSWesley Cheng  * snd_soc_usb_free_port() - free a SoC USB port used for offloading support
205dba7759aSWesley Cheng  * @usb: allocated SoC USB port
206dba7759aSWesley Cheng  *
207dba7759aSWesley Cheng  * Free and remove the SoC USB port from the available list of ports.  This will
208dba7759aSWesley Cheng  * ensure that the communication between USB SND and ASoC is halted.
209dba7759aSWesley Cheng  *
210dba7759aSWesley Cheng  */
snd_soc_usb_free_port(struct snd_soc_usb * usb)211dba7759aSWesley Cheng void snd_soc_usb_free_port(struct snd_soc_usb *usb)
212dba7759aSWesley Cheng {
213dba7759aSWesley Cheng 	snd_soc_usb_remove_port(usb);
214dba7759aSWesley Cheng 	kfree(usb);
215dba7759aSWesley Cheng }
216dba7759aSWesley Cheng EXPORT_SYMBOL_GPL(snd_soc_usb_free_port);
217dba7759aSWesley Cheng 
218dba7759aSWesley Cheng /**
219dba7759aSWesley Cheng  * snd_soc_usb_add_port() - Add a USB backend port
220dba7759aSWesley Cheng  * @usb: soc usb port to add
221dba7759aSWesley Cheng  *
222dba7759aSWesley Cheng  * Register a USB backend DAI link to the USB SoC framework.  Memory is allocated
223dba7759aSWesley Cheng  * as part of the USB backend DAI link.
224dba7759aSWesley Cheng  *
225dba7759aSWesley Cheng  */
snd_soc_usb_add_port(struct snd_soc_usb * usb)226dba7759aSWesley Cheng void snd_soc_usb_add_port(struct snd_soc_usb *usb)
227dba7759aSWesley Cheng {
228dba7759aSWesley Cheng 	mutex_lock(&ctx_mutex);
229dba7759aSWesley Cheng 	list_add_tail(&usb->list, &usb_ctx_list);
230dba7759aSWesley Cheng 	mutex_unlock(&ctx_mutex);
231*f98cd6ecSWesley Cheng 
232*f98cd6ecSWesley Cheng 	snd_usb_rediscover_devices();
233dba7759aSWesley Cheng }
234dba7759aSWesley Cheng EXPORT_SYMBOL_GPL(snd_soc_usb_add_port);
235dba7759aSWesley Cheng 
236dba7759aSWesley Cheng /**
237dba7759aSWesley Cheng  * snd_soc_usb_remove_port() - Remove a USB backend port
238dba7759aSWesley Cheng  * @usb: soc usb port to remove
239dba7759aSWesley Cheng  *
240dba7759aSWesley Cheng  * Remove a USB backend DAI link from USB SoC.  Memory is freed when USB backend
241dba7759aSWesley Cheng  * DAI is removed, or when snd_soc_usb_free_port() is called.
242dba7759aSWesley Cheng  *
243dba7759aSWesley Cheng  */
snd_soc_usb_remove_port(struct snd_soc_usb * usb)244dba7759aSWesley Cheng void snd_soc_usb_remove_port(struct snd_soc_usb *usb)
245dba7759aSWesley Cheng {
246dba7759aSWesley Cheng 	struct snd_soc_usb *ctx, *tmp;
247dba7759aSWesley Cheng 
248dba7759aSWesley Cheng 	mutex_lock(&ctx_mutex);
249dba7759aSWesley Cheng 	list_for_each_entry_safe(ctx, tmp, &usb_ctx_list, list) {
250dba7759aSWesley Cheng 		if (ctx == usb) {
251dba7759aSWesley Cheng 			list_del(&ctx->list);
252dba7759aSWesley Cheng 			break;
253dba7759aSWesley Cheng 		}
254dba7759aSWesley Cheng 	}
255dba7759aSWesley Cheng 	mutex_unlock(&ctx_mutex);
256dba7759aSWesley Cheng }
257dba7759aSWesley Cheng EXPORT_SYMBOL_GPL(snd_soc_usb_remove_port);
258dba7759aSWesley Cheng 
259dba7759aSWesley Cheng /**
260dba7759aSWesley Cheng  * snd_soc_usb_connect() - Notification of USB device connection
261dba7759aSWesley Cheng  * @usbdev: USB bus device
262dba7759aSWesley Cheng  * @sdev: USB SND device to add
263dba7759aSWesley Cheng  *
264dba7759aSWesley Cheng  * Notify of a new USB SND device connection.  The sdev->card_idx can be used to
265dba7759aSWesley Cheng  * handle how the DPCM backend selects, which device to enable USB offloading
266dba7759aSWesley Cheng  * on.
267dba7759aSWesley Cheng  *
268dba7759aSWesley Cheng  */
snd_soc_usb_connect(struct device * usbdev,struct snd_soc_usb_device * sdev)269dba7759aSWesley Cheng int snd_soc_usb_connect(struct device *usbdev, struct snd_soc_usb_device *sdev)
270dba7759aSWesley Cheng {
271dba7759aSWesley Cheng 	struct snd_soc_usb *ctx;
272dba7759aSWesley Cheng 
273dba7759aSWesley Cheng 	if (!usbdev)
274dba7759aSWesley Cheng 		return -ENODEV;
275dba7759aSWesley Cheng 
276dba7759aSWesley Cheng 	mutex_lock(&ctx_mutex);
277dba7759aSWesley Cheng 	ctx = snd_soc_find_usb_ctx(usbdev);
278dba7759aSWesley Cheng 	if (!ctx)
279dba7759aSWesley Cheng 		goto exit;
280dba7759aSWesley Cheng 
281dba7759aSWesley Cheng 	if (ctx->connection_status_cb)
282dba7759aSWesley Cheng 		ctx->connection_status_cb(ctx, sdev, true);
283dba7759aSWesley Cheng 
284dba7759aSWesley Cheng exit:
285dba7759aSWesley Cheng 	mutex_unlock(&ctx_mutex);
286dba7759aSWesley Cheng 
287dba7759aSWesley Cheng 	return 0;
288dba7759aSWesley Cheng }
289dba7759aSWesley Cheng EXPORT_SYMBOL_GPL(snd_soc_usb_connect);
290dba7759aSWesley Cheng 
291dba7759aSWesley Cheng /**
292dba7759aSWesley Cheng  * snd_soc_usb_disconnect() - Notification of USB device disconnection
293dba7759aSWesley Cheng  * @usbdev: USB bus device
294dba7759aSWesley Cheng  * @sdev: USB SND device to remove
295dba7759aSWesley Cheng  *
296dba7759aSWesley Cheng  * Notify of a new USB SND device disconnection to the USB backend.
297dba7759aSWesley Cheng  *
298dba7759aSWesley Cheng  */
snd_soc_usb_disconnect(struct device * usbdev,struct snd_soc_usb_device * sdev)299dba7759aSWesley Cheng int snd_soc_usb_disconnect(struct device *usbdev, struct snd_soc_usb_device *sdev)
300dba7759aSWesley Cheng {
301dba7759aSWesley Cheng 	struct snd_soc_usb *ctx;
302dba7759aSWesley Cheng 
303dba7759aSWesley Cheng 	if (!usbdev)
304dba7759aSWesley Cheng 		return -ENODEV;
305dba7759aSWesley Cheng 
306dba7759aSWesley Cheng 	mutex_lock(&ctx_mutex);
307dba7759aSWesley Cheng 	ctx = snd_soc_find_usb_ctx(usbdev);
308dba7759aSWesley Cheng 	if (!ctx)
309dba7759aSWesley Cheng 		goto exit;
310dba7759aSWesley Cheng 
311dba7759aSWesley Cheng 	if (ctx->connection_status_cb)
312dba7759aSWesley Cheng 		ctx->connection_status_cb(ctx, sdev, false);
313dba7759aSWesley Cheng 
314dba7759aSWesley Cheng exit:
315dba7759aSWesley Cheng 	mutex_unlock(&ctx_mutex);
316dba7759aSWesley Cheng 
317dba7759aSWesley Cheng 	return 0;
318dba7759aSWesley Cheng }
319dba7759aSWesley Cheng EXPORT_SYMBOL_GPL(snd_soc_usb_disconnect);
320dba7759aSWesley Cheng 
321dba7759aSWesley Cheng MODULE_LICENSE("GPL");
322dba7759aSWesley Cheng MODULE_DESCRIPTION("SoC USB driver for offloading");
323