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