1326bbc34SWesley Cheng // SPDX-License-Identifier: GPL-2.0
2326bbc34SWesley Cheng /*
3326bbc34SWesley Cheng * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved.
4326bbc34SWesley Cheng */
5326bbc34SWesley Cheng
6326bbc34SWesley Cheng #include <linux/auxiliary_bus.h>
7326bbc34SWesley Cheng #include <linux/ctype.h>
8326bbc34SWesley Cheng #include <linux/dma-mapping.h>
9326bbc34SWesley Cheng #include <linux/dma-map-ops.h>
10326bbc34SWesley Cheng #include <linux/init.h>
11326bbc34SWesley Cheng #include <linux/iommu.h>
12326bbc34SWesley Cheng #include <linux/module.h>
13326bbc34SWesley Cheng #include <linux/moduleparam.h>
14326bbc34SWesley Cheng #include <linux/soc/qcom/qmi.h>
15326bbc34SWesley Cheng #include <linux/usb.h>
16326bbc34SWesley Cheng #include <linux/usb/audio.h>
17326bbc34SWesley Cheng #include <linux/usb/audio-v2.h>
18326bbc34SWesley Cheng #include <linux/usb/audio-v3.h>
19326bbc34SWesley Cheng #include <linux/usb/hcd.h>
20326bbc34SWesley Cheng #include <linux/usb/quirks.h>
21326bbc34SWesley Cheng #include <linux/usb/xhci-sideband.h>
22326bbc34SWesley Cheng
23326bbc34SWesley Cheng #include <sound/control.h>
24326bbc34SWesley Cheng #include <sound/core.h>
25326bbc34SWesley Cheng #include <sound/info.h>
26326bbc34SWesley Cheng #include <sound/initval.h>
27326bbc34SWesley Cheng #include <sound/pcm.h>
28326bbc34SWesley Cheng #include <sound/pcm_params.h>
29326bbc34SWesley Cheng #include <sound/q6usboffload.h>
30326bbc34SWesley Cheng #include <sound/soc.h>
31326bbc34SWesley Cheng #include <sound/soc-usb.h>
32326bbc34SWesley Cheng
33326bbc34SWesley Cheng #include "../usbaudio.h"
34326bbc34SWesley Cheng #include "../card.h"
35326bbc34SWesley Cheng #include "../endpoint.h"
36326bbc34SWesley Cheng #include "../format.h"
37326bbc34SWesley Cheng #include "../helper.h"
38326bbc34SWesley Cheng #include "../pcm.h"
39326bbc34SWesley Cheng #include "../power.h"
40326bbc34SWesley Cheng
41a67656f0SWesley Cheng #include "mixer_usb_offload.h"
42326bbc34SWesley Cheng #include "usb_audio_qmi_v01.h"
43326bbc34SWesley Cheng
44326bbc34SWesley Cheng /* Stream disable request timeout during USB device disconnect */
45326bbc34SWesley Cheng #define DEV_RELEASE_WAIT_TIMEOUT 10000 /* in ms */
46326bbc34SWesley Cheng
47326bbc34SWesley Cheng /* Data interval calculation parameters */
48326bbc34SWesley Cheng #define BUS_INTERVAL_FULL_SPEED 1000 /* in us */
49326bbc34SWesley Cheng #define BUS_INTERVAL_HIGHSPEED_AND_ABOVE 125 /* in us */
50326bbc34SWesley Cheng #define MAX_BINTERVAL_ISOC_EP 16
51326bbc34SWesley Cheng
52326bbc34SWesley Cheng #define QMI_STREAM_REQ_CARD_NUM_MASK 0xffff0000
53326bbc34SWesley Cheng #define QMI_STREAM_REQ_DEV_NUM_MASK 0xff00
54326bbc34SWesley Cheng #define QMI_STREAM_REQ_DIRECTION 0xff
55326bbc34SWesley Cheng
56326bbc34SWesley Cheng /* iommu resource parameters and management */
57326bbc34SWesley Cheng #define PREPEND_SID_TO_IOVA(iova, sid) ((u64)(((u64)(iova)) | \
58326bbc34SWesley Cheng (((u64)sid) << 32)))
59326bbc34SWesley Cheng #define IOVA_MASK(iova) (((u64)(iova)) & 0xFFFFFFFF)
60326bbc34SWesley Cheng #define IOVA_BASE 0x1000
61326bbc34SWesley Cheng #define IOVA_XFER_RING_BASE (IOVA_BASE + PAGE_SIZE * (SNDRV_CARDS + 1))
62326bbc34SWesley Cheng #define IOVA_XFER_BUF_BASE (IOVA_XFER_RING_BASE + PAGE_SIZE * SNDRV_CARDS * 32)
63326bbc34SWesley Cheng #define IOVA_XFER_RING_MAX (IOVA_XFER_BUF_BASE - PAGE_SIZE)
64326bbc34SWesley Cheng #define IOVA_XFER_BUF_MAX (0xfffff000 - PAGE_SIZE)
65326bbc34SWesley Cheng
66326bbc34SWesley Cheng #define MAX_XFER_BUFF_LEN (24 * PAGE_SIZE)
67326bbc34SWesley Cheng
68326bbc34SWesley Cheng struct iova_info {
69326bbc34SWesley Cheng struct list_head list;
70326bbc34SWesley Cheng unsigned long start_iova;
71326bbc34SWesley Cheng size_t size;
72326bbc34SWesley Cheng bool in_use;
73326bbc34SWesley Cheng };
74326bbc34SWesley Cheng
75326bbc34SWesley Cheng struct intf_info {
76326bbc34SWesley Cheng /* IOMMU ring/buffer mapping information */
77326bbc34SWesley Cheng unsigned long data_xfer_ring_va;
78326bbc34SWesley Cheng size_t data_xfer_ring_size;
79326bbc34SWesley Cheng unsigned long sync_xfer_ring_va;
80326bbc34SWesley Cheng size_t sync_xfer_ring_size;
813335a1bbSArnd Bergmann dma_addr_t xfer_buf_iova;
82326bbc34SWesley Cheng size_t xfer_buf_size;
833335a1bbSArnd Bergmann dma_addr_t xfer_buf_dma;
84485ae085SArnd Bergmann u8 *xfer_buf_cpu;
85326bbc34SWesley Cheng
86326bbc34SWesley Cheng /* USB endpoint information */
87326bbc34SWesley Cheng unsigned int data_ep_pipe;
88326bbc34SWesley Cheng unsigned int sync_ep_pipe;
89326bbc34SWesley Cheng unsigned int data_ep_idx;
90326bbc34SWesley Cheng unsigned int sync_ep_idx;
91326bbc34SWesley Cheng
92326bbc34SWesley Cheng u8 intf_num;
93326bbc34SWesley Cheng u8 pcm_card_num;
94326bbc34SWesley Cheng u8 pcm_dev_num;
95326bbc34SWesley Cheng u8 direction;
96326bbc34SWesley Cheng bool in_use;
97326bbc34SWesley Cheng };
98326bbc34SWesley Cheng
99326bbc34SWesley Cheng struct uaudio_qmi_dev {
100326bbc34SWesley Cheng struct device *dev;
101326bbc34SWesley Cheng struct q6usb_offload *data;
102326bbc34SWesley Cheng struct auxiliary_device *auxdev;
103326bbc34SWesley Cheng
104326bbc34SWesley Cheng /* list to keep track of available iova */
105326bbc34SWesley Cheng struct list_head xfer_ring_list;
106326bbc34SWesley Cheng size_t xfer_ring_iova_size;
107326bbc34SWesley Cheng unsigned long curr_xfer_ring_iova;
108326bbc34SWesley Cheng struct list_head xfer_buf_list;
109326bbc34SWesley Cheng size_t xfer_buf_iova_size;
110326bbc34SWesley Cheng unsigned long curr_xfer_buf_iova;
111326bbc34SWesley Cheng
112326bbc34SWesley Cheng /* bit fields representing pcm card enabled */
113326bbc34SWesley Cheng unsigned long card_slot;
114326bbc34SWesley Cheng /* indicate event ring mapped or not */
115326bbc34SWesley Cheng bool er_mapped;
116326bbc34SWesley Cheng };
117326bbc34SWesley Cheng
118326bbc34SWesley Cheng struct uaudio_dev {
119326bbc34SWesley Cheng struct usb_device *udev;
120326bbc34SWesley Cheng /* audio control interface */
121326bbc34SWesley Cheng struct usb_host_interface *ctrl_intf;
122326bbc34SWesley Cheng unsigned int usb_core_id;
123326bbc34SWesley Cheng atomic_t in_use;
124326bbc34SWesley Cheng struct kref kref;
125326bbc34SWesley Cheng wait_queue_head_t disconnect_wq;
126326bbc34SWesley Cheng
127326bbc34SWesley Cheng /* interface specific */
128326bbc34SWesley Cheng int num_intf;
129326bbc34SWesley Cheng struct intf_info *info;
130326bbc34SWesley Cheng struct snd_usb_audio *chip;
131326bbc34SWesley Cheng
132326bbc34SWesley Cheng /* xhci sideband */
133326bbc34SWesley Cheng struct xhci_sideband *sb;
134326bbc34SWesley Cheng
135326bbc34SWesley Cheng /* SoC USB device */
136326bbc34SWesley Cheng struct snd_soc_usb_device *sdev;
137326bbc34SWesley Cheng };
138326bbc34SWesley Cheng
139326bbc34SWesley Cheng static struct uaudio_dev uadev[SNDRV_CARDS];
140326bbc34SWesley Cheng static struct uaudio_qmi_dev *uaudio_qdev;
141326bbc34SWesley Cheng static struct uaudio_qmi_svc *uaudio_svc;
142326bbc34SWesley Cheng static DEFINE_MUTEX(qdev_mutex);
143326bbc34SWesley Cheng
144326bbc34SWesley Cheng struct uaudio_qmi_svc {
145326bbc34SWesley Cheng struct qmi_handle *uaudio_svc_hdl;
146326bbc34SWesley Cheng struct sockaddr_qrtr client_sq;
147326bbc34SWesley Cheng bool client_connected;
148326bbc34SWesley Cheng };
149326bbc34SWesley Cheng
150326bbc34SWesley Cheng enum mem_type {
151326bbc34SWesley Cheng MEM_EVENT_RING,
152326bbc34SWesley Cheng MEM_XFER_RING,
153326bbc34SWesley Cheng MEM_XFER_BUF,
154326bbc34SWesley Cheng };
155326bbc34SWesley Cheng
156326bbc34SWesley Cheng /* Supported audio formats */
157326bbc34SWesley Cheng enum usb_qmi_audio_format {
158326bbc34SWesley Cheng USB_QMI_PCM_FORMAT_S8 = 0,
159326bbc34SWesley Cheng USB_QMI_PCM_FORMAT_U8,
160326bbc34SWesley Cheng USB_QMI_PCM_FORMAT_S16_LE,
161326bbc34SWesley Cheng USB_QMI_PCM_FORMAT_S16_BE,
162326bbc34SWesley Cheng USB_QMI_PCM_FORMAT_U16_LE,
163326bbc34SWesley Cheng USB_QMI_PCM_FORMAT_U16_BE,
164326bbc34SWesley Cheng USB_QMI_PCM_FORMAT_S24_LE,
165326bbc34SWesley Cheng USB_QMI_PCM_FORMAT_S24_BE,
166326bbc34SWesley Cheng USB_QMI_PCM_FORMAT_U24_LE,
167326bbc34SWesley Cheng USB_QMI_PCM_FORMAT_U24_BE,
168326bbc34SWesley Cheng USB_QMI_PCM_FORMAT_S24_3LE,
169326bbc34SWesley Cheng USB_QMI_PCM_FORMAT_S24_3BE,
170326bbc34SWesley Cheng USB_QMI_PCM_FORMAT_U24_3LE,
171326bbc34SWesley Cheng USB_QMI_PCM_FORMAT_U24_3BE,
172326bbc34SWesley Cheng USB_QMI_PCM_FORMAT_S32_LE,
173326bbc34SWesley Cheng USB_QMI_PCM_FORMAT_S32_BE,
174326bbc34SWesley Cheng USB_QMI_PCM_FORMAT_U32_LE,
175326bbc34SWesley Cheng USB_QMI_PCM_FORMAT_U32_BE,
176326bbc34SWesley Cheng };
177326bbc34SWesley Cheng
usb_qmi_get_pcm_num(struct snd_usb_audio * chip,int direction)178326bbc34SWesley Cheng static int usb_qmi_get_pcm_num(struct snd_usb_audio *chip, int direction)
179326bbc34SWesley Cheng {
180326bbc34SWesley Cheng struct snd_usb_substream *subs = NULL;
181326bbc34SWesley Cheng struct snd_usb_stream *as;
182326bbc34SWesley Cheng int count = 0;
183326bbc34SWesley Cheng
184326bbc34SWesley Cheng list_for_each_entry(as, &chip->pcm_list, list) {
185326bbc34SWesley Cheng subs = &as->substream[direction];
186326bbc34SWesley Cheng if (subs->ep_num)
187326bbc34SWesley Cheng count++;
188326bbc34SWesley Cheng }
189326bbc34SWesley Cheng
190326bbc34SWesley Cheng return count;
191326bbc34SWesley Cheng }
192326bbc34SWesley Cheng
193326bbc34SWesley Cheng static enum usb_qmi_audio_device_speed_enum_v01
get_speed_info(enum usb_device_speed udev_speed)194326bbc34SWesley Cheng get_speed_info(enum usb_device_speed udev_speed)
195326bbc34SWesley Cheng {
196326bbc34SWesley Cheng switch (udev_speed) {
197326bbc34SWesley Cheng case USB_SPEED_LOW:
198326bbc34SWesley Cheng return USB_QMI_DEVICE_SPEED_LOW_V01;
199326bbc34SWesley Cheng case USB_SPEED_FULL:
200326bbc34SWesley Cheng return USB_QMI_DEVICE_SPEED_FULL_V01;
201326bbc34SWesley Cheng case USB_SPEED_HIGH:
202326bbc34SWesley Cheng return USB_QMI_DEVICE_SPEED_HIGH_V01;
203326bbc34SWesley Cheng case USB_SPEED_SUPER:
204326bbc34SWesley Cheng return USB_QMI_DEVICE_SPEED_SUPER_V01;
205326bbc34SWesley Cheng case USB_SPEED_SUPER_PLUS:
206326bbc34SWesley Cheng return USB_QMI_DEVICE_SPEED_SUPER_PLUS_V01;
207326bbc34SWesley Cheng default:
208326bbc34SWesley Cheng return USB_QMI_DEVICE_SPEED_INVALID_V01;
209326bbc34SWesley Cheng }
210326bbc34SWesley Cheng }
211326bbc34SWesley Cheng
find_substream(unsigned int card_num,unsigned int pcm_idx,unsigned int direction)212326bbc34SWesley Cheng static struct snd_usb_substream *find_substream(unsigned int card_num,
213326bbc34SWesley Cheng unsigned int pcm_idx,
214326bbc34SWesley Cheng unsigned int direction)
215326bbc34SWesley Cheng {
216326bbc34SWesley Cheng struct snd_usb_substream *subs = NULL;
217326bbc34SWesley Cheng struct snd_usb_audio *chip;
218326bbc34SWesley Cheng struct snd_usb_stream *as;
219326bbc34SWesley Cheng
220326bbc34SWesley Cheng chip = uadev[card_num].chip;
221326bbc34SWesley Cheng if (!chip || atomic_read(&chip->shutdown))
222326bbc34SWesley Cheng goto done;
223326bbc34SWesley Cheng
224326bbc34SWesley Cheng if (pcm_idx >= chip->pcm_devs)
225326bbc34SWesley Cheng goto done;
226326bbc34SWesley Cheng
227326bbc34SWesley Cheng if (direction > SNDRV_PCM_STREAM_CAPTURE)
228326bbc34SWesley Cheng goto done;
229326bbc34SWesley Cheng
230326bbc34SWesley Cheng list_for_each_entry(as, &chip->pcm_list, list) {
231326bbc34SWesley Cheng if (as->pcm_index == pcm_idx) {
232326bbc34SWesley Cheng subs = &as->substream[direction];
233326bbc34SWesley Cheng goto done;
234326bbc34SWesley Cheng }
235326bbc34SWesley Cheng }
236326bbc34SWesley Cheng
237326bbc34SWesley Cheng done:
238326bbc34SWesley Cheng return subs;
239326bbc34SWesley Cheng }
240326bbc34SWesley Cheng
info_idx_from_ifnum(int card_num,int intf_num,bool enable)241326bbc34SWesley Cheng static int info_idx_from_ifnum(int card_num, int intf_num, bool enable)
242326bbc34SWesley Cheng {
243326bbc34SWesley Cheng int i;
244326bbc34SWesley Cheng
245326bbc34SWesley Cheng /*
246326bbc34SWesley Cheng * default index 0 is used when info is allocated upon
247326bbc34SWesley Cheng * first enable audio stream req for a pcm device
248326bbc34SWesley Cheng */
249326bbc34SWesley Cheng if (enable && !uadev[card_num].info)
250326bbc34SWesley Cheng return 0;
251326bbc34SWesley Cheng
252326bbc34SWesley Cheng for (i = 0; i < uadev[card_num].num_intf; i++) {
253326bbc34SWesley Cheng if (enable && !uadev[card_num].info[i].in_use)
254326bbc34SWesley Cheng return i;
255326bbc34SWesley Cheng else if (!enable &&
256326bbc34SWesley Cheng uadev[card_num].info[i].intf_num == intf_num)
257326bbc34SWesley Cheng return i;
258326bbc34SWesley Cheng }
259326bbc34SWesley Cheng
260326bbc34SWesley Cheng return -EINVAL;
261326bbc34SWesley Cheng }
262326bbc34SWesley Cheng
get_data_interval_from_si(struct snd_usb_substream * subs,u32 service_interval)263326bbc34SWesley Cheng static int get_data_interval_from_si(struct snd_usb_substream *subs,
264326bbc34SWesley Cheng u32 service_interval)
265326bbc34SWesley Cheng {
266326bbc34SWesley Cheng unsigned int bus_intval_mult;
267326bbc34SWesley Cheng unsigned int bus_intval;
268326bbc34SWesley Cheng unsigned int binterval;
269326bbc34SWesley Cheng
270326bbc34SWesley Cheng if (subs->dev->speed >= USB_SPEED_HIGH)
271326bbc34SWesley Cheng bus_intval = BUS_INTERVAL_HIGHSPEED_AND_ABOVE;
272326bbc34SWesley Cheng else
273326bbc34SWesley Cheng bus_intval = BUS_INTERVAL_FULL_SPEED;
274326bbc34SWesley Cheng
275326bbc34SWesley Cheng if (service_interval % bus_intval)
276326bbc34SWesley Cheng return -EINVAL;
277326bbc34SWesley Cheng
278326bbc34SWesley Cheng bus_intval_mult = service_interval / bus_intval;
279326bbc34SWesley Cheng binterval = ffs(bus_intval_mult);
280326bbc34SWesley Cheng if (!binterval || binterval > MAX_BINTERVAL_ISOC_EP)
281326bbc34SWesley Cheng return -EINVAL;
282326bbc34SWesley Cheng
283326bbc34SWesley Cheng /* check if another bit is set then bail out */
284326bbc34SWesley Cheng bus_intval_mult = bus_intval_mult >> binterval;
285326bbc34SWesley Cheng if (bus_intval_mult)
286326bbc34SWesley Cheng return -EINVAL;
287326bbc34SWesley Cheng
288326bbc34SWesley Cheng return (binterval - 1);
289326bbc34SWesley Cheng }
290326bbc34SWesley Cheng
291326bbc34SWesley Cheng /* maps audio format received over QMI to asound.h based pcm format */
map_pcm_format(enum usb_qmi_audio_format fmt_received)292326bbc34SWesley Cheng static snd_pcm_format_t map_pcm_format(enum usb_qmi_audio_format fmt_received)
293326bbc34SWesley Cheng {
294326bbc34SWesley Cheng switch (fmt_received) {
295326bbc34SWesley Cheng case USB_QMI_PCM_FORMAT_S8:
296326bbc34SWesley Cheng return SNDRV_PCM_FORMAT_S8;
297326bbc34SWesley Cheng case USB_QMI_PCM_FORMAT_U8:
298326bbc34SWesley Cheng return SNDRV_PCM_FORMAT_U8;
299326bbc34SWesley Cheng case USB_QMI_PCM_FORMAT_S16_LE:
300326bbc34SWesley Cheng return SNDRV_PCM_FORMAT_S16_LE;
301326bbc34SWesley Cheng case USB_QMI_PCM_FORMAT_S16_BE:
302326bbc34SWesley Cheng return SNDRV_PCM_FORMAT_S16_BE;
303326bbc34SWesley Cheng case USB_QMI_PCM_FORMAT_U16_LE:
304326bbc34SWesley Cheng return SNDRV_PCM_FORMAT_U16_LE;
305326bbc34SWesley Cheng case USB_QMI_PCM_FORMAT_U16_BE:
306326bbc34SWesley Cheng return SNDRV_PCM_FORMAT_U16_BE;
307326bbc34SWesley Cheng case USB_QMI_PCM_FORMAT_S24_LE:
308326bbc34SWesley Cheng return SNDRV_PCM_FORMAT_S24_LE;
309326bbc34SWesley Cheng case USB_QMI_PCM_FORMAT_S24_BE:
310326bbc34SWesley Cheng return SNDRV_PCM_FORMAT_S24_BE;
311326bbc34SWesley Cheng case USB_QMI_PCM_FORMAT_U24_LE:
312326bbc34SWesley Cheng return SNDRV_PCM_FORMAT_U24_LE;
313326bbc34SWesley Cheng case USB_QMI_PCM_FORMAT_U24_BE:
314326bbc34SWesley Cheng return SNDRV_PCM_FORMAT_U24_BE;
315326bbc34SWesley Cheng case USB_QMI_PCM_FORMAT_S24_3LE:
316326bbc34SWesley Cheng return SNDRV_PCM_FORMAT_S24_3LE;
317326bbc34SWesley Cheng case USB_QMI_PCM_FORMAT_S24_3BE:
318326bbc34SWesley Cheng return SNDRV_PCM_FORMAT_S24_3BE;
319326bbc34SWesley Cheng case USB_QMI_PCM_FORMAT_U24_3LE:
320326bbc34SWesley Cheng return SNDRV_PCM_FORMAT_U24_3LE;
321326bbc34SWesley Cheng case USB_QMI_PCM_FORMAT_U24_3BE:
322326bbc34SWesley Cheng return SNDRV_PCM_FORMAT_U24_3BE;
323326bbc34SWesley Cheng case USB_QMI_PCM_FORMAT_S32_LE:
324326bbc34SWesley Cheng return SNDRV_PCM_FORMAT_S32_LE;
325326bbc34SWesley Cheng case USB_QMI_PCM_FORMAT_S32_BE:
326326bbc34SWesley Cheng return SNDRV_PCM_FORMAT_S32_BE;
327326bbc34SWesley Cheng case USB_QMI_PCM_FORMAT_U32_LE:
328326bbc34SWesley Cheng return SNDRV_PCM_FORMAT_U32_LE;
329326bbc34SWesley Cheng case USB_QMI_PCM_FORMAT_U32_BE:
330326bbc34SWesley Cheng return SNDRV_PCM_FORMAT_U32_BE;
331326bbc34SWesley Cheng default:
332326bbc34SWesley Cheng /*
333326bbc34SWesley Cheng * We expect the caller to do input validation so we should
334326bbc34SWesley Cheng * never hit this. But we do have to return a proper
335326bbc34SWesley Cheng * snd_pcm_format_t value due to the __bitwise attribute; so
336326bbc34SWesley Cheng * just return the equivalent of 0 in case of bad input.
337326bbc34SWesley Cheng */
338326bbc34SWesley Cheng return SNDRV_PCM_FORMAT_S8;
339326bbc34SWesley Cheng }
340326bbc34SWesley Cheng }
341326bbc34SWesley Cheng
342326bbc34SWesley Cheng /*
343326bbc34SWesley Cheng * Sends QMI disconnect indication message, assumes chip->mutex and qdev_mutex
344326bbc34SWesley Cheng * lock held by caller.
345326bbc34SWesley Cheng */
uaudio_send_disconnect_ind(struct snd_usb_audio * chip)346326bbc34SWesley Cheng static int uaudio_send_disconnect_ind(struct snd_usb_audio *chip)
347326bbc34SWesley Cheng {
348326bbc34SWesley Cheng struct qmi_uaudio_stream_ind_msg_v01 disconnect_ind = {0};
349326bbc34SWesley Cheng struct uaudio_qmi_svc *svc = uaudio_svc;
350326bbc34SWesley Cheng struct uaudio_dev *dev;
351326bbc34SWesley Cheng int ret = 0;
352326bbc34SWesley Cheng
353326bbc34SWesley Cheng dev = &uadev[chip->card->number];
354326bbc34SWesley Cheng
355326bbc34SWesley Cheng if (atomic_read(&dev->in_use)) {
356326bbc34SWesley Cheng mutex_unlock(&chip->mutex);
357326bbc34SWesley Cheng mutex_unlock(&qdev_mutex);
358326bbc34SWesley Cheng dev_dbg(uaudio_qdev->data->dev, "sending qmi indication suspend\n");
359326bbc34SWesley Cheng disconnect_ind.dev_event = USB_QMI_DEV_DISCONNECT_V01;
360326bbc34SWesley Cheng disconnect_ind.slot_id = dev->udev->slot_id;
361326bbc34SWesley Cheng disconnect_ind.controller_num = dev->usb_core_id;
362326bbc34SWesley Cheng disconnect_ind.controller_num_valid = 1;
363326bbc34SWesley Cheng ret = qmi_send_indication(svc->uaudio_svc_hdl, &svc->client_sq,
364326bbc34SWesley Cheng QMI_UAUDIO_STREAM_IND_V01,
365326bbc34SWesley Cheng QMI_UAUDIO_STREAM_IND_MSG_V01_MAX_MSG_LEN,
366326bbc34SWesley Cheng qmi_uaudio_stream_ind_msg_v01_ei,
367326bbc34SWesley Cheng &disconnect_ind);
368326bbc34SWesley Cheng if (ret < 0)
369326bbc34SWesley Cheng dev_err(uaudio_qdev->data->dev,
370326bbc34SWesley Cheng "qmi send failed with err: %d\n", ret);
371326bbc34SWesley Cheng
372326bbc34SWesley Cheng ret = wait_event_interruptible_timeout(dev->disconnect_wq,
373326bbc34SWesley Cheng !atomic_read(&dev->in_use),
374326bbc34SWesley Cheng msecs_to_jiffies(DEV_RELEASE_WAIT_TIMEOUT));
375326bbc34SWesley Cheng if (!ret) {
376326bbc34SWesley Cheng dev_err(uaudio_qdev->data->dev,
377326bbc34SWesley Cheng "timeout while waiting for dev_release\n");
378326bbc34SWesley Cheng atomic_set(&dev->in_use, 0);
379326bbc34SWesley Cheng } else if (ret < 0) {
380326bbc34SWesley Cheng dev_err(uaudio_qdev->data->dev,
381326bbc34SWesley Cheng "failed with ret %d\n", ret);
382326bbc34SWesley Cheng atomic_set(&dev->in_use, 0);
383326bbc34SWesley Cheng }
384326bbc34SWesley Cheng mutex_lock(&qdev_mutex);
385326bbc34SWesley Cheng mutex_lock(&chip->mutex);
386326bbc34SWesley Cheng }
387326bbc34SWesley Cheng
388326bbc34SWesley Cheng return ret;
389326bbc34SWesley Cheng }
390326bbc34SWesley Cheng
391326bbc34SWesley Cheng /* Offloading IOMMU management */
uaudio_get_iova(unsigned long * curr_iova,size_t * curr_iova_size,struct list_head * head,size_t size)392326bbc34SWesley Cheng static unsigned long uaudio_get_iova(unsigned long *curr_iova,
393326bbc34SWesley Cheng size_t *curr_iova_size,
394326bbc34SWesley Cheng struct list_head *head, size_t size)
395326bbc34SWesley Cheng {
396326bbc34SWesley Cheng struct iova_info *info, *new_info = NULL;
397326bbc34SWesley Cheng struct list_head *curr_head;
398326bbc34SWesley Cheng size_t tmp_size = size;
399485ae085SArnd Bergmann unsigned long iova = 0;
400326bbc34SWesley Cheng
401326bbc34SWesley Cheng if (size % PAGE_SIZE)
402326bbc34SWesley Cheng goto done;
403326bbc34SWesley Cheng
404326bbc34SWesley Cheng if (size > *curr_iova_size)
405326bbc34SWesley Cheng goto done;
406326bbc34SWesley Cheng
407326bbc34SWesley Cheng if (*curr_iova_size == 0)
408326bbc34SWesley Cheng goto done;
409326bbc34SWesley Cheng
410326bbc34SWesley Cheng list_for_each_entry(info, head, list) {
411326bbc34SWesley Cheng /* exact size iova_info */
412326bbc34SWesley Cheng if (!info->in_use && info->size == size) {
413326bbc34SWesley Cheng info->in_use = true;
414485ae085SArnd Bergmann iova = info->start_iova;
415326bbc34SWesley Cheng *curr_iova_size -= size;
416326bbc34SWesley Cheng goto done;
417326bbc34SWesley Cheng } else if (!info->in_use && tmp_size >= info->size) {
418326bbc34SWesley Cheng if (!new_info)
419326bbc34SWesley Cheng new_info = info;
420326bbc34SWesley Cheng tmp_size -= info->size;
421326bbc34SWesley Cheng if (tmp_size)
422326bbc34SWesley Cheng continue;
423326bbc34SWesley Cheng
424485ae085SArnd Bergmann iova = new_info->start_iova;
425326bbc34SWesley Cheng for (curr_head = &new_info->list; curr_head !=
426326bbc34SWesley Cheng &info->list; curr_head = curr_head->next) {
427326bbc34SWesley Cheng new_info = list_entry(curr_head, struct
428326bbc34SWesley Cheng iova_info, list);
429326bbc34SWesley Cheng new_info->in_use = true;
430326bbc34SWesley Cheng }
431326bbc34SWesley Cheng info->in_use = true;
432326bbc34SWesley Cheng *curr_iova_size -= size;
433326bbc34SWesley Cheng goto done;
434326bbc34SWesley Cheng } else {
435326bbc34SWesley Cheng /* iova region in use */
436326bbc34SWesley Cheng new_info = NULL;
437326bbc34SWesley Cheng tmp_size = size;
438326bbc34SWesley Cheng }
439326bbc34SWesley Cheng }
440326bbc34SWesley Cheng
441326bbc34SWesley Cheng info = kzalloc(sizeof(*info), GFP_KERNEL);
442326bbc34SWesley Cheng if (!info) {
443485ae085SArnd Bergmann iova = 0;
444326bbc34SWesley Cheng goto done;
445326bbc34SWesley Cheng }
446326bbc34SWesley Cheng
447485ae085SArnd Bergmann iova = *curr_iova;
448326bbc34SWesley Cheng info->start_iova = *curr_iova;
449326bbc34SWesley Cheng info->size = size;
450326bbc34SWesley Cheng info->in_use = true;
451326bbc34SWesley Cheng *curr_iova += size;
452326bbc34SWesley Cheng *curr_iova_size -= size;
453326bbc34SWesley Cheng list_add_tail(&info->list, head);
454326bbc34SWesley Cheng
455326bbc34SWesley Cheng done:
456485ae085SArnd Bergmann return iova;
457326bbc34SWesley Cheng }
458326bbc34SWesley Cheng
uaudio_put_iova(unsigned long iova,size_t size,struct list_head * head,size_t * curr_iova_size)459485ae085SArnd Bergmann static void uaudio_put_iova(unsigned long iova, size_t size, struct list_head
460326bbc34SWesley Cheng *head, size_t *curr_iova_size)
461326bbc34SWesley Cheng {
462326bbc34SWesley Cheng struct iova_info *info;
463326bbc34SWesley Cheng size_t tmp_size = size;
464326bbc34SWesley Cheng bool found = false;
465326bbc34SWesley Cheng
466326bbc34SWesley Cheng list_for_each_entry(info, head, list) {
467485ae085SArnd Bergmann if (info->start_iova == iova) {
468326bbc34SWesley Cheng if (!info->in_use)
469326bbc34SWesley Cheng return;
470326bbc34SWesley Cheng
471326bbc34SWesley Cheng found = true;
472326bbc34SWesley Cheng info->in_use = false;
473326bbc34SWesley Cheng if (info->size == size)
474326bbc34SWesley Cheng goto done;
475326bbc34SWesley Cheng }
476326bbc34SWesley Cheng
477326bbc34SWesley Cheng if (found && tmp_size >= info->size) {
478326bbc34SWesley Cheng info->in_use = false;
479326bbc34SWesley Cheng tmp_size -= info->size;
480326bbc34SWesley Cheng if (!tmp_size)
481326bbc34SWesley Cheng goto done;
482326bbc34SWesley Cheng }
483326bbc34SWesley Cheng }
484326bbc34SWesley Cheng
485326bbc34SWesley Cheng if (!found)
486326bbc34SWesley Cheng return;
487326bbc34SWesley Cheng
488326bbc34SWesley Cheng done:
489326bbc34SWesley Cheng *curr_iova_size += size;
490326bbc34SWesley Cheng }
491326bbc34SWesley Cheng
492326bbc34SWesley Cheng /**
493326bbc34SWesley Cheng * uaudio_iommu_unmap() - unmaps iommu memory for adsp
494326bbc34SWesley Cheng * @mtype: ring type
495485ae085SArnd Bergmann * @iova: virtual address to unmap
496326bbc34SWesley Cheng * @iova_size: region size
497326bbc34SWesley Cheng * @mapped_iova_size: mapped region size
498326bbc34SWesley Cheng *
499326bbc34SWesley Cheng * Unmaps the memory region that was previously assigned to the adsp.
500326bbc34SWesley Cheng *
501326bbc34SWesley Cheng */
uaudio_iommu_unmap(enum mem_type mtype,unsigned long iova,size_t iova_size,size_t mapped_iova_size)502485ae085SArnd Bergmann static void uaudio_iommu_unmap(enum mem_type mtype, unsigned long iova,
503326bbc34SWesley Cheng size_t iova_size, size_t mapped_iova_size)
504326bbc34SWesley Cheng {
505326bbc34SWesley Cheng size_t umap_size;
506326bbc34SWesley Cheng bool unmap = true;
507326bbc34SWesley Cheng
508485ae085SArnd Bergmann if (!iova || !iova_size)
509326bbc34SWesley Cheng return;
510326bbc34SWesley Cheng
511326bbc34SWesley Cheng switch (mtype) {
512326bbc34SWesley Cheng case MEM_EVENT_RING:
513326bbc34SWesley Cheng if (uaudio_qdev->er_mapped)
514326bbc34SWesley Cheng uaudio_qdev->er_mapped = false;
515326bbc34SWesley Cheng else
516326bbc34SWesley Cheng unmap = false;
517326bbc34SWesley Cheng break;
518326bbc34SWesley Cheng
519326bbc34SWesley Cheng case MEM_XFER_RING:
520485ae085SArnd Bergmann uaudio_put_iova(iova, iova_size, &uaudio_qdev->xfer_ring_list,
521326bbc34SWesley Cheng &uaudio_qdev->xfer_ring_iova_size);
522326bbc34SWesley Cheng break;
523326bbc34SWesley Cheng case MEM_XFER_BUF:
524485ae085SArnd Bergmann uaudio_put_iova(iova, iova_size, &uaudio_qdev->xfer_buf_list,
525326bbc34SWesley Cheng &uaudio_qdev->xfer_buf_iova_size);
526326bbc34SWesley Cheng break;
527326bbc34SWesley Cheng default:
528326bbc34SWesley Cheng unmap = false;
529326bbc34SWesley Cheng }
530326bbc34SWesley Cheng
531326bbc34SWesley Cheng if (!unmap || !mapped_iova_size)
532326bbc34SWesley Cheng return;
533326bbc34SWesley Cheng
534485ae085SArnd Bergmann umap_size = iommu_unmap(uaudio_qdev->data->domain, iova, mapped_iova_size);
535326bbc34SWesley Cheng if (umap_size != mapped_iova_size)
536326bbc34SWesley Cheng dev_err(uaudio_qdev->data->dev,
537326bbc34SWesley Cheng "unmapped size %zu for iova 0x%08lx of mapped size %zu\n",
538485ae085SArnd Bergmann umap_size, iova, mapped_iova_size);
539326bbc34SWesley Cheng }
540326bbc34SWesley Cheng
541326bbc34SWesley Cheng /**
542326bbc34SWesley Cheng * uaudio_iommu_map() - maps iommu memory for adsp
543326bbc34SWesley Cheng * @mtype: ring type
544326bbc34SWesley Cheng * @dma_coherent: dma coherent
545326bbc34SWesley Cheng * @pa: physical address for ring/buffer
546326bbc34SWesley Cheng * @size: size of memory region
547326bbc34SWesley Cheng * @sgt: sg table for memory region
548326bbc34SWesley Cheng *
549326bbc34SWesley Cheng * Maps the XHCI related resources to a memory region that is assigned to be
550326bbc34SWesley Cheng * used by the adsp. This will be mapped to the domain, which is created by
551326bbc34SWesley Cheng * the ASoC USB backend driver.
552326bbc34SWesley Cheng *
553326bbc34SWesley Cheng */
uaudio_iommu_map(enum mem_type mtype,bool dma_coherent,phys_addr_t pa,size_t size,struct sg_table * sgt)554326bbc34SWesley Cheng static unsigned long uaudio_iommu_map(enum mem_type mtype, bool dma_coherent,
555326bbc34SWesley Cheng phys_addr_t pa, size_t size,
556326bbc34SWesley Cheng struct sg_table *sgt)
557326bbc34SWesley Cheng {
558326bbc34SWesley Cheng struct scatterlist *sg;
559485ae085SArnd Bergmann unsigned long iova = 0;
560326bbc34SWesley Cheng size_t total_len = 0;
561485ae085SArnd Bergmann unsigned long iova_sg;
562326bbc34SWesley Cheng phys_addr_t pa_sg;
563326bbc34SWesley Cheng bool map = true;
564326bbc34SWesley Cheng size_t sg_len;
565326bbc34SWesley Cheng int prot;
566326bbc34SWesley Cheng int ret;
567326bbc34SWesley Cheng int i;
568326bbc34SWesley Cheng
569326bbc34SWesley Cheng prot = IOMMU_READ | IOMMU_WRITE;
570326bbc34SWesley Cheng
571326bbc34SWesley Cheng if (dma_coherent)
572326bbc34SWesley Cheng prot |= IOMMU_CACHE;
573326bbc34SWesley Cheng
574326bbc34SWesley Cheng switch (mtype) {
575326bbc34SWesley Cheng case MEM_EVENT_RING:
576485ae085SArnd Bergmann iova = IOVA_BASE;
577326bbc34SWesley Cheng /* er already mapped */
578326bbc34SWesley Cheng if (uaudio_qdev->er_mapped)
579326bbc34SWesley Cheng map = false;
580326bbc34SWesley Cheng break;
581326bbc34SWesley Cheng case MEM_XFER_RING:
582485ae085SArnd Bergmann iova = uaudio_get_iova(&uaudio_qdev->curr_xfer_ring_iova,
583326bbc34SWesley Cheng &uaudio_qdev->xfer_ring_iova_size,
584326bbc34SWesley Cheng &uaudio_qdev->xfer_ring_list, size);
585326bbc34SWesley Cheng break;
586326bbc34SWesley Cheng case MEM_XFER_BUF:
587485ae085SArnd Bergmann iova = uaudio_get_iova(&uaudio_qdev->curr_xfer_buf_iova,
588326bbc34SWesley Cheng &uaudio_qdev->xfer_buf_iova_size,
589326bbc34SWesley Cheng &uaudio_qdev->xfer_buf_list, size);
590326bbc34SWesley Cheng break;
591326bbc34SWesley Cheng default:
592326bbc34SWesley Cheng dev_err(uaudio_qdev->data->dev, "unknown mem type %d\n", mtype);
593326bbc34SWesley Cheng }
594326bbc34SWesley Cheng
595485ae085SArnd Bergmann if (!iova || !map)
596326bbc34SWesley Cheng goto done;
597326bbc34SWesley Cheng
598326bbc34SWesley Cheng if (!sgt)
599326bbc34SWesley Cheng goto skip_sgt_map;
600326bbc34SWesley Cheng
601485ae085SArnd Bergmann iova_sg = iova;
602326bbc34SWesley Cheng for_each_sg(sgt->sgl, sg, sgt->nents, i) {
603326bbc34SWesley Cheng sg_len = PAGE_ALIGN(sg->offset + sg->length);
604326bbc34SWesley Cheng pa_sg = page_to_phys(sg_page(sg));
605485ae085SArnd Bergmann ret = iommu_map(uaudio_qdev->data->domain, iova_sg, pa_sg, sg_len,
606326bbc34SWesley Cheng prot, GFP_KERNEL);
607326bbc34SWesley Cheng if (ret) {
608485ae085SArnd Bergmann uaudio_iommu_unmap(MEM_XFER_BUF, iova, size, total_len);
609485ae085SArnd Bergmann iova = 0;
610326bbc34SWesley Cheng goto done;
611326bbc34SWesley Cheng }
612326bbc34SWesley Cheng
613485ae085SArnd Bergmann iova_sg += sg_len;
614326bbc34SWesley Cheng total_len += sg_len;
615326bbc34SWesley Cheng }
616326bbc34SWesley Cheng
617326bbc34SWesley Cheng if (size != total_len) {
618485ae085SArnd Bergmann uaudio_iommu_unmap(MEM_XFER_BUF, iova, size, total_len);
619485ae085SArnd Bergmann iova = 0;
620326bbc34SWesley Cheng }
621485ae085SArnd Bergmann return iova;
622326bbc34SWesley Cheng
623326bbc34SWesley Cheng skip_sgt_map:
624485ae085SArnd Bergmann iommu_map(uaudio_qdev->data->domain, iova, pa, size, prot, GFP_KERNEL);
625326bbc34SWesley Cheng
626326bbc34SWesley Cheng done:
627485ae085SArnd Bergmann return iova;
628326bbc34SWesley Cheng }
629326bbc34SWesley Cheng
630326bbc34SWesley Cheng /* looks up alias, if any, for controller DT node and returns the index */
usb_get_controller_id(struct usb_device * udev)631326bbc34SWesley Cheng static int usb_get_controller_id(struct usb_device *udev)
632326bbc34SWesley Cheng {
633326bbc34SWesley Cheng if (udev->bus->sysdev && udev->bus->sysdev->of_node)
634326bbc34SWesley Cheng return of_alias_get_id(udev->bus->sysdev->of_node, "usb");
635326bbc34SWesley Cheng
636326bbc34SWesley Cheng return -ENODEV;
637326bbc34SWesley Cheng }
638326bbc34SWesley Cheng
639326bbc34SWesley Cheng /**
640326bbc34SWesley Cheng * uaudio_dev_intf_cleanup() - cleanup transfer resources
641326bbc34SWesley Cheng * @udev: usb device
642326bbc34SWesley Cheng * @info: usb offloading interface
643326bbc34SWesley Cheng *
644326bbc34SWesley Cheng * Cleans up the transfer ring related resources which are assigned per
645326bbc34SWesley Cheng * endpoint from XHCI. This is invoked when the USB endpoints are no
646326bbc34SWesley Cheng * longer in use by the adsp.
647326bbc34SWesley Cheng *
648326bbc34SWesley Cheng */
uaudio_dev_intf_cleanup(struct usb_device * udev,struct intf_info * info)649326bbc34SWesley Cheng static void uaudio_dev_intf_cleanup(struct usb_device *udev, struct intf_info *info)
650326bbc34SWesley Cheng {
651326bbc34SWesley Cheng uaudio_iommu_unmap(MEM_XFER_RING, info->data_xfer_ring_va,
652326bbc34SWesley Cheng info->data_xfer_ring_size, info->data_xfer_ring_size);
653326bbc34SWesley Cheng info->data_xfer_ring_va = 0;
654326bbc34SWesley Cheng info->data_xfer_ring_size = 0;
655326bbc34SWesley Cheng
656326bbc34SWesley Cheng uaudio_iommu_unmap(MEM_XFER_RING, info->sync_xfer_ring_va,
657326bbc34SWesley Cheng info->sync_xfer_ring_size, info->sync_xfer_ring_size);
658326bbc34SWesley Cheng info->sync_xfer_ring_va = 0;
659326bbc34SWesley Cheng info->sync_xfer_ring_size = 0;
660326bbc34SWesley Cheng
661485ae085SArnd Bergmann uaudio_iommu_unmap(MEM_XFER_BUF, info->xfer_buf_iova, info->xfer_buf_size,
662326bbc34SWesley Cheng info->xfer_buf_size);
663485ae085SArnd Bergmann info->xfer_buf_iova = 0;
664326bbc34SWesley Cheng
665485ae085SArnd Bergmann usb_free_coherent(udev, info->xfer_buf_size, info->xfer_buf_cpu,
666485ae085SArnd Bergmann info->xfer_buf_dma);
667326bbc34SWesley Cheng info->xfer_buf_size = 0;
668485ae085SArnd Bergmann info->xfer_buf_cpu = NULL;
669485ae085SArnd Bergmann info->xfer_buf_dma = 0;
670326bbc34SWesley Cheng
671326bbc34SWesley Cheng info->in_use = false;
672326bbc34SWesley Cheng }
673326bbc34SWesley Cheng
674326bbc34SWesley Cheng /**
675326bbc34SWesley Cheng * uaudio_event_ring_cleanup_free() - cleanup secondary event ring
676326bbc34SWesley Cheng * @dev: usb offload device
677326bbc34SWesley Cheng *
678326bbc34SWesley Cheng * Cleans up the secondary event ring that was requested. This will
679326bbc34SWesley Cheng * occur when the adsp is no longer transferring data on the USB bus
680326bbc34SWesley Cheng * across all endpoints.
681326bbc34SWesley Cheng *
682326bbc34SWesley Cheng */
uaudio_event_ring_cleanup_free(struct uaudio_dev * dev)683326bbc34SWesley Cheng static void uaudio_event_ring_cleanup_free(struct uaudio_dev *dev)
684326bbc34SWesley Cheng {
685326bbc34SWesley Cheng clear_bit(dev->chip->card->number, &uaudio_qdev->card_slot);
686326bbc34SWesley Cheng /* all audio devices are disconnected */
687326bbc34SWesley Cheng if (!uaudio_qdev->card_slot) {
688326bbc34SWesley Cheng uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, PAGE_SIZE,
689326bbc34SWesley Cheng PAGE_SIZE);
690326bbc34SWesley Cheng xhci_sideband_remove_interrupter(uadev[dev->chip->card->number].sb);
691326bbc34SWesley Cheng }
692326bbc34SWesley Cheng }
693326bbc34SWesley Cheng
uaudio_dev_cleanup(struct uaudio_dev * dev)694326bbc34SWesley Cheng static void uaudio_dev_cleanup(struct uaudio_dev *dev)
695326bbc34SWesley Cheng {
696326bbc34SWesley Cheng int if_idx;
697326bbc34SWesley Cheng
698326bbc34SWesley Cheng if (!dev->udev)
699326bbc34SWesley Cheng return;
700326bbc34SWesley Cheng
701326bbc34SWesley Cheng /* free xfer buffer and unmap xfer ring and buf per interface */
702326bbc34SWesley Cheng for (if_idx = 0; if_idx < dev->num_intf; if_idx++) {
703326bbc34SWesley Cheng if (!dev->info[if_idx].in_use)
704326bbc34SWesley Cheng continue;
705326bbc34SWesley Cheng uaudio_dev_intf_cleanup(dev->udev, &dev->info[if_idx]);
706326bbc34SWesley Cheng dev_dbg(uaudio_qdev->data->dev,
707326bbc34SWesley Cheng "release resources: intf# %d card# %d\n",
708326bbc34SWesley Cheng dev->info[if_idx].intf_num, dev->chip->card->number);
709326bbc34SWesley Cheng }
710326bbc34SWesley Cheng
711326bbc34SWesley Cheng dev->num_intf = 0;
712326bbc34SWesley Cheng
713326bbc34SWesley Cheng /* free interface info */
714326bbc34SWesley Cheng kfree(dev->info);
715326bbc34SWesley Cheng dev->info = NULL;
716326bbc34SWesley Cheng uaudio_event_ring_cleanup_free(dev);
717326bbc34SWesley Cheng dev->udev = NULL;
718326bbc34SWesley Cheng }
719326bbc34SWesley Cheng
720326bbc34SWesley Cheng /**
721326bbc34SWesley Cheng * disable_audio_stream() - disable usb snd endpoints
722326bbc34SWesley Cheng * @subs: usb substream
723326bbc34SWesley Cheng *
724326bbc34SWesley Cheng * Closes the USB SND endpoints associated with the current audio stream
725326bbc34SWesley Cheng * used. This will decrement the USB SND endpoint opened reference count.
726326bbc34SWesley Cheng *
727326bbc34SWesley Cheng */
disable_audio_stream(struct snd_usb_substream * subs)728326bbc34SWesley Cheng static void disable_audio_stream(struct snd_usb_substream *subs)
729326bbc34SWesley Cheng {
730326bbc34SWesley Cheng struct snd_usb_audio *chip = subs->stream->chip;
731326bbc34SWesley Cheng
732326bbc34SWesley Cheng snd_usb_hw_free(subs);
733326bbc34SWesley Cheng snd_usb_autosuspend(chip);
734326bbc34SWesley Cheng }
735326bbc34SWesley Cheng
736326bbc34SWesley Cheng /* QMI service disconnect handlers */
qmi_stop_session(void)737326bbc34SWesley Cheng static void qmi_stop_session(void)
738326bbc34SWesley Cheng {
739326bbc34SWesley Cheng struct snd_usb_substream *subs;
740326bbc34SWesley Cheng struct usb_host_endpoint *ep;
741326bbc34SWesley Cheng struct snd_usb_audio *chip;
742326bbc34SWesley Cheng struct intf_info *info;
743326bbc34SWesley Cheng int pcm_card_num;
744326bbc34SWesley Cheng int if_idx;
745326bbc34SWesley Cheng int idx;
746326bbc34SWesley Cheng
747326bbc34SWesley Cheng mutex_lock(&qdev_mutex);
748326bbc34SWesley Cheng /* find all active intf for set alt 0 and cleanup usb audio dev */
749326bbc34SWesley Cheng for (idx = 0; idx < SNDRV_CARDS; idx++) {
750326bbc34SWesley Cheng if (!atomic_read(&uadev[idx].in_use))
751326bbc34SWesley Cheng continue;
752326bbc34SWesley Cheng
753326bbc34SWesley Cheng chip = uadev[idx].chip;
754326bbc34SWesley Cheng for (if_idx = 0; if_idx < uadev[idx].num_intf; if_idx++) {
755326bbc34SWesley Cheng if (!uadev[idx].info || !uadev[idx].info[if_idx].in_use)
756326bbc34SWesley Cheng continue;
757326bbc34SWesley Cheng info = &uadev[idx].info[if_idx];
758326bbc34SWesley Cheng pcm_card_num = info->pcm_card_num;
759326bbc34SWesley Cheng subs = find_substream(pcm_card_num, info->pcm_dev_num,
760326bbc34SWesley Cheng info->direction);
761326bbc34SWesley Cheng if (!subs || !chip || atomic_read(&chip->shutdown)) {
762*5e957175SPei Xiao dev_err(&uadev[idx].udev->dev,
763326bbc34SWesley Cheng "no sub for c#%u dev#%u dir%u\n",
764326bbc34SWesley Cheng info->pcm_card_num,
765326bbc34SWesley Cheng info->pcm_dev_num,
766326bbc34SWesley Cheng info->direction);
767326bbc34SWesley Cheng continue;
768326bbc34SWesley Cheng }
769326bbc34SWesley Cheng /* Release XHCI endpoints */
770326bbc34SWesley Cheng if (info->data_ep_pipe)
771326bbc34SWesley Cheng ep = usb_pipe_endpoint(uadev[pcm_card_num].udev,
772326bbc34SWesley Cheng info->data_ep_pipe);
773326bbc34SWesley Cheng xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb, ep);
774326bbc34SWesley Cheng
775326bbc34SWesley Cheng if (info->sync_ep_pipe)
776326bbc34SWesley Cheng ep = usb_pipe_endpoint(uadev[pcm_card_num].udev,
777326bbc34SWesley Cheng info->sync_ep_pipe);
778326bbc34SWesley Cheng xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb, ep);
779326bbc34SWesley Cheng
780326bbc34SWesley Cheng disable_audio_stream(subs);
781326bbc34SWesley Cheng }
782326bbc34SWesley Cheng atomic_set(&uadev[idx].in_use, 0);
783326bbc34SWesley Cheng mutex_lock(&chip->mutex);
784326bbc34SWesley Cheng uaudio_dev_cleanup(&uadev[idx]);
785326bbc34SWesley Cheng mutex_unlock(&chip->mutex);
786326bbc34SWesley Cheng }
787326bbc34SWesley Cheng mutex_unlock(&qdev_mutex);
788326bbc34SWesley Cheng }
789326bbc34SWesley Cheng
790326bbc34SWesley Cheng /**
791326bbc34SWesley Cheng * uaudio_sideband_notifier() - xHCI sideband event handler
792326bbc34SWesley Cheng * @intf: USB interface handle
793326bbc34SWesley Cheng * @evt: xHCI sideband event type
794326bbc34SWesley Cheng *
795326bbc34SWesley Cheng * This callback is executed when the xHCI sideband encounters a sequence
796326bbc34SWesley Cheng * that requires the sideband clients to take action. An example, is when
797326bbc34SWesley Cheng * xHCI frees the transfer ring, so the client has to ensure that the
798326bbc34SWesley Cheng * offload path is halted.
799326bbc34SWesley Cheng *
800326bbc34SWesley Cheng */
uaudio_sideband_notifier(struct usb_interface * intf,struct xhci_sideband_event * evt)801326bbc34SWesley Cheng static int uaudio_sideband_notifier(struct usb_interface *intf,
802326bbc34SWesley Cheng struct xhci_sideband_event *evt)
803326bbc34SWesley Cheng {
804326bbc34SWesley Cheng struct snd_usb_audio *chip;
805326bbc34SWesley Cheng struct uaudio_dev *dev;
806326bbc34SWesley Cheng int if_idx;
807326bbc34SWesley Cheng
808326bbc34SWesley Cheng if (!intf || !evt)
809326bbc34SWesley Cheng return 0;
810326bbc34SWesley Cheng
811326bbc34SWesley Cheng chip = usb_get_intfdata(intf);
812326bbc34SWesley Cheng
813326bbc34SWesley Cheng mutex_lock(&qdev_mutex);
814326bbc34SWesley Cheng mutex_lock(&chip->mutex);
815326bbc34SWesley Cheng
816326bbc34SWesley Cheng dev = &uadev[chip->card->number];
817326bbc34SWesley Cheng
818326bbc34SWesley Cheng if (evt->type == XHCI_SIDEBAND_XFER_RING_FREE) {
819326bbc34SWesley Cheng unsigned int *ep = (unsigned int *) evt->evt_data;
820326bbc34SWesley Cheng
821326bbc34SWesley Cheng for (if_idx = 0; if_idx < dev->num_intf; if_idx++) {
822326bbc34SWesley Cheng if (dev->info[if_idx].data_ep_idx == *ep ||
823326bbc34SWesley Cheng dev->info[if_idx].sync_ep_idx == *ep)
824326bbc34SWesley Cheng uaudio_send_disconnect_ind(chip);
825326bbc34SWesley Cheng }
826326bbc34SWesley Cheng }
827326bbc34SWesley Cheng
828326bbc34SWesley Cheng mutex_unlock(&qdev_mutex);
829326bbc34SWesley Cheng mutex_unlock(&chip->mutex);
830326bbc34SWesley Cheng
831326bbc34SWesley Cheng return 0;
832326bbc34SWesley Cheng }
833326bbc34SWesley Cheng
834326bbc34SWesley Cheng /**
835326bbc34SWesley Cheng * qmi_bye_cb() - qmi bye message callback
836326bbc34SWesley Cheng * @handle: QMI handle
837326bbc34SWesley Cheng * @node: id of the dying node
838326bbc34SWesley Cheng *
839326bbc34SWesley Cheng * This callback is invoked when the QMI bye control message is received
840326bbc34SWesley Cheng * from the QMI client. Handle the message accordingly by ensuring that
841326bbc34SWesley Cheng * the USB offload path is disabled and cleaned up. At this point, ADSP
842326bbc34SWesley Cheng * is not utilizing the USB bus.
843326bbc34SWesley Cheng *
844326bbc34SWesley Cheng */
qmi_bye_cb(struct qmi_handle * handle,unsigned int node)845326bbc34SWesley Cheng static void qmi_bye_cb(struct qmi_handle *handle, unsigned int node)
846326bbc34SWesley Cheng {
847326bbc34SWesley Cheng struct uaudio_qmi_svc *svc = uaudio_svc;
848326bbc34SWesley Cheng
849326bbc34SWesley Cheng if (svc->uaudio_svc_hdl != handle)
850326bbc34SWesley Cheng return;
851326bbc34SWesley Cheng
852326bbc34SWesley Cheng if (svc->client_connected && svc->client_sq.sq_node == node) {
853326bbc34SWesley Cheng qmi_stop_session();
854326bbc34SWesley Cheng
855326bbc34SWesley Cheng /* clear QMI client parameters to block further QMI messages */
856326bbc34SWesley Cheng svc->client_sq.sq_node = 0;
857326bbc34SWesley Cheng svc->client_sq.sq_port = 0;
858326bbc34SWesley Cheng svc->client_sq.sq_family = 0;
859326bbc34SWesley Cheng svc->client_connected = false;
860326bbc34SWesley Cheng }
861326bbc34SWesley Cheng }
862326bbc34SWesley Cheng
863326bbc34SWesley Cheng /**
864326bbc34SWesley Cheng * qmi_svc_disconnect_cb() - qmi client disconnected
865326bbc34SWesley Cheng * @handle: QMI handle
866326bbc34SWesley Cheng * @node: id of the dying node
867326bbc34SWesley Cheng * @port: port of the dying client
868326bbc34SWesley Cheng *
869326bbc34SWesley Cheng * Invoked when the remote QMI client is disconnected. Handle this event
870326bbc34SWesley Cheng * the same way as when the QMI bye message is received. This will ensure
871326bbc34SWesley Cheng * the USB offloading path is disabled and cleaned up.
872326bbc34SWesley Cheng *
873326bbc34SWesley Cheng */
qmi_svc_disconnect_cb(struct qmi_handle * handle,unsigned int node,unsigned int port)874326bbc34SWesley Cheng static void qmi_svc_disconnect_cb(struct qmi_handle *handle,
875326bbc34SWesley Cheng unsigned int node, unsigned int port)
876326bbc34SWesley Cheng {
877326bbc34SWesley Cheng struct uaudio_qmi_svc *svc;
878326bbc34SWesley Cheng
879326bbc34SWesley Cheng if (!uaudio_svc)
880326bbc34SWesley Cheng return;
881326bbc34SWesley Cheng
882326bbc34SWesley Cheng svc = uaudio_svc;
883326bbc34SWesley Cheng if (svc->uaudio_svc_hdl != handle)
884326bbc34SWesley Cheng return;
885326bbc34SWesley Cheng
886326bbc34SWesley Cheng if (svc->client_connected && svc->client_sq.sq_node == node &&
887326bbc34SWesley Cheng svc->client_sq.sq_port == port) {
888326bbc34SWesley Cheng qmi_stop_session();
889326bbc34SWesley Cheng
890326bbc34SWesley Cheng /* clear QMI client parameters to block further QMI messages */
891326bbc34SWesley Cheng svc->client_sq.sq_node = 0;
892326bbc34SWesley Cheng svc->client_sq.sq_port = 0;
893326bbc34SWesley Cheng svc->client_sq.sq_family = 0;
894326bbc34SWesley Cheng svc->client_connected = false;
895326bbc34SWesley Cheng }
896326bbc34SWesley Cheng }
897326bbc34SWesley Cheng
898326bbc34SWesley Cheng /* QMI client callback handlers from QMI interface */
899326bbc34SWesley Cheng static struct qmi_ops uaudio_svc_ops_options = {
900326bbc34SWesley Cheng .bye = qmi_bye_cb,
901326bbc34SWesley Cheng .del_client = qmi_svc_disconnect_cb,
902326bbc34SWesley Cheng };
903326bbc34SWesley Cheng
904326bbc34SWesley Cheng /* kref release callback when all streams are disabled */
uaudio_dev_release(struct kref * kref)905326bbc34SWesley Cheng static void uaudio_dev_release(struct kref *kref)
906326bbc34SWesley Cheng {
907326bbc34SWesley Cheng struct uaudio_dev *dev = container_of(kref, struct uaudio_dev, kref);
908326bbc34SWesley Cheng
909326bbc34SWesley Cheng uaudio_event_ring_cleanup_free(dev);
910326bbc34SWesley Cheng atomic_set(&dev->in_use, 0);
911326bbc34SWesley Cheng wake_up(&dev->disconnect_wq);
912326bbc34SWesley Cheng }
913326bbc34SWesley Cheng
914326bbc34SWesley Cheng /**
915326bbc34SWesley Cheng * enable_audio_stream() - enable usb snd endpoints
916326bbc34SWesley Cheng * @subs: usb substream
917326bbc34SWesley Cheng * @pcm_format: pcm format requested
918326bbc34SWesley Cheng * @channels: number of channels
919326bbc34SWesley Cheng * @cur_rate: sample rate
920326bbc34SWesley Cheng * @datainterval: interval
921326bbc34SWesley Cheng *
922326bbc34SWesley Cheng * Opens all USB SND endpoints used for the data interface. This will increment
923326bbc34SWesley Cheng * the USB SND endpoint's opened count. Requests to keep the interface resumed
924326bbc34SWesley Cheng * until the audio stream is stopped. Will issue the USB set interface control
925326bbc34SWesley Cheng * message to enable the data interface.
926326bbc34SWesley Cheng *
927326bbc34SWesley Cheng */
enable_audio_stream(struct snd_usb_substream * subs,snd_pcm_format_t pcm_format,unsigned int channels,unsigned int cur_rate,int datainterval)928326bbc34SWesley Cheng static int enable_audio_stream(struct snd_usb_substream *subs,
929326bbc34SWesley Cheng snd_pcm_format_t pcm_format,
930326bbc34SWesley Cheng unsigned int channels, unsigned int cur_rate,
931326bbc34SWesley Cheng int datainterval)
932326bbc34SWesley Cheng {
933326bbc34SWesley Cheng struct snd_pcm_hw_params params;
934326bbc34SWesley Cheng struct snd_usb_audio *chip;
935326bbc34SWesley Cheng struct snd_interval *i;
936326bbc34SWesley Cheng struct snd_mask *m;
937326bbc34SWesley Cheng int ret;
938326bbc34SWesley Cheng
939326bbc34SWesley Cheng chip = subs->stream->chip;
940326bbc34SWesley Cheng
941326bbc34SWesley Cheng _snd_pcm_hw_params_any(¶ms);
942326bbc34SWesley Cheng
943326bbc34SWesley Cheng m = hw_param_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT);
944326bbc34SWesley Cheng snd_mask_leave(m, pcm_format);
945326bbc34SWesley Cheng
946326bbc34SWesley Cheng i = hw_param_interval(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS);
947326bbc34SWesley Cheng snd_interval_setinteger(i);
948326bbc34SWesley Cheng i->min = channels;
949326bbc34SWesley Cheng i->max = channels;
950326bbc34SWesley Cheng
951326bbc34SWesley Cheng i = hw_param_interval(¶ms, SNDRV_PCM_HW_PARAM_RATE);
952326bbc34SWesley Cheng snd_interval_setinteger(i);
953326bbc34SWesley Cheng i->min = cur_rate;
954326bbc34SWesley Cheng i->max = cur_rate;
955326bbc34SWesley Cheng
956326bbc34SWesley Cheng pm_runtime_barrier(&chip->intf[0]->dev);
957326bbc34SWesley Cheng snd_usb_autoresume(chip);
958326bbc34SWesley Cheng
959326bbc34SWesley Cheng ret = snd_usb_hw_params(subs, ¶ms);
960326bbc34SWesley Cheng if (ret < 0)
961326bbc34SWesley Cheng goto put_suspend;
962326bbc34SWesley Cheng
963326bbc34SWesley Cheng if (!atomic_read(&chip->shutdown)) {
964326bbc34SWesley Cheng ret = snd_usb_lock_shutdown(chip);
965326bbc34SWesley Cheng if (ret < 0)
966326bbc34SWesley Cheng goto detach_ep;
967326bbc34SWesley Cheng
968326bbc34SWesley Cheng if (subs->sync_endpoint) {
969326bbc34SWesley Cheng ret = snd_usb_endpoint_prepare(chip, subs->sync_endpoint);
970326bbc34SWesley Cheng if (ret < 0)
971326bbc34SWesley Cheng goto unlock;
972326bbc34SWesley Cheng }
973326bbc34SWesley Cheng
974326bbc34SWesley Cheng ret = snd_usb_endpoint_prepare(chip, subs->data_endpoint);
975326bbc34SWesley Cheng if (ret < 0)
976326bbc34SWesley Cheng goto unlock;
977326bbc34SWesley Cheng
978326bbc34SWesley Cheng snd_usb_unlock_shutdown(chip);
979326bbc34SWesley Cheng
980326bbc34SWesley Cheng dev_dbg(uaudio_qdev->data->dev,
981326bbc34SWesley Cheng "selected %s iface:%d altsetting:%d datainterval:%dus\n",
982326bbc34SWesley Cheng subs->direction ? "capture" : "playback",
983326bbc34SWesley Cheng subs->cur_audiofmt->iface, subs->cur_audiofmt->altsetting,
984326bbc34SWesley Cheng (1 << subs->cur_audiofmt->datainterval) *
985326bbc34SWesley Cheng (subs->dev->speed >= USB_SPEED_HIGH ?
986326bbc34SWesley Cheng BUS_INTERVAL_HIGHSPEED_AND_ABOVE :
987326bbc34SWesley Cheng BUS_INTERVAL_FULL_SPEED));
988326bbc34SWesley Cheng }
989326bbc34SWesley Cheng
990326bbc34SWesley Cheng return 0;
991326bbc34SWesley Cheng
992326bbc34SWesley Cheng unlock:
993326bbc34SWesley Cheng snd_usb_unlock_shutdown(chip);
994326bbc34SWesley Cheng
995326bbc34SWesley Cheng detach_ep:
996326bbc34SWesley Cheng snd_usb_hw_free(subs);
997326bbc34SWesley Cheng
998326bbc34SWesley Cheng put_suspend:
999326bbc34SWesley Cheng snd_usb_autosuspend(chip);
1000326bbc34SWesley Cheng
1001326bbc34SWesley Cheng return ret;
1002326bbc34SWesley Cheng }
1003326bbc34SWesley Cheng
1004326bbc34SWesley Cheng /**
1005326bbc34SWesley Cheng * uaudio_transfer_buffer_setup() - fetch and populate xfer buffer params
1006326bbc34SWesley Cheng * @subs: usb substream
1007326bbc34SWesley Cheng * @xfer_buf: xfer buf to be allocated
1008326bbc34SWesley Cheng * @xfer_buf_len: size of allocation
1009326bbc34SWesley Cheng * @mem_info: QMI response info
1010326bbc34SWesley Cheng *
1011326bbc34SWesley Cheng * Allocates and maps the transfer buffers that will be utilized by the
1012326bbc34SWesley Cheng * audio DSP. Will populate the information in the QMI response that is
1013326bbc34SWesley Cheng * sent back to the stream enable request.
1014326bbc34SWesley Cheng *
1015326bbc34SWesley Cheng */
uaudio_transfer_buffer_setup(struct snd_usb_substream * subs,void ** xfer_buf_cpu,u32 xfer_buf_len,struct mem_info_v01 * mem_info)1016326bbc34SWesley Cheng static int uaudio_transfer_buffer_setup(struct snd_usb_substream *subs,
10175c7ef500SArnd Bergmann void **xfer_buf_cpu, u32 xfer_buf_len,
1018326bbc34SWesley Cheng struct mem_info_v01 *mem_info)
1019326bbc34SWesley Cheng {
1020326bbc34SWesley Cheng struct sg_table xfer_buf_sgt;
10213335a1bbSArnd Bergmann dma_addr_t xfer_buf_dma;
10225c7ef500SArnd Bergmann void *xfer_buf;
1023326bbc34SWesley Cheng phys_addr_t xfer_buf_pa;
1024326bbc34SWesley Cheng u32 len = xfer_buf_len;
1025326bbc34SWesley Cheng bool dma_coherent;
10263335a1bbSArnd Bergmann dma_addr_t xfer_buf_dma_sysdev;
1027326bbc34SWesley Cheng u32 remainder;
1028326bbc34SWesley Cheng u32 mult;
1029326bbc34SWesley Cheng int ret;
1030326bbc34SWesley Cheng
1031326bbc34SWesley Cheng dma_coherent = dev_is_dma_coherent(subs->dev->bus->sysdev);
1032326bbc34SWesley Cheng
1033326bbc34SWesley Cheng /* xfer buffer, multiple of 4K only */
1034326bbc34SWesley Cheng if (!len)
1035326bbc34SWesley Cheng len = PAGE_SIZE;
1036326bbc34SWesley Cheng
1037326bbc34SWesley Cheng mult = len / PAGE_SIZE;
1038326bbc34SWesley Cheng remainder = len % PAGE_SIZE;
1039326bbc34SWesley Cheng len = mult * PAGE_SIZE;
1040326bbc34SWesley Cheng len += remainder ? PAGE_SIZE : 0;
1041326bbc34SWesley Cheng
1042326bbc34SWesley Cheng if (len > MAX_XFER_BUFF_LEN) {
1043326bbc34SWesley Cheng dev_err(uaudio_qdev->data->dev,
1044326bbc34SWesley Cheng "req buf len %d > max buf len %lu, setting %lu\n",
1045326bbc34SWesley Cheng len, MAX_XFER_BUFF_LEN, MAX_XFER_BUFF_LEN);
1046326bbc34SWesley Cheng len = MAX_XFER_BUFF_LEN;
1047326bbc34SWesley Cheng }
1048326bbc34SWesley Cheng
10493335a1bbSArnd Bergmann /* get buffer mapped into subs->dev */
10503335a1bbSArnd Bergmann xfer_buf = usb_alloc_coherent(subs->dev, len, GFP_KERNEL, &xfer_buf_dma);
1051326bbc34SWesley Cheng if (!xfer_buf)
1052326bbc34SWesley Cheng return -ENOMEM;
1053326bbc34SWesley Cheng
10543335a1bbSArnd Bergmann /* Remapping is not possible if xfer_buf is outside of linear map */
10553335a1bbSArnd Bergmann xfer_buf_pa = virt_to_phys(xfer_buf);
10563335a1bbSArnd Bergmann if (WARN_ON(!page_is_ram(PFN_DOWN(xfer_buf_pa)))) {
10573335a1bbSArnd Bergmann ret = -ENXIO;
10583335a1bbSArnd Bergmann goto unmap_sync;
10593335a1bbSArnd Bergmann }
1060326bbc34SWesley Cheng dma_get_sgtable(subs->dev->bus->sysdev, &xfer_buf_sgt, xfer_buf,
10613335a1bbSArnd Bergmann xfer_buf_dma, len);
10623335a1bbSArnd Bergmann
10633335a1bbSArnd Bergmann /* map the physical buffer into sysdev as well */
10643335a1bbSArnd Bergmann xfer_buf_dma_sysdev = uaudio_iommu_map(MEM_XFER_BUF, dma_coherent,
10653335a1bbSArnd Bergmann xfer_buf_pa, len, &xfer_buf_sgt);
10663335a1bbSArnd Bergmann if (!xfer_buf_dma_sysdev) {
1067326bbc34SWesley Cheng ret = -ENOMEM;
1068326bbc34SWesley Cheng goto unmap_sync;
1069326bbc34SWesley Cheng }
1070326bbc34SWesley Cheng
10713335a1bbSArnd Bergmann mem_info->dma = xfer_buf_dma;
1072326bbc34SWesley Cheng mem_info->size = len;
10733335a1bbSArnd Bergmann mem_info->iova = PREPEND_SID_TO_IOVA(xfer_buf_dma_sysdev, uaudio_qdev->data->sid);
10745c7ef500SArnd Bergmann *xfer_buf_cpu = xfer_buf;
1075326bbc34SWesley Cheng sg_free_table(&xfer_buf_sgt);
1076326bbc34SWesley Cheng
1077326bbc34SWesley Cheng return 0;
1078326bbc34SWesley Cheng
1079326bbc34SWesley Cheng unmap_sync:
10803335a1bbSArnd Bergmann usb_free_coherent(subs->dev, len, xfer_buf, xfer_buf_dma);
1081326bbc34SWesley Cheng
1082326bbc34SWesley Cheng return ret;
1083326bbc34SWesley Cheng }
1084326bbc34SWesley Cheng
1085326bbc34SWesley Cheng /**
1086326bbc34SWesley Cheng * uaudio_endpoint_setup() - fetch and populate endpoint params
1087326bbc34SWesley Cheng * @subs: usb substream
1088326bbc34SWesley Cheng * @endpoint: usb endpoint to add
1089326bbc34SWesley Cheng * @card_num: uadev index
1090326bbc34SWesley Cheng * @mem_info: QMI response info
1091326bbc34SWesley Cheng * @ep_desc: QMI ep desc response field
1092326bbc34SWesley Cheng *
1093326bbc34SWesley Cheng * Initialize the USB endpoint being used for a particular USB
1094326bbc34SWesley Cheng * stream. Will request XHCI sec intr to reserve the EP for
1095326bbc34SWesley Cheng * offloading as well as populating the QMI response with the
1096326bbc34SWesley Cheng * transfer ring parameters.
1097326bbc34SWesley Cheng *
1098326bbc34SWesley Cheng */
1099326bbc34SWesley Cheng static phys_addr_t
uaudio_endpoint_setup(struct snd_usb_substream * subs,struct snd_usb_endpoint * endpoint,int card_num,struct mem_info_v01 * mem_info,struct usb_endpoint_descriptor_v01 * ep_desc)1100326bbc34SWesley Cheng uaudio_endpoint_setup(struct snd_usb_substream *subs,
1101326bbc34SWesley Cheng struct snd_usb_endpoint *endpoint, int card_num,
1102326bbc34SWesley Cheng struct mem_info_v01 *mem_info,
1103326bbc34SWesley Cheng struct usb_endpoint_descriptor_v01 *ep_desc)
1104326bbc34SWesley Cheng {
1105326bbc34SWesley Cheng struct usb_host_endpoint *ep;
1106326bbc34SWesley Cheng phys_addr_t tr_pa = 0;
1107326bbc34SWesley Cheng struct sg_table *sgt;
1108326bbc34SWesley Cheng bool dma_coherent;
1109485ae085SArnd Bergmann unsigned long iova;
1110326bbc34SWesley Cheng struct page *pg;
1111326bbc34SWesley Cheng int ret = -ENODEV;
1112326bbc34SWesley Cheng
1113326bbc34SWesley Cheng dma_coherent = dev_is_dma_coherent(subs->dev->bus->sysdev);
1114326bbc34SWesley Cheng
1115326bbc34SWesley Cheng ep = usb_pipe_endpoint(subs->dev, endpoint->pipe);
1116326bbc34SWesley Cheng if (!ep) {
1117326bbc34SWesley Cheng dev_err(uaudio_qdev->data->dev, "data ep # %d context is null\n",
1118326bbc34SWesley Cheng subs->data_endpoint->ep_num);
1119326bbc34SWesley Cheng goto exit;
1120326bbc34SWesley Cheng }
1121326bbc34SWesley Cheng
1122326bbc34SWesley Cheng memcpy(ep_desc, &ep->desc, sizeof(ep->desc));
1123326bbc34SWesley Cheng
1124326bbc34SWesley Cheng ret = xhci_sideband_add_endpoint(uadev[card_num].sb, ep);
1125326bbc34SWesley Cheng if (ret < 0) {
1126326bbc34SWesley Cheng dev_err(&subs->dev->dev,
1127326bbc34SWesley Cheng "failed to add data ep to sec intr\n");
1128326bbc34SWesley Cheng ret = -ENODEV;
1129326bbc34SWesley Cheng goto exit;
1130326bbc34SWesley Cheng }
1131326bbc34SWesley Cheng
1132326bbc34SWesley Cheng sgt = xhci_sideband_get_endpoint_buffer(uadev[card_num].sb, ep);
1133326bbc34SWesley Cheng if (!sgt) {
1134326bbc34SWesley Cheng dev_err(&subs->dev->dev,
1135326bbc34SWesley Cheng "failed to get data ep ring address\n");
1136326bbc34SWesley Cheng ret = -ENODEV;
1137326bbc34SWesley Cheng goto remove_ep;
1138326bbc34SWesley Cheng }
1139326bbc34SWesley Cheng
1140326bbc34SWesley Cheng pg = sg_page(sgt->sgl);
1141326bbc34SWesley Cheng tr_pa = page_to_phys(pg);
1142485ae085SArnd Bergmann mem_info->dma = sg_dma_address(sgt->sgl);
1143326bbc34SWesley Cheng sg_free_table(sgt);
1144326bbc34SWesley Cheng
1145326bbc34SWesley Cheng /* data transfer ring */
1146485ae085SArnd Bergmann iova = uaudio_iommu_map(MEM_XFER_RING, dma_coherent, tr_pa,
1147326bbc34SWesley Cheng PAGE_SIZE, NULL);
1148485ae085SArnd Bergmann if (!iova) {
1149326bbc34SWesley Cheng ret = -ENOMEM;
1150326bbc34SWesley Cheng goto clear_pa;
1151326bbc34SWesley Cheng }
1152326bbc34SWesley Cheng
1153485ae085SArnd Bergmann mem_info->iova = PREPEND_SID_TO_IOVA(iova, uaudio_qdev->data->sid);
1154326bbc34SWesley Cheng mem_info->size = PAGE_SIZE;
1155326bbc34SWesley Cheng
1156326bbc34SWesley Cheng return 0;
1157326bbc34SWesley Cheng
1158326bbc34SWesley Cheng clear_pa:
1159485ae085SArnd Bergmann mem_info->dma = 0;
1160326bbc34SWesley Cheng remove_ep:
1161326bbc34SWesley Cheng xhci_sideband_remove_endpoint(uadev[card_num].sb, ep);
1162326bbc34SWesley Cheng exit:
1163326bbc34SWesley Cheng return ret;
1164326bbc34SWesley Cheng }
1165326bbc34SWesley Cheng
1166326bbc34SWesley Cheng /**
1167326bbc34SWesley Cheng * uaudio_event_ring_setup() - fetch and populate event ring params
1168326bbc34SWesley Cheng * @subs: usb substream
1169326bbc34SWesley Cheng * @card_num: uadev index
1170326bbc34SWesley Cheng * @mem_info: QMI response info
1171326bbc34SWesley Cheng *
1172326bbc34SWesley Cheng * Register secondary interrupter to XHCI and fetch the event buffer info
1173326bbc34SWesley Cheng * and populate the information into the QMI response.
1174326bbc34SWesley Cheng *
1175326bbc34SWesley Cheng */
uaudio_event_ring_setup(struct snd_usb_substream * subs,int card_num,struct mem_info_v01 * mem_info)1176326bbc34SWesley Cheng static int uaudio_event_ring_setup(struct snd_usb_substream *subs,
1177326bbc34SWesley Cheng int card_num, struct mem_info_v01 *mem_info)
1178326bbc34SWesley Cheng {
1179326bbc34SWesley Cheng struct sg_table *sgt;
1180326bbc34SWesley Cheng phys_addr_t er_pa;
1181326bbc34SWesley Cheng bool dma_coherent;
1182485ae085SArnd Bergmann unsigned long iova;
1183326bbc34SWesley Cheng struct page *pg;
1184326bbc34SWesley Cheng int ret;
1185326bbc34SWesley Cheng
1186326bbc34SWesley Cheng dma_coherent = dev_is_dma_coherent(subs->dev->bus->sysdev);
1187326bbc34SWesley Cheng er_pa = 0;
1188326bbc34SWesley Cheng
1189326bbc34SWesley Cheng /* event ring */
1190326bbc34SWesley Cheng ret = xhci_sideband_create_interrupter(uadev[card_num].sb, 1, false,
1191326bbc34SWesley Cheng 0, uaudio_qdev->data->intr_num);
1192326bbc34SWesley Cheng if (ret < 0) {
1193326bbc34SWesley Cheng dev_err(&subs->dev->dev, "failed to fetch interrupter\n");
1194326bbc34SWesley Cheng goto exit;
1195326bbc34SWesley Cheng }
1196326bbc34SWesley Cheng
1197326bbc34SWesley Cheng sgt = xhci_sideband_get_event_buffer(uadev[card_num].sb);
1198326bbc34SWesley Cheng if (!sgt) {
1199326bbc34SWesley Cheng dev_err(&subs->dev->dev,
1200326bbc34SWesley Cheng "failed to get event ring address\n");
1201326bbc34SWesley Cheng ret = -ENODEV;
1202326bbc34SWesley Cheng goto remove_interrupter;
1203326bbc34SWesley Cheng }
1204326bbc34SWesley Cheng
1205326bbc34SWesley Cheng pg = sg_page(sgt->sgl);
1206326bbc34SWesley Cheng er_pa = page_to_phys(pg);
1207485ae085SArnd Bergmann mem_info->dma = sg_dma_address(sgt->sgl);
1208326bbc34SWesley Cheng sg_free_table(sgt);
1209326bbc34SWesley Cheng
1210485ae085SArnd Bergmann iova = uaudio_iommu_map(MEM_EVENT_RING, dma_coherent, er_pa,
1211326bbc34SWesley Cheng PAGE_SIZE, NULL);
1212485ae085SArnd Bergmann if (!iova) {
1213326bbc34SWesley Cheng ret = -ENOMEM;
1214326bbc34SWesley Cheng goto clear_pa;
1215326bbc34SWesley Cheng }
1216326bbc34SWesley Cheng
1217485ae085SArnd Bergmann mem_info->iova = PREPEND_SID_TO_IOVA(iova, uaudio_qdev->data->sid);
1218326bbc34SWesley Cheng mem_info->size = PAGE_SIZE;
1219326bbc34SWesley Cheng
1220326bbc34SWesley Cheng return 0;
1221326bbc34SWesley Cheng
1222326bbc34SWesley Cheng clear_pa:
1223485ae085SArnd Bergmann mem_info->dma = 0;
1224326bbc34SWesley Cheng remove_interrupter:
1225326bbc34SWesley Cheng xhci_sideband_remove_interrupter(uadev[card_num].sb);
1226326bbc34SWesley Cheng exit:
1227326bbc34SWesley Cheng return ret;
1228326bbc34SWesley Cheng }
1229326bbc34SWesley Cheng
1230326bbc34SWesley Cheng /**
1231326bbc34SWesley Cheng * uaudio_populate_uac_desc() - parse UAC parameters and populate QMI resp
1232326bbc34SWesley Cheng * @subs: usb substream
1233326bbc34SWesley Cheng * @resp: QMI response buffer
1234326bbc34SWesley Cheng *
1235326bbc34SWesley Cheng * Parses information specified within UAC descriptors which explain the
1236326bbc34SWesley Cheng * sample parameters that the device expects. This information is populated
1237326bbc34SWesley Cheng * to the QMI response sent back to the audio DSP.
1238326bbc34SWesley Cheng *
1239326bbc34SWesley Cheng */
uaudio_populate_uac_desc(struct snd_usb_substream * subs,struct qmi_uaudio_stream_resp_msg_v01 * resp)1240326bbc34SWesley Cheng static int uaudio_populate_uac_desc(struct snd_usb_substream *subs,
1241326bbc34SWesley Cheng struct qmi_uaudio_stream_resp_msg_v01 *resp)
1242326bbc34SWesley Cheng {
1243326bbc34SWesley Cheng struct usb_interface_descriptor *altsd;
1244326bbc34SWesley Cheng struct usb_host_interface *alts;
1245326bbc34SWesley Cheng struct usb_interface *iface;
1246326bbc34SWesley Cheng int protocol;
1247326bbc34SWesley Cheng
1248326bbc34SWesley Cheng iface = usb_ifnum_to_if(subs->dev, subs->cur_audiofmt->iface);
1249326bbc34SWesley Cheng if (!iface) {
1250326bbc34SWesley Cheng dev_err(&subs->dev->dev, "interface # %d does not exist\n",
1251326bbc34SWesley Cheng subs->cur_audiofmt->iface);
1252326bbc34SWesley Cheng return -ENODEV;
1253326bbc34SWesley Cheng }
1254326bbc34SWesley Cheng
1255326bbc34SWesley Cheng alts = &iface->altsetting[subs->cur_audiofmt->altset_idx];
1256326bbc34SWesley Cheng altsd = get_iface_desc(alts);
1257326bbc34SWesley Cheng protocol = altsd->bInterfaceProtocol;
1258326bbc34SWesley Cheng
1259326bbc34SWesley Cheng if (protocol == UAC_VERSION_1) {
1260326bbc34SWesley Cheng struct uac1_as_header_descriptor *as;
1261326bbc34SWesley Cheng
1262326bbc34SWesley Cheng as = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL,
1263326bbc34SWesley Cheng UAC_AS_GENERAL);
1264326bbc34SWesley Cheng if (!as) {
1265326bbc34SWesley Cheng dev_err(&subs->dev->dev,
1266326bbc34SWesley Cheng "%u:%d : no UAC_AS_GENERAL desc\n",
1267326bbc34SWesley Cheng subs->cur_audiofmt->iface,
1268326bbc34SWesley Cheng subs->cur_audiofmt->altset_idx);
1269326bbc34SWesley Cheng return -ENODEV;
1270326bbc34SWesley Cheng }
1271326bbc34SWesley Cheng
1272326bbc34SWesley Cheng resp->data_path_delay = as->bDelay;
1273326bbc34SWesley Cheng resp->data_path_delay_valid = 1;
1274326bbc34SWesley Cheng
1275326bbc34SWesley Cheng resp->usb_audio_subslot_size = subs->cur_audiofmt->fmt_sz;
1276326bbc34SWesley Cheng resp->usb_audio_subslot_size_valid = 1;
1277326bbc34SWesley Cheng
1278326bbc34SWesley Cheng resp->usb_audio_spec_revision = le16_to_cpu((__force __le16)0x0100);
1279326bbc34SWesley Cheng resp->usb_audio_spec_revision_valid = 1;
1280326bbc34SWesley Cheng } else if (protocol == UAC_VERSION_2) {
1281326bbc34SWesley Cheng resp->usb_audio_subslot_size = subs->cur_audiofmt->fmt_sz;
1282326bbc34SWesley Cheng resp->usb_audio_subslot_size_valid = 1;
1283326bbc34SWesley Cheng
1284326bbc34SWesley Cheng resp->usb_audio_spec_revision = le16_to_cpu((__force __le16)0x0200);
1285326bbc34SWesley Cheng resp->usb_audio_spec_revision_valid = 1;
1286326bbc34SWesley Cheng } else if (protocol == UAC_VERSION_3) {
1287326bbc34SWesley Cheng if (iface->intf_assoc->bFunctionSubClass ==
1288326bbc34SWesley Cheng UAC3_FUNCTION_SUBCLASS_FULL_ADC_3_0) {
1289326bbc34SWesley Cheng dev_err(&subs->dev->dev,
1290326bbc34SWesley Cheng "full adc is not supported\n");
1291326bbc34SWesley Cheng return -EINVAL;
1292326bbc34SWesley Cheng }
1293326bbc34SWesley Cheng
1294326bbc34SWesley Cheng switch (le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize)) {
1295326bbc34SWesley Cheng case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_16:
1296326bbc34SWesley Cheng case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_16:
1297326bbc34SWesley Cheng case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_16:
1298326bbc34SWesley Cheng case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_16: {
1299326bbc34SWesley Cheng resp->usb_audio_subslot_size = 0x2;
1300326bbc34SWesley Cheng break;
1301326bbc34SWesley Cheng }
1302326bbc34SWesley Cheng
1303326bbc34SWesley Cheng case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_24:
1304326bbc34SWesley Cheng case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_24:
1305326bbc34SWesley Cheng case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_24:
1306326bbc34SWesley Cheng case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_24: {
1307326bbc34SWesley Cheng resp->usb_audio_subslot_size = 0x3;
1308326bbc34SWesley Cheng break;
1309326bbc34SWesley Cheng }
1310326bbc34SWesley Cheng
1311326bbc34SWesley Cheng default:
1312326bbc34SWesley Cheng dev_err(&subs->dev->dev,
1313326bbc34SWesley Cheng "%d: %u: Invalid wMaxPacketSize\n",
1314326bbc34SWesley Cheng subs->cur_audiofmt->iface,
1315326bbc34SWesley Cheng subs->cur_audiofmt->altset_idx);
1316326bbc34SWesley Cheng return -EINVAL;
1317326bbc34SWesley Cheng }
1318326bbc34SWesley Cheng resp->usb_audio_subslot_size_valid = 1;
1319326bbc34SWesley Cheng } else {
1320326bbc34SWesley Cheng dev_err(&subs->dev->dev, "unknown protocol version %x\n",
1321326bbc34SWesley Cheng protocol);
1322326bbc34SWesley Cheng return -ENODEV;
1323326bbc34SWesley Cheng }
1324326bbc34SWesley Cheng
1325326bbc34SWesley Cheng memcpy(&resp->std_as_opr_intf_desc, &alts->desc, sizeof(alts->desc));
1326326bbc34SWesley Cheng
1327326bbc34SWesley Cheng return 0;
1328326bbc34SWesley Cheng }
1329326bbc34SWesley Cheng
1330326bbc34SWesley Cheng /**
1331326bbc34SWesley Cheng * prepare_qmi_response() - prepare stream enable response
1332326bbc34SWesley Cheng * @subs: usb substream
1333326bbc34SWesley Cheng * @req_msg: QMI request message
1334326bbc34SWesley Cheng * @resp: QMI response buffer
1335326bbc34SWesley Cheng * @info_idx: usb interface array index
1336326bbc34SWesley Cheng *
1337326bbc34SWesley Cheng * Prepares the QMI response for a USB QMI stream enable request. Will parse
1338326bbc34SWesley Cheng * out the parameters within the stream enable request, in order to match
1339326bbc34SWesley Cheng * requested audio profile to the ones exposed by the USB device connected.
1340326bbc34SWesley Cheng *
1341326bbc34SWesley Cheng * In addition, will fetch the XHCI transfer resources needed for the handoff to
1342326bbc34SWesley Cheng * happen. This includes, transfer ring and buffer addresses and secondary event
1343326bbc34SWesley Cheng * ring address. These parameters will be communicated as part of the USB QMI
1344326bbc34SWesley Cheng * stream enable response.
1345326bbc34SWesley Cheng *
1346326bbc34SWesley Cheng */
prepare_qmi_response(struct snd_usb_substream * subs,struct qmi_uaudio_stream_req_msg_v01 * req_msg,struct qmi_uaudio_stream_resp_msg_v01 * resp,int info_idx)1347326bbc34SWesley Cheng static int prepare_qmi_response(struct snd_usb_substream *subs,
1348326bbc34SWesley Cheng struct qmi_uaudio_stream_req_msg_v01 *req_msg,
1349326bbc34SWesley Cheng struct qmi_uaudio_stream_resp_msg_v01 *resp,
1350326bbc34SWesley Cheng int info_idx)
1351326bbc34SWesley Cheng {
1352326bbc34SWesley Cheng struct q6usb_offload *data;
1353326bbc34SWesley Cheng int pcm_dev_num;
1354326bbc34SWesley Cheng int card_num;
13555c7ef500SArnd Bergmann void *xfer_buf_cpu;
1356326bbc34SWesley Cheng int ret;
1357326bbc34SWesley Cheng
1358326bbc34SWesley Cheng pcm_dev_num = (req_msg->usb_token & QMI_STREAM_REQ_DEV_NUM_MASK) >> 8;
1359326bbc34SWesley Cheng card_num = (req_msg->usb_token & QMI_STREAM_REQ_CARD_NUM_MASK) >> 16;
1360326bbc34SWesley Cheng
1361326bbc34SWesley Cheng if (!uadev[card_num].ctrl_intf) {
1362326bbc34SWesley Cheng dev_err(&subs->dev->dev, "audio ctrl intf info not cached\n");
1363b272f425SHarshit Mogalapalli return -ENODEV;
1364326bbc34SWesley Cheng }
1365326bbc34SWesley Cheng
1366326bbc34SWesley Cheng ret = uaudio_populate_uac_desc(subs, resp);
1367326bbc34SWesley Cheng if (ret < 0)
1368b272f425SHarshit Mogalapalli return ret;
1369326bbc34SWesley Cheng
1370326bbc34SWesley Cheng resp->slot_id = subs->dev->slot_id;
1371326bbc34SWesley Cheng resp->slot_id_valid = 1;
1372326bbc34SWesley Cheng
1373326bbc34SWesley Cheng data = snd_soc_usb_find_priv_data(uaudio_qdev->auxdev->dev.parent);
1374b272f425SHarshit Mogalapalli if (!data) {
1375b272f425SHarshit Mogalapalli dev_err(&subs->dev->dev, "No private data found\n");
1376b272f425SHarshit Mogalapalli return -ENODEV;
1377b272f425SHarshit Mogalapalli }
1378326bbc34SWesley Cheng
1379326bbc34SWesley Cheng uaudio_qdev->data = data;
1380326bbc34SWesley Cheng
1381326bbc34SWesley Cheng resp->std_as_opr_intf_desc_valid = 1;
1382326bbc34SWesley Cheng ret = uaudio_endpoint_setup(subs, subs->data_endpoint, card_num,
1383326bbc34SWesley Cheng &resp->xhci_mem_info.tr_data,
1384326bbc34SWesley Cheng &resp->std_as_data_ep_desc);
1385326bbc34SWesley Cheng if (ret < 0)
1386b272f425SHarshit Mogalapalli return ret;
1387326bbc34SWesley Cheng
1388326bbc34SWesley Cheng resp->std_as_data_ep_desc_valid = 1;
1389326bbc34SWesley Cheng
1390326bbc34SWesley Cheng if (subs->sync_endpoint) {
1391326bbc34SWesley Cheng ret = uaudio_endpoint_setup(subs, subs->sync_endpoint, card_num,
1392326bbc34SWesley Cheng &resp->xhci_mem_info.tr_sync,
1393326bbc34SWesley Cheng &resp->std_as_sync_ep_desc);
1394326bbc34SWesley Cheng if (ret < 0)
1395326bbc34SWesley Cheng goto drop_data_ep;
1396326bbc34SWesley Cheng
1397326bbc34SWesley Cheng resp->std_as_sync_ep_desc_valid = 1;
1398326bbc34SWesley Cheng }
1399326bbc34SWesley Cheng
1400326bbc34SWesley Cheng resp->interrupter_num_valid = 1;
1401326bbc34SWesley Cheng resp->controller_num_valid = 0;
1402326bbc34SWesley Cheng ret = usb_get_controller_id(subs->dev);
1403326bbc34SWesley Cheng if (ret >= 0) {
1404326bbc34SWesley Cheng resp->controller_num = ret;
1405326bbc34SWesley Cheng resp->controller_num_valid = 1;
1406326bbc34SWesley Cheng }
1407326bbc34SWesley Cheng
1408326bbc34SWesley Cheng /* event ring */
1409326bbc34SWesley Cheng ret = uaudio_event_ring_setup(subs, card_num,
1410326bbc34SWesley Cheng &resp->xhci_mem_info.evt_ring);
1411326bbc34SWesley Cheng if (ret < 0)
1412326bbc34SWesley Cheng goto drop_sync_ep;
1413326bbc34SWesley Cheng
1414326bbc34SWesley Cheng uaudio_qdev->er_mapped = true;
1415326bbc34SWesley Cheng resp->interrupter_num = xhci_sideband_interrupter_id(uadev[card_num].sb);
1416326bbc34SWesley Cheng
1417326bbc34SWesley Cheng resp->speed_info = get_speed_info(subs->dev->speed);
1418326bbc34SWesley Cheng if (resp->speed_info == USB_QMI_DEVICE_SPEED_INVALID_V01) {
1419326bbc34SWesley Cheng ret = -ENODEV;
1420326bbc34SWesley Cheng goto free_sec_ring;
1421326bbc34SWesley Cheng }
1422326bbc34SWesley Cheng
1423326bbc34SWesley Cheng resp->speed_info_valid = 1;
1424326bbc34SWesley Cheng
14255c7ef500SArnd Bergmann ret = uaudio_transfer_buffer_setup(subs, &xfer_buf_cpu, req_msg->xfer_buff_size,
1426326bbc34SWesley Cheng &resp->xhci_mem_info.xfer_buff);
1427326bbc34SWesley Cheng if (ret < 0) {
1428326bbc34SWesley Cheng ret = -ENOMEM;
1429326bbc34SWesley Cheng goto free_sec_ring;
1430326bbc34SWesley Cheng }
1431326bbc34SWesley Cheng
1432326bbc34SWesley Cheng resp->xhci_mem_info_valid = 1;
1433326bbc34SWesley Cheng
1434326bbc34SWesley Cheng if (!atomic_read(&uadev[card_num].in_use)) {
1435326bbc34SWesley Cheng kref_init(&uadev[card_num].kref);
1436326bbc34SWesley Cheng init_waitqueue_head(&uadev[card_num].disconnect_wq);
1437326bbc34SWesley Cheng uadev[card_num].num_intf =
1438326bbc34SWesley Cheng subs->dev->config->desc.bNumInterfaces;
1439326bbc34SWesley Cheng uadev[card_num].info = kcalloc(uadev[card_num].num_intf,
1440326bbc34SWesley Cheng sizeof(struct intf_info),
1441326bbc34SWesley Cheng GFP_KERNEL);
1442326bbc34SWesley Cheng if (!uadev[card_num].info) {
1443326bbc34SWesley Cheng ret = -ENOMEM;
1444326bbc34SWesley Cheng goto unmap_er;
1445326bbc34SWesley Cheng }
1446326bbc34SWesley Cheng uadev[card_num].udev = subs->dev;
1447326bbc34SWesley Cheng atomic_set(&uadev[card_num].in_use, 1);
1448326bbc34SWesley Cheng } else {
1449326bbc34SWesley Cheng kref_get(&uadev[card_num].kref);
1450326bbc34SWesley Cheng }
1451326bbc34SWesley Cheng
1452326bbc34SWesley Cheng uadev[card_num].usb_core_id = resp->controller_num;
1453326bbc34SWesley Cheng
1454326bbc34SWesley Cheng /* cache intf specific info to use it for unmap and free xfer buf */
1455326bbc34SWesley Cheng uadev[card_num].info[info_idx].data_xfer_ring_va =
1456485ae085SArnd Bergmann IOVA_MASK(resp->xhci_mem_info.tr_data.iova);
1457326bbc34SWesley Cheng uadev[card_num].info[info_idx].data_xfer_ring_size = PAGE_SIZE;
1458326bbc34SWesley Cheng uadev[card_num].info[info_idx].sync_xfer_ring_va =
1459485ae085SArnd Bergmann IOVA_MASK(resp->xhci_mem_info.tr_sync.iova);
1460326bbc34SWesley Cheng uadev[card_num].info[info_idx].sync_xfer_ring_size = PAGE_SIZE;
1461485ae085SArnd Bergmann uadev[card_num].info[info_idx].xfer_buf_iova =
1462485ae085SArnd Bergmann IOVA_MASK(resp->xhci_mem_info.xfer_buff.iova);
1463485ae085SArnd Bergmann uadev[card_num].info[info_idx].xfer_buf_dma =
1464485ae085SArnd Bergmann resp->xhci_mem_info.xfer_buff.dma;
1465326bbc34SWesley Cheng uadev[card_num].info[info_idx].xfer_buf_size =
1466326bbc34SWesley Cheng resp->xhci_mem_info.xfer_buff.size;
1467326bbc34SWesley Cheng uadev[card_num].info[info_idx].data_ep_pipe = subs->data_endpoint ?
1468326bbc34SWesley Cheng subs->data_endpoint->pipe : 0;
1469326bbc34SWesley Cheng uadev[card_num].info[info_idx].sync_ep_pipe = subs->sync_endpoint ?
1470326bbc34SWesley Cheng subs->sync_endpoint->pipe : 0;
1471326bbc34SWesley Cheng uadev[card_num].info[info_idx].data_ep_idx = subs->data_endpoint ?
1472326bbc34SWesley Cheng subs->data_endpoint->ep_num : 0;
1473326bbc34SWesley Cheng uadev[card_num].info[info_idx].sync_ep_idx = subs->sync_endpoint ?
1474326bbc34SWesley Cheng subs->sync_endpoint->ep_num : 0;
1475485ae085SArnd Bergmann uadev[card_num].info[info_idx].xfer_buf_cpu = xfer_buf_cpu;
1476326bbc34SWesley Cheng uadev[card_num].info[info_idx].pcm_card_num = card_num;
1477326bbc34SWesley Cheng uadev[card_num].info[info_idx].pcm_dev_num = pcm_dev_num;
1478326bbc34SWesley Cheng uadev[card_num].info[info_idx].direction = subs->direction;
1479326bbc34SWesley Cheng uadev[card_num].info[info_idx].intf_num = subs->cur_audiofmt->iface;
1480326bbc34SWesley Cheng uadev[card_num].info[info_idx].in_use = true;
1481326bbc34SWesley Cheng
1482326bbc34SWesley Cheng set_bit(card_num, &uaudio_qdev->card_slot);
1483326bbc34SWesley Cheng
1484326bbc34SWesley Cheng return 0;
1485326bbc34SWesley Cheng
1486326bbc34SWesley Cheng unmap_er:
1487326bbc34SWesley Cheng uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, PAGE_SIZE, PAGE_SIZE);
1488326bbc34SWesley Cheng free_sec_ring:
1489326bbc34SWesley Cheng xhci_sideband_remove_interrupter(uadev[card_num].sb);
1490326bbc34SWesley Cheng drop_sync_ep:
1491326bbc34SWesley Cheng if (subs->sync_endpoint) {
1492326bbc34SWesley Cheng uaudio_iommu_unmap(MEM_XFER_RING,
1493485ae085SArnd Bergmann IOVA_MASK(resp->xhci_mem_info.tr_sync.iova),
1494326bbc34SWesley Cheng PAGE_SIZE, PAGE_SIZE);
1495326bbc34SWesley Cheng xhci_sideband_remove_endpoint(uadev[card_num].sb,
1496326bbc34SWesley Cheng usb_pipe_endpoint(subs->dev, subs->sync_endpoint->pipe));
1497326bbc34SWesley Cheng }
1498326bbc34SWesley Cheng drop_data_ep:
1499485ae085SArnd Bergmann uaudio_iommu_unmap(MEM_XFER_RING, IOVA_MASK(resp->xhci_mem_info.tr_data.iova),
1500326bbc34SWesley Cheng PAGE_SIZE, PAGE_SIZE);
1501326bbc34SWesley Cheng xhci_sideband_remove_endpoint(uadev[card_num].sb,
1502326bbc34SWesley Cheng usb_pipe_endpoint(subs->dev, subs->data_endpoint->pipe));
1503326bbc34SWesley Cheng
1504326bbc34SWesley Cheng return ret;
1505326bbc34SWesley Cheng }
1506326bbc34SWesley Cheng
1507326bbc34SWesley Cheng /**
1508326bbc34SWesley Cheng * handle_uaudio_stream_req() - handle stream enable/disable request
1509326bbc34SWesley Cheng * @handle: QMI client handle
1510326bbc34SWesley Cheng * @sq: qrtr socket
1511326bbc34SWesley Cheng * @txn: QMI transaction context
1512326bbc34SWesley Cheng * @decoded_msg: decoded QMI message
1513326bbc34SWesley Cheng *
1514326bbc34SWesley Cheng * Main handler for the QMI stream enable/disable requests. This executes the
1515326bbc34SWesley Cheng * corresponding enable/disable stream apis, respectively.
1516326bbc34SWesley Cheng *
1517326bbc34SWesley Cheng */
handle_uaudio_stream_req(struct qmi_handle * handle,struct sockaddr_qrtr * sq,struct qmi_txn * txn,const void * decoded_msg)1518326bbc34SWesley Cheng static void handle_uaudio_stream_req(struct qmi_handle *handle,
1519326bbc34SWesley Cheng struct sockaddr_qrtr *sq,
1520326bbc34SWesley Cheng struct qmi_txn *txn,
1521326bbc34SWesley Cheng const void *decoded_msg)
1522326bbc34SWesley Cheng {
1523326bbc34SWesley Cheng struct qmi_uaudio_stream_req_msg_v01 *req_msg;
1524326bbc34SWesley Cheng struct qmi_uaudio_stream_resp_msg_v01 resp = {{0}, 0};
1525326bbc34SWesley Cheng struct uaudio_qmi_svc *svc = uaudio_svc;
1526326bbc34SWesley Cheng struct snd_usb_audio *chip = NULL;
1527326bbc34SWesley Cheng struct snd_usb_substream *subs;
1528326bbc34SWesley Cheng struct usb_host_endpoint *ep;
1529326bbc34SWesley Cheng int datainterval = -EINVAL;
1530326bbc34SWesley Cheng int info_idx = -EINVAL;
1531326bbc34SWesley Cheng struct intf_info *info;
1532326bbc34SWesley Cheng u8 pcm_card_num;
1533326bbc34SWesley Cheng u8 pcm_dev_num;
1534326bbc34SWesley Cheng u8 direction;
1535326bbc34SWesley Cheng int ret = 0;
1536326bbc34SWesley Cheng
1537326bbc34SWesley Cheng if (!svc->client_connected) {
1538326bbc34SWesley Cheng svc->client_sq = *sq;
1539326bbc34SWesley Cheng svc->client_connected = true;
1540326bbc34SWesley Cheng }
1541326bbc34SWesley Cheng
1542326bbc34SWesley Cheng mutex_lock(&qdev_mutex);
1543326bbc34SWesley Cheng req_msg = (struct qmi_uaudio_stream_req_msg_v01 *)decoded_msg;
1544326bbc34SWesley Cheng if (!req_msg->audio_format_valid || !req_msg->bit_rate_valid ||
1545326bbc34SWesley Cheng !req_msg->number_of_ch_valid || !req_msg->xfer_buff_size_valid) {
1546326bbc34SWesley Cheng ret = -EINVAL;
1547326bbc34SWesley Cheng goto response;
1548326bbc34SWesley Cheng }
1549326bbc34SWesley Cheng
1550326bbc34SWesley Cheng if (!uaudio_qdev) {
1551326bbc34SWesley Cheng ret = -EINVAL;
1552326bbc34SWesley Cheng goto response;
1553326bbc34SWesley Cheng }
1554326bbc34SWesley Cheng
1555326bbc34SWesley Cheng direction = (req_msg->usb_token & QMI_STREAM_REQ_DIRECTION);
1556326bbc34SWesley Cheng pcm_dev_num = (req_msg->usb_token & QMI_STREAM_REQ_DEV_NUM_MASK) >> 8;
1557326bbc34SWesley Cheng pcm_card_num = (req_msg->usb_token & QMI_STREAM_REQ_CARD_NUM_MASK) >> 16;
1558326bbc34SWesley Cheng if (pcm_card_num >= SNDRV_CARDS) {
1559326bbc34SWesley Cheng ret = -EINVAL;
1560326bbc34SWesley Cheng goto response;
1561326bbc34SWesley Cheng }
1562326bbc34SWesley Cheng
1563326bbc34SWesley Cheng if (req_msg->audio_format > USB_QMI_PCM_FORMAT_U32_BE) {
1564326bbc34SWesley Cheng ret = -EINVAL;
1565326bbc34SWesley Cheng goto response;
1566326bbc34SWesley Cheng }
1567326bbc34SWesley Cheng
1568326bbc34SWesley Cheng subs = find_substream(pcm_card_num, pcm_dev_num, direction);
1569326bbc34SWesley Cheng chip = uadev[pcm_card_num].chip;
1570326bbc34SWesley Cheng if (!subs || !chip || atomic_read(&chip->shutdown)) {
1571326bbc34SWesley Cheng ret = -ENODEV;
1572326bbc34SWesley Cheng goto response;
1573326bbc34SWesley Cheng }
1574326bbc34SWesley Cheng
1575326bbc34SWesley Cheng info_idx = info_idx_from_ifnum(pcm_card_num, subs->cur_audiofmt ?
1576326bbc34SWesley Cheng subs->cur_audiofmt->iface : -1, req_msg->enable);
1577326bbc34SWesley Cheng if (atomic_read(&chip->shutdown) || !subs->stream || !subs->stream->pcm ||
1578326bbc34SWesley Cheng !subs->stream->chip) {
1579326bbc34SWesley Cheng ret = -ENODEV;
1580326bbc34SWesley Cheng goto response;
1581326bbc34SWesley Cheng }
1582326bbc34SWesley Cheng
15836a348e92SWesley Cheng mutex_lock(&chip->mutex);
1584326bbc34SWesley Cheng if (req_msg->enable) {
15856a348e92SWesley Cheng if (info_idx < 0 || chip->system_suspend || subs->opened) {
1586326bbc34SWesley Cheng ret = -EBUSY;
15876a348e92SWesley Cheng mutex_unlock(&chip->mutex);
15886a348e92SWesley Cheng
1589326bbc34SWesley Cheng goto response;
1590326bbc34SWesley Cheng }
15916a348e92SWesley Cheng subs->opened = 1;
1592326bbc34SWesley Cheng }
15936a348e92SWesley Cheng mutex_unlock(&chip->mutex);
1594326bbc34SWesley Cheng
1595326bbc34SWesley Cheng if (req_msg->service_interval_valid) {
1596326bbc34SWesley Cheng ret = get_data_interval_from_si(subs,
1597326bbc34SWesley Cheng req_msg->service_interval);
1598326bbc34SWesley Cheng if (ret == -EINVAL)
1599326bbc34SWesley Cheng goto response;
1600326bbc34SWesley Cheng
1601326bbc34SWesley Cheng datainterval = ret;
1602326bbc34SWesley Cheng }
1603326bbc34SWesley Cheng
1604326bbc34SWesley Cheng uadev[pcm_card_num].ctrl_intf = chip->ctrl_intf;
1605326bbc34SWesley Cheng
1606326bbc34SWesley Cheng if (req_msg->enable) {
1607326bbc34SWesley Cheng ret = enable_audio_stream(subs,
1608326bbc34SWesley Cheng map_pcm_format(req_msg->audio_format),
1609326bbc34SWesley Cheng req_msg->number_of_ch, req_msg->bit_rate,
1610326bbc34SWesley Cheng datainterval);
1611326bbc34SWesley Cheng
1612326bbc34SWesley Cheng if (!ret)
1613326bbc34SWesley Cheng ret = prepare_qmi_response(subs, req_msg, &resp,
1614326bbc34SWesley Cheng info_idx);
16156a348e92SWesley Cheng if (ret < 0) {
16166a348e92SWesley Cheng mutex_lock(&chip->mutex);
16176a348e92SWesley Cheng subs->opened = 0;
16186a348e92SWesley Cheng mutex_unlock(&chip->mutex);
16196a348e92SWesley Cheng }
1620326bbc34SWesley Cheng } else {
1621326bbc34SWesley Cheng info = &uadev[pcm_card_num].info[info_idx];
1622326bbc34SWesley Cheng if (info->data_ep_pipe) {
1623326bbc34SWesley Cheng ep = usb_pipe_endpoint(uadev[pcm_card_num].udev,
1624326bbc34SWesley Cheng info->data_ep_pipe);
1625326bbc34SWesley Cheng if (ep) {
1626326bbc34SWesley Cheng xhci_sideband_stop_endpoint(uadev[pcm_card_num].sb,
1627326bbc34SWesley Cheng ep);
1628326bbc34SWesley Cheng xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb,
1629326bbc34SWesley Cheng ep);
1630326bbc34SWesley Cheng }
1631326bbc34SWesley Cheng
1632326bbc34SWesley Cheng info->data_ep_pipe = 0;
1633326bbc34SWesley Cheng }
1634326bbc34SWesley Cheng
1635326bbc34SWesley Cheng if (info->sync_ep_pipe) {
1636326bbc34SWesley Cheng ep = usb_pipe_endpoint(uadev[pcm_card_num].udev,
1637326bbc34SWesley Cheng info->sync_ep_pipe);
1638326bbc34SWesley Cheng if (ep) {
1639326bbc34SWesley Cheng xhci_sideband_stop_endpoint(uadev[pcm_card_num].sb,
1640326bbc34SWesley Cheng ep);
1641326bbc34SWesley Cheng xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb,
1642326bbc34SWesley Cheng ep);
1643326bbc34SWesley Cheng }
1644326bbc34SWesley Cheng
1645326bbc34SWesley Cheng info->sync_ep_pipe = 0;
1646326bbc34SWesley Cheng }
1647326bbc34SWesley Cheng
1648326bbc34SWesley Cheng disable_audio_stream(subs);
16496a348e92SWesley Cheng mutex_lock(&chip->mutex);
16506a348e92SWesley Cheng subs->opened = 0;
16516a348e92SWesley Cheng mutex_unlock(&chip->mutex);
1652326bbc34SWesley Cheng }
1653326bbc34SWesley Cheng
1654326bbc34SWesley Cheng response:
1655326bbc34SWesley Cheng if (!req_msg->enable && ret != -EINVAL && ret != -ENODEV) {
1656326bbc34SWesley Cheng mutex_lock(&chip->mutex);
1657326bbc34SWesley Cheng if (info_idx >= 0) {
1658326bbc34SWesley Cheng info = &uadev[pcm_card_num].info[info_idx];
1659326bbc34SWesley Cheng uaudio_dev_intf_cleanup(uadev[pcm_card_num].udev,
1660326bbc34SWesley Cheng info);
1661326bbc34SWesley Cheng }
1662326bbc34SWesley Cheng if (atomic_read(&uadev[pcm_card_num].in_use))
1663326bbc34SWesley Cheng kref_put(&uadev[pcm_card_num].kref,
1664326bbc34SWesley Cheng uaudio_dev_release);
1665326bbc34SWesley Cheng mutex_unlock(&chip->mutex);
1666326bbc34SWesley Cheng }
1667326bbc34SWesley Cheng mutex_unlock(&qdev_mutex);
1668326bbc34SWesley Cheng
1669326bbc34SWesley Cheng resp.usb_token = req_msg->usb_token;
1670326bbc34SWesley Cheng resp.usb_token_valid = 1;
1671326bbc34SWesley Cheng resp.internal_status = ret;
1672326bbc34SWesley Cheng resp.internal_status_valid = 1;
1673326bbc34SWesley Cheng resp.status = ret ? USB_QMI_STREAM_REQ_FAILURE_V01 : ret;
1674326bbc34SWesley Cheng resp.status_valid = 1;
1675326bbc34SWesley Cheng ret = qmi_send_response(svc->uaudio_svc_hdl, sq, txn,
1676326bbc34SWesley Cheng QMI_UAUDIO_STREAM_RESP_V01,
1677326bbc34SWesley Cheng QMI_UAUDIO_STREAM_RESP_MSG_V01_MAX_MSG_LEN,
1678326bbc34SWesley Cheng qmi_uaudio_stream_resp_msg_v01_ei, &resp);
1679326bbc34SWesley Cheng }
1680326bbc34SWesley Cheng
1681326bbc34SWesley Cheng static struct qmi_msg_handler uaudio_stream_req_handlers = {
1682326bbc34SWesley Cheng .type = QMI_REQUEST,
1683326bbc34SWesley Cheng .msg_id = QMI_UAUDIO_STREAM_REQ_V01,
1684326bbc34SWesley Cheng .ei = qmi_uaudio_stream_req_msg_v01_ei,
1685326bbc34SWesley Cheng .decoded_size = QMI_UAUDIO_STREAM_REQ_MSG_V01_MAX_MSG_LEN,
1686326bbc34SWesley Cheng .fn = handle_uaudio_stream_req,
1687326bbc34SWesley Cheng };
1688326bbc34SWesley Cheng
1689326bbc34SWesley Cheng /**
1690326bbc34SWesley Cheng * qc_usb_audio_offload_init_qmi_dev() - initializes qmi dev
1691326bbc34SWesley Cheng *
1692326bbc34SWesley Cheng * Initializes the USB qdev, which is used to carry information pertaining to
1693326bbc34SWesley Cheng * the offloading resources. This device is freed only when there are no longer
1694326bbc34SWesley Cheng * any offloading candidates. (i.e, when all audio devices are disconnected)
1695326bbc34SWesley Cheng *
1696326bbc34SWesley Cheng */
qc_usb_audio_offload_init_qmi_dev(void)1697326bbc34SWesley Cheng static int qc_usb_audio_offload_init_qmi_dev(void)
1698326bbc34SWesley Cheng {
1699326bbc34SWesley Cheng uaudio_qdev = kzalloc(sizeof(*uaudio_qdev), GFP_KERNEL);
1700326bbc34SWesley Cheng if (!uaudio_qdev)
1701326bbc34SWesley Cheng return -ENOMEM;
1702326bbc34SWesley Cheng
1703326bbc34SWesley Cheng /* initialize xfer ring and xfer buf iova list */
1704326bbc34SWesley Cheng INIT_LIST_HEAD(&uaudio_qdev->xfer_ring_list);
1705326bbc34SWesley Cheng uaudio_qdev->curr_xfer_ring_iova = IOVA_XFER_RING_BASE;
1706326bbc34SWesley Cheng uaudio_qdev->xfer_ring_iova_size =
1707326bbc34SWesley Cheng IOVA_XFER_RING_MAX - IOVA_XFER_RING_BASE;
1708326bbc34SWesley Cheng
1709326bbc34SWesley Cheng INIT_LIST_HEAD(&uaudio_qdev->xfer_buf_list);
1710326bbc34SWesley Cheng uaudio_qdev->curr_xfer_buf_iova = IOVA_XFER_BUF_BASE;
1711326bbc34SWesley Cheng uaudio_qdev->xfer_buf_iova_size =
1712326bbc34SWesley Cheng IOVA_XFER_BUF_MAX - IOVA_XFER_BUF_BASE;
1713326bbc34SWesley Cheng
1714326bbc34SWesley Cheng return 0;
1715326bbc34SWesley Cheng }
1716326bbc34SWesley Cheng
1717326bbc34SWesley Cheng /* Populates ppcm_idx array with supported PCM indexes */
qc_usb_audio_offload_fill_avail_pcms(struct snd_usb_audio * chip,struct snd_soc_usb_device * sdev)1718326bbc34SWesley Cheng static int qc_usb_audio_offload_fill_avail_pcms(struct snd_usb_audio *chip,
1719326bbc34SWesley Cheng struct snd_soc_usb_device *sdev)
1720326bbc34SWesley Cheng {
1721326bbc34SWesley Cheng struct snd_usb_stream *as;
1722326bbc34SWesley Cheng struct snd_usb_substream *subs;
1723326bbc34SWesley Cheng int idx = 0;
1724326bbc34SWesley Cheng
1725326bbc34SWesley Cheng list_for_each_entry(as, &chip->pcm_list, list) {
1726326bbc34SWesley Cheng subs = &as->substream[SNDRV_PCM_STREAM_PLAYBACK];
1727326bbc34SWesley Cheng if (subs->ep_num) {
1728326bbc34SWesley Cheng sdev->ppcm_idx[idx] = as->pcm->device;
1729326bbc34SWesley Cheng idx++;
1730326bbc34SWesley Cheng }
1731326bbc34SWesley Cheng /*
1732326bbc34SWesley Cheng * Break if the current index exceeds the number of possible
1733326bbc34SWesley Cheng * playback streams counted from the UAC descriptors.
1734326bbc34SWesley Cheng */
1735326bbc34SWesley Cheng if (idx >= sdev->num_playback)
1736326bbc34SWesley Cheng break;
1737326bbc34SWesley Cheng }
1738326bbc34SWesley Cheng
1739326bbc34SWesley Cheng return -1;
1740326bbc34SWesley Cheng }
1741326bbc34SWesley Cheng
1742326bbc34SWesley Cheng /**
1743326bbc34SWesley Cheng * qc_usb_audio_offload_probe() - platform op connect handler
1744326bbc34SWesley Cheng * @chip: USB SND device
1745326bbc34SWesley Cheng *
1746326bbc34SWesley Cheng * Platform connect handler when a USB SND device is detected. Will
1747326bbc34SWesley Cheng * notify SOC USB about the connection to enable the USB ASoC backend
1748326bbc34SWesley Cheng * and populate internal USB chip array.
1749326bbc34SWesley Cheng *
1750326bbc34SWesley Cheng */
qc_usb_audio_offload_probe(struct snd_usb_audio * chip)1751326bbc34SWesley Cheng static void qc_usb_audio_offload_probe(struct snd_usb_audio *chip)
1752326bbc34SWesley Cheng {
1753326bbc34SWesley Cheng struct usb_interface *intf = chip->intf[chip->num_interfaces - 1];
1754326bbc34SWesley Cheng struct usb_interface_descriptor *altsd;
1755326bbc34SWesley Cheng struct usb_host_interface *alts;
1756326bbc34SWesley Cheng struct snd_soc_usb_device *sdev;
1757326bbc34SWesley Cheng struct xhci_sideband *sb;
1758326bbc34SWesley Cheng
1759326bbc34SWesley Cheng /*
1760326bbc34SWesley Cheng * If there is no priv_data, or no playback paths, the connected
1761326bbc34SWesley Cheng * device doesn't support offloading. Avoid populating entries for
1762326bbc34SWesley Cheng * this device.
1763326bbc34SWesley Cheng */
1764326bbc34SWesley Cheng if (!snd_soc_usb_find_priv_data(uaudio_qdev->auxdev->dev.parent) ||
1765326bbc34SWesley Cheng !usb_qmi_get_pcm_num(chip, 0))
1766326bbc34SWesley Cheng return;
1767326bbc34SWesley Cheng
1768326bbc34SWesley Cheng mutex_lock(&qdev_mutex);
1769326bbc34SWesley Cheng mutex_lock(&chip->mutex);
1770326bbc34SWesley Cheng if (!uadev[chip->card->number].chip) {
1771326bbc34SWesley Cheng sdev = kzalloc(sizeof(*sdev), GFP_KERNEL);
1772326bbc34SWesley Cheng if (!sdev)
1773326bbc34SWesley Cheng goto exit;
1774326bbc34SWesley Cheng
1775326bbc34SWesley Cheng sb = xhci_sideband_register(intf, XHCI_SIDEBAND_VENDOR,
1776326bbc34SWesley Cheng uaudio_sideband_notifier);
1777326bbc34SWesley Cheng if (!sb)
1778326bbc34SWesley Cheng goto free_sdev;
1779326bbc34SWesley Cheng } else {
1780326bbc34SWesley Cheng sb = uadev[chip->card->number].sb;
1781326bbc34SWesley Cheng sdev = uadev[chip->card->number].sdev;
1782326bbc34SWesley Cheng }
1783326bbc34SWesley Cheng
1784326bbc34SWesley Cheng uadev[chip->card->number].sb = sb;
1785326bbc34SWesley Cheng uadev[chip->card->number].chip = chip;
1786326bbc34SWesley Cheng uadev[chip->card->number].sdev = sdev;
1787326bbc34SWesley Cheng
1788326bbc34SWesley Cheng alts = &intf->altsetting[0];
1789326bbc34SWesley Cheng altsd = get_iface_desc(alts);
1790326bbc34SWesley Cheng
1791326bbc34SWesley Cheng /* Wait until all PCM devices are populated before notifying soc-usb */
1792326bbc34SWesley Cheng if (altsd->bInterfaceNumber == chip->last_iface) {
1793326bbc34SWesley Cheng sdev->num_playback = usb_qmi_get_pcm_num(chip, 0);
1794326bbc34SWesley Cheng
1795326bbc34SWesley Cheng /*
1796326bbc34SWesley Cheng * Allocate playback pcm index array based on number of possible
1797326bbc34SWesley Cheng * playback paths within the UAC descriptors.
1798326bbc34SWesley Cheng */
1799326bbc34SWesley Cheng sdev->ppcm_idx = kcalloc(sdev->num_playback, sizeof(unsigned int),
1800326bbc34SWesley Cheng GFP_KERNEL);
1801326bbc34SWesley Cheng if (!sdev->ppcm_idx)
1802326bbc34SWesley Cheng goto unreg_xhci;
1803326bbc34SWesley Cheng
1804326bbc34SWesley Cheng qc_usb_audio_offload_fill_avail_pcms(chip, sdev);
1805326bbc34SWesley Cheng sdev->card_idx = chip->card->number;
1806326bbc34SWesley Cheng sdev->chip_idx = chip->index;
1807326bbc34SWesley Cheng
1808a67656f0SWesley Cheng snd_usb_offload_create_ctl(chip, uaudio_qdev->auxdev->dev.parent);
1809326bbc34SWesley Cheng snd_soc_usb_connect(uaudio_qdev->auxdev->dev.parent, sdev);
1810326bbc34SWesley Cheng }
1811326bbc34SWesley Cheng
1812326bbc34SWesley Cheng mutex_unlock(&chip->mutex);
1813326bbc34SWesley Cheng mutex_unlock(&qdev_mutex);
1814326bbc34SWesley Cheng
1815326bbc34SWesley Cheng return;
1816326bbc34SWesley Cheng
1817326bbc34SWesley Cheng unreg_xhci:
1818326bbc34SWesley Cheng xhci_sideband_unregister(sb);
1819326bbc34SWesley Cheng uadev[chip->card->number].sb = NULL;
1820326bbc34SWesley Cheng free_sdev:
1821326bbc34SWesley Cheng kfree(sdev);
1822326bbc34SWesley Cheng uadev[chip->card->number].sdev = NULL;
1823326bbc34SWesley Cheng uadev[chip->card->number].chip = NULL;
1824326bbc34SWesley Cheng exit:
1825326bbc34SWesley Cheng mutex_unlock(&chip->mutex);
1826326bbc34SWesley Cheng mutex_unlock(&qdev_mutex);
1827326bbc34SWesley Cheng }
1828326bbc34SWesley Cheng
1829326bbc34SWesley Cheng /**
1830326bbc34SWesley Cheng * qc_usb_audio_cleanup_qmi_dev() - release qmi device
1831326bbc34SWesley Cheng *
1832326bbc34SWesley Cheng * Frees the USB qdev. Only occurs when there are no longer any potential
1833326bbc34SWesley Cheng * devices that can utilize USB audio offloading.
1834326bbc34SWesley Cheng *
1835326bbc34SWesley Cheng */
qc_usb_audio_cleanup_qmi_dev(void)1836326bbc34SWesley Cheng static void qc_usb_audio_cleanup_qmi_dev(void)
1837326bbc34SWesley Cheng {
1838326bbc34SWesley Cheng kfree(uaudio_qdev);
1839326bbc34SWesley Cheng uaudio_qdev = NULL;
1840326bbc34SWesley Cheng }
1841326bbc34SWesley Cheng
1842326bbc34SWesley Cheng /**
1843326bbc34SWesley Cheng * qc_usb_audio_offload_disconnect() - platform op disconnect handler
1844326bbc34SWesley Cheng * @chip: USB SND device
1845326bbc34SWesley Cheng *
1846326bbc34SWesley Cheng * Platform disconnect handler. Will ensure that any pending stream is
1847326bbc34SWesley Cheng * halted by issuing a QMI disconnect indication packet to the adsp.
1848326bbc34SWesley Cheng *
1849326bbc34SWesley Cheng */
qc_usb_audio_offload_disconnect(struct snd_usb_audio * chip)1850326bbc34SWesley Cheng static void qc_usb_audio_offload_disconnect(struct snd_usb_audio *chip)
1851326bbc34SWesley Cheng {
1852326bbc34SWesley Cheng struct uaudio_dev *dev;
1853326bbc34SWesley Cheng int card_num;
1854326bbc34SWesley Cheng
1855326bbc34SWesley Cheng if (!chip)
1856326bbc34SWesley Cheng return;
1857326bbc34SWesley Cheng
1858326bbc34SWesley Cheng card_num = chip->card->number;
1859326bbc34SWesley Cheng if (card_num >= SNDRV_CARDS)
1860326bbc34SWesley Cheng return;
1861326bbc34SWesley Cheng
1862326bbc34SWesley Cheng mutex_lock(&qdev_mutex);
1863326bbc34SWesley Cheng mutex_lock(&chip->mutex);
1864326bbc34SWesley Cheng dev = &uadev[card_num];
1865326bbc34SWesley Cheng
1866326bbc34SWesley Cheng /* Device has already been cleaned up, or never populated */
1867326bbc34SWesley Cheng if (!dev->chip) {
1868326bbc34SWesley Cheng mutex_unlock(&qdev_mutex);
1869326bbc34SWesley Cheng mutex_unlock(&chip->mutex);
1870326bbc34SWesley Cheng return;
1871326bbc34SWesley Cheng }
1872326bbc34SWesley Cheng
1873326bbc34SWesley Cheng /* cleaned up already */
1874326bbc34SWesley Cheng if (!dev->udev)
1875326bbc34SWesley Cheng goto done;
1876326bbc34SWesley Cheng
1877326bbc34SWesley Cheng uaudio_send_disconnect_ind(chip);
1878326bbc34SWesley Cheng uaudio_dev_cleanup(dev);
1879326bbc34SWesley Cheng done:
1880326bbc34SWesley Cheng /*
1881326bbc34SWesley Cheng * If num_interfaces == 1, the last USB SND interface is being removed.
1882326bbc34SWesley Cheng * This is to accommodate for devices w/ multiple UAC functions.
1883326bbc34SWesley Cheng */
1884326bbc34SWesley Cheng if (chip->num_interfaces == 1) {
1885326bbc34SWesley Cheng snd_soc_usb_disconnect(uaudio_qdev->auxdev->dev.parent, dev->sdev);
1886326bbc34SWesley Cheng xhci_sideband_unregister(dev->sb);
1887326bbc34SWesley Cheng dev->chip = NULL;
1888326bbc34SWesley Cheng kfree(dev->sdev->ppcm_idx);
1889326bbc34SWesley Cheng kfree(dev->sdev);
1890326bbc34SWesley Cheng dev->sdev = NULL;
1891326bbc34SWesley Cheng }
1892326bbc34SWesley Cheng mutex_unlock(&chip->mutex);
1893326bbc34SWesley Cheng
1894326bbc34SWesley Cheng mutex_unlock(&qdev_mutex);
1895326bbc34SWesley Cheng }
1896326bbc34SWesley Cheng
1897326bbc34SWesley Cheng /**
1898326bbc34SWesley Cheng * qc_usb_audio_offload_suspend() - USB offload PM suspend handler
1899326bbc34SWesley Cheng * @intf: USB interface
1900326bbc34SWesley Cheng * @message: suspend type
1901326bbc34SWesley Cheng *
1902326bbc34SWesley Cheng * PM suspend handler to ensure that the USB offloading driver is able to stop
1903326bbc34SWesley Cheng * any pending traffic, so that the bus can be suspended.
1904326bbc34SWesley Cheng *
1905326bbc34SWesley Cheng */
qc_usb_audio_offload_suspend(struct usb_interface * intf,pm_message_t message)1906326bbc34SWesley Cheng static void qc_usb_audio_offload_suspend(struct usb_interface *intf,
1907326bbc34SWesley Cheng pm_message_t message)
1908326bbc34SWesley Cheng {
1909326bbc34SWesley Cheng struct snd_usb_audio *chip = usb_get_intfdata(intf);
1910326bbc34SWesley Cheng int card_num;
1911326bbc34SWesley Cheng
1912326bbc34SWesley Cheng if (!chip)
1913326bbc34SWesley Cheng return;
1914326bbc34SWesley Cheng
1915326bbc34SWesley Cheng card_num = chip->card->number;
1916326bbc34SWesley Cheng if (card_num >= SNDRV_CARDS)
1917326bbc34SWesley Cheng return;
1918326bbc34SWesley Cheng
1919326bbc34SWesley Cheng mutex_lock(&qdev_mutex);
1920326bbc34SWesley Cheng mutex_lock(&chip->mutex);
1921326bbc34SWesley Cheng
1922326bbc34SWesley Cheng uaudio_send_disconnect_ind(chip);
1923326bbc34SWesley Cheng
1924326bbc34SWesley Cheng mutex_unlock(&qdev_mutex);
1925326bbc34SWesley Cheng mutex_unlock(&chip->mutex);
1926326bbc34SWesley Cheng }
1927326bbc34SWesley Cheng
1928326bbc34SWesley Cheng static struct snd_usb_platform_ops offload_ops = {
1929326bbc34SWesley Cheng .connect_cb = qc_usb_audio_offload_probe,
1930326bbc34SWesley Cheng .disconnect_cb = qc_usb_audio_offload_disconnect,
1931326bbc34SWesley Cheng .suspend_cb = qc_usb_audio_offload_suspend,
1932326bbc34SWesley Cheng };
1933326bbc34SWesley Cheng
qc_usb_audio_probe(struct auxiliary_device * auxdev,const struct auxiliary_device_id * id)1934326bbc34SWesley Cheng static int qc_usb_audio_probe(struct auxiliary_device *auxdev,
1935326bbc34SWesley Cheng const struct auxiliary_device_id *id)
1936326bbc34SWesley Cheng
1937326bbc34SWesley Cheng {
1938326bbc34SWesley Cheng struct uaudio_qmi_svc *svc;
1939326bbc34SWesley Cheng int ret;
1940326bbc34SWesley Cheng
1941326bbc34SWesley Cheng svc = kzalloc(sizeof(*svc), GFP_KERNEL);
1942326bbc34SWesley Cheng if (!svc)
1943326bbc34SWesley Cheng return -ENOMEM;
1944326bbc34SWesley Cheng
1945326bbc34SWesley Cheng svc->uaudio_svc_hdl = kzalloc(sizeof(*svc->uaudio_svc_hdl), GFP_KERNEL);
1946326bbc34SWesley Cheng if (!svc->uaudio_svc_hdl) {
1947326bbc34SWesley Cheng ret = -ENOMEM;
1948326bbc34SWesley Cheng goto free_svc;
1949326bbc34SWesley Cheng }
1950326bbc34SWesley Cheng
1951326bbc34SWesley Cheng ret = qmi_handle_init(svc->uaudio_svc_hdl,
1952326bbc34SWesley Cheng QMI_UAUDIO_STREAM_REQ_MSG_V01_MAX_MSG_LEN,
1953326bbc34SWesley Cheng &uaudio_svc_ops_options,
1954326bbc34SWesley Cheng &uaudio_stream_req_handlers);
1955326bbc34SWesley Cheng ret = qmi_add_server(svc->uaudio_svc_hdl, UAUDIO_STREAM_SERVICE_ID_V01,
1956326bbc34SWesley Cheng UAUDIO_STREAM_SERVICE_VERS_V01, 0);
1957326bbc34SWesley Cheng
1958326bbc34SWesley Cheng uaudio_svc = svc;
1959326bbc34SWesley Cheng
1960326bbc34SWesley Cheng qc_usb_audio_offload_init_qmi_dev();
1961326bbc34SWesley Cheng uaudio_qdev->auxdev = auxdev;
1962326bbc34SWesley Cheng
1963326bbc34SWesley Cheng ret = snd_usb_register_platform_ops(&offload_ops);
1964326bbc34SWesley Cheng if (ret < 0)
1965326bbc34SWesley Cheng goto release_qmi;
1966326bbc34SWesley Cheng
19679bf4294dSWesley Cheng snd_usb_rediscover_devices();
19689bf4294dSWesley Cheng
1969326bbc34SWesley Cheng return 0;
1970326bbc34SWesley Cheng
1971326bbc34SWesley Cheng release_qmi:
1972e7144a2bSChristophe JAILLET qc_usb_audio_cleanup_qmi_dev();
1973326bbc34SWesley Cheng qmi_handle_release(svc->uaudio_svc_hdl);
1974326bbc34SWesley Cheng free_svc:
1975326bbc34SWesley Cheng kfree(svc);
1976326bbc34SWesley Cheng
1977326bbc34SWesley Cheng return ret;
1978326bbc34SWesley Cheng }
1979326bbc34SWesley Cheng
qc_usb_audio_remove(struct auxiliary_device * auxdev)1980326bbc34SWesley Cheng static void qc_usb_audio_remove(struct auxiliary_device *auxdev)
1981326bbc34SWesley Cheng {
1982326bbc34SWesley Cheng struct uaudio_qmi_svc *svc = uaudio_svc;
1983326bbc34SWesley Cheng int idx;
1984326bbc34SWesley Cheng
1985326bbc34SWesley Cheng /*
1986326bbc34SWesley Cheng * Remove all connected devices after unregistering ops, to ensure
1987326bbc34SWesley Cheng * that no further connect events will occur. The disconnect routine
1988326bbc34SWesley Cheng * will issue the QMI disconnect indication, which results in the
1989326bbc34SWesley Cheng * external DSP to stop issuing transfers.
1990326bbc34SWesley Cheng */
1991326bbc34SWesley Cheng snd_usb_unregister_platform_ops();
1992326bbc34SWesley Cheng for (idx = 0; idx < SNDRV_CARDS; idx++)
1993326bbc34SWesley Cheng qc_usb_audio_offload_disconnect(uadev[idx].chip);
1994326bbc34SWesley Cheng
1995326bbc34SWesley Cheng qc_usb_audio_cleanup_qmi_dev();
1996326bbc34SWesley Cheng
1997326bbc34SWesley Cheng qmi_handle_release(svc->uaudio_svc_hdl);
1998326bbc34SWesley Cheng kfree(svc);
1999326bbc34SWesley Cheng uaudio_svc = NULL;
2000326bbc34SWesley Cheng }
2001326bbc34SWesley Cheng
2002326bbc34SWesley Cheng static const struct auxiliary_device_id qc_usb_audio_table[] = {
2003326bbc34SWesley Cheng { .name = "q6usb.qc-usb-audio-offload" },
2004326bbc34SWesley Cheng {},
2005326bbc34SWesley Cheng };
2006326bbc34SWesley Cheng MODULE_DEVICE_TABLE(auxiliary, qc_usb_audio_table);
2007326bbc34SWesley Cheng
2008326bbc34SWesley Cheng static struct auxiliary_driver qc_usb_audio_offload_drv = {
2009326bbc34SWesley Cheng .name = "qc-usb-audio-offload",
2010326bbc34SWesley Cheng .id_table = qc_usb_audio_table,
2011326bbc34SWesley Cheng .probe = qc_usb_audio_probe,
2012326bbc34SWesley Cheng .remove = qc_usb_audio_remove,
2013326bbc34SWesley Cheng };
2014326bbc34SWesley Cheng module_auxiliary_driver(qc_usb_audio_offload_drv);
2015326bbc34SWesley Cheng
2016326bbc34SWesley Cheng MODULE_DESCRIPTION("QC USB Audio Offloading");
2017326bbc34SWesley Cheng MODULE_LICENSE("GPL");
2018