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> | |
11 | ||
aa7b8230 KM |
12 | #define soc_dai_ret(dai, ret) _soc_dai_ret(dai, __func__, ret) |
13 | static inline int _soc_dai_ret(struct snd_soc_dai *dai, | |
14 | const char *func, int ret) | |
15 | { | |
16 | switch (ret) { | |
17 | case -EPROBE_DEFER: | |
18 | case -ENOTSUPP: | |
19 | case 0: | |
20 | break; | |
21 | default: | |
22 | dev_err(dai->dev, | |
23 | "ASoC: error at %s on %s: %d\n", | |
24 | func, dai->name, ret); | |
25 | } | |
26 | ||
27 | return ret; | |
28 | } | |
29 | ||
06f6e1d4 KM |
30 | /** |
31 | * snd_soc_dai_set_sysclk - configure DAI system or master clock. | |
32 | * @dai: DAI | |
33 | * @clk_id: DAI specific clock ID | |
34 | * @freq: new clock frequency in Hz | |
35 | * @dir: new clock direction - input/output. | |
36 | * | |
37 | * Configures the DAI master (MCLK) or system (SYSCLK) clocking. | |
38 | */ | |
39 | int snd_soc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, | |
40 | unsigned int freq, int dir) | |
41 | { | |
aa7b8230 KM |
42 | int ret; |
43 | ||
479914ed KM |
44 | if (dai->driver->ops && |
45 | dai->driver->ops->set_sysclk) | |
aa7b8230 KM |
46 | ret = dai->driver->ops->set_sysclk(dai, clk_id, freq, dir); |
47 | else | |
48 | ret = snd_soc_component_set_sysclk(dai->component, clk_id, 0, | |
49 | freq, dir); | |
06f6e1d4 | 50 | |
aa7b8230 | 51 | return soc_dai_ret(dai, ret); |
06f6e1d4 KM |
52 | } |
53 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_sysclk); | |
54 | ||
55 | /** | |
56 | * snd_soc_dai_set_clkdiv - configure DAI clock dividers. | |
57 | * @dai: DAI | |
58 | * @div_id: DAI specific clock divider ID | |
59 | * @div: new clock divisor. | |
60 | * | |
61 | * Configures the clock dividers. This is used to derive the best DAI bit and | |
62 | * frame clocks from the system or master clock. It's best to set the DAI bit | |
63 | * and frame clocks as low as possible to save system power. | |
64 | */ | |
65 | int snd_soc_dai_set_clkdiv(struct snd_soc_dai *dai, | |
66 | int div_id, int div) | |
67 | { | |
aa7b8230 KM |
68 | int ret = -EINVAL; |
69 | ||
479914ed KM |
70 | if (dai->driver->ops && |
71 | dai->driver->ops->set_clkdiv) | |
aa7b8230 KM |
72 | ret = dai->driver->ops->set_clkdiv(dai, div_id, div); |
73 | ||
74 | return soc_dai_ret(dai, ret); | |
06f6e1d4 KM |
75 | } |
76 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_clkdiv); | |
77 | ||
78 | /** | |
79 | * snd_soc_dai_set_pll - configure DAI PLL. | |
80 | * @dai: DAI | |
81 | * @pll_id: DAI specific PLL ID | |
82 | * @source: DAI specific source for the PLL | |
83 | * @freq_in: PLL input clock frequency in Hz | |
84 | * @freq_out: requested PLL output clock frequency in Hz | |
85 | * | |
86 | * Configures and enables PLL to generate output clock based on input clock. | |
87 | */ | |
88 | int snd_soc_dai_set_pll(struct snd_soc_dai *dai, int pll_id, int source, | |
89 | unsigned int freq_in, unsigned int freq_out) | |
90 | { | |
aa7b8230 KM |
91 | int ret; |
92 | ||
479914ed KM |
93 | if (dai->driver->ops && |
94 | dai->driver->ops->set_pll) | |
aa7b8230 KM |
95 | ret = dai->driver->ops->set_pll(dai, pll_id, source, |
96 | freq_in, freq_out); | |
97 | else | |
98 | ret = snd_soc_component_set_pll(dai->component, pll_id, source, | |
99 | freq_in, freq_out); | |
06f6e1d4 | 100 | |
aa7b8230 | 101 | return soc_dai_ret(dai, ret); |
06f6e1d4 KM |
102 | } |
103 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_pll); | |
104 | ||
105 | /** | |
106 | * snd_soc_dai_set_bclk_ratio - configure BCLK to sample rate ratio. | |
107 | * @dai: DAI | |
108 | * @ratio: Ratio of BCLK to Sample rate. | |
109 | * | |
110 | * Configures the DAI for a preset BCLK to sample rate ratio. | |
111 | */ | |
112 | int snd_soc_dai_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) | |
113 | { | |
aa7b8230 KM |
114 | int ret = -EINVAL; |
115 | ||
479914ed KM |
116 | if (dai->driver->ops && |
117 | dai->driver->ops->set_bclk_ratio) | |
aa7b8230 KM |
118 | ret = dai->driver->ops->set_bclk_ratio(dai, ratio); |
119 | ||
120 | return soc_dai_ret(dai, ret); | |
06f6e1d4 KM |
121 | } |
122 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_bclk_ratio); | |
123 | ||
124 | /** | |
125 | * snd_soc_dai_set_fmt - configure DAI hardware audio format. | |
126 | * @dai: DAI | |
127 | * @fmt: SND_SOC_DAIFMT_* format value. | |
128 | * | |
129 | * Configures the DAI hardware format and clocking. | |
130 | */ | |
131 | int snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) | |
132 | { | |
aa7b8230 KM |
133 | int ret = -ENOTSUPP; |
134 | ||
479914ed KM |
135 | if (dai->driver->ops && |
136 | dai->driver->ops->set_fmt) | |
aa7b8230 KM |
137 | ret = dai->driver->ops->set_fmt(dai, fmt); |
138 | ||
139 | return soc_dai_ret(dai, ret); | |
06f6e1d4 KM |
140 | } |
141 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_fmt); | |
142 | ||
143 | /** | |
144 | * snd_soc_xlate_tdm_slot - generate tx/rx slot mask. | |
145 | * @slots: Number of slots in use. | |
146 | * @tx_mask: bitmask representing active TX slots. | |
147 | * @rx_mask: bitmask representing active RX slots. | |
148 | * | |
149 | * Generates the TDM tx and rx slot default masks for DAI. | |
150 | */ | |
151 | static int snd_soc_xlate_tdm_slot_mask(unsigned int slots, | |
152 | unsigned int *tx_mask, | |
153 | unsigned int *rx_mask) | |
154 | { | |
155 | if (*tx_mask || *rx_mask) | |
156 | return 0; | |
157 | ||
158 | if (!slots) | |
159 | return -EINVAL; | |
160 | ||
161 | *tx_mask = (1 << slots) - 1; | |
162 | *rx_mask = (1 << slots) - 1; | |
163 | ||
164 | return 0; | |
165 | } | |
166 | ||
167 | /** | |
168 | * snd_soc_dai_set_tdm_slot() - Configures a DAI for TDM operation | |
169 | * @dai: The DAI to configure | |
170 | * @tx_mask: bitmask representing active TX slots. | |
171 | * @rx_mask: bitmask representing active RX slots. | |
172 | * @slots: Number of slots in use. | |
173 | * @slot_width: Width in bits for each slot. | |
174 | * | |
175 | * This function configures the specified DAI for TDM operation. @slot contains | |
176 | * the total number of slots of the TDM stream and @slot_with the width of each | |
177 | * slot in bit clock cycles. @tx_mask and @rx_mask are bitmasks specifying the | |
178 | * active slots of the TDM stream for the specified DAI, i.e. which slots the | |
179 | * DAI should write to or read from. If a bit is set the corresponding slot is | |
180 | * active, if a bit is cleared the corresponding slot is inactive. Bit 0 maps to | |
181 | * the first slot, bit 1 to the second slot and so on. The first active slot | |
182 | * maps to the first channel of the DAI, the second active slot to the second | |
183 | * channel and so on. | |
184 | * | |
185 | * TDM mode can be disabled by passing 0 for @slots. In this case @tx_mask, | |
186 | * @rx_mask and @slot_width will be ignored. | |
187 | * | |
188 | * Returns 0 on success, a negative error code otherwise. | |
189 | */ | |
190 | int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai, | |
191 | unsigned int tx_mask, unsigned int rx_mask, | |
192 | int slots, int slot_width) | |
193 | { | |
aa7b8230 KM |
194 | int ret = -ENOTSUPP; |
195 | ||
479914ed KM |
196 | if (dai->driver->ops && |
197 | dai->driver->ops->xlate_tdm_slot_mask) | |
06f6e1d4 KM |
198 | dai->driver->ops->xlate_tdm_slot_mask(slots, |
199 | &tx_mask, &rx_mask); | |
200 | else | |
201 | snd_soc_xlate_tdm_slot_mask(slots, &tx_mask, &rx_mask); | |
202 | ||
203 | dai->tx_mask = tx_mask; | |
204 | dai->rx_mask = rx_mask; | |
205 | ||
479914ed KM |
206 | if (dai->driver->ops && |
207 | dai->driver->ops->set_tdm_slot) | |
aa7b8230 | 208 | ret = dai->driver->ops->set_tdm_slot(dai, tx_mask, rx_mask, |
06f6e1d4 | 209 | slots, slot_width); |
aa7b8230 | 210 | return soc_dai_ret(dai, ret); |
06f6e1d4 KM |
211 | } |
212 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_tdm_slot); | |
213 | ||
214 | /** | |
215 | * snd_soc_dai_set_channel_map - configure DAI audio channel map | |
216 | * @dai: DAI | |
217 | * @tx_num: how many TX channels | |
218 | * @tx_slot: pointer to an array which imply the TX slot number channel | |
219 | * 0~num-1 uses | |
220 | * @rx_num: how many RX channels | |
221 | * @rx_slot: pointer to an array which imply the RX slot number channel | |
222 | * 0~num-1 uses | |
223 | * | |
224 | * configure the relationship between channel number and TDM slot number. | |
225 | */ | |
226 | int snd_soc_dai_set_channel_map(struct snd_soc_dai *dai, | |
227 | unsigned int tx_num, unsigned int *tx_slot, | |
228 | unsigned int rx_num, unsigned int *rx_slot) | |
229 | { | |
aa7b8230 KM |
230 | int ret = -ENOTSUPP; |
231 | ||
479914ed KM |
232 | if (dai->driver->ops && |
233 | dai->driver->ops->set_channel_map) | |
aa7b8230 KM |
234 | ret = dai->driver->ops->set_channel_map(dai, tx_num, tx_slot, |
235 | rx_num, rx_slot); | |
236 | return soc_dai_ret(dai, ret); | |
06f6e1d4 KM |
237 | } |
238 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_channel_map); | |
239 | ||
240 | /** | |
241 | * snd_soc_dai_get_channel_map - Get DAI audio channel map | |
242 | * @dai: DAI | |
243 | * @tx_num: how many TX channels | |
244 | * @tx_slot: pointer to an array which imply the TX slot number channel | |
245 | * 0~num-1 uses | |
246 | * @rx_num: how many RX channels | |
247 | * @rx_slot: pointer to an array which imply the RX slot number channel | |
248 | * 0~num-1 uses | |
249 | */ | |
250 | int snd_soc_dai_get_channel_map(struct snd_soc_dai *dai, | |
251 | unsigned int *tx_num, unsigned int *tx_slot, | |
252 | unsigned int *rx_num, unsigned int *rx_slot) | |
253 | { | |
aa7b8230 KM |
254 | int ret = -ENOTSUPP; |
255 | ||
479914ed KM |
256 | if (dai->driver->ops && |
257 | dai->driver->ops->get_channel_map) | |
aa7b8230 KM |
258 | ret = dai->driver->ops->get_channel_map(dai, tx_num, tx_slot, |
259 | rx_num, rx_slot); | |
260 | return soc_dai_ret(dai, ret); | |
06f6e1d4 KM |
261 | } |
262 | EXPORT_SYMBOL_GPL(snd_soc_dai_get_channel_map); | |
263 | ||
264 | /** | |
265 | * snd_soc_dai_set_tristate - configure DAI system or master clock. | |
266 | * @dai: DAI | |
267 | * @tristate: tristate enable | |
268 | * | |
269 | * Tristates the DAI so that others can use it. | |
270 | */ | |
271 | int snd_soc_dai_set_tristate(struct snd_soc_dai *dai, int tristate) | |
272 | { | |
aa7b8230 KM |
273 | int ret = -EINVAL; |
274 | ||
479914ed KM |
275 | if (dai->driver->ops && |
276 | dai->driver->ops->set_tristate) | |
aa7b8230 KM |
277 | ret = dai->driver->ops->set_tristate(dai, tristate); |
278 | ||
279 | return soc_dai_ret(dai, ret); | |
06f6e1d4 KM |
280 | } |
281 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_tristate); | |
282 | ||
283 | /** | |
284 | * snd_soc_dai_digital_mute - configure DAI system or master clock. | |
285 | * @dai: DAI | |
286 | * @mute: mute enable | |
287 | * @direction: stream to mute | |
288 | * | |
289 | * Mutes the DAI DAC. | |
290 | */ | |
291 | int snd_soc_dai_digital_mute(struct snd_soc_dai *dai, int mute, | |
292 | int direction) | |
293 | { | |
aa7b8230 KM |
294 | int ret = -ENOTSUPP; |
295 | ||
479914ed KM |
296 | if (dai->driver->ops && |
297 | dai->driver->ops->mute_stream) | |
aa7b8230 | 298 | ret = dai->driver->ops->mute_stream(dai, mute, direction); |
06f6e1d4 | 299 | else if (direction == SNDRV_PCM_STREAM_PLAYBACK && |
479914ed | 300 | dai->driver->ops && |
06f6e1d4 | 301 | dai->driver->ops->digital_mute) |
aa7b8230 KM |
302 | ret = dai->driver->ops->digital_mute(dai, mute); |
303 | ||
304 | return soc_dai_ret(dai, ret); | |
06f6e1d4 KM |
305 | } |
306 | EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute); | |
aa6166c2 KM |
307 | |
308 | int snd_soc_dai_hw_params(struct snd_soc_dai *dai, | |
309 | struct snd_pcm_substream *substream, | |
310 | struct snd_pcm_hw_params *params) | |
311 | { | |
312 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
aa7b8230 | 313 | int ret = 0; |
aa6166c2 KM |
314 | |
315 | /* perform any topology hw_params fixups before DAI */ | |
316 | if (rtd->dai_link->be_hw_params_fixup) { | |
317 | ret = rtd->dai_link->be_hw_params_fixup(rtd, params); | |
aa7b8230 KM |
318 | if (ret < 0) |
319 | goto end; | |
aa6166c2 KM |
320 | } |
321 | ||
479914ed KM |
322 | if (dai->driver->ops && |
323 | dai->driver->ops->hw_params) | |
aa6166c2 | 324 | ret = dai->driver->ops->hw_params(substream, params, dai); |
aa7b8230 KM |
325 | end: |
326 | return soc_dai_ret(dai, ret); | |
aa6166c2 | 327 | } |
846faaed KM |
328 | |
329 | void snd_soc_dai_hw_free(struct snd_soc_dai *dai, | |
330 | struct snd_pcm_substream *substream) | |
331 | { | |
479914ed KM |
332 | if (dai->driver->ops && |
333 | dai->driver->ops->hw_free) | |
846faaed KM |
334 | dai->driver->ops->hw_free(substream, dai); |
335 | } | |
5a52a045 KM |
336 | |
337 | int snd_soc_dai_startup(struct snd_soc_dai *dai, | |
338 | struct snd_pcm_substream *substream) | |
339 | { | |
340 | int ret = 0; | |
341 | ||
479914ed KM |
342 | if (dai->driver->ops && |
343 | dai->driver->ops->startup) | |
5a52a045 KM |
344 | ret = dai->driver->ops->startup(substream, dai); |
345 | ||
aa7b8230 | 346 | return soc_dai_ret(dai, ret); |
5a52a045 | 347 | } |
330fcb51 KM |
348 | |
349 | void snd_soc_dai_shutdown(struct snd_soc_dai *dai, | |
350 | struct snd_pcm_substream *substream) | |
351 | { | |
479914ed KM |
352 | if (dai->driver->ops && |
353 | dai->driver->ops->shutdown) | |
330fcb51 KM |
354 | dai->driver->ops->shutdown(substream, dai); |
355 | } | |
4beb8e10 | 356 | |
1dea80d4 KM |
357 | snd_pcm_sframes_t snd_soc_dai_delay(struct snd_soc_dai *dai, |
358 | struct snd_pcm_substream *substream) | |
359 | { | |
360 | int delay = 0; | |
361 | ||
479914ed KM |
362 | if (dai->driver->ops && |
363 | dai->driver->ops->delay) | |
1dea80d4 KM |
364 | delay = dai->driver->ops->delay(substream, dai); |
365 | ||
366 | return delay; | |
367 | } | |
e0f22622 | 368 | |
b423c420 KM |
369 | int snd_soc_dai_compress_new(struct snd_soc_dai *dai, |
370 | struct snd_soc_pcm_runtime *rtd, int num) | |
371 | { | |
aa7b8230 | 372 | int ret = -ENOTSUPP; |
b423c420 | 373 | if (dai->driver->compress_new) |
aa7b8230 KM |
374 | ret = dai->driver->compress_new(rtd, num); |
375 | return soc_dai_ret(dai, ret); | |
b423c420 | 376 | } |
467fece8 KM |
377 | |
378 | /* | |
379 | * snd_soc_dai_stream_valid() - check if a DAI supports the given stream | |
380 | * | |
381 | * Returns true if the DAI supports the indicated stream type. | |
382 | */ | |
383 | bool snd_soc_dai_stream_valid(struct snd_soc_dai *dai, int dir) | |
384 | { | |
acf253c1 | 385 | struct snd_soc_pcm_stream *stream = snd_soc_dai_get_pcm_stream(dai, dir); |
467fece8 KM |
386 | |
387 | /* If the codec specifies any channels at all, it supports the stream */ | |
388 | return stream->channels_min; | |
389 | } | |
0b73ba55 | 390 | |
51801aea KM |
391 | int snd_soc_pcm_dai_probe(struct snd_soc_pcm_runtime *rtd, int order) |
392 | { | |
393 | struct snd_soc_dai *dai; | |
394 | int i; | |
395 | ||
396 | for_each_rtd_dais(rtd, i, dai) { | |
397 | if (dai->driver->probe_order != order) | |
398 | continue; | |
399 | ||
400 | if (dai->driver->probe) { | |
401 | int ret = dai->driver->probe(dai); | |
402 | ||
403 | if (ret < 0) | |
404 | return soc_dai_ret(dai, ret); | |
405 | } | |
406 | ||
407 | dai->probed = 1; | |
408 | } | |
409 | ||
410 | return 0; | |
411 | } | |
412 | ||
7eaa313b KM |
413 | int snd_soc_pcm_dai_remove(struct snd_soc_pcm_runtime *rtd, int order) |
414 | { | |
415 | struct snd_soc_dai *dai; | |
416 | int i, r, ret = 0; | |
417 | ||
418 | for_each_rtd_dais(rtd, i, dai) { | |
419 | if (dai->driver->remove_order != order) | |
420 | continue; | |
421 | ||
422 | if (dai->probed && | |
423 | dai->driver->remove) { | |
424 | r = dai->driver->remove(dai); | |
425 | if (r < 0) | |
426 | ret = r; /* use last error */ | |
427 | } | |
428 | ||
429 | dai->probed = 0; | |
430 | } | |
431 | ||
432 | return ret; | |
433 | } | |
434 | ||
0b73ba55 KM |
435 | int snd_soc_pcm_dai_new(struct snd_soc_pcm_runtime *rtd) |
436 | { | |
437 | struct snd_soc_dai *dai; | |
438 | int i, ret = 0; | |
439 | ||
440 | for_each_rtd_dais(rtd, i, dai) { | |
441 | if (dai->driver->pcm_new) { | |
442 | ret = dai->driver->pcm_new(rtd, dai); | |
443 | if (ret < 0) | |
444 | return soc_dai_ret(dai, ret); | |
445 | } | |
446 | } | |
447 | ||
448 | return 0; | |
449 | } | |
d108c7fd KM |
450 | |
451 | int snd_soc_pcm_dai_prepare(struct snd_pcm_substream *substream) | |
452 | { | |
453 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
454 | struct snd_soc_dai *dai; | |
455 | int i, ret; | |
456 | ||
457 | for_each_rtd_dais(rtd, i, dai) { | |
458 | if (dai->driver->ops && | |
459 | dai->driver->ops->prepare) { | |
460 | ret = dai->driver->ops->prepare(substream, dai); | |
461 | if (ret < 0) | |
462 | return soc_dai_ret(dai, ret); | |
463 | } | |
464 | } | |
465 | ||
466 | return 0; | |
467 | } | |
42f2472d KM |
468 | |
469 | int snd_soc_pcm_dai_trigger(struct snd_pcm_substream *substream, | |
470 | int cmd) | |
471 | { | |
472 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
473 | struct snd_soc_dai *dai; | |
474 | int i, ret; | |
475 | ||
476 | for_each_rtd_dais(rtd, i, dai) { | |
477 | if (dai->driver->ops && | |
478 | dai->driver->ops->trigger) { | |
479 | ret = dai->driver->ops->trigger(substream, cmd, dai); | |
480 | if (ret < 0) | |
481 | return soc_dai_ret(dai, ret); | |
482 | } | |
483 | } | |
484 | ||
485 | return 0; | |
486 | } | |
30819358 KM |
487 | |
488 | int snd_soc_pcm_dai_bespoke_trigger(struct snd_pcm_substream *substream, | |
489 | int cmd) | |
490 | { | |
491 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
492 | struct snd_soc_dai *dai; | |
493 | int i, ret; | |
494 | ||
495 | for_each_rtd_dais(rtd, i, dai) { | |
496 | if (dai->driver->ops && | |
497 | dai->driver->ops->bespoke_trigger) { | |
498 | ret = dai->driver->ops->bespoke_trigger(substream, | |
499 | cmd, dai); | |
500 | if (ret < 0) | |
501 | return soc_dai_ret(dai, ret); | |
502 | } | |
503 | } | |
504 | ||
505 | return 0; | |
506 | } |