]> Git Repo - linux.git/blob - sound/hda/ext/hdac_ext_stream.c
Merge tag 'amd-drm-next-6.5-2023-06-09' of https://gitlab.freedesktop.org/agd5f/linux...
[linux.git] / sound / hda / ext / hdac_ext_stream.c
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 <[email protected]>
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  */
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  */
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  */
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
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  */
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  */
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  */
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  */
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  */
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 *
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 *
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  */
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  */
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  */
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);
This page took 0.057388 seconds and 4 git commands to generate.