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