1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  *  hdac-ext-stream.c - HD-audio extended stream operations.
4  *
5  *  Copyright (C) 2015 Intel Corp
6  *  Author: Jeeja KP <jeeja.kp@intel.com>
7  *  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8  *
9  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
10  */
11 
12 #include <linux/delay.h>
13 #include <linux/pci.h>
14 #include <linux/pci_ids.h>
15 #include <linux/slab.h>
16 #include <sound/pcm.h>
17 #include <sound/hda_register.h>
18 #include <sound/hdaudio_ext.h>
19 #include <sound/compress_driver.h>
20 
21 /**
22  * snd_hdac_ext_host_stream_setup - Setup a HOST stream.
23  * @hext_stream: HDAudio stream to set up.
24  * @code_loading: Whether the stream is for PCM or code-loading.
25  *
26  * Return: Zero on success or negative error code.
27  */
snd_hdac_ext_host_stream_setup(struct hdac_ext_stream * hext_stream,bool code_loading)28 int snd_hdac_ext_host_stream_setup(struct hdac_ext_stream *hext_stream, bool code_loading)
29 {
30 	return hext_stream->host_setup(hdac_stream(hext_stream), code_loading);
31 }
32 EXPORT_SYMBOL_GPL(snd_hdac_ext_host_stream_setup);
33 
34 /**
35  * snd_hdac_apl_host_stream_setup - Setup a HOST stream following procedure
36  *                                  recommended for ApolloLake devices.
37  * @hstream: HDAudio stream to set up.
38  * @code_loading: Whether the stream is for PCM or code-loading.
39  *
40  * Return: Zero on success or negative error code.
41  */
snd_hdac_apl_host_stream_setup(struct hdac_stream * hstream,bool code_loading)42 static int snd_hdac_apl_host_stream_setup(struct hdac_stream *hstream, bool code_loading)
43 {
44 	struct hdac_ext_stream *hext_stream = stream_to_hdac_ext_stream(hstream);
45 	int ret;
46 
47 	snd_hdac_ext_stream_decouple(hstream->bus, hext_stream, false);
48 	ret = snd_hdac_stream_setup(hstream, code_loading);
49 	snd_hdac_ext_stream_decouple(hstream->bus, hext_stream, true);
50 
51 	return ret;
52 }
53 
54 /**
55  * snd_hdac_ext_stream_init - initialize each stream (aka device)
56  * @bus: HD-audio core bus
57  * @hext_stream: HD-audio ext core stream object to initialize
58  * @idx: stream index number
59  * @direction: stream direction (SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE)
60  * @tag: the tag id to assign
61  *
62  * initialize the stream, if ppcap is enabled then init those and then
63  * invoke hdac stream initialization routine
64  */
snd_hdac_ext_stream_init(struct hdac_bus * bus,struct hdac_ext_stream * hext_stream,int idx,int direction,int tag)65 static void snd_hdac_ext_stream_init(struct hdac_bus *bus,
66 				     struct hdac_ext_stream *hext_stream,
67 				     int idx, int direction, int tag)
68 {
69 	if (bus->ppcap) {
70 		hext_stream->pphc_addr = bus->ppcap + AZX_PPHC_BASE +
71 				AZX_PPHC_INTERVAL * idx;
72 
73 		hext_stream->pplc_addr = bus->ppcap + AZX_PPLC_BASE +
74 				AZX_PPLC_MULTI * bus->num_streams +
75 				AZX_PPLC_INTERVAL * idx;
76 	}
77 
78 	hext_stream->decoupled = false;
79 	snd_hdac_stream_init(bus, &hext_stream->hstream, idx, direction, tag);
80 }
81 
82 /**
83  * snd_hdac_ext_stream_init_all - create and initialize the stream objects
84  *   for an extended hda bus
85  * @bus: HD-audio core bus
86  * @start_idx: start index for streams
87  * @num_stream: number of streams to initialize
88  * @dir: direction of streams
89  */
snd_hdac_ext_stream_init_all(struct hdac_bus * bus,int start_idx,int num_stream,int dir)90 int snd_hdac_ext_stream_init_all(struct hdac_bus *bus, int start_idx,
91 				 int num_stream, int dir)
92 {
93 	struct pci_dev *pci = to_pci_dev(bus->dev);
94 	int (*setup_op)(struct hdac_stream *, bool);
95 	int stream_tag = 0;
96 	int i, tag, idx = start_idx;
97 
98 	if (pci->device == PCI_DEVICE_ID_INTEL_HDA_APL)
99 		setup_op = snd_hdac_apl_host_stream_setup;
100 	else
101 		setup_op = snd_hdac_stream_setup;
102 
103 	for (i = 0; i < num_stream; i++) {
104 		struct hdac_ext_stream *hext_stream =
105 				kzalloc(sizeof(*hext_stream), GFP_KERNEL);
106 		if (!hext_stream)
107 			return -ENOMEM;
108 		tag = ++stream_tag;
109 		snd_hdac_ext_stream_init(bus, hext_stream, idx, dir, tag);
110 		idx++;
111 		hext_stream->host_setup = setup_op;
112 	}
113 
114 	return 0;
115 
116 }
117 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_init_all);
118 
119 /**
120  * snd_hdac_ext_stream_free_all - free hdac extended stream objects
121  *
122  * @bus: HD-audio core bus
123  */
snd_hdac_ext_stream_free_all(struct hdac_bus * bus)124 void snd_hdac_ext_stream_free_all(struct hdac_bus *bus)
125 {
126 	struct hdac_stream *s, *_s;
127 	struct hdac_ext_stream *hext_stream;
128 
129 	list_for_each_entry_safe(s, _s, &bus->stream_list, list) {
130 		hext_stream = stream_to_hdac_ext_stream(s);
131 		snd_hdac_ext_stream_decouple(bus, hext_stream, false);
132 		list_del(&s->list);
133 		kfree(hext_stream);
134 	}
135 }
136 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_free_all);
137 
snd_hdac_ext_stream_decouple_locked(struct hdac_bus * bus,struct hdac_ext_stream * hext_stream,bool decouple)138 void snd_hdac_ext_stream_decouple_locked(struct hdac_bus *bus,
139 					 struct hdac_ext_stream *hext_stream,
140 					 bool decouple)
141 {
142 	struct hdac_stream *hstream = &hext_stream->hstream;
143 	u32 val;
144 	int mask = AZX_PPCTL_PROCEN(hstream->index);
145 
146 	val = readw(bus->ppcap + AZX_REG_PP_PPCTL) & mask;
147 
148 	if (decouple && !val)
149 		snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, mask);
150 	else if (!decouple && val)
151 		snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, 0);
152 
153 	hext_stream->decoupled = decouple;
154 }
155 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple_locked);
156 
157 /**
158  * snd_hdac_ext_stream_decouple - decouple the hdac stream
159  * @bus: HD-audio core bus
160  * @hext_stream: HD-audio ext core stream object to initialize
161  * @decouple: flag to decouple
162  */
snd_hdac_ext_stream_decouple(struct hdac_bus * bus,struct hdac_ext_stream * hext_stream,bool decouple)163 void snd_hdac_ext_stream_decouple(struct hdac_bus *bus,
164 				  struct hdac_ext_stream *hext_stream, bool decouple)
165 {
166 	spin_lock_irq(&bus->reg_lock);
167 	snd_hdac_ext_stream_decouple_locked(bus, hext_stream, decouple);
168 	spin_unlock_irq(&bus->reg_lock);
169 }
170 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple);
171 
172 /**
173  * snd_hdac_ext_stream_start - start a stream
174  * @hext_stream: HD-audio ext core stream to start
175  */
snd_hdac_ext_stream_start(struct hdac_ext_stream * hext_stream)176 void snd_hdac_ext_stream_start(struct hdac_ext_stream *hext_stream)
177 {
178 	snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL,
179 			 AZX_PPLCCTL_RUN, AZX_PPLCCTL_RUN);
180 }
181 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_start);
182 
183 /**
184  * snd_hdac_ext_stream_clear - stop a stream DMA
185  * @hext_stream: HD-audio ext core stream to stop
186  */
snd_hdac_ext_stream_clear(struct hdac_ext_stream * hext_stream)187 void snd_hdac_ext_stream_clear(struct hdac_ext_stream *hext_stream)
188 {
189 	snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL, AZX_PPLCCTL_RUN, 0);
190 }
191 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_clear);
192 
193 /**
194  * snd_hdac_ext_stream_reset - reset a stream
195  * @hext_stream: HD-audio ext core stream to reset
196  */
snd_hdac_ext_stream_reset(struct hdac_ext_stream * hext_stream)197 void snd_hdac_ext_stream_reset(struct hdac_ext_stream *hext_stream)
198 {
199 	unsigned char val;
200 	int timeout;
201 
202 	snd_hdac_ext_stream_clear(hext_stream);
203 
204 	snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL,
205 			 AZX_PPLCCTL_STRST, AZX_PPLCCTL_STRST);
206 	udelay(3);
207 	timeout = 50;
208 	do {
209 		val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL) &
210 				AZX_PPLCCTL_STRST;
211 		if (val)
212 			break;
213 		udelay(3);
214 	} while (--timeout);
215 	val &= ~AZX_PPLCCTL_STRST;
216 	writel(val, hext_stream->pplc_addr + AZX_REG_PPLCCTL);
217 	udelay(3);
218 
219 	timeout = 50;
220 	/* waiting for hardware to report that the stream is out of reset */
221 	do {
222 		val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL) & AZX_PPLCCTL_STRST;
223 		if (!val)
224 			break;
225 		udelay(3);
226 	} while (--timeout);
227 
228 }
229 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_reset);
230 
231 /**
232  * snd_hdac_ext_stream_setup -  set up the SD for streaming
233  * @hext_stream: HD-audio ext core stream to set up
234  * @fmt: stream format
235  */
snd_hdac_ext_stream_setup(struct hdac_ext_stream * hext_stream,int fmt)236 int snd_hdac_ext_stream_setup(struct hdac_ext_stream *hext_stream, int fmt)
237 {
238 	struct hdac_stream *hstream = &hext_stream->hstream;
239 	unsigned int val;
240 
241 	/* make sure the run bit is zero for SD */
242 	snd_hdac_ext_stream_clear(hext_stream);
243 	/* program the stream_tag */
244 	val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL);
245 	val = (val & ~AZX_PPLCCTL_STRM_MASK) |
246 		(hstream->stream_tag << AZX_PPLCCTL_STRM_SHIFT);
247 	writel(val, hext_stream->pplc_addr + AZX_REG_PPLCCTL);
248 
249 	/* program the stream format */
250 	writew(fmt, hext_stream->pplc_addr + AZX_REG_PPLCFMT);
251 
252 	return 0;
253 }
254 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_setup);
255 
256 static struct hdac_ext_stream *
hdac_ext_link_dma_stream_assign(struct hdac_bus * bus,struct snd_pcm_substream * substream)257 hdac_ext_link_dma_stream_assign(struct hdac_bus *bus,
258 				struct snd_pcm_substream *substream)
259 {
260 	struct hdac_ext_stream *res = NULL;
261 	struct hdac_stream *hstream = NULL;
262 
263 	if (!bus->ppcap) {
264 		dev_err(bus->dev, "stream type not supported\n");
265 		return NULL;
266 	}
267 
268 	spin_lock_irq(&bus->reg_lock);
269 	list_for_each_entry(hstream, &bus->stream_list, list) {
270 		struct hdac_ext_stream *hext_stream = container_of(hstream,
271 								 struct hdac_ext_stream,
272 								 hstream);
273 		if (hstream->direction != substream->stream)
274 			continue;
275 
276 		/* check if link stream is available */
277 		if (!hext_stream->link_locked) {
278 			res = hext_stream;
279 			break;
280 		}
281 
282 	}
283 	if (res) {
284 		snd_hdac_ext_stream_decouple_locked(bus, res, true);
285 		res->link_locked = 1;
286 		res->link_substream = substream;
287 	}
288 	spin_unlock_irq(&bus->reg_lock);
289 	return res;
290 }
291 
292 static struct hdac_ext_stream *
hdac_ext_host_dma_stream_assign(struct hdac_bus * bus,struct snd_pcm_substream * substream)293 hdac_ext_host_dma_stream_assign(struct hdac_bus *bus,
294 				struct snd_pcm_substream *substream)
295 {
296 	struct hdac_ext_stream *res = NULL;
297 	struct hdac_stream *hstream = NULL;
298 
299 	if (!bus->ppcap) {
300 		dev_err(bus->dev, "stream type not supported\n");
301 		return NULL;
302 	}
303 
304 	spin_lock_irq(&bus->reg_lock);
305 	list_for_each_entry(hstream, &bus->stream_list, list) {
306 		struct hdac_ext_stream *hext_stream = container_of(hstream,
307 								 struct hdac_ext_stream,
308 								 hstream);
309 		if (hstream->direction != substream->stream)
310 			continue;
311 
312 		if (!hstream->opened) {
313 			res = hext_stream;
314 			break;
315 		}
316 	}
317 	if (res) {
318 		snd_hdac_ext_stream_decouple_locked(bus, res, true);
319 		res->hstream.opened = 1;
320 		res->hstream.running = 0;
321 		res->hstream.substream = substream;
322 	}
323 	spin_unlock_irq(&bus->reg_lock);
324 
325 	return res;
326 }
327 
328 /**
329  * snd_hdac_ext_stream_assign - assign a stream for the PCM
330  * @bus: HD-audio core bus
331  * @substream: PCM substream to assign
332  * @type: type of stream (coupled, host or link stream)
333  *
334  * This assigns the stream based on the type (coupled/host/link), for the
335  * given PCM substream, assigns it and returns the stream object
336  *
337  * coupled: Looks for an unused stream
338  * host: Looks for an unused decoupled host stream
339  * link: Looks for an unused decoupled link stream
340  *
341  * If no stream is free, returns NULL. The function tries to keep using
342  * the same stream object when it's used beforehand.  when a stream is
343  * decoupled, it becomes a host stream and link stream.
344  */
snd_hdac_ext_stream_assign(struct hdac_bus * bus,struct snd_pcm_substream * substream,int type)345 struct hdac_ext_stream *snd_hdac_ext_stream_assign(struct hdac_bus *bus,
346 					   struct snd_pcm_substream *substream,
347 					   int type)
348 {
349 	struct hdac_ext_stream *hext_stream = NULL;
350 	struct hdac_stream *hstream = NULL;
351 
352 	switch (type) {
353 	case HDAC_EXT_STREAM_TYPE_COUPLED:
354 		hstream = snd_hdac_stream_assign(bus, substream);
355 		if (hstream)
356 			hext_stream = container_of(hstream,
357 						   struct hdac_ext_stream,
358 						   hstream);
359 		return hext_stream;
360 
361 	case HDAC_EXT_STREAM_TYPE_HOST:
362 		return hdac_ext_host_dma_stream_assign(bus, substream);
363 
364 	case HDAC_EXT_STREAM_TYPE_LINK:
365 		return hdac_ext_link_dma_stream_assign(bus, substream);
366 
367 	default:
368 		return NULL;
369 	}
370 }
371 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_assign);
372 
373 /**
374  * snd_hdac_ext_stream_release - release the assigned stream
375  * @hext_stream: HD-audio ext core stream to release
376  * @type: type of stream (coupled, host or link stream)
377  *
378  * Release the stream that has been assigned by snd_hdac_ext_stream_assign().
379  */
snd_hdac_ext_stream_release(struct hdac_ext_stream * hext_stream,int type)380 void snd_hdac_ext_stream_release(struct hdac_ext_stream *hext_stream, int type)
381 {
382 	struct hdac_bus *bus = hext_stream->hstream.bus;
383 
384 	switch (type) {
385 	case HDAC_EXT_STREAM_TYPE_COUPLED:
386 		snd_hdac_stream_release(&hext_stream->hstream);
387 		break;
388 
389 	case HDAC_EXT_STREAM_TYPE_HOST:
390 		spin_lock_irq(&bus->reg_lock);
391 		/* couple link only if not in use */
392 		if (!hext_stream->link_locked)
393 			snd_hdac_ext_stream_decouple_locked(bus, hext_stream, false);
394 		snd_hdac_stream_release_locked(&hext_stream->hstream);
395 		spin_unlock_irq(&bus->reg_lock);
396 		break;
397 
398 	case HDAC_EXT_STREAM_TYPE_LINK:
399 		spin_lock_irq(&bus->reg_lock);
400 		/* couple host only if not in use */
401 		if (!hext_stream->hstream.opened)
402 			snd_hdac_ext_stream_decouple_locked(bus, hext_stream, false);
403 		hext_stream->link_locked = 0;
404 		hext_stream->link_substream = NULL;
405 		spin_unlock_irq(&bus->reg_lock);
406 		break;
407 
408 	default:
409 		dev_dbg(bus->dev, "Invalid type %d\n", type);
410 	}
411 
412 }
413 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_release);
414 
415 /**
416  * snd_hdac_ext_cstream_assign - assign a host stream for compress
417  * @bus: HD-audio core bus
418  * @cstream: Compress stream to assign
419  *
420  * Assign an unused host stream for the given compress stream.
421  * If no stream is free, NULL is returned. Stream is decoupled
422  * before assignment.
423  */
snd_hdac_ext_cstream_assign(struct hdac_bus * bus,struct snd_compr_stream * cstream)424 struct hdac_ext_stream *snd_hdac_ext_cstream_assign(struct hdac_bus *bus,
425 						    struct snd_compr_stream *cstream)
426 {
427 	struct hdac_ext_stream *res = NULL;
428 	struct hdac_stream *hstream;
429 
430 	spin_lock_irq(&bus->reg_lock);
431 	list_for_each_entry(hstream, &bus->stream_list, list) {
432 		struct hdac_ext_stream *hext_stream = stream_to_hdac_ext_stream(hstream);
433 
434 		if (hstream->direction != cstream->direction)
435 			continue;
436 
437 		if (!hstream->opened) {
438 			res = hext_stream;
439 			break;
440 		}
441 	}
442 
443 	if (res) {
444 		snd_hdac_ext_stream_decouple_locked(bus, res, true);
445 		res->hstream.opened = 1;
446 		res->hstream.running = 0;
447 		res->hstream.cstream = cstream;
448 	}
449 	spin_unlock_irq(&bus->reg_lock);
450 
451 	return res;
452 }
453 EXPORT_SYMBOL_GPL(snd_hdac_ext_cstream_assign);
454