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