xref: /linux/sound/usb/qcom/qc_audio_offload.c (revision 9c2f970518c900821acdac47bbd681b99a325e3d)
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(&params);
942326bbc34SWesley Cheng 
943326bbc34SWesley Cheng 	m = hw_param_mask(&params, SNDRV_PCM_HW_PARAM_FORMAT);
944326bbc34SWesley Cheng 	snd_mask_leave(m, pcm_format);
945326bbc34SWesley Cheng 
946326bbc34SWesley Cheng 	i = hw_param_interval(&params, 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(&params, 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, &params);
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