xref: /linux/sound/hda/core/ext/stream.c (revision 32a92f8c89326985e05dce8b22d3f0aa07a3e1bd)
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 = kzalloc_obj(*hext_stream);
105 		if (!hext_stream)
106 			return -ENOMEM;
107 		tag = ++stream_tag;
108 		snd_hdac_ext_stream_init(bus, hext_stream, idx, dir, tag);
109 		idx++;
110 		hext_stream->host_setup = setup_op;
111 	}
112 
113 	return 0;
114 
115 }
116 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_init_all);
117 
118 /**
119  * snd_hdac_ext_stream_free_all - free hdac extended stream objects
120  *
121  * @bus: HD-audio core bus
122  */
snd_hdac_ext_stream_free_all(struct hdac_bus * bus)123 void snd_hdac_ext_stream_free_all(struct hdac_bus *bus)
124 {
125 	struct hdac_stream *s, *_s;
126 	struct hdac_ext_stream *hext_stream;
127 
128 	list_for_each_entry_safe(s, _s, &bus->stream_list, list) {
129 		hext_stream = stream_to_hdac_ext_stream(s);
130 		snd_hdac_ext_stream_decouple(bus, hext_stream, false);
131 		list_del(&s->list);
132 		kfree(hext_stream);
133 	}
134 }
135 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_free_all);
136 
snd_hdac_ext_stream_decouple_locked(struct hdac_bus * bus,struct hdac_ext_stream * hext_stream,bool decouple)137 void snd_hdac_ext_stream_decouple_locked(struct hdac_bus *bus,
138 					 struct hdac_ext_stream *hext_stream,
139 					 bool decouple)
140 {
141 	struct hdac_stream *hstream = &hext_stream->hstream;
142 	u32 val;
143 	int mask = AZX_PPCTL_PROCEN(hstream->index);
144 
145 	val = readw(bus->ppcap + AZX_REG_PP_PPCTL) & mask;
146 
147 	if (decouple && !val)
148 		snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, mask);
149 	else if (!decouple && val)
150 		snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, 0);
151 
152 	hext_stream->decoupled = decouple;
153 }
154 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple_locked);
155 
156 /**
157  * snd_hdac_ext_stream_decouple - decouple the hdac stream
158  * @bus: HD-audio core bus
159  * @hext_stream: HD-audio ext core stream object to initialize
160  * @decouple: flag to decouple
161  */
snd_hdac_ext_stream_decouple(struct hdac_bus * bus,struct hdac_ext_stream * hext_stream,bool decouple)162 void snd_hdac_ext_stream_decouple(struct hdac_bus *bus,
163 				  struct hdac_ext_stream *hext_stream, bool decouple)
164 {
165 	guard(spinlock_irq)(&bus->reg_lock);
166 	snd_hdac_ext_stream_decouple_locked(bus, hext_stream, decouple);
167 }
168 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple);
169 
170 /**
171  * snd_hdac_ext_stream_start - start a stream
172  * @hext_stream: HD-audio ext core stream to start
173  */
snd_hdac_ext_stream_start(struct hdac_ext_stream * hext_stream)174 void snd_hdac_ext_stream_start(struct hdac_ext_stream *hext_stream)
175 {
176 	snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL,
177 			 AZX_PPLCCTL_RUN, AZX_PPLCCTL_RUN);
178 }
179 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_start);
180 
181 /**
182  * snd_hdac_ext_stream_clear - stop a stream DMA
183  * @hext_stream: HD-audio ext core stream to stop
184  */
snd_hdac_ext_stream_clear(struct hdac_ext_stream * hext_stream)185 void snd_hdac_ext_stream_clear(struct hdac_ext_stream *hext_stream)
186 {
187 	snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL, AZX_PPLCCTL_RUN, 0);
188 }
189 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_clear);
190 
191 /**
192  * snd_hdac_ext_stream_reset - reset a stream
193  * @hext_stream: HD-audio ext core stream to reset
194  */
snd_hdac_ext_stream_reset(struct hdac_ext_stream * hext_stream)195 void snd_hdac_ext_stream_reset(struct hdac_ext_stream *hext_stream)
196 {
197 	unsigned char val;
198 	int timeout;
199 
200 	snd_hdac_ext_stream_clear(hext_stream);
201 
202 	snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL,
203 			 AZX_PPLCCTL_STRST, AZX_PPLCCTL_STRST);
204 	udelay(3);
205 	timeout = 50;
206 	do {
207 		val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL) &
208 				AZX_PPLCCTL_STRST;
209 		if (val)
210 			break;
211 		udelay(3);
212 	} while (--timeout);
213 	val &= ~AZX_PPLCCTL_STRST;
214 	writel(val, hext_stream->pplc_addr + AZX_REG_PPLCCTL);
215 	udelay(3);
216 
217 	timeout = 50;
218 	/* waiting for hardware to report that the stream is out of reset */
219 	do {
220 		val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL) & AZX_PPLCCTL_STRST;
221 		if (!val)
222 			break;
223 		udelay(3);
224 	} while (--timeout);
225 
226 }
227 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_reset);
228 
229 /**
230  * snd_hdac_ext_stream_setup -  set up the SD for streaming
231  * @hext_stream: HD-audio ext core stream to set up
232  * @fmt: stream format
233  */
snd_hdac_ext_stream_setup(struct hdac_ext_stream * hext_stream,int fmt)234 int snd_hdac_ext_stream_setup(struct hdac_ext_stream *hext_stream, int fmt)
235 {
236 	struct hdac_stream *hstream = &hext_stream->hstream;
237 	unsigned int val;
238 
239 	/* make sure the run bit is zero for SD */
240 	snd_hdac_ext_stream_clear(hext_stream);
241 	/* program the stream_tag */
242 	val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL);
243 	val = (val & ~AZX_PPLCCTL_STRM_MASK) |
244 		(hstream->stream_tag << AZX_PPLCCTL_STRM_SHIFT);
245 	writel(val, hext_stream->pplc_addr + AZX_REG_PPLCCTL);
246 
247 	/* program the stream format */
248 	writew(fmt, hext_stream->pplc_addr + AZX_REG_PPLCFMT);
249 
250 	return 0;
251 }
252 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_setup);
253 
254 static struct hdac_ext_stream *
hdac_ext_link_dma_stream_assign(struct hdac_bus * bus,struct snd_pcm_substream * substream)255 hdac_ext_link_dma_stream_assign(struct hdac_bus *bus,
256 				struct snd_pcm_substream *substream)
257 {
258 	struct hdac_ext_stream *res = NULL;
259 	struct hdac_stream *hstream = NULL;
260 
261 	if (!bus->ppcap) {
262 		dev_err(bus->dev, "stream type not supported\n");
263 		return NULL;
264 	}
265 
266 	guard(spinlock_irq)(&bus->reg_lock);
267 	list_for_each_entry(hstream, &bus->stream_list, list) {
268 		struct hdac_ext_stream *hext_stream = container_of(hstream,
269 								 struct hdac_ext_stream,
270 								 hstream);
271 		if (hstream->direction != substream->stream)
272 			continue;
273 
274 		/* check if link stream is available */
275 		if (!hext_stream->link_locked) {
276 			res = hext_stream;
277 			break;
278 		}
279 
280 	}
281 	if (res) {
282 		snd_hdac_ext_stream_decouple_locked(bus, res, true);
283 		res->link_locked = 1;
284 		res->link_substream = substream;
285 	}
286 	return res;
287 }
288 
289 static struct hdac_ext_stream *
hdac_ext_host_dma_stream_assign(struct hdac_bus * bus,struct snd_pcm_substream * substream)290 hdac_ext_host_dma_stream_assign(struct hdac_bus *bus,
291 				struct snd_pcm_substream *substream)
292 {
293 	struct hdac_ext_stream *res = NULL;
294 	struct hdac_stream *hstream = NULL;
295 
296 	if (!bus->ppcap) {
297 		dev_err(bus->dev, "stream type not supported\n");
298 		return NULL;
299 	}
300 
301 	guard(spinlock_irq)(&bus->reg_lock);
302 	list_for_each_entry(hstream, &bus->stream_list, list) {
303 		struct hdac_ext_stream *hext_stream = container_of(hstream,
304 								 struct hdac_ext_stream,
305 								 hstream);
306 		if (hstream->direction != substream->stream)
307 			continue;
308 
309 		if (!hstream->opened) {
310 			res = hext_stream;
311 			break;
312 		}
313 	}
314 	if (res) {
315 		snd_hdac_ext_stream_decouple_locked(bus, res, true);
316 		res->hstream.opened = 1;
317 		res->hstream.running = 0;
318 		res->hstream.substream = substream;
319 	}
320 
321 	return res;
322 }
323 
324 /**
325  * snd_hdac_ext_stream_assign - assign a stream for the PCM
326  * @bus: HD-audio core bus
327  * @substream: PCM substream to assign
328  * @type: type of stream (coupled, host or link stream)
329  *
330  * This assigns the stream based on the type (coupled/host/link), for the
331  * given PCM substream, assigns it and returns the stream object
332  *
333  * coupled: Looks for an unused stream
334  * host: Looks for an unused decoupled host stream
335  * link: Looks for an unused decoupled link stream
336  *
337  * If no stream is free, returns NULL. The function tries to keep using
338  * the same stream object when it's used beforehand.  when a stream is
339  * decoupled, it becomes a host stream and link stream.
340  */
snd_hdac_ext_stream_assign(struct hdac_bus * bus,struct snd_pcm_substream * substream,int type)341 struct hdac_ext_stream *snd_hdac_ext_stream_assign(struct hdac_bus *bus,
342 					   struct snd_pcm_substream *substream,
343 					   int type)
344 {
345 	struct hdac_ext_stream *hext_stream = NULL;
346 	struct hdac_stream *hstream = NULL;
347 
348 	switch (type) {
349 	case HDAC_EXT_STREAM_TYPE_COUPLED:
350 		hstream = snd_hdac_stream_assign(bus, substream);
351 		if (hstream)
352 			hext_stream = container_of(hstream,
353 						   struct hdac_ext_stream,
354 						   hstream);
355 		return hext_stream;
356 
357 	case HDAC_EXT_STREAM_TYPE_HOST:
358 		return hdac_ext_host_dma_stream_assign(bus, substream);
359 
360 	case HDAC_EXT_STREAM_TYPE_LINK:
361 		return hdac_ext_link_dma_stream_assign(bus, substream);
362 
363 	default:
364 		return NULL;
365 	}
366 }
367 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_assign);
368 
369 /**
370  * snd_hdac_ext_stream_release - release the assigned stream
371  * @hext_stream: HD-audio ext core stream to release
372  * @type: type of stream (coupled, host or link stream)
373  *
374  * Release the stream that has been assigned by snd_hdac_ext_stream_assign().
375  */
snd_hdac_ext_stream_release(struct hdac_ext_stream * hext_stream,int type)376 void snd_hdac_ext_stream_release(struct hdac_ext_stream *hext_stream, int type)
377 {
378 	struct hdac_bus *bus = hext_stream->hstream.bus;
379 
380 	switch (type) {
381 	case HDAC_EXT_STREAM_TYPE_COUPLED:
382 		snd_hdac_stream_release(&hext_stream->hstream);
383 		break;
384 
385 	case HDAC_EXT_STREAM_TYPE_HOST:
386 		scoped_guard(spinlock_irq, &bus->reg_lock) {
387 			/* couple link only if not in use */
388 			if (!hext_stream->link_locked)
389 				snd_hdac_ext_stream_decouple_locked(bus, hext_stream, false);
390 			snd_hdac_stream_release_locked(&hext_stream->hstream);
391 		}
392 		break;
393 
394 	case HDAC_EXT_STREAM_TYPE_LINK:
395 		scoped_guard(spinlock_irq, &bus->reg_lock) {
396 			/* couple host only if not in use */
397 			if (!hext_stream->hstream.opened)
398 				snd_hdac_ext_stream_decouple_locked(bus, hext_stream, false);
399 			hext_stream->link_locked = 0;
400 			hext_stream->link_substream = NULL;
401 		}
402 		break;
403 
404 	default:
405 		dev_dbg(bus->dev, "Invalid type %d\n", type);
406 	}
407 
408 }
409 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_release);
410 
411 /**
412  * snd_hdac_ext_cstream_assign - assign a host stream for compress
413  * @bus: HD-audio core bus
414  * @cstream: Compress stream to assign
415  *
416  * Assign an unused host stream for the given compress stream.
417  * If no stream is free, NULL is returned. Stream is decoupled
418  * before assignment.
419  */
snd_hdac_ext_cstream_assign(struct hdac_bus * bus,struct snd_compr_stream * cstream)420 struct hdac_ext_stream *snd_hdac_ext_cstream_assign(struct hdac_bus *bus,
421 						    struct snd_compr_stream *cstream)
422 {
423 	struct hdac_ext_stream *res = NULL;
424 	struct hdac_stream *hstream;
425 
426 	guard(spinlock_irq)(&bus->reg_lock);
427 	list_for_each_entry(hstream, &bus->stream_list, list) {
428 		struct hdac_ext_stream *hext_stream = stream_to_hdac_ext_stream(hstream);
429 
430 		if (hstream->direction != cstream->direction)
431 			continue;
432 
433 		if (!hstream->opened) {
434 			res = hext_stream;
435 			break;
436 		}
437 	}
438 
439 	if (res) {
440 		snd_hdac_ext_stream_decouple_locked(bus, res, true);
441 		res->hstream.opened = 1;
442 		res->hstream.running = 0;
443 		res->hstream.cstream = cstream;
444 	}
445 
446 	return res;
447 }
448 EXPORT_SYMBOL_GPL(snd_hdac_ext_cstream_assign);
449