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/slab.h>
14 #include <sound/pcm.h>
15 #include <sound/hda_register.h>
16 #include <sound/hdaudio_ext.h>
17 
18 /**
19  * snd_hdac_ext_stream_init - initialize each stream (aka device)
20  * @bus: HD-audio core bus
21  * @hext_stream: HD-audio ext core stream object to initialize
22  * @idx: stream index number
23  * @direction: stream direction (SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE)
24  * @tag: the tag id to assign
25  *
26  * initialize the stream, if ppcap is enabled then init those and then
27  * invoke hdac stream initialization routine
28  */
snd_hdac_ext_stream_init(struct hdac_bus * bus,struct hdac_ext_stream * hext_stream,int idx,int direction,int tag)29 static void snd_hdac_ext_stream_init(struct hdac_bus *bus,
30 				     struct hdac_ext_stream *hext_stream,
31 				     int idx, int direction, int tag)
32 {
33 	if (bus->ppcap) {
34 		hext_stream->pphc_addr = bus->ppcap + AZX_PPHC_BASE +
35 				AZX_PPHC_INTERVAL * idx;
36 
37 		hext_stream->pplc_addr = bus->ppcap + AZX_PPLC_BASE +
38 				AZX_PPLC_MULTI * bus->num_streams +
39 				AZX_PPLC_INTERVAL * idx;
40 	}
41 
42 	if (bus->spbcap) {
43 		hext_stream->spib_addr = bus->spbcap + AZX_SPB_BASE +
44 					AZX_SPB_INTERVAL * idx +
45 					AZX_SPB_SPIB;
46 
47 		hext_stream->fifo_addr = bus->spbcap + AZX_SPB_BASE +
48 					AZX_SPB_INTERVAL * idx +
49 					AZX_SPB_MAXFIFO;
50 	}
51 
52 	if (bus->drsmcap)
53 		hext_stream->dpibr_addr = bus->drsmcap + AZX_DRSM_BASE +
54 					AZX_DRSM_INTERVAL * idx;
55 
56 	hext_stream->decoupled = false;
57 	snd_hdac_stream_init(bus, &hext_stream->hstream, idx, direction, tag);
58 }
59 
60 /**
61  * snd_hdac_ext_stream_init_all - create and initialize the stream objects
62  *   for an extended hda bus
63  * @bus: HD-audio core bus
64  * @start_idx: start index for streams
65  * @num_stream: number of streams to initialize
66  * @dir: direction of streams
67  */
snd_hdac_ext_stream_init_all(struct hdac_bus * bus,int start_idx,int num_stream,int dir)68 int snd_hdac_ext_stream_init_all(struct hdac_bus *bus, int start_idx,
69 				 int num_stream, int dir)
70 {
71 	int stream_tag = 0;
72 	int i, tag, idx = start_idx;
73 
74 	for (i = 0; i < num_stream; i++) {
75 		struct hdac_ext_stream *hext_stream =
76 				kzalloc(sizeof(*hext_stream), GFP_KERNEL);
77 		if (!hext_stream)
78 			return -ENOMEM;
79 		tag = ++stream_tag;
80 		snd_hdac_ext_stream_init(bus, hext_stream, idx, dir, tag);
81 		idx++;
82 	}
83 
84 	return 0;
85 
86 }
87 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_init_all);
88 
89 /**
90  * snd_hdac_ext_stream_free_all - free hdac extended stream objects
91  *
92  * @bus: HD-audio core bus
93  */
snd_hdac_ext_stream_free_all(struct hdac_bus * bus)94 void snd_hdac_ext_stream_free_all(struct hdac_bus *bus)
95 {
96 	struct hdac_stream *s, *_s;
97 	struct hdac_ext_stream *hext_stream;
98 
99 	list_for_each_entry_safe(s, _s, &bus->stream_list, list) {
100 		hext_stream = stream_to_hdac_ext_stream(s);
101 		snd_hdac_ext_stream_decouple(bus, hext_stream, false);
102 		list_del(&s->list);
103 		kfree(hext_stream);
104 	}
105 }
106 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_free_all);
107 
snd_hdac_ext_stream_decouple_locked(struct hdac_bus * bus,struct hdac_ext_stream * hext_stream,bool decouple)108 void snd_hdac_ext_stream_decouple_locked(struct hdac_bus *bus,
109 					 struct hdac_ext_stream *hext_stream,
110 					 bool decouple)
111 {
112 	struct hdac_stream *hstream = &hext_stream->hstream;
113 	u32 val;
114 	int mask = AZX_PPCTL_PROCEN(hstream->index);
115 
116 	val = readw(bus->ppcap + AZX_REG_PP_PPCTL) & mask;
117 
118 	if (decouple && !val)
119 		snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, mask);
120 	else if (!decouple && val)
121 		snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, 0);
122 
123 	hext_stream->decoupled = decouple;
124 }
125 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple_locked);
126 
127 /**
128  * snd_hdac_ext_stream_decouple - decouple the hdac stream
129  * @bus: HD-audio core bus
130  * @hext_stream: HD-audio ext core stream object to initialize
131  * @decouple: flag to decouple
132  */
snd_hdac_ext_stream_decouple(struct hdac_bus * bus,struct hdac_ext_stream * hext_stream,bool decouple)133 void snd_hdac_ext_stream_decouple(struct hdac_bus *bus,
134 				  struct hdac_ext_stream *hext_stream, bool decouple)
135 {
136 	spin_lock_irq(&bus->reg_lock);
137 	snd_hdac_ext_stream_decouple_locked(bus, hext_stream, decouple);
138 	spin_unlock_irq(&bus->reg_lock);
139 }
140 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple);
141 
142 /**
143  * snd_hdac_ext_link_stream_start - start a stream
144  * @hext_stream: HD-audio ext core stream to start
145  */
snd_hdac_ext_link_stream_start(struct hdac_ext_stream * hext_stream)146 void snd_hdac_ext_link_stream_start(struct hdac_ext_stream *hext_stream)
147 {
148 	snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL,
149 			 AZX_PPLCCTL_RUN, AZX_PPLCCTL_RUN);
150 }
151 EXPORT_SYMBOL_GPL(snd_hdac_ext_link_stream_start);
152 
153 /**
154  * snd_hdac_ext_link_stream_clear - stop a stream DMA
155  * @hext_stream: HD-audio ext core stream to stop
156  */
snd_hdac_ext_link_stream_clear(struct hdac_ext_stream * hext_stream)157 void snd_hdac_ext_link_stream_clear(struct hdac_ext_stream *hext_stream)
158 {
159 	snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL, AZX_PPLCCTL_RUN, 0);
160 }
161 EXPORT_SYMBOL_GPL(snd_hdac_ext_link_stream_clear);
162 
163 /**
164  * snd_hdac_ext_link_stream_reset - reset a stream
165  * @hext_stream: HD-audio ext core stream to reset
166  */
snd_hdac_ext_link_stream_reset(struct hdac_ext_stream * hext_stream)167 void snd_hdac_ext_link_stream_reset(struct hdac_ext_stream *hext_stream)
168 {
169 	unsigned char val;
170 	int timeout;
171 
172 	snd_hdac_ext_link_stream_clear(hext_stream);
173 
174 	snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL,
175 			 AZX_PPLCCTL_STRST, AZX_PPLCCTL_STRST);
176 	udelay(3);
177 	timeout = 50;
178 	do {
179 		val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL) &
180 				AZX_PPLCCTL_STRST;
181 		if (val)
182 			break;
183 		udelay(3);
184 	} while (--timeout);
185 	val &= ~AZX_PPLCCTL_STRST;
186 	writel(val, hext_stream->pplc_addr + AZX_REG_PPLCCTL);
187 	udelay(3);
188 
189 	timeout = 50;
190 	/* waiting for hardware to report that the stream is out of reset */
191 	do {
192 		val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL) & AZX_PPLCCTL_STRST;
193 		if (!val)
194 			break;
195 		udelay(3);
196 	} while (--timeout);
197 
198 }
199 EXPORT_SYMBOL_GPL(snd_hdac_ext_link_stream_reset);
200 
201 /**
202  * snd_hdac_ext_link_stream_setup -  set up the SD for streaming
203  * @hext_stream: HD-audio ext core stream to set up
204  * @fmt: stream format
205  */
snd_hdac_ext_link_stream_setup(struct hdac_ext_stream * hext_stream,int fmt)206 int snd_hdac_ext_link_stream_setup(struct hdac_ext_stream *hext_stream, int fmt)
207 {
208 	struct hdac_stream *hstream = &hext_stream->hstream;
209 	unsigned int val;
210 
211 	/* make sure the run bit is zero for SD */
212 	snd_hdac_ext_link_stream_clear(hext_stream);
213 	/* program the stream_tag */
214 	val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL);
215 	val = (val & ~AZX_PPLCCTL_STRM_MASK) |
216 		(hstream->stream_tag << AZX_PPLCCTL_STRM_SHIFT);
217 	writel(val, hext_stream->pplc_addr + AZX_REG_PPLCCTL);
218 
219 	/* program the stream format */
220 	writew(fmt, hext_stream->pplc_addr + AZX_REG_PPLCFMT);
221 
222 	return 0;
223 }
224 EXPORT_SYMBOL_GPL(snd_hdac_ext_link_stream_setup);
225 
226 /**
227  * snd_hdac_ext_link_set_stream_id - maps stream id to link output
228  * @link: HD-audio ext link to set up
229  * @stream: stream id
230  */
snd_hdac_ext_link_set_stream_id(struct hdac_ext_link * link,int stream)231 void snd_hdac_ext_link_set_stream_id(struct hdac_ext_link *link,
232 				     int stream)
233 {
234 	snd_hdac_updatew(link->ml_addr, AZX_REG_ML_LOSIDV, (1 << stream), 1 << stream);
235 }
236 EXPORT_SYMBOL_GPL(snd_hdac_ext_link_set_stream_id);
237 
238 /**
239  * snd_hdac_ext_link_clear_stream_id - maps stream id to link output
240  * @link: HD-audio ext link to set up
241  * @stream: stream id
242  */
snd_hdac_ext_link_clear_stream_id(struct hdac_ext_link * link,int stream)243 void snd_hdac_ext_link_clear_stream_id(struct hdac_ext_link *link,
244 				 int stream)
245 {
246 	snd_hdac_updatew(link->ml_addr, AZX_REG_ML_LOSIDV, (1 << stream), 0);
247 }
248 EXPORT_SYMBOL_GPL(snd_hdac_ext_link_clear_stream_id);
249 
250 static struct hdac_ext_stream *
hdac_ext_link_stream_assign(struct hdac_bus * bus,struct snd_pcm_substream * substream)251 hdac_ext_link_stream_assign(struct hdac_bus *bus,
252 			    struct snd_pcm_substream *substream)
253 {
254 	struct hdac_ext_stream *res = NULL;
255 	struct hdac_stream *hstream = NULL;
256 
257 	if (!bus->ppcap) {
258 		dev_err(bus->dev, "stream type not supported\n");
259 		return NULL;
260 	}
261 
262 	spin_lock_irq(&bus->reg_lock);
263 	list_for_each_entry(hstream, &bus->stream_list, list) {
264 		struct hdac_ext_stream *hext_stream = container_of(hstream,
265 								 struct hdac_ext_stream,
266 								 hstream);
267 		if (hstream->direction != substream->stream)
268 			continue;
269 
270 		/* check if link stream is available */
271 		if (!hext_stream->link_locked) {
272 			res = hext_stream;
273 			break;
274 		}
275 
276 	}
277 	if (res) {
278 		snd_hdac_ext_stream_decouple_locked(bus, res, true);
279 		res->link_locked = 1;
280 		res->link_substream = substream;
281 	}
282 	spin_unlock_irq(&bus->reg_lock);
283 	return res;
284 }
285 
286 static struct hdac_ext_stream *
hdac_ext_host_stream_assign(struct hdac_bus * bus,struct snd_pcm_substream * substream)287 hdac_ext_host_stream_assign(struct hdac_bus *bus,
288 			    struct snd_pcm_substream *substream)
289 {
290 	struct hdac_ext_stream *res = NULL;
291 	struct hdac_stream *hstream = NULL;
292 
293 	if (!bus->ppcap) {
294 		dev_err(bus->dev, "stream type not supported\n");
295 		return NULL;
296 	}
297 
298 	spin_lock_irq(&bus->reg_lock);
299 	list_for_each_entry(hstream, &bus->stream_list, list) {
300 		struct hdac_ext_stream *hext_stream = container_of(hstream,
301 								 struct hdac_ext_stream,
302 								 hstream);
303 		if (hstream->direction != substream->stream)
304 			continue;
305 
306 		if (!hstream->opened) {
307 			res = hext_stream;
308 			break;
309 		}
310 	}
311 	if (res) {
312 		snd_hdac_ext_stream_decouple_locked(bus, res, true);
313 		res->hstream.opened = 1;
314 		res->hstream.running = 0;
315 		res->hstream.substream = substream;
316 	}
317 	spin_unlock_irq(&bus->reg_lock);
318 
319 	return res;
320 }
321 
322 /**
323  * snd_hdac_ext_stream_assign - assign a stream for the PCM
324  * @bus: HD-audio core bus
325  * @substream: PCM substream to assign
326  * @type: type of stream (coupled, host or link stream)
327  *
328  * This assigns the stream based on the type (coupled/host/link), for the
329  * given PCM substream, assigns it and returns the stream object
330  *
331  * coupled: Looks for an unused stream
332  * host: Looks for an unused decoupled host stream
333  * link: Looks for an unused decoupled link stream
334  *
335  * If no stream is free, returns NULL. The function tries to keep using
336  * the same stream object when it's used beforehand.  when a stream is
337  * decoupled, it becomes a host stream and link stream.
338  */
snd_hdac_ext_stream_assign(struct hdac_bus * bus,struct snd_pcm_substream * substream,int type)339 struct hdac_ext_stream *snd_hdac_ext_stream_assign(struct hdac_bus *bus,
340 					   struct snd_pcm_substream *substream,
341 					   int type)
342 {
343 	struct hdac_ext_stream *hext_stream = NULL;
344 	struct hdac_stream *hstream = NULL;
345 
346 	switch (type) {
347 	case HDAC_EXT_STREAM_TYPE_COUPLED:
348 		hstream = snd_hdac_stream_assign(bus, substream);
349 		if (hstream)
350 			hext_stream = container_of(hstream,
351 						   struct hdac_ext_stream,
352 						   hstream);
353 		return hext_stream;
354 
355 	case HDAC_EXT_STREAM_TYPE_HOST:
356 		return hdac_ext_host_stream_assign(bus, substream);
357 
358 	case HDAC_EXT_STREAM_TYPE_LINK:
359 		return hdac_ext_link_stream_assign(bus, substream);
360 
361 	default:
362 		return NULL;
363 	}
364 }
365 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_assign);
366 
367 /**
368  * snd_hdac_ext_stream_release - release the assigned stream
369  * @hext_stream: HD-audio ext core stream to release
370  * @type: type of stream (coupled, host or link stream)
371  *
372  * Release the stream that has been assigned by snd_hdac_ext_stream_assign().
373  */
snd_hdac_ext_stream_release(struct hdac_ext_stream * hext_stream,int type)374 void snd_hdac_ext_stream_release(struct hdac_ext_stream *hext_stream, int type)
375 {
376 	struct hdac_bus *bus = hext_stream->hstream.bus;
377 
378 	switch (type) {
379 	case HDAC_EXT_STREAM_TYPE_COUPLED:
380 		snd_hdac_stream_release(&hext_stream->hstream);
381 		break;
382 
383 	case HDAC_EXT_STREAM_TYPE_HOST:
384 		spin_lock_irq(&bus->reg_lock);
385 		/* couple link only if not in use */
386 		if (!hext_stream->link_locked)
387 			snd_hdac_ext_stream_decouple_locked(bus, hext_stream, false);
388 		snd_hdac_stream_release_locked(&hext_stream->hstream);
389 		spin_unlock_irq(&bus->reg_lock);
390 		break;
391 
392 	case HDAC_EXT_STREAM_TYPE_LINK:
393 		spin_lock_irq(&bus->reg_lock);
394 		/* couple host only if not in use */
395 		if (!hext_stream->hstream.opened)
396 			snd_hdac_ext_stream_decouple_locked(bus, hext_stream, false);
397 		hext_stream->link_locked = 0;
398 		hext_stream->link_substream = NULL;
399 		spin_unlock_irq(&bus->reg_lock);
400 		break;
401 
402 	default:
403 		dev_dbg(bus->dev, "Invalid type %d\n", type);
404 	}
405 
406 }
407 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_release);
408 
409 /**
410  * snd_hdac_ext_stream_spbcap_enable - enable SPIB for a stream
411  * @bus: HD-audio core bus
412  * @enable: flag to enable/disable SPIB
413  * @index: stream index for which SPIB need to be enabled
414  */
snd_hdac_ext_stream_spbcap_enable(struct hdac_bus * bus,bool enable,int index)415 void snd_hdac_ext_stream_spbcap_enable(struct hdac_bus *bus,
416 				 bool enable, int index)
417 {
418 	u32 mask = 0;
419 
420 	if (!bus->spbcap) {
421 		dev_err(bus->dev, "Address of SPB capability is NULL\n");
422 		return;
423 	}
424 
425 	mask |= (1 << index);
426 
427 	if (enable)
428 		snd_hdac_updatel(bus->spbcap, AZX_REG_SPB_SPBFCCTL, mask, mask);
429 	else
430 		snd_hdac_updatel(bus->spbcap, AZX_REG_SPB_SPBFCCTL, mask, 0);
431 }
432 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_spbcap_enable);
433 
434 /**
435  * snd_hdac_ext_stream_set_spib - sets the spib value of a stream
436  * @bus: HD-audio core bus
437  * @hext_stream: hdac_ext_stream
438  * @value: spib value to set
439  */
snd_hdac_ext_stream_set_spib(struct hdac_bus * bus,struct hdac_ext_stream * hext_stream,u32 value)440 int snd_hdac_ext_stream_set_spib(struct hdac_bus *bus,
441 				 struct hdac_ext_stream *hext_stream, u32 value)
442 {
443 
444 	if (!bus->spbcap) {
445 		dev_err(bus->dev, "Address of SPB capability is NULL\n");
446 		return -EINVAL;
447 	}
448 
449 	writel(value, hext_stream->spib_addr);
450 
451 	return 0;
452 }
453 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_set_spib);
454 
455 /**
456  * snd_hdac_ext_stream_get_spbmaxfifo - gets the spib value of a stream
457  * @bus: HD-audio core bus
458  * @hext_stream: hdac_ext_stream
459  *
460  * Return maxfifo for the stream
461  */
snd_hdac_ext_stream_get_spbmaxfifo(struct hdac_bus * bus,struct hdac_ext_stream * hext_stream)462 int snd_hdac_ext_stream_get_spbmaxfifo(struct hdac_bus *bus,
463 				 struct hdac_ext_stream *hext_stream)
464 {
465 
466 	if (!bus->spbcap) {
467 		dev_err(bus->dev, "Address of SPB capability is NULL\n");
468 		return -EINVAL;
469 	}
470 
471 	return readl(hext_stream->fifo_addr);
472 }
473 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_get_spbmaxfifo);
474 
475 /**
476  * snd_hdac_ext_stream_drsm_enable - enable DMA resume for a stream
477  * @bus: HD-audio core bus
478  * @enable: flag to enable/disable DRSM
479  * @index: stream index for which DRSM need to be enabled
480  */
snd_hdac_ext_stream_drsm_enable(struct hdac_bus * bus,bool enable,int index)481 void snd_hdac_ext_stream_drsm_enable(struct hdac_bus *bus,
482 				bool enable, int index)
483 {
484 	u32 mask = 0;
485 
486 	if (!bus->drsmcap) {
487 		dev_err(bus->dev, "Address of DRSM capability is NULL\n");
488 		return;
489 	}
490 
491 	mask |= (1 << index);
492 
493 	if (enable)
494 		snd_hdac_updatel(bus->drsmcap, AZX_REG_DRSM_CTL, mask, mask);
495 	else
496 		snd_hdac_updatel(bus->drsmcap, AZX_REG_DRSM_CTL, mask, 0);
497 }
498 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_drsm_enable);
499 
500 /**
501  * snd_hdac_ext_stream_set_dpibr - sets the dpibr value of a stream
502  * @bus: HD-audio core bus
503  * @hext_stream: hdac_ext_stream
504  * @value: dpib value to set
505  */
snd_hdac_ext_stream_set_dpibr(struct hdac_bus * bus,struct hdac_ext_stream * hext_stream,u32 value)506 int snd_hdac_ext_stream_set_dpibr(struct hdac_bus *bus,
507 				  struct hdac_ext_stream *hext_stream, u32 value)
508 {
509 
510 	if (!bus->drsmcap) {
511 		dev_err(bus->dev, "Address of DRSM capability is NULL\n");
512 		return -EINVAL;
513 	}
514 
515 	writel(value, hext_stream->dpibr_addr);
516 
517 	return 0;
518 }
519 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_set_dpibr);
520 
521 /**
522  * snd_hdac_ext_stream_set_lpib - sets the lpib value of a stream
523  * @hext_stream: hdac_ext_stream
524  * @value: lpib value to set
525  */
snd_hdac_ext_stream_set_lpib(struct hdac_ext_stream * hext_stream,u32 value)526 int snd_hdac_ext_stream_set_lpib(struct hdac_ext_stream *hext_stream, u32 value)
527 {
528 	snd_hdac_stream_writel(&hext_stream->hstream, SD_LPIB, value);
529 
530 	return 0;
531 }
532 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_set_lpib);
533