Commit | Line | Data |
---|---|---|
06f6e1d4 KM |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // | |
3 | // soc-dai.c | |
4 | // | |
5 | // Copyright (C) 2019 Renesas Electronics Corp. | |
6 | // Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> | |
7 | // | |
8 | ||
9 | #include <sound/soc.h> | |
10 | #include <sound/soc-dai.h> | |
0cbbf8a0 | 11 | #include <sound/soc-link.h> |
06f6e1d4 | 12 | |
aa7b8230 KM |
13 | #define soc_dai_ret(dai, ret) _soc_dai_ret(dai, __func__, ret) |
14 | static inline int _soc_dai_ret(struct snd_soc_dai *dai, | |
15 | const char *func, int ret) | |
16 | { | |
28ff437a PLB |
17 | /* Positive, Zero values are not errors */ |
18 | if (ret >= 0) | |
19 | return ret; | |
20 | ||
21 | /* Negative values might be errors */ | |
aa7b8230 KM |
22 | switch (ret) { |
23 | case -EPROBE_DEFER: | |
24 | case -ENOTSUPP: | |
aa7b8230 KM |
25 | break; |
26 | default: | |
27 | dev_err(dai->dev, | |
28 | "ASoC: error at %s on %s: %d\n", | |
29 | func, dai->name, ret); | |
30 | } | |
31 | ||
32 | return ret; | |
33 | } | |
34 | ||
00a0b46c KM |
35 | /* |
36 | * We might want to check substream by using list. | |
37 | * In such case, we can update these macros. | |
38 | */ | |
39 | #define soc_dai_mark_push(dai, substream, tgt) ((dai)->mark_##tgt = substream) | |
40 | #define soc_dai_mark_pop(dai, substream, tgt) ((dai)->mark_##tgt = NULL) | |
41 | #define soc_dai_mark_match(dai, substream, tgt) ((dai)->mark_##tgt == substream) | |
42 | ||
06f6e1d4 KM |
43 | /** |
44 | * snd_soc_dai_set_sysclk - configure DAI system or master clock. | |
45 | * @dai: DAI | |
46 | * @clk_id: DAI specific clock ID | |
47 | * @freq: new clock frequency in Hz | |
48 | * @dir: new clock direction - input/output. | |
49 | * | |
50 | * Configures the DAI master (MCLK) or system (SYSCLK) clocking. | |
51 | */ | |
52 | int snd_soc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, | |
53 | unsigned int freq, int dir) | |
54 | { | |
aa7b8230 KM |
55 | int ret; |
56 | ||
479914ed KM |
57 | if (dai->driver->ops && |
58 | dai->driver->ops->set_sysclk) | |
aa7b8230 KM |
59 | ret = dai->driver->ops->set_sysclk(dai, clk_id, freq, dir); |
60 | else | |
61 | ret = snd_soc_component_set_sysclk(dai->component, clk_id, 0, | |
62 | freq, dir); | |
06f6e1d4 | 63 | |
aa7b8230 | 64 | return soc_dai_ret(dai, ret); |
06f6e1d4 KM |
65 | } |
66 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_sysclk); | |
67 | ||
68 | /** | |
69 | * snd_soc_dai_set_clkdiv - configure DAI clock dividers. | |
70 | * @dai: DAI | |
71 | * @div_id: DAI specific clock divider ID | |
72 | * @div: new clock divisor. | |
73 | * | |
74 | * Configures the clock dividers. This is used to derive the best DAI bit and | |
75 | * frame clocks from the system or master clock. It's best to set the DAI bit | |
76 | * and frame clocks as low as possible to save system power. | |
77 | */ | |
78 | int snd_soc_dai_set_clkdiv(struct snd_soc_dai *dai, | |
79 | int div_id, int div) | |
80 | { | |
aa7b8230 KM |
81 | int ret = -EINVAL; |
82 | ||
479914ed KM |
83 | if (dai->driver->ops && |
84 | dai->driver->ops->set_clkdiv) | |
aa7b8230 KM |
85 | ret = dai->driver->ops->set_clkdiv(dai, div_id, div); |
86 | ||
87 | return soc_dai_ret(dai, ret); | |
06f6e1d4 KM |
88 | } |
89 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_clkdiv); | |
90 | ||
91 | /** | |
92 | * snd_soc_dai_set_pll - configure DAI PLL. | |
93 | * @dai: DAI | |
94 | * @pll_id: DAI specific PLL ID | |
95 | * @source: DAI specific source for the PLL | |
96 | * @freq_in: PLL input clock frequency in Hz | |
97 | * @freq_out: requested PLL output clock frequency in Hz | |
98 | * | |
99 | * Configures and enables PLL to generate output clock based on input clock. | |
100 | */ | |
101 | int snd_soc_dai_set_pll(struct snd_soc_dai *dai, int pll_id, int source, | |
102 | unsigned int freq_in, unsigned int freq_out) | |
103 | { | |
aa7b8230 KM |
104 | int ret; |
105 | ||
479914ed KM |
106 | if (dai->driver->ops && |
107 | dai->driver->ops->set_pll) | |
aa7b8230 KM |
108 | ret = dai->driver->ops->set_pll(dai, pll_id, source, |
109 | freq_in, freq_out); | |
110 | else | |
111 | ret = snd_soc_component_set_pll(dai->component, pll_id, source, | |
112 | freq_in, freq_out); | |
06f6e1d4 | 113 | |
aa7b8230 | 114 | return soc_dai_ret(dai, ret); |
06f6e1d4 KM |
115 | } |
116 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_pll); | |
117 | ||
118 | /** | |
119 | * snd_soc_dai_set_bclk_ratio - configure BCLK to sample rate ratio. | |
120 | * @dai: DAI | |
121 | * @ratio: Ratio of BCLK to Sample rate. | |
122 | * | |
123 | * Configures the DAI for a preset BCLK to sample rate ratio. | |
124 | */ | |
125 | int snd_soc_dai_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) | |
126 | { | |
ceff365a | 127 | int ret = -ENOTSUPP; |
aa7b8230 | 128 | |
479914ed KM |
129 | if (dai->driver->ops && |
130 | dai->driver->ops->set_bclk_ratio) | |
aa7b8230 KM |
131 | ret = dai->driver->ops->set_bclk_ratio(dai, ratio); |
132 | ||
133 | return soc_dai_ret(dai, ret); | |
06f6e1d4 KM |
134 | } |
135 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_bclk_ratio); | |
136 | ||
ba9e82a1 KM |
137 | int snd_soc_dai_get_fmt_max_priority(struct snd_soc_pcm_runtime *rtd) |
138 | { | |
139 | struct snd_soc_dai *dai; | |
140 | int i, max = 0; | |
141 | ||
142 | /* | |
143 | * return max num if *ALL* DAIs have .auto_selectable_formats | |
144 | */ | |
145 | for_each_rtd_dais(rtd, i, dai) { | |
146 | if (dai->driver->ops && | |
147 | dai->driver->ops->num_auto_selectable_formats) | |
148 | max = max(max, dai->driver->ops->num_auto_selectable_formats); | |
149 | else | |
150 | return 0; | |
151 | } | |
152 | ||
153 | return max; | |
154 | } | |
155 | ||
156 | /** | |
157 | * snd_soc_dai_get_fmt - get supported audio format. | |
158 | * @dai: DAI | |
159 | * @priority: priority level of supported audio format. | |
160 | * | |
161 | * This should return only formats implemented with high | |
162 | * quality by the DAI so that the core can configure a | |
163 | * format which will work well with other devices. | |
164 | * For example devices which don't support both edges of the | |
165 | * LRCLK signal in I2S style formats should only list DSP | |
166 | * modes. This will mean that sometimes fewer formats | |
167 | * are reported here than are supported by set_fmt(). | |
168 | */ | |
169 | u64 snd_soc_dai_get_fmt(struct snd_soc_dai *dai, int priority) | |
170 | { | |
171 | const struct snd_soc_dai_ops *ops = dai->driver->ops; | |
172 | u64 fmt = 0; | |
173 | int i, max = 0, until = priority; | |
174 | ||
175 | /* | |
176 | * Collect auto_selectable_formats until priority | |
177 | * | |
178 | * ex) | |
179 | * auto_selectable_formats[] = { A, B, C }; | |
180 | * (A, B, C = SND_SOC_POSSIBLE_DAIFMT_xxx) | |
181 | * | |
182 | * priority = 1 : A | |
183 | * priority = 2 : A | B | |
184 | * priority = 3 : A | B | C | |
185 | * priority = 4 : A | B | C | |
186 | * ... | |
187 | */ | |
188 | if (ops) | |
189 | max = ops->num_auto_selectable_formats; | |
190 | ||
191 | if (max < until) | |
192 | until = max; | |
193 | ||
194 | for (i = 0; i < until; i++) | |
195 | fmt |= ops->auto_selectable_formats[i]; | |
196 | ||
197 | return fmt; | |
198 | } | |
199 | ||
06f6e1d4 KM |
200 | /** |
201 | * snd_soc_dai_set_fmt - configure DAI hardware audio format. | |
202 | * @dai: DAI | |
203 | * @fmt: SND_SOC_DAIFMT_* format value. | |
204 | * | |
205 | * Configures the DAI hardware format and clocking. | |
206 | */ | |
207 | int snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) | |
208 | { | |
aa7b8230 KM |
209 | int ret = -ENOTSUPP; |
210 | ||
19423951 | 211 | if (dai->driver->ops && dai->driver->ops->set_fmt) |
aa7b8230 KM |
212 | ret = dai->driver->ops->set_fmt(dai, fmt); |
213 | ||
214 | return soc_dai_ret(dai, ret); | |
06f6e1d4 KM |
215 | } |
216 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_fmt); | |
217 | ||
218 | /** | |
2fb87110 | 219 | * snd_soc_xlate_tdm_slot_mask - generate tx/rx slot mask. |
06f6e1d4 KM |
220 | * @slots: Number of slots in use. |
221 | * @tx_mask: bitmask representing active TX slots. | |
222 | * @rx_mask: bitmask representing active RX slots. | |
223 | * | |
224 | * Generates the TDM tx and rx slot default masks for DAI. | |
225 | */ | |
226 | static int snd_soc_xlate_tdm_slot_mask(unsigned int slots, | |
227 | unsigned int *tx_mask, | |
228 | unsigned int *rx_mask) | |
229 | { | |
230 | if (*tx_mask || *rx_mask) | |
231 | return 0; | |
232 | ||
233 | if (!slots) | |
234 | return -EINVAL; | |
235 | ||
236 | *tx_mask = (1 << slots) - 1; | |
237 | *rx_mask = (1 << slots) - 1; | |
238 | ||
239 | return 0; | |
240 | } | |
241 | ||
242 | /** | |
243 | * snd_soc_dai_set_tdm_slot() - Configures a DAI for TDM operation | |
244 | * @dai: The DAI to configure | |
245 | * @tx_mask: bitmask representing active TX slots. | |
246 | * @rx_mask: bitmask representing active RX slots. | |
247 | * @slots: Number of slots in use. | |
248 | * @slot_width: Width in bits for each slot. | |
249 | * | |
250 | * This function configures the specified DAI for TDM operation. @slot contains | |
251 | * the total number of slots of the TDM stream and @slot_with the width of each | |
252 | * slot in bit clock cycles. @tx_mask and @rx_mask are bitmasks specifying the | |
253 | * active slots of the TDM stream for the specified DAI, i.e. which slots the | |
254 | * DAI should write to or read from. If a bit is set the corresponding slot is | |
255 | * active, if a bit is cleared the corresponding slot is inactive. Bit 0 maps to | |
256 | * the first slot, bit 1 to the second slot and so on. The first active slot | |
257 | * maps to the first channel of the DAI, the second active slot to the second | |
258 | * channel and so on. | |
259 | * | |
260 | * TDM mode can be disabled by passing 0 for @slots. In this case @tx_mask, | |
261 | * @rx_mask and @slot_width will be ignored. | |
262 | * | |
263 | * Returns 0 on success, a negative error code otherwise. | |
264 | */ | |
265 | int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai, | |
266 | unsigned int tx_mask, unsigned int rx_mask, | |
267 | int slots, int slot_width) | |
268 | { | |
aa7b8230 | 269 | int ret = -ENOTSUPP; |
8ede4b71 KM |
270 | int stream; |
271 | unsigned int *tdm_mask[] = { | |
272 | &tx_mask, | |
273 | &rx_mask, | |
274 | }; | |
aa7b8230 | 275 | |
479914ed KM |
276 | if (dai->driver->ops && |
277 | dai->driver->ops->xlate_tdm_slot_mask) | |
06f6e1d4 KM |
278 | dai->driver->ops->xlate_tdm_slot_mask(slots, |
279 | &tx_mask, &rx_mask); | |
280 | else | |
281 | snd_soc_xlate_tdm_slot_mask(slots, &tx_mask, &rx_mask); | |
282 | ||
8ede4b71 KM |
283 | for_each_pcm_streams(stream) |
284 | snd_soc_dai_tdm_mask_set(dai, stream, *tdm_mask[stream]); | |
06f6e1d4 | 285 | |
479914ed KM |
286 | if (dai->driver->ops && |
287 | dai->driver->ops->set_tdm_slot) | |
aa7b8230 | 288 | ret = dai->driver->ops->set_tdm_slot(dai, tx_mask, rx_mask, |
06f6e1d4 | 289 | slots, slot_width); |
aa7b8230 | 290 | return soc_dai_ret(dai, ret); |
06f6e1d4 KM |
291 | } |
292 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_tdm_slot); | |
293 | ||
294 | /** | |
295 | * snd_soc_dai_set_channel_map - configure DAI audio channel map | |
296 | * @dai: DAI | |
297 | * @tx_num: how many TX channels | |
298 | * @tx_slot: pointer to an array which imply the TX slot number channel | |
299 | * 0~num-1 uses | |
300 | * @rx_num: how many RX channels | |
301 | * @rx_slot: pointer to an array which imply the RX slot number channel | |
302 | * 0~num-1 uses | |
303 | * | |
304 | * configure the relationship between channel number and TDM slot number. | |
305 | */ | |
306 | int snd_soc_dai_set_channel_map(struct snd_soc_dai *dai, | |
307 | unsigned int tx_num, unsigned int *tx_slot, | |
308 | unsigned int rx_num, unsigned int *rx_slot) | |
309 | { | |
aa7b8230 KM |
310 | int ret = -ENOTSUPP; |
311 | ||
479914ed KM |
312 | if (dai->driver->ops && |
313 | dai->driver->ops->set_channel_map) | |
aa7b8230 KM |
314 | ret = dai->driver->ops->set_channel_map(dai, tx_num, tx_slot, |
315 | rx_num, rx_slot); | |
316 | return soc_dai_ret(dai, ret); | |
06f6e1d4 KM |
317 | } |
318 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_channel_map); | |
319 | ||
320 | /** | |
321 | * snd_soc_dai_get_channel_map - Get DAI audio channel map | |
322 | * @dai: DAI | |
323 | * @tx_num: how many TX channels | |
324 | * @tx_slot: pointer to an array which imply the TX slot number channel | |
325 | * 0~num-1 uses | |
326 | * @rx_num: how many RX channels | |
327 | * @rx_slot: pointer to an array which imply the RX slot number channel | |
328 | * 0~num-1 uses | |
329 | */ | |
330 | int snd_soc_dai_get_channel_map(struct snd_soc_dai *dai, | |
331 | unsigned int *tx_num, unsigned int *tx_slot, | |
332 | unsigned int *rx_num, unsigned int *rx_slot) | |
333 | { | |
aa7b8230 KM |
334 | int ret = -ENOTSUPP; |
335 | ||
479914ed KM |
336 | if (dai->driver->ops && |
337 | dai->driver->ops->get_channel_map) | |
aa7b8230 KM |
338 | ret = dai->driver->ops->get_channel_map(dai, tx_num, tx_slot, |
339 | rx_num, rx_slot); | |
340 | return soc_dai_ret(dai, ret); | |
06f6e1d4 KM |
341 | } |
342 | EXPORT_SYMBOL_GPL(snd_soc_dai_get_channel_map); | |
343 | ||
344 | /** | |
345 | * snd_soc_dai_set_tristate - configure DAI system or master clock. | |
346 | * @dai: DAI | |
347 | * @tristate: tristate enable | |
348 | * | |
349 | * Tristates the DAI so that others can use it. | |
350 | */ | |
351 | int snd_soc_dai_set_tristate(struct snd_soc_dai *dai, int tristate) | |
352 | { | |
aa7b8230 KM |
353 | int ret = -EINVAL; |
354 | ||
479914ed KM |
355 | if (dai->driver->ops && |
356 | dai->driver->ops->set_tristate) | |
aa7b8230 KM |
357 | ret = dai->driver->ops->set_tristate(dai, tristate); |
358 | ||
359 | return soc_dai_ret(dai, ret); | |
06f6e1d4 KM |
360 | } |
361 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_tristate); | |
362 | ||
363 | /** | |
364 | * snd_soc_dai_digital_mute - configure DAI system or master clock. | |
365 | * @dai: DAI | |
366 | * @mute: mute enable | |
367 | * @direction: stream to mute | |
368 | * | |
369 | * Mutes the DAI DAC. | |
370 | */ | |
371 | int snd_soc_dai_digital_mute(struct snd_soc_dai *dai, int mute, | |
372 | int direction) | |
373 | { | |
aa7b8230 KM |
374 | int ret = -ENOTSUPP; |
375 | ||
350d9935 KM |
376 | /* |
377 | * ignore if direction was CAPTURE | |
378 | * and it had .no_capture_mute flag | |
379 | */ | |
479914ed | 380 | if (dai->driver->ops && |
350d9935 KM |
381 | dai->driver->ops->mute_stream && |
382 | (direction == SNDRV_PCM_STREAM_PLAYBACK || | |
383 | !dai->driver->ops->no_capture_mute)) | |
aa7b8230 | 384 | ret = dai->driver->ops->mute_stream(dai, mute, direction); |
aa7b8230 KM |
385 | |
386 | return soc_dai_ret(dai, ret); | |
06f6e1d4 KM |
387 | } |
388 | EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute); | |
aa6166c2 KM |
389 | |
390 | int snd_soc_dai_hw_params(struct snd_soc_dai *dai, | |
391 | struct snd_pcm_substream *substream, | |
392 | struct snd_pcm_hw_params *params) | |
393 | { | |
aa7b8230 | 394 | int ret = 0; |
aa6166c2 | 395 | |
479914ed | 396 | if (dai->driver->ops && |
3115be55 | 397 | dai->driver->ops->hw_params) |
aa6166c2 | 398 | ret = dai->driver->ops->hw_params(substream, params, dai); |
c304c9ac KM |
399 | |
400 | /* mark substream if succeeded */ | |
401 | if (ret == 0) | |
402 | soc_dai_mark_push(dai, substream, hw_params); | |
3115be55 | 403 | |
aa7b8230 | 404 | return soc_dai_ret(dai, ret); |
aa6166c2 | 405 | } |
846faaed KM |
406 | |
407 | void snd_soc_dai_hw_free(struct snd_soc_dai *dai, | |
c304c9ac KM |
408 | struct snd_pcm_substream *substream, |
409 | int rollback) | |
846faaed | 410 | { |
c304c9ac KM |
411 | if (rollback && !soc_dai_mark_match(dai, substream, hw_params)) |
412 | return; | |
413 | ||
479914ed KM |
414 | if (dai->driver->ops && |
415 | dai->driver->ops->hw_free) | |
846faaed | 416 | dai->driver->ops->hw_free(substream, dai); |
c304c9ac KM |
417 | |
418 | /* remove marked substream */ | |
419 | soc_dai_mark_pop(dai, substream, hw_params); | |
846faaed | 420 | } |
5a52a045 KM |
421 | |
422 | int snd_soc_dai_startup(struct snd_soc_dai *dai, | |
423 | struct snd_pcm_substream *substream) | |
424 | { | |
425 | int ret = 0; | |
426 | ||
4005d1ba PS |
427 | if (!snd_soc_dai_stream_valid(dai, substream->stream)) |
428 | return 0; | |
429 | ||
479914ed KM |
430 | if (dai->driver->ops && |
431 | dai->driver->ops->startup) | |
5a52a045 KM |
432 | ret = dai->driver->ops->startup(substream, dai); |
433 | ||
00a0b46c KM |
434 | /* mark substream if succeeded */ |
435 | if (ret == 0) | |
436 | soc_dai_mark_push(dai, substream, startup); | |
437 | ||
aa7b8230 | 438 | return soc_dai_ret(dai, ret); |
5a52a045 | 439 | } |
330fcb51 KM |
440 | |
441 | void snd_soc_dai_shutdown(struct snd_soc_dai *dai, | |
00a0b46c KM |
442 | struct snd_pcm_substream *substream, |
443 | int rollback) | |
330fcb51 | 444 | { |
4005d1ba PS |
445 | if (!snd_soc_dai_stream_valid(dai, substream->stream)) |
446 | return; | |
447 | ||
00a0b46c KM |
448 | if (rollback && !soc_dai_mark_match(dai, substream, startup)) |
449 | return; | |
450 | ||
479914ed KM |
451 | if (dai->driver->ops && |
452 | dai->driver->ops->shutdown) | |
330fcb51 | 453 | dai->driver->ops->shutdown(substream, dai); |
00a0b46c KM |
454 | |
455 | /* remove marked substream */ | |
456 | soc_dai_mark_pop(dai, substream, startup); | |
330fcb51 | 457 | } |
4beb8e10 | 458 | |
b423c420 KM |
459 | int snd_soc_dai_compress_new(struct snd_soc_dai *dai, |
460 | struct snd_soc_pcm_runtime *rtd, int num) | |
461 | { | |
aa7b8230 | 462 | int ret = -ENOTSUPP; |
624fee45 KM |
463 | if (dai->driver->ops && |
464 | dai->driver->ops->compress_new) | |
465 | ret = dai->driver->ops->compress_new(rtd, num); | |
aa7b8230 | 466 | return soc_dai_ret(dai, ret); |
b423c420 | 467 | } |
467fece8 KM |
468 | |
469 | /* | |
470 | * snd_soc_dai_stream_valid() - check if a DAI supports the given stream | |
471 | * | |
472 | * Returns true if the DAI supports the indicated stream type. | |
473 | */ | |
474 | bool snd_soc_dai_stream_valid(struct snd_soc_dai *dai, int dir) | |
475 | { | |
acf253c1 | 476 | struct snd_soc_pcm_stream *stream = snd_soc_dai_get_pcm_stream(dai, dir); |
467fece8 KM |
477 | |
478 | /* If the codec specifies any channels at all, it supports the stream */ | |
479 | return stream->channels_min; | |
480 | } | |
0b73ba55 | 481 | |
25612477 PLB |
482 | /* |
483 | * snd_soc_dai_link_set_capabilities() - set dai_link properties based on its DAIs | |
484 | */ | |
485 | void snd_soc_dai_link_set_capabilities(struct snd_soc_dai_link *dai_link) | |
486 | { | |
25612477 PLB |
487 | bool supported[SNDRV_PCM_STREAM_LAST + 1]; |
488 | int direction; | |
25612477 PLB |
489 | |
490 | for_each_pcm_streams(direction) { | |
d490f4e7 KM |
491 | struct snd_soc_dai_link_component *cpu; |
492 | struct snd_soc_dai_link_component *codec; | |
493 | struct snd_soc_dai *dai; | |
494 | bool supported_cpu = false; | |
495 | bool supported_codec = false; | |
496 | int i; | |
25612477 PLB |
497 | |
498 | for_each_link_cpus(dai_link, i, cpu) { | |
c1c277b2 | 499 | dai = snd_soc_find_dai_with_mutex(cpu); |
4f872154 PLB |
500 | if (dai && snd_soc_dai_stream_valid(dai, direction)) { |
501 | supported_cpu = true; | |
25612477 PLB |
502 | break; |
503 | } | |
504 | } | |
25612477 | 505 | for_each_link_codecs(dai_link, i, codec) { |
c1c277b2 | 506 | dai = snd_soc_find_dai_with_mutex(codec); |
4f872154 PLB |
507 | if (dai && snd_soc_dai_stream_valid(dai, direction)) { |
508 | supported_codec = true; | |
25612477 PLB |
509 | break; |
510 | } | |
511 | } | |
4f872154 | 512 | supported[direction] = supported_cpu && supported_codec; |
25612477 PLB |
513 | } |
514 | ||
515 | dai_link->dpcm_playback = supported[SNDRV_PCM_STREAM_PLAYBACK]; | |
516 | dai_link->dpcm_capture = supported[SNDRV_PCM_STREAM_CAPTURE]; | |
517 | } | |
518 | EXPORT_SYMBOL_GPL(snd_soc_dai_link_set_capabilities); | |
519 | ||
dc829106 KM |
520 | void snd_soc_dai_action(struct snd_soc_dai *dai, |
521 | int stream, int action) | |
522 | { | |
5552f8d7 | 523 | /* see snd_soc_dai_stream_active() */ |
3653480c | 524 | dai->stream[stream].active += action; |
0812a08a | 525 | |
488b2ca5 | 526 | /* see snd_soc_component_active() */ |
dc829106 KM |
527 | dai->component->active += action; |
528 | } | |
529 | EXPORT_SYMBOL_GPL(snd_soc_dai_action); | |
530 | ||
efffd9b3 KM |
531 | int snd_soc_dai_active(struct snd_soc_dai *dai) |
532 | { | |
533 | int stream, active; | |
534 | ||
535 | active = 0; | |
536 | for_each_pcm_streams(stream) | |
3653480c | 537 | active += dai->stream[stream].active; |
efffd9b3 KM |
538 | |
539 | return active; | |
540 | } | |
541 | EXPORT_SYMBOL_GPL(snd_soc_dai_active); | |
542 | ||
51801aea KM |
543 | int snd_soc_pcm_dai_probe(struct snd_soc_pcm_runtime *rtd, int order) |
544 | { | |
545 | struct snd_soc_dai *dai; | |
546 | int i; | |
547 | ||
548 | for_each_rtd_dais(rtd, i, dai) { | |
5c5a7521 KM |
549 | if (dai->probed) |
550 | continue; | |
551 | ||
624fee45 KM |
552 | if (dai->driver->ops) { |
553 | if (dai->driver->ops->probe_order != order) | |
554 | continue; | |
51801aea | 555 | |
624fee45 KM |
556 | if (dai->driver->ops->probe) { |
557 | int ret = dai->driver->ops->probe(dai); | |
51801aea | 558 | |
624fee45 KM |
559 | if (ret < 0) |
560 | return soc_dai_ret(dai, ret); | |
561 | } | |
562 | } | |
51801aea KM |
563 | dai->probed = 1; |
564 | } | |
565 | ||
566 | return 0; | |
567 | } | |
568 | ||
7eaa313b KM |
569 | int snd_soc_pcm_dai_remove(struct snd_soc_pcm_runtime *rtd, int order) |
570 | { | |
571 | struct snd_soc_dai *dai; | |
572 | int i, r, ret = 0; | |
573 | ||
574 | for_each_rtd_dais(rtd, i, dai) { | |
624fee45 | 575 | if (!dai->probed) |
7eaa313b KM |
576 | continue; |
577 | ||
624fee45 KM |
578 | if (dai->driver->ops) { |
579 | if (dai->driver->ops->remove_order != order) | |
580 | continue; | |
7eaa313b | 581 | |
624fee45 KM |
582 | if (dai->driver->ops->remove) { |
583 | r = dai->driver->ops->remove(dai); | |
584 | if (r < 0) | |
585 | ret = r; /* use last error */ | |
586 | } | |
587 | } | |
7eaa313b KM |
588 | dai->probed = 0; |
589 | } | |
590 | ||
591 | return ret; | |
592 | } | |
593 | ||
0b73ba55 KM |
594 | int snd_soc_pcm_dai_new(struct snd_soc_pcm_runtime *rtd) |
595 | { | |
596 | struct snd_soc_dai *dai; | |
454a7422 | 597 | int i; |
0b73ba55 KM |
598 | |
599 | for_each_rtd_dais(rtd, i, dai) { | |
624fee45 KM |
600 | if (dai->driver->ops && |
601 | dai->driver->ops->pcm_new) { | |
602 | int ret = dai->driver->ops->pcm_new(rtd, dai); | |
0b73ba55 KM |
603 | if (ret < 0) |
604 | return soc_dai_ret(dai, ret); | |
605 | } | |
606 | } | |
607 | ||
608 | return 0; | |
609 | } | |
d108c7fd KM |
610 | |
611 | int snd_soc_pcm_dai_prepare(struct snd_pcm_substream *substream) | |
612 | { | |
52d98d06 | 613 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
d108c7fd KM |
614 | struct snd_soc_dai *dai; |
615 | int i, ret; | |
616 | ||
617 | for_each_rtd_dais(rtd, i, dai) { | |
4005d1ba PS |
618 | if (!snd_soc_dai_stream_valid(dai, substream->stream)) |
619 | continue; | |
d108c7fd KM |
620 | if (dai->driver->ops && |
621 | dai->driver->ops->prepare) { | |
622 | ret = dai->driver->ops->prepare(substream, dai); | |
623 | if (ret < 0) | |
624 | return soc_dai_ret(dai, ret); | |
625 | } | |
626 | } | |
627 | ||
628 | return 0; | |
629 | } | |
42f2472d | 630 | |
6374f493 KM |
631 | static int soc_dai_trigger(struct snd_soc_dai *dai, |
632 | struct snd_pcm_substream *substream, int cmd) | |
633 | { | |
634 | int ret = 0; | |
635 | ||
4005d1ba PS |
636 | if (!snd_soc_dai_stream_valid(dai, substream->stream)) |
637 | return 0; | |
638 | ||
6374f493 KM |
639 | if (dai->driver->ops && |
640 | dai->driver->ops->trigger) | |
641 | ret = dai->driver->ops->trigger(substream, cmd, dai); | |
642 | ||
643 | return soc_dai_ret(dai, ret); | |
644 | } | |
645 | ||
42f2472d | 646 | int snd_soc_pcm_dai_trigger(struct snd_pcm_substream *substream, |
6374f493 | 647 | int cmd, int rollback) |
42f2472d | 648 | { |
52d98d06 | 649 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
42f2472d | 650 | struct snd_soc_dai *dai; |
6374f493 | 651 | int i, r, ret = 0; |
42f2472d | 652 | |
6374f493 KM |
653 | switch (cmd) { |
654 | case SNDRV_PCM_TRIGGER_START: | |
655 | case SNDRV_PCM_TRIGGER_RESUME: | |
656 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | |
657 | for_each_rtd_dais(rtd, i, dai) { | |
658 | ret = soc_dai_trigger(dai, substream, cmd); | |
42f2472d | 659 | if (ret < 0) |
6374f493 | 660 | break; |
f0220575 SK |
661 | |
662 | if (dai->driver->ops && dai->driver->ops->mute_unmute_on_trigger) | |
663 | snd_soc_dai_digital_mute(dai, 0, substream->stream); | |
664 | ||
6374f493 KM |
665 | soc_dai_mark_push(dai, substream, trigger); |
666 | } | |
667 | break; | |
668 | case SNDRV_PCM_TRIGGER_STOP: | |
669 | case SNDRV_PCM_TRIGGER_SUSPEND: | |
670 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | |
671 | for_each_rtd_dais(rtd, i, dai) { | |
672 | if (rollback && !soc_dai_mark_match(dai, substream, trigger)) | |
673 | continue; | |
674 | ||
f0220575 SK |
675 | if (dai->driver->ops && dai->driver->ops->mute_unmute_on_trigger) |
676 | snd_soc_dai_digital_mute(dai, 1, substream->stream); | |
677 | ||
6374f493 KM |
678 | r = soc_dai_trigger(dai, substream, cmd); |
679 | if (r < 0) | |
680 | ret = r; /* use last ret */ | |
681 | soc_dai_mark_pop(dai, substream, trigger); | |
42f2472d KM |
682 | } |
683 | } | |
684 | ||
6374f493 | 685 | return ret; |
42f2472d | 686 | } |
30819358 KM |
687 | |
688 | int snd_soc_pcm_dai_bespoke_trigger(struct snd_pcm_substream *substream, | |
689 | int cmd) | |
690 | { | |
52d98d06 | 691 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
30819358 KM |
692 | struct snd_soc_dai *dai; |
693 | int i, ret; | |
694 | ||
695 | for_each_rtd_dais(rtd, i, dai) { | |
696 | if (dai->driver->ops && | |
697 | dai->driver->ops->bespoke_trigger) { | |
698 | ret = dai->driver->ops->bespoke_trigger(substream, | |
699 | cmd, dai); | |
700 | if (ret < 0) | |
701 | return soc_dai_ret(dai, ret); | |
702 | } | |
703 | } | |
704 | ||
705 | return 0; | |
706 | } | |
b5ae4cce | 707 | |
8544f08c KM |
708 | void snd_soc_pcm_dai_delay(struct snd_pcm_substream *substream, |
709 | snd_pcm_sframes_t *cpu_delay, | |
710 | snd_pcm_sframes_t *codec_delay) | |
711 | { | |
52d98d06 | 712 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
8544f08c KM |
713 | struct snd_soc_dai *dai; |
714 | int i; | |
715 | ||
716 | /* | |
717 | * We're looking for the delay through the full audio path so it needs to | |
718 | * be the maximum of the DAIs doing transmit and the maximum of the DAIs | |
719 | * doing receive (ie, all CPUs and all CODECs) rather than just the maximum | |
720 | * of all DAIs. | |
721 | */ | |
722 | ||
723 | /* for CPU */ | |
724 | for_each_rtd_cpu_dais(rtd, i, dai) | |
725 | if (dai->driver->ops && | |
726 | dai->driver->ops->delay) | |
727 | *cpu_delay = max(*cpu_delay, dai->driver->ops->delay(substream, dai)); | |
728 | ||
729 | /* for Codec */ | |
730 | for_each_rtd_codec_dais(rtd, i, dai) | |
731 | if (dai->driver->ops && | |
732 | dai->driver->ops->delay) | |
733 | *codec_delay = max(*codec_delay, dai->driver->ops->delay(substream, dai)); | |
734 | } | |
735 | ||
b5ae4cce KM |
736 | int snd_soc_dai_compr_startup(struct snd_soc_dai *dai, |
737 | struct snd_compr_stream *cstream) | |
738 | { | |
739 | int ret = 0; | |
740 | ||
741 | if (dai->driver->cops && | |
742 | dai->driver->cops->startup) | |
743 | ret = dai->driver->cops->startup(cstream, dai); | |
744 | ||
1e6a93cf KM |
745 | /* mark cstream if succeeded */ |
746 | if (ret == 0) | |
747 | soc_dai_mark_push(dai, cstream, compr_startup); | |
748 | ||
b5ae4cce KM |
749 | return soc_dai_ret(dai, ret); |
750 | } | |
751 | EXPORT_SYMBOL_GPL(snd_soc_dai_compr_startup); | |
2b25f81d KM |
752 | |
753 | void snd_soc_dai_compr_shutdown(struct snd_soc_dai *dai, | |
1e6a93cf KM |
754 | struct snd_compr_stream *cstream, |
755 | int rollback) | |
2b25f81d | 756 | { |
1e6a93cf KM |
757 | if (rollback && !soc_dai_mark_match(dai, cstream, compr_startup)) |
758 | return; | |
759 | ||
2b25f81d KM |
760 | if (dai->driver->cops && |
761 | dai->driver->cops->shutdown) | |
762 | dai->driver->cops->shutdown(cstream, dai); | |
1e6a93cf KM |
763 | |
764 | /* remove marked cstream */ | |
765 | soc_dai_mark_pop(dai, cstream, compr_startup); | |
2b25f81d KM |
766 | } |
767 | EXPORT_SYMBOL_GPL(snd_soc_dai_compr_shutdown); | |
eb08411b KM |
768 | |
769 | int snd_soc_dai_compr_trigger(struct snd_soc_dai *dai, | |
770 | struct snd_compr_stream *cstream, int cmd) | |
771 | { | |
772 | int ret = 0; | |
773 | ||
774 | if (dai->driver->cops && | |
775 | dai->driver->cops->trigger) | |
776 | ret = dai->driver->cops->trigger(cstream, cmd, dai); | |
777 | ||
778 | return soc_dai_ret(dai, ret); | |
779 | } | |
780 | EXPORT_SYMBOL_GPL(snd_soc_dai_compr_trigger); | |
8dfedafb KM |
781 | |
782 | int snd_soc_dai_compr_set_params(struct snd_soc_dai *dai, | |
783 | struct snd_compr_stream *cstream, | |
784 | struct snd_compr_params *params) | |
785 | { | |
786 | int ret = 0; | |
787 | ||
788 | if (dai->driver->cops && | |
789 | dai->driver->cops->set_params) | |
790 | ret = dai->driver->cops->set_params(cstream, params, dai); | |
791 | ||
792 | return soc_dai_ret(dai, ret); | |
793 | } | |
794 | EXPORT_SYMBOL_GPL(snd_soc_dai_compr_set_params); | |
adbef543 KM |
795 | |
796 | int snd_soc_dai_compr_get_params(struct snd_soc_dai *dai, | |
797 | struct snd_compr_stream *cstream, | |
798 | struct snd_codec *params) | |
799 | { | |
800 | int ret = 0; | |
801 | ||
802 | if (dai->driver->cops && | |
803 | dai->driver->cops->get_params) | |
804 | ret = dai->driver->cops->get_params(cstream, params, dai); | |
805 | ||
806 | return soc_dai_ret(dai, ret); | |
807 | } | |
808 | EXPORT_SYMBOL_GPL(snd_soc_dai_compr_get_params); | |
53294353 KM |
809 | |
810 | int snd_soc_dai_compr_ack(struct snd_soc_dai *dai, | |
811 | struct snd_compr_stream *cstream, | |
812 | size_t bytes) | |
813 | { | |
814 | int ret = 0; | |
815 | ||
816 | if (dai->driver->cops && | |
817 | dai->driver->cops->ack) | |
818 | ret = dai->driver->cops->ack(cstream, bytes, dai); | |
819 | ||
820 | return soc_dai_ret(dai, ret); | |
821 | } | |
822 | EXPORT_SYMBOL_GPL(snd_soc_dai_compr_ack); | |
ed38cc59 KM |
823 | |
824 | int snd_soc_dai_compr_pointer(struct snd_soc_dai *dai, | |
825 | struct snd_compr_stream *cstream, | |
826 | struct snd_compr_tstamp *tstamp) | |
827 | { | |
828 | int ret = 0; | |
829 | ||
830 | if (dai->driver->cops && | |
831 | dai->driver->cops->pointer) | |
832 | ret = dai->driver->cops->pointer(cstream, tstamp, dai); | |
833 | ||
834 | return soc_dai_ret(dai, ret); | |
835 | } | |
836 | EXPORT_SYMBOL_GPL(snd_soc_dai_compr_pointer); | |
88b3a7df KM |
837 | |
838 | int snd_soc_dai_compr_set_metadata(struct snd_soc_dai *dai, | |
839 | struct snd_compr_stream *cstream, | |
840 | struct snd_compr_metadata *metadata) | |
841 | { | |
842 | int ret = 0; | |
843 | ||
844 | if (dai->driver->cops && | |
845 | dai->driver->cops->set_metadata) | |
846 | ret = dai->driver->cops->set_metadata(cstream, metadata, dai); | |
847 | ||
848 | return soc_dai_ret(dai, ret); | |
849 | } | |
850 | EXPORT_SYMBOL_GPL(snd_soc_dai_compr_set_metadata); | |
94d72819 KM |
851 | |
852 | int snd_soc_dai_compr_get_metadata(struct snd_soc_dai *dai, | |
853 | struct snd_compr_stream *cstream, | |
854 | struct snd_compr_metadata *metadata) | |
855 | { | |
856 | int ret = 0; | |
857 | ||
858 | if (dai->driver->cops && | |
859 | dai->driver->cops->get_metadata) | |
860 | ret = dai->driver->cops->get_metadata(cstream, metadata, dai); | |
861 | ||
862 | return soc_dai_ret(dai, ret); | |
863 | } | |
864 | EXPORT_SYMBOL_GPL(snd_soc_dai_compr_get_metadata); |