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 | { | |
aa7b8230 KM |
127 | int ret = -EINVAL; |
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 | ||
479914ed KM |
211 | if (dai->driver->ops && |
212 | dai->driver->ops->set_fmt) | |
aa7b8230 KM |
213 | ret = dai->driver->ops->set_fmt(dai, fmt); |
214 | ||
215 | return soc_dai_ret(dai, ret); | |
06f6e1d4 KM |
216 | } |
217 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_fmt); | |
218 | ||
219 | /** | |
2fb87110 | 220 | * snd_soc_xlate_tdm_slot_mask - generate tx/rx slot mask. |
06f6e1d4 KM |
221 | * @slots: Number of slots in use. |
222 | * @tx_mask: bitmask representing active TX slots. | |
223 | * @rx_mask: bitmask representing active RX slots. | |
224 | * | |
225 | * Generates the TDM tx and rx slot default masks for DAI. | |
226 | */ | |
227 | static int snd_soc_xlate_tdm_slot_mask(unsigned int slots, | |
228 | unsigned int *tx_mask, | |
229 | unsigned int *rx_mask) | |
230 | { | |
231 | if (*tx_mask || *rx_mask) | |
232 | return 0; | |
233 | ||
234 | if (!slots) | |
235 | return -EINVAL; | |
236 | ||
237 | *tx_mask = (1 << slots) - 1; | |
238 | *rx_mask = (1 << slots) - 1; | |
239 | ||
240 | return 0; | |
241 | } | |
242 | ||
243 | /** | |
244 | * snd_soc_dai_set_tdm_slot() - Configures a DAI for TDM operation | |
245 | * @dai: The DAI to configure | |
246 | * @tx_mask: bitmask representing active TX slots. | |
247 | * @rx_mask: bitmask representing active RX slots. | |
248 | * @slots: Number of slots in use. | |
249 | * @slot_width: Width in bits for each slot. | |
250 | * | |
251 | * This function configures the specified DAI for TDM operation. @slot contains | |
252 | * the total number of slots of the TDM stream and @slot_with the width of each | |
253 | * slot in bit clock cycles. @tx_mask and @rx_mask are bitmasks specifying the | |
254 | * active slots of the TDM stream for the specified DAI, i.e. which slots the | |
255 | * DAI should write to or read from. If a bit is set the corresponding slot is | |
256 | * active, if a bit is cleared the corresponding slot is inactive. Bit 0 maps to | |
257 | * the first slot, bit 1 to the second slot and so on. The first active slot | |
258 | * maps to the first channel of the DAI, the second active slot to the second | |
259 | * channel and so on. | |
260 | * | |
261 | * TDM mode can be disabled by passing 0 for @slots. In this case @tx_mask, | |
262 | * @rx_mask and @slot_width will be ignored. | |
263 | * | |
264 | * Returns 0 on success, a negative error code otherwise. | |
265 | */ | |
266 | int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai, | |
267 | unsigned int tx_mask, unsigned int rx_mask, | |
268 | int slots, int slot_width) | |
269 | { | |
aa7b8230 KM |
270 | int ret = -ENOTSUPP; |
271 | ||
479914ed KM |
272 | if (dai->driver->ops && |
273 | dai->driver->ops->xlate_tdm_slot_mask) | |
06f6e1d4 KM |
274 | dai->driver->ops->xlate_tdm_slot_mask(slots, |
275 | &tx_mask, &rx_mask); | |
276 | else | |
277 | snd_soc_xlate_tdm_slot_mask(slots, &tx_mask, &rx_mask); | |
278 | ||
279 | dai->tx_mask = tx_mask; | |
280 | dai->rx_mask = rx_mask; | |
281 | ||
479914ed KM |
282 | if (dai->driver->ops && |
283 | dai->driver->ops->set_tdm_slot) | |
aa7b8230 | 284 | ret = dai->driver->ops->set_tdm_slot(dai, tx_mask, rx_mask, |
06f6e1d4 | 285 | slots, slot_width); |
aa7b8230 | 286 | return soc_dai_ret(dai, ret); |
06f6e1d4 KM |
287 | } |
288 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_tdm_slot); | |
289 | ||
290 | /** | |
291 | * snd_soc_dai_set_channel_map - configure DAI audio channel map | |
292 | * @dai: DAI | |
293 | * @tx_num: how many TX channels | |
294 | * @tx_slot: pointer to an array which imply the TX slot number channel | |
295 | * 0~num-1 uses | |
296 | * @rx_num: how many RX channels | |
297 | * @rx_slot: pointer to an array which imply the RX slot number channel | |
298 | * 0~num-1 uses | |
299 | * | |
300 | * configure the relationship between channel number and TDM slot number. | |
301 | */ | |
302 | int snd_soc_dai_set_channel_map(struct snd_soc_dai *dai, | |
303 | unsigned int tx_num, unsigned int *tx_slot, | |
304 | unsigned int rx_num, unsigned int *rx_slot) | |
305 | { | |
aa7b8230 KM |
306 | int ret = -ENOTSUPP; |
307 | ||
479914ed KM |
308 | if (dai->driver->ops && |
309 | dai->driver->ops->set_channel_map) | |
aa7b8230 KM |
310 | ret = dai->driver->ops->set_channel_map(dai, tx_num, tx_slot, |
311 | rx_num, rx_slot); | |
312 | return soc_dai_ret(dai, ret); | |
06f6e1d4 KM |
313 | } |
314 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_channel_map); | |
315 | ||
316 | /** | |
317 | * snd_soc_dai_get_channel_map - Get DAI audio channel map | |
318 | * @dai: DAI | |
319 | * @tx_num: how many TX channels | |
320 | * @tx_slot: pointer to an array which imply the TX slot number channel | |
321 | * 0~num-1 uses | |
322 | * @rx_num: how many RX channels | |
323 | * @rx_slot: pointer to an array which imply the RX slot number channel | |
324 | * 0~num-1 uses | |
325 | */ | |
326 | int snd_soc_dai_get_channel_map(struct snd_soc_dai *dai, | |
327 | unsigned int *tx_num, unsigned int *tx_slot, | |
328 | unsigned int *rx_num, unsigned int *rx_slot) | |
329 | { | |
aa7b8230 KM |
330 | int ret = -ENOTSUPP; |
331 | ||
479914ed KM |
332 | if (dai->driver->ops && |
333 | dai->driver->ops->get_channel_map) | |
aa7b8230 KM |
334 | ret = dai->driver->ops->get_channel_map(dai, tx_num, tx_slot, |
335 | rx_num, rx_slot); | |
336 | return soc_dai_ret(dai, ret); | |
06f6e1d4 KM |
337 | } |
338 | EXPORT_SYMBOL_GPL(snd_soc_dai_get_channel_map); | |
339 | ||
340 | /** | |
341 | * snd_soc_dai_set_tristate - configure DAI system or master clock. | |
342 | * @dai: DAI | |
343 | * @tristate: tristate enable | |
344 | * | |
345 | * Tristates the DAI so that others can use it. | |
346 | */ | |
347 | int snd_soc_dai_set_tristate(struct snd_soc_dai *dai, int tristate) | |
348 | { | |
aa7b8230 KM |
349 | int ret = -EINVAL; |
350 | ||
479914ed KM |
351 | if (dai->driver->ops && |
352 | dai->driver->ops->set_tristate) | |
aa7b8230 KM |
353 | ret = dai->driver->ops->set_tristate(dai, tristate); |
354 | ||
355 | return soc_dai_ret(dai, ret); | |
06f6e1d4 KM |
356 | } |
357 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_tristate); | |
358 | ||
359 | /** | |
360 | * snd_soc_dai_digital_mute - configure DAI system or master clock. | |
361 | * @dai: DAI | |
362 | * @mute: mute enable | |
363 | * @direction: stream to mute | |
364 | * | |
365 | * Mutes the DAI DAC. | |
366 | */ | |
367 | int snd_soc_dai_digital_mute(struct snd_soc_dai *dai, int mute, | |
368 | int direction) | |
369 | { | |
aa7b8230 KM |
370 | int ret = -ENOTSUPP; |
371 | ||
350d9935 KM |
372 | /* |
373 | * ignore if direction was CAPTURE | |
374 | * and it had .no_capture_mute flag | |
375 | */ | |
479914ed | 376 | if (dai->driver->ops && |
350d9935 KM |
377 | dai->driver->ops->mute_stream && |
378 | (direction == SNDRV_PCM_STREAM_PLAYBACK || | |
379 | !dai->driver->ops->no_capture_mute)) | |
aa7b8230 | 380 | ret = dai->driver->ops->mute_stream(dai, mute, direction); |
aa7b8230 KM |
381 | |
382 | return soc_dai_ret(dai, ret); | |
06f6e1d4 KM |
383 | } |
384 | EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute); | |
aa6166c2 KM |
385 | |
386 | int snd_soc_dai_hw_params(struct snd_soc_dai *dai, | |
387 | struct snd_pcm_substream *substream, | |
388 | struct snd_pcm_hw_params *params) | |
389 | { | |
0ceef681 | 390 | struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); |
aa7b8230 | 391 | int ret = 0; |
aa6166c2 | 392 | |
479914ed | 393 | if (dai->driver->ops && |
8b4ba1d3 GL |
394 | dai->driver->ops->hw_params) { |
395 | /* perform any topology hw_params fixups before DAI */ | |
396 | ret = snd_soc_link_be_hw_params_fixup(rtd, params); | |
397 | if (ret < 0) | |
398 | goto end; | |
399 | ||
aa6166c2 | 400 | ret = dai->driver->ops->hw_params(substream, params, dai); |
8b4ba1d3 | 401 | } |
c304c9ac KM |
402 | |
403 | /* mark substream if succeeded */ | |
404 | if (ret == 0) | |
405 | soc_dai_mark_push(dai, substream, hw_params); | |
aa7b8230 KM |
406 | end: |
407 | return soc_dai_ret(dai, ret); | |
aa6166c2 | 408 | } |
846faaed KM |
409 | |
410 | void snd_soc_dai_hw_free(struct snd_soc_dai *dai, | |
c304c9ac KM |
411 | struct snd_pcm_substream *substream, |
412 | int rollback) | |
846faaed | 413 | { |
c304c9ac KM |
414 | if (rollback && !soc_dai_mark_match(dai, substream, hw_params)) |
415 | return; | |
416 | ||
479914ed KM |
417 | if (dai->driver->ops && |
418 | dai->driver->ops->hw_free) | |
846faaed | 419 | dai->driver->ops->hw_free(substream, dai); |
c304c9ac KM |
420 | |
421 | /* remove marked substream */ | |
422 | soc_dai_mark_pop(dai, substream, hw_params); | |
846faaed | 423 | } |
5a52a045 KM |
424 | |
425 | int snd_soc_dai_startup(struct snd_soc_dai *dai, | |
426 | struct snd_pcm_substream *substream) | |
427 | { | |
428 | int ret = 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 | { |
00a0b46c KM |
445 | if (rollback && !soc_dai_mark_match(dai, substream, startup)) |
446 | return; | |
447 | ||
479914ed KM |
448 | if (dai->driver->ops && |
449 | dai->driver->ops->shutdown) | |
330fcb51 | 450 | dai->driver->ops->shutdown(substream, dai); |
00a0b46c KM |
451 | |
452 | /* remove marked substream */ | |
453 | soc_dai_mark_pop(dai, substream, startup); | |
330fcb51 | 454 | } |
4beb8e10 | 455 | |
b423c420 KM |
456 | int snd_soc_dai_compress_new(struct snd_soc_dai *dai, |
457 | struct snd_soc_pcm_runtime *rtd, int num) | |
458 | { | |
aa7b8230 | 459 | int ret = -ENOTSUPP; |
b423c420 | 460 | if (dai->driver->compress_new) |
aa7b8230 KM |
461 | ret = dai->driver->compress_new(rtd, num); |
462 | return soc_dai_ret(dai, ret); | |
b423c420 | 463 | } |
467fece8 KM |
464 | |
465 | /* | |
466 | * snd_soc_dai_stream_valid() - check if a DAI supports the given stream | |
467 | * | |
468 | * Returns true if the DAI supports the indicated stream type. | |
469 | */ | |
470 | bool snd_soc_dai_stream_valid(struct snd_soc_dai *dai, int dir) | |
471 | { | |
acf253c1 | 472 | struct snd_soc_pcm_stream *stream = snd_soc_dai_get_pcm_stream(dai, dir); |
467fece8 KM |
473 | |
474 | /* If the codec specifies any channels at all, it supports the stream */ | |
475 | return stream->channels_min; | |
476 | } | |
0b73ba55 | 477 | |
25612477 PLB |
478 | /* |
479 | * snd_soc_dai_link_set_capabilities() - set dai_link properties based on its DAIs | |
480 | */ | |
481 | void snd_soc_dai_link_set_capabilities(struct snd_soc_dai_link *dai_link) | |
482 | { | |
25612477 PLB |
483 | bool supported[SNDRV_PCM_STREAM_LAST + 1]; |
484 | int direction; | |
25612477 PLB |
485 | |
486 | for_each_pcm_streams(direction) { | |
d490f4e7 KM |
487 | struct snd_soc_dai_link_component *cpu; |
488 | struct snd_soc_dai_link_component *codec; | |
489 | struct snd_soc_dai *dai; | |
490 | bool supported_cpu = false; | |
491 | bool supported_codec = false; | |
492 | int i; | |
25612477 PLB |
493 | |
494 | for_each_link_cpus(dai_link, i, cpu) { | |
c1c277b2 | 495 | dai = snd_soc_find_dai_with_mutex(cpu); |
4f872154 PLB |
496 | if (dai && snd_soc_dai_stream_valid(dai, direction)) { |
497 | supported_cpu = true; | |
25612477 PLB |
498 | break; |
499 | } | |
500 | } | |
25612477 | 501 | for_each_link_codecs(dai_link, i, codec) { |
c1c277b2 | 502 | dai = snd_soc_find_dai_with_mutex(codec); |
4f872154 PLB |
503 | if (dai && snd_soc_dai_stream_valid(dai, direction)) { |
504 | supported_codec = true; | |
25612477 PLB |
505 | break; |
506 | } | |
507 | } | |
4f872154 | 508 | supported[direction] = supported_cpu && supported_codec; |
25612477 PLB |
509 | } |
510 | ||
511 | dai_link->dpcm_playback = supported[SNDRV_PCM_STREAM_PLAYBACK]; | |
512 | dai_link->dpcm_capture = supported[SNDRV_PCM_STREAM_CAPTURE]; | |
513 | } | |
514 | EXPORT_SYMBOL_GPL(snd_soc_dai_link_set_capabilities); | |
515 | ||
dc829106 KM |
516 | void snd_soc_dai_action(struct snd_soc_dai *dai, |
517 | int stream, int action) | |
518 | { | |
5552f8d7 | 519 | /* see snd_soc_dai_stream_active() */ |
dc829106 | 520 | dai->stream_active[stream] += action; |
0812a08a | 521 | |
488b2ca5 | 522 | /* see snd_soc_component_active() */ |
dc829106 KM |
523 | dai->component->active += action; |
524 | } | |
525 | EXPORT_SYMBOL_GPL(snd_soc_dai_action); | |
526 | ||
efffd9b3 KM |
527 | int snd_soc_dai_active(struct snd_soc_dai *dai) |
528 | { | |
529 | int stream, active; | |
530 | ||
531 | active = 0; | |
532 | for_each_pcm_streams(stream) | |
533 | active += dai->stream_active[stream]; | |
534 | ||
535 | return active; | |
536 | } | |
537 | EXPORT_SYMBOL_GPL(snd_soc_dai_active); | |
538 | ||
51801aea KM |
539 | int snd_soc_pcm_dai_probe(struct snd_soc_pcm_runtime *rtd, int order) |
540 | { | |
541 | struct snd_soc_dai *dai; | |
542 | int i; | |
543 | ||
544 | for_each_rtd_dais(rtd, i, dai) { | |
545 | if (dai->driver->probe_order != order) | |
546 | continue; | |
547 | ||
548 | if (dai->driver->probe) { | |
549 | int ret = dai->driver->probe(dai); | |
550 | ||
551 | if (ret < 0) | |
552 | return soc_dai_ret(dai, ret); | |
553 | } | |
554 | ||
555 | dai->probed = 1; | |
556 | } | |
557 | ||
558 | return 0; | |
559 | } | |
560 | ||
7eaa313b KM |
561 | int snd_soc_pcm_dai_remove(struct snd_soc_pcm_runtime *rtd, int order) |
562 | { | |
563 | struct snd_soc_dai *dai; | |
564 | int i, r, ret = 0; | |
565 | ||
566 | for_each_rtd_dais(rtd, i, dai) { | |
567 | if (dai->driver->remove_order != order) | |
568 | continue; | |
569 | ||
570 | if (dai->probed && | |
571 | dai->driver->remove) { | |
572 | r = dai->driver->remove(dai); | |
573 | if (r < 0) | |
574 | ret = r; /* use last error */ | |
575 | } | |
576 | ||
577 | dai->probed = 0; | |
578 | } | |
579 | ||
580 | return ret; | |
581 | } | |
582 | ||
0b73ba55 KM |
583 | int snd_soc_pcm_dai_new(struct snd_soc_pcm_runtime *rtd) |
584 | { | |
585 | struct snd_soc_dai *dai; | |
454a7422 | 586 | int i; |
0b73ba55 KM |
587 | |
588 | for_each_rtd_dais(rtd, i, dai) { | |
589 | if (dai->driver->pcm_new) { | |
454a7422 | 590 | int ret = dai->driver->pcm_new(rtd, dai); |
0b73ba55 KM |
591 | if (ret < 0) |
592 | return soc_dai_ret(dai, ret); | |
593 | } | |
594 | } | |
595 | ||
596 | return 0; | |
597 | } | |
d108c7fd KM |
598 | |
599 | int snd_soc_pcm_dai_prepare(struct snd_pcm_substream *substream) | |
600 | { | |
0ceef681 | 601 | struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); |
d108c7fd KM |
602 | struct snd_soc_dai *dai; |
603 | int i, ret; | |
604 | ||
605 | for_each_rtd_dais(rtd, i, dai) { | |
606 | if (dai->driver->ops && | |
607 | dai->driver->ops->prepare) { | |
608 | ret = dai->driver->ops->prepare(substream, dai); | |
609 | if (ret < 0) | |
610 | return soc_dai_ret(dai, ret); | |
611 | } | |
612 | } | |
613 | ||
614 | return 0; | |
615 | } | |
42f2472d | 616 | |
6374f493 KM |
617 | static int soc_dai_trigger(struct snd_soc_dai *dai, |
618 | struct snd_pcm_substream *substream, int cmd) | |
619 | { | |
620 | int ret = 0; | |
621 | ||
622 | if (dai->driver->ops && | |
623 | dai->driver->ops->trigger) | |
624 | ret = dai->driver->ops->trigger(substream, cmd, dai); | |
625 | ||
626 | return soc_dai_ret(dai, ret); | |
627 | } | |
628 | ||
42f2472d | 629 | int snd_soc_pcm_dai_trigger(struct snd_pcm_substream *substream, |
6374f493 | 630 | int cmd, int rollback) |
42f2472d | 631 | { |
0ceef681 | 632 | struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); |
42f2472d | 633 | struct snd_soc_dai *dai; |
6374f493 | 634 | int i, r, ret = 0; |
42f2472d | 635 | |
6374f493 KM |
636 | switch (cmd) { |
637 | case SNDRV_PCM_TRIGGER_START: | |
638 | case SNDRV_PCM_TRIGGER_RESUME: | |
639 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | |
640 | for_each_rtd_dais(rtd, i, dai) { | |
641 | ret = soc_dai_trigger(dai, substream, cmd); | |
42f2472d | 642 | if (ret < 0) |
6374f493 KM |
643 | break; |
644 | soc_dai_mark_push(dai, substream, trigger); | |
645 | } | |
646 | break; | |
647 | case SNDRV_PCM_TRIGGER_STOP: | |
648 | case SNDRV_PCM_TRIGGER_SUSPEND: | |
649 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | |
650 | for_each_rtd_dais(rtd, i, dai) { | |
651 | if (rollback && !soc_dai_mark_match(dai, substream, trigger)) | |
652 | continue; | |
653 | ||
654 | r = soc_dai_trigger(dai, substream, cmd); | |
655 | if (r < 0) | |
656 | ret = r; /* use last ret */ | |
657 | soc_dai_mark_pop(dai, substream, trigger); | |
42f2472d KM |
658 | } |
659 | } | |
660 | ||
6374f493 | 661 | return ret; |
42f2472d | 662 | } |
30819358 KM |
663 | |
664 | int snd_soc_pcm_dai_bespoke_trigger(struct snd_pcm_substream *substream, | |
665 | int cmd) | |
666 | { | |
0ceef681 | 667 | struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); |
30819358 KM |
668 | struct snd_soc_dai *dai; |
669 | int i, ret; | |
670 | ||
671 | for_each_rtd_dais(rtd, i, dai) { | |
672 | if (dai->driver->ops && | |
673 | dai->driver->ops->bespoke_trigger) { | |
674 | ret = dai->driver->ops->bespoke_trigger(substream, | |
675 | cmd, dai); | |
676 | if (ret < 0) | |
677 | return soc_dai_ret(dai, ret); | |
678 | } | |
679 | } | |
680 | ||
681 | return 0; | |
682 | } | |
b5ae4cce | 683 | |
8544f08c KM |
684 | void snd_soc_pcm_dai_delay(struct snd_pcm_substream *substream, |
685 | snd_pcm_sframes_t *cpu_delay, | |
686 | snd_pcm_sframes_t *codec_delay) | |
687 | { | |
688 | struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); | |
689 | struct snd_soc_dai *dai; | |
690 | int i; | |
691 | ||
692 | /* | |
693 | * We're looking for the delay through the full audio path so it needs to | |
694 | * be the maximum of the DAIs doing transmit and the maximum of the DAIs | |
695 | * doing receive (ie, all CPUs and all CODECs) rather than just the maximum | |
696 | * of all DAIs. | |
697 | */ | |
698 | ||
699 | /* for CPU */ | |
700 | for_each_rtd_cpu_dais(rtd, i, dai) | |
701 | if (dai->driver->ops && | |
702 | dai->driver->ops->delay) | |
703 | *cpu_delay = max(*cpu_delay, dai->driver->ops->delay(substream, dai)); | |
704 | ||
705 | /* for Codec */ | |
706 | for_each_rtd_codec_dais(rtd, i, dai) | |
707 | if (dai->driver->ops && | |
708 | dai->driver->ops->delay) | |
709 | *codec_delay = max(*codec_delay, dai->driver->ops->delay(substream, dai)); | |
710 | } | |
711 | ||
b5ae4cce KM |
712 | int snd_soc_dai_compr_startup(struct snd_soc_dai *dai, |
713 | struct snd_compr_stream *cstream) | |
714 | { | |
715 | int ret = 0; | |
716 | ||
717 | if (dai->driver->cops && | |
718 | dai->driver->cops->startup) | |
719 | ret = dai->driver->cops->startup(cstream, dai); | |
720 | ||
1e6a93cf KM |
721 | /* mark cstream if succeeded */ |
722 | if (ret == 0) | |
723 | soc_dai_mark_push(dai, cstream, compr_startup); | |
724 | ||
b5ae4cce KM |
725 | return soc_dai_ret(dai, ret); |
726 | } | |
727 | EXPORT_SYMBOL_GPL(snd_soc_dai_compr_startup); | |
2b25f81d KM |
728 | |
729 | void snd_soc_dai_compr_shutdown(struct snd_soc_dai *dai, | |
1e6a93cf KM |
730 | struct snd_compr_stream *cstream, |
731 | int rollback) | |
2b25f81d | 732 | { |
1e6a93cf KM |
733 | if (rollback && !soc_dai_mark_match(dai, cstream, compr_startup)) |
734 | return; | |
735 | ||
2b25f81d KM |
736 | if (dai->driver->cops && |
737 | dai->driver->cops->shutdown) | |
738 | dai->driver->cops->shutdown(cstream, dai); | |
1e6a93cf KM |
739 | |
740 | /* remove marked cstream */ | |
741 | soc_dai_mark_pop(dai, cstream, compr_startup); | |
2b25f81d KM |
742 | } |
743 | EXPORT_SYMBOL_GPL(snd_soc_dai_compr_shutdown); | |
eb08411b KM |
744 | |
745 | int snd_soc_dai_compr_trigger(struct snd_soc_dai *dai, | |
746 | struct snd_compr_stream *cstream, int cmd) | |
747 | { | |
748 | int ret = 0; | |
749 | ||
750 | if (dai->driver->cops && | |
751 | dai->driver->cops->trigger) | |
752 | ret = dai->driver->cops->trigger(cstream, cmd, dai); | |
753 | ||
754 | return soc_dai_ret(dai, ret); | |
755 | } | |
756 | EXPORT_SYMBOL_GPL(snd_soc_dai_compr_trigger); | |
8dfedafb KM |
757 | |
758 | int snd_soc_dai_compr_set_params(struct snd_soc_dai *dai, | |
759 | struct snd_compr_stream *cstream, | |
760 | struct snd_compr_params *params) | |
761 | { | |
762 | int ret = 0; | |
763 | ||
764 | if (dai->driver->cops && | |
765 | dai->driver->cops->set_params) | |
766 | ret = dai->driver->cops->set_params(cstream, params, dai); | |
767 | ||
768 | return soc_dai_ret(dai, ret); | |
769 | } | |
770 | EXPORT_SYMBOL_GPL(snd_soc_dai_compr_set_params); | |
adbef543 KM |
771 | |
772 | int snd_soc_dai_compr_get_params(struct snd_soc_dai *dai, | |
773 | struct snd_compr_stream *cstream, | |
774 | struct snd_codec *params) | |
775 | { | |
776 | int ret = 0; | |
777 | ||
778 | if (dai->driver->cops && | |
779 | dai->driver->cops->get_params) | |
780 | ret = dai->driver->cops->get_params(cstream, params, dai); | |
781 | ||
782 | return soc_dai_ret(dai, ret); | |
783 | } | |
784 | EXPORT_SYMBOL_GPL(snd_soc_dai_compr_get_params); | |
53294353 KM |
785 | |
786 | int snd_soc_dai_compr_ack(struct snd_soc_dai *dai, | |
787 | struct snd_compr_stream *cstream, | |
788 | size_t bytes) | |
789 | { | |
790 | int ret = 0; | |
791 | ||
792 | if (dai->driver->cops && | |
793 | dai->driver->cops->ack) | |
794 | ret = dai->driver->cops->ack(cstream, bytes, dai); | |
795 | ||
796 | return soc_dai_ret(dai, ret); | |
797 | } | |
798 | EXPORT_SYMBOL_GPL(snd_soc_dai_compr_ack); | |
ed38cc59 KM |
799 | |
800 | int snd_soc_dai_compr_pointer(struct snd_soc_dai *dai, | |
801 | struct snd_compr_stream *cstream, | |
802 | struct snd_compr_tstamp *tstamp) | |
803 | { | |
804 | int ret = 0; | |
805 | ||
806 | if (dai->driver->cops && | |
807 | dai->driver->cops->pointer) | |
808 | ret = dai->driver->cops->pointer(cstream, tstamp, dai); | |
809 | ||
810 | return soc_dai_ret(dai, ret); | |
811 | } | |
812 | EXPORT_SYMBOL_GPL(snd_soc_dai_compr_pointer); | |
88b3a7df KM |
813 | |
814 | int snd_soc_dai_compr_set_metadata(struct snd_soc_dai *dai, | |
815 | struct snd_compr_stream *cstream, | |
816 | struct snd_compr_metadata *metadata) | |
817 | { | |
818 | int ret = 0; | |
819 | ||
820 | if (dai->driver->cops && | |
821 | dai->driver->cops->set_metadata) | |
822 | ret = dai->driver->cops->set_metadata(cstream, metadata, dai); | |
823 | ||
824 | return soc_dai_ret(dai, ret); | |
825 | } | |
826 | EXPORT_SYMBOL_GPL(snd_soc_dai_compr_set_metadata); | |
94d72819 KM |
827 | |
828 | int snd_soc_dai_compr_get_metadata(struct snd_soc_dai *dai, | |
829 | struct snd_compr_stream *cstream, | |
830 | struct snd_compr_metadata *metadata) | |
831 | { | |
832 | int ret = 0; | |
833 | ||
834 | if (dai->driver->cops && | |
835 | dai->driver->cops->get_metadata) | |
836 | ret = dai->driver->cops->get_metadata(cstream, metadata, dai); | |
837 | ||
838 | return soc_dai_ret(dai, ret); | |
839 | } | |
840 | EXPORT_SYMBOL_GPL(snd_soc_dai_compr_get_metadata); |