]>
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 | ||
12 | /** | |
13 | * snd_soc_dai_set_sysclk - configure DAI system or master clock. | |
14 | * @dai: DAI | |
15 | * @clk_id: DAI specific clock ID | |
16 | * @freq: new clock frequency in Hz | |
17 | * @dir: new clock direction - input/output. | |
18 | * | |
19 | * Configures the DAI master (MCLK) or system (SYSCLK) clocking. | |
20 | */ | |
21 | int snd_soc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, | |
22 | unsigned int freq, int dir) | |
23 | { | |
24 | if (dai->driver->ops->set_sysclk) | |
25 | return dai->driver->ops->set_sysclk(dai, clk_id, freq, dir); | |
26 | ||
27 | return snd_soc_component_set_sysclk(dai->component, clk_id, 0, | |
28 | freq, dir); | |
29 | } | |
30 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_sysclk); | |
31 | ||
32 | /** | |
33 | * snd_soc_dai_set_clkdiv - configure DAI clock dividers. | |
34 | * @dai: DAI | |
35 | * @div_id: DAI specific clock divider ID | |
36 | * @div: new clock divisor. | |
37 | * | |
38 | * Configures the clock dividers. This is used to derive the best DAI bit and | |
39 | * frame clocks from the system or master clock. It's best to set the DAI bit | |
40 | * and frame clocks as low as possible to save system power. | |
41 | */ | |
42 | int snd_soc_dai_set_clkdiv(struct snd_soc_dai *dai, | |
43 | int div_id, int div) | |
44 | { | |
45 | if (dai->driver->ops->set_clkdiv) | |
46 | return dai->driver->ops->set_clkdiv(dai, div_id, div); | |
47 | else | |
48 | return -EINVAL; | |
49 | } | |
50 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_clkdiv); | |
51 | ||
52 | /** | |
53 | * snd_soc_dai_set_pll - configure DAI PLL. | |
54 | * @dai: DAI | |
55 | * @pll_id: DAI specific PLL ID | |
56 | * @source: DAI specific source for the PLL | |
57 | * @freq_in: PLL input clock frequency in Hz | |
58 | * @freq_out: requested PLL output clock frequency in Hz | |
59 | * | |
60 | * Configures and enables PLL to generate output clock based on input clock. | |
61 | */ | |
62 | int snd_soc_dai_set_pll(struct snd_soc_dai *dai, int pll_id, int source, | |
63 | unsigned int freq_in, unsigned int freq_out) | |
64 | { | |
65 | if (dai->driver->ops->set_pll) | |
66 | return dai->driver->ops->set_pll(dai, pll_id, source, | |
67 | freq_in, freq_out); | |
68 | ||
69 | return snd_soc_component_set_pll(dai->component, pll_id, source, | |
70 | freq_in, freq_out); | |
71 | } | |
72 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_pll); | |
73 | ||
74 | /** | |
75 | * snd_soc_dai_set_bclk_ratio - configure BCLK to sample rate ratio. | |
76 | * @dai: DAI | |
77 | * @ratio: Ratio of BCLK to Sample rate. | |
78 | * | |
79 | * Configures the DAI for a preset BCLK to sample rate ratio. | |
80 | */ | |
81 | int snd_soc_dai_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) | |
82 | { | |
83 | if (dai->driver->ops->set_bclk_ratio) | |
84 | return dai->driver->ops->set_bclk_ratio(dai, ratio); | |
85 | else | |
86 | return -EINVAL; | |
87 | } | |
88 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_bclk_ratio); | |
89 | ||
90 | /** | |
91 | * snd_soc_dai_set_fmt - configure DAI hardware audio format. | |
92 | * @dai: DAI | |
93 | * @fmt: SND_SOC_DAIFMT_* format value. | |
94 | * | |
95 | * Configures the DAI hardware format and clocking. | |
96 | */ | |
97 | int snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) | |
98 | { | |
99 | if (dai->driver->ops->set_fmt == NULL) | |
100 | return -ENOTSUPP; | |
101 | return dai->driver->ops->set_fmt(dai, fmt); | |
102 | } | |
103 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_fmt); | |
104 | ||
105 | /** | |
106 | * snd_soc_xlate_tdm_slot - generate tx/rx slot mask. | |
107 | * @slots: Number of slots in use. | |
108 | * @tx_mask: bitmask representing active TX slots. | |
109 | * @rx_mask: bitmask representing active RX slots. | |
110 | * | |
111 | * Generates the TDM tx and rx slot default masks for DAI. | |
112 | */ | |
113 | static int snd_soc_xlate_tdm_slot_mask(unsigned int slots, | |
114 | unsigned int *tx_mask, | |
115 | unsigned int *rx_mask) | |
116 | { | |
117 | if (*tx_mask || *rx_mask) | |
118 | return 0; | |
119 | ||
120 | if (!slots) | |
121 | return -EINVAL; | |
122 | ||
123 | *tx_mask = (1 << slots) - 1; | |
124 | *rx_mask = (1 << slots) - 1; | |
125 | ||
126 | return 0; | |
127 | } | |
128 | ||
129 | /** | |
130 | * snd_soc_dai_set_tdm_slot() - Configures a DAI for TDM operation | |
131 | * @dai: The DAI to configure | |
132 | * @tx_mask: bitmask representing active TX slots. | |
133 | * @rx_mask: bitmask representing active RX slots. | |
134 | * @slots: Number of slots in use. | |
135 | * @slot_width: Width in bits for each slot. | |
136 | * | |
137 | * This function configures the specified DAI for TDM operation. @slot contains | |
138 | * the total number of slots of the TDM stream and @slot_with the width of each | |
139 | * slot in bit clock cycles. @tx_mask and @rx_mask are bitmasks specifying the | |
140 | * active slots of the TDM stream for the specified DAI, i.e. which slots the | |
141 | * DAI should write to or read from. If a bit is set the corresponding slot is | |
142 | * active, if a bit is cleared the corresponding slot is inactive. Bit 0 maps to | |
143 | * the first slot, bit 1 to the second slot and so on. The first active slot | |
144 | * maps to the first channel of the DAI, the second active slot to the second | |
145 | * channel and so on. | |
146 | * | |
147 | * TDM mode can be disabled by passing 0 for @slots. In this case @tx_mask, | |
148 | * @rx_mask and @slot_width will be ignored. | |
149 | * | |
150 | * Returns 0 on success, a negative error code otherwise. | |
151 | */ | |
152 | int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai, | |
153 | unsigned int tx_mask, unsigned int rx_mask, | |
154 | int slots, int slot_width) | |
155 | { | |
156 | if (dai->driver->ops->xlate_tdm_slot_mask) | |
157 | dai->driver->ops->xlate_tdm_slot_mask(slots, | |
158 | &tx_mask, &rx_mask); | |
159 | else | |
160 | snd_soc_xlate_tdm_slot_mask(slots, &tx_mask, &rx_mask); | |
161 | ||
162 | dai->tx_mask = tx_mask; | |
163 | dai->rx_mask = rx_mask; | |
164 | ||
165 | if (dai->driver->ops->set_tdm_slot) | |
166 | return dai->driver->ops->set_tdm_slot(dai, tx_mask, rx_mask, | |
167 | slots, slot_width); | |
168 | else | |
169 | return -ENOTSUPP; | |
170 | } | |
171 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_tdm_slot); | |
172 | ||
173 | /** | |
174 | * snd_soc_dai_set_channel_map - configure DAI audio channel map | |
175 | * @dai: DAI | |
176 | * @tx_num: how many TX channels | |
177 | * @tx_slot: pointer to an array which imply the TX slot number channel | |
178 | * 0~num-1 uses | |
179 | * @rx_num: how many RX channels | |
180 | * @rx_slot: pointer to an array which imply the RX slot number channel | |
181 | * 0~num-1 uses | |
182 | * | |
183 | * configure the relationship between channel number and TDM slot number. | |
184 | */ | |
185 | int snd_soc_dai_set_channel_map(struct snd_soc_dai *dai, | |
186 | unsigned int tx_num, unsigned int *tx_slot, | |
187 | unsigned int rx_num, unsigned int *rx_slot) | |
188 | { | |
189 | if (dai->driver->ops->set_channel_map) | |
190 | return dai->driver->ops->set_channel_map(dai, tx_num, tx_slot, | |
191 | rx_num, rx_slot); | |
192 | else | |
193 | return -ENOTSUPP; | |
194 | } | |
195 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_channel_map); | |
196 | ||
197 | /** | |
198 | * snd_soc_dai_get_channel_map - Get DAI audio channel map | |
199 | * @dai: DAI | |
200 | * @tx_num: how many TX channels | |
201 | * @tx_slot: pointer to an array which imply the TX slot number channel | |
202 | * 0~num-1 uses | |
203 | * @rx_num: how many RX channels | |
204 | * @rx_slot: pointer to an array which imply the RX slot number channel | |
205 | * 0~num-1 uses | |
206 | */ | |
207 | int snd_soc_dai_get_channel_map(struct snd_soc_dai *dai, | |
208 | unsigned int *tx_num, unsigned int *tx_slot, | |
209 | unsigned int *rx_num, unsigned int *rx_slot) | |
210 | { | |
211 | if (dai->driver->ops->get_channel_map) | |
212 | return dai->driver->ops->get_channel_map(dai, tx_num, tx_slot, | |
213 | rx_num, rx_slot); | |
214 | else | |
215 | return -ENOTSUPP; | |
216 | } | |
217 | EXPORT_SYMBOL_GPL(snd_soc_dai_get_channel_map); | |
218 | ||
219 | /** | |
220 | * snd_soc_dai_set_tristate - configure DAI system or master clock. | |
221 | * @dai: DAI | |
222 | * @tristate: tristate enable | |
223 | * | |
224 | * Tristates the DAI so that others can use it. | |
225 | */ | |
226 | int snd_soc_dai_set_tristate(struct snd_soc_dai *dai, int tristate) | |
227 | { | |
228 | if (dai->driver->ops->set_tristate) | |
229 | return dai->driver->ops->set_tristate(dai, tristate); | |
230 | else | |
231 | return -EINVAL; | |
232 | } | |
233 | EXPORT_SYMBOL_GPL(snd_soc_dai_set_tristate); | |
234 | ||
235 | /** | |
236 | * snd_soc_dai_digital_mute - configure DAI system or master clock. | |
237 | * @dai: DAI | |
238 | * @mute: mute enable | |
239 | * @direction: stream to mute | |
240 | * | |
241 | * Mutes the DAI DAC. | |
242 | */ | |
243 | int snd_soc_dai_digital_mute(struct snd_soc_dai *dai, int mute, | |
244 | int direction) | |
245 | { | |
246 | if (dai->driver->ops->mute_stream) | |
247 | return dai->driver->ops->mute_stream(dai, mute, direction); | |
248 | else if (direction == SNDRV_PCM_STREAM_PLAYBACK && | |
249 | dai->driver->ops->digital_mute) | |
250 | return dai->driver->ops->digital_mute(dai, mute); | |
251 | else | |
252 | return -ENOTSUPP; | |
253 | } | |
254 | EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute); | |
aa6166c2 KM |
255 | |
256 | int snd_soc_dai_hw_params(struct snd_soc_dai *dai, | |
257 | struct snd_pcm_substream *substream, | |
258 | struct snd_pcm_hw_params *params) | |
259 | { | |
260 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
261 | int ret; | |
262 | ||
263 | /* perform any topology hw_params fixups before DAI */ | |
264 | if (rtd->dai_link->be_hw_params_fixup) { | |
265 | ret = rtd->dai_link->be_hw_params_fixup(rtd, params); | |
266 | if (ret < 0) { | |
267 | dev_err(rtd->dev, | |
268 | "ASoC: hw_params topology fixup failed %d\n", | |
269 | ret); | |
270 | return ret; | |
271 | } | |
272 | } | |
273 | ||
274 | if (dai->driver->ops->hw_params) { | |
275 | ret = dai->driver->ops->hw_params(substream, params, dai); | |
276 | if (ret < 0) { | |
277 | dev_err(dai->dev, "ASoC: can't set %s hw params: %d\n", | |
278 | dai->name, ret); | |
279 | return ret; | |
280 | } | |
281 | } | |
282 | ||
283 | return 0; | |
284 | } | |
846faaed KM |
285 | |
286 | void snd_soc_dai_hw_free(struct snd_soc_dai *dai, | |
287 | struct snd_pcm_substream *substream) | |
288 | { | |
289 | if (dai->driver->ops->hw_free) | |
290 | dai->driver->ops->hw_free(substream, dai); | |
291 | } | |
5a52a045 KM |
292 | |
293 | int snd_soc_dai_startup(struct snd_soc_dai *dai, | |
294 | struct snd_pcm_substream *substream) | |
295 | { | |
296 | int ret = 0; | |
297 | ||
5bd70440 | 298 | if (dai->driver->ops->startup) |
5a52a045 KM |
299 | ret = dai->driver->ops->startup(substream, dai); |
300 | ||
301 | return ret; | |
302 | } | |
330fcb51 KM |
303 | |
304 | void snd_soc_dai_shutdown(struct snd_soc_dai *dai, | |
305 | struct snd_pcm_substream *substream) | |
306 | { | |
5bd70440 | 307 | if (dai->driver->ops->shutdown) |
330fcb51 KM |
308 | dai->driver->ops->shutdown(substream, dai); |
309 | } | |
4beb8e10 KM |
310 | |
311 | int snd_soc_dai_prepare(struct snd_soc_dai *dai, | |
312 | struct snd_pcm_substream *substream) | |
313 | { | |
314 | int ret = 0; | |
315 | ||
316 | if (dai->driver->ops->prepare) | |
317 | ret = dai->driver->ops->prepare(substream, dai); | |
318 | ||
319 | return ret; | |
320 | } | |
95aef355 KM |
321 | |
322 | int snd_soc_dai_trigger(struct snd_soc_dai *dai, | |
323 | struct snd_pcm_substream *substream, | |
324 | int cmd) | |
325 | { | |
326 | int ret = 0; | |
327 | ||
328 | if (dai->driver->ops->trigger) | |
329 | ret = dai->driver->ops->trigger(substream, cmd, dai); | |
330 | ||
331 | return ret; | |
332 | } | |
5c0769af KM |
333 | |
334 | int snd_soc_dai_bespoke_trigger(struct snd_soc_dai *dai, | |
335 | struct snd_pcm_substream *substream, | |
336 | int cmd) | |
337 | { | |
338 | int ret = 0; | |
339 | ||
340 | if (dai->driver->ops->bespoke_trigger) | |
341 | ret = dai->driver->ops->bespoke_trigger(substream, cmd, dai); | |
342 | ||
343 | return ret; | |
344 | } | |
1dea80d4 KM |
345 | |
346 | snd_pcm_sframes_t snd_soc_dai_delay(struct snd_soc_dai *dai, | |
347 | struct snd_pcm_substream *substream) | |
348 | { | |
349 | int delay = 0; | |
350 | ||
351 | if (dai->driver->ops->delay) | |
352 | delay = dai->driver->ops->delay(substream, dai); | |
353 | ||
354 | return delay; | |
355 | } | |
e0f22622 | 356 | |
cfd9b5fb KM |
357 | int snd_soc_dai_probe(struct snd_soc_dai *dai) |
358 | { | |
359 | if (dai->driver->probe) | |
360 | return dai->driver->probe(dai); | |
361 | return 0; | |
362 | } | |
dcdab582 KM |
363 | |
364 | int snd_soc_dai_remove(struct snd_soc_dai *dai) | |
365 | { | |
366 | if (dai->driver->remove) | |
367 | return dai->driver->remove(dai); | |
368 | return 0; | |
369 | } | |
b423c420 KM |
370 | |
371 | int snd_soc_dai_compress_new(struct snd_soc_dai *dai, | |
372 | struct snd_soc_pcm_runtime *rtd, int num) | |
373 | { | |
374 | if (dai->driver->compress_new) | |
375 | return dai->driver->compress_new(rtd, num); | |
376 | return -ENOTSUPP; | |
377 | } | |
467fece8 KM |
378 | |
379 | /* | |
380 | * snd_soc_dai_stream_valid() - check if a DAI supports the given stream | |
381 | * | |
382 | * Returns true if the DAI supports the indicated stream type. | |
383 | */ | |
384 | bool snd_soc_dai_stream_valid(struct snd_soc_dai *dai, int dir) | |
385 | { | |
acf253c1 | 386 | struct snd_soc_pcm_stream *stream = snd_soc_dai_get_pcm_stream(dai, dir); |
467fece8 KM |
387 | |
388 | /* If the codec specifies any channels at all, it supports the stream */ | |
389 | return stream->channels_min; | |
390 | } |