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 */
30static 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 */
55int 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}
74EXPORT_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 */
81void 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}
93EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_free_all);
94
95void 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}
112EXPORT_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 */
120void 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}
127EXPORT_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 */
133void 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}
138EXPORT_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 */
144void 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}
148EXPORT_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 */
154void 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}
186EXPORT_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 */
193int 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}
211EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_setup);
212
213static struct hdac_ext_stream *
214hdac_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
249static struct hdac_ext_stream *
250hdac_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 */
302struct 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}
328EXPORT_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 */
337void 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}
370EXPORT_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 */
381struct 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}
410EXPORT_SYMBOL_GPL(snd_hdac_ext_cstream_assign);
411