1 // SPDX-License-Identifier: GPL-2.0-only
3 * hdac-ext-stream.c - HD-audio extended stream operations.
5 * Copyright (C) 2015 Intel Corp
7 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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>
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
27 * initialize the stream, if ppcap is enabled then init those and then
28 * invoke hdac stream initialization routine
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)
35 hext_stream->pphc_addr = bus->ppcap + AZX_PPHC_BASE +
36 AZX_PPHC_INTERVAL * idx;
38 hext_stream->pplc_addr = bus->ppcap + AZX_PPLC_BASE +
39 AZX_PPLC_MULTI * bus->num_streams +
40 AZX_PPLC_INTERVAL * idx;
43 hext_stream->decoupled = false;
44 snd_hdac_stream_init(bus, &hext_stream->hstream, idx, direction, tag);
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
55 int snd_hdac_ext_stream_init_all(struct hdac_bus *bus, int start_idx,
56 int num_stream, int dir)
59 int i, tag, idx = start_idx;
61 for (i = 0; i < num_stream; i++) {
62 struct hdac_ext_stream *hext_stream =
63 kzalloc(sizeof(*hext_stream), GFP_KERNEL);
67 snd_hdac_ext_stream_init(bus, hext_stream, idx, dir, tag);
74 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_init_all);
77 * snd_hdac_ext_stream_free_all - free hdac extended stream objects
79 * @bus: HD-audio core bus
81 void snd_hdac_ext_stream_free_all(struct hdac_bus *bus)
83 struct hdac_stream *s, *_s;
84 struct hdac_ext_stream *hext_stream;
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);
93 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_free_all);
95 void snd_hdac_ext_stream_decouple_locked(struct hdac_bus *bus,
96 struct hdac_ext_stream *hext_stream,
99 struct hdac_stream *hstream = &hext_stream->hstream;
101 int mask = AZX_PPCTL_PROCEN(hstream->index);
103 val = readw(bus->ppcap + AZX_REG_PP_PPCTL) & mask;
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);
110 hext_stream->decoupled = decouple;
112 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple_locked);
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
120 void snd_hdac_ext_stream_decouple(struct hdac_bus *bus,
121 struct hdac_ext_stream *hext_stream, bool decouple)
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);
127 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple);
130 * snd_hdac_ext_stream_start - start a stream
131 * @hext_stream: HD-audio ext core stream to start
133 void snd_hdac_ext_stream_start(struct hdac_ext_stream *hext_stream)
135 snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL,
136 AZX_PPLCCTL_RUN, AZX_PPLCCTL_RUN);
138 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_start);
141 * snd_hdac_ext_stream_clear - stop a stream DMA
142 * @hext_stream: HD-audio ext core stream to stop
144 void snd_hdac_ext_stream_clear(struct hdac_ext_stream *hext_stream)
146 snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL, AZX_PPLCCTL_RUN, 0);
148 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_clear);
151 * snd_hdac_ext_stream_reset - reset a stream
152 * @hext_stream: HD-audio ext core stream to reset
154 void snd_hdac_ext_stream_reset(struct hdac_ext_stream *hext_stream)
159 snd_hdac_ext_stream_clear(hext_stream);
161 snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL,
162 AZX_PPLCCTL_STRST, AZX_PPLCCTL_STRST);
166 val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL) &
172 val &= ~AZX_PPLCCTL_STRST;
173 writel(val, hext_stream->pplc_addr + AZX_REG_PPLCCTL);
177 /* waiting for hardware to report that the stream is out of reset */
179 val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL) & AZX_PPLCCTL_STRST;
186 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_reset);
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
193 int snd_hdac_ext_stream_setup(struct hdac_ext_stream *hext_stream, int fmt)
195 struct hdac_stream *hstream = &hext_stream->hstream;
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);
206 /* program the stream format */
207 writew(fmt, hext_stream->pplc_addr + AZX_REG_PPLCFMT);
211 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_setup);
213 static struct hdac_ext_stream *
214 hdac_ext_link_dma_stream_assign(struct hdac_bus *bus,
215 struct snd_pcm_substream *substream)
217 struct hdac_ext_stream *res = NULL;
218 struct hdac_stream *hstream = NULL;
221 dev_err(bus->dev, "stream type not supported\n");
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,
230 if (hstream->direction != substream->stream)
233 /* check if link stream is available */
234 if (!hext_stream->link_locked) {
241 snd_hdac_ext_stream_decouple_locked(bus, res, true);
242 res->link_locked = 1;
243 res->link_substream = substream;
245 spin_unlock_irq(&bus->reg_lock);
249 static struct hdac_ext_stream *
250 hdac_ext_host_dma_stream_assign(struct hdac_bus *bus,
251 struct snd_pcm_substream *substream)
253 struct hdac_ext_stream *res = NULL;
254 struct hdac_stream *hstream = NULL;
257 dev_err(bus->dev, "stream type not supported\n");
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,
266 if (hstream->direction != substream->stream)
269 if (!hstream->opened) {
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;
280 spin_unlock_irq(&bus->reg_lock);
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)
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
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
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.
302 struct hdac_ext_stream *snd_hdac_ext_stream_assign(struct hdac_bus *bus,
303 struct snd_pcm_substream *substream,
306 struct hdac_ext_stream *hext_stream = NULL;
307 struct hdac_stream *hstream = NULL;
310 case HDAC_EXT_STREAM_TYPE_COUPLED:
311 hstream = snd_hdac_stream_assign(bus, substream);
313 hext_stream = container_of(hstream,
314 struct hdac_ext_stream,
318 case HDAC_EXT_STREAM_TYPE_HOST:
319 return hdac_ext_host_dma_stream_assign(bus, substream);
321 case HDAC_EXT_STREAM_TYPE_LINK:
322 return hdac_ext_link_dma_stream_assign(bus, substream);
328 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_assign);
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)
335 * Release the stream that has been assigned by snd_hdac_ext_stream_assign().
337 void snd_hdac_ext_stream_release(struct hdac_ext_stream *hext_stream, int type)
339 struct hdac_bus *bus = hext_stream->hstream.bus;
342 case HDAC_EXT_STREAM_TYPE_COUPLED:
343 snd_hdac_stream_release(&hext_stream->hstream);
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);
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);
366 dev_dbg(bus->dev, "Invalid type %d\n", type);
370 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_release);
373 * snd_hdac_ext_cstream_assign - assign a host stream for compress
374 * @bus: HD-audio core bus
375 * @cstream: Compress stream to assign
377 * Assign an unused host stream for the given compress stream.
378 * If no stream is free, NULL is returned. Stream is decoupled
381 struct hdac_ext_stream *snd_hdac_ext_cstream_assign(struct hdac_bus *bus,
382 struct snd_compr_stream *cstream)
384 struct hdac_ext_stream *res = NULL;
385 struct hdac_stream *hstream;
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);
391 if (hstream->direction != cstream->direction)
394 if (!hstream->opened) {
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;
406 spin_unlock_irq(&bus->reg_lock);
410 EXPORT_SYMBOL_GPL(snd_hdac_ext_cstream_assign);