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