Commit | Line | Data |
---|---|---|
d60e4f1e JB |
1 | // SPDX-License-Identifier: (GPL-2.0 OR MIT) |
2 | // | |
3 | // Copyright (c) 2018 BayLibre, SAS. | |
4 | // Author: Jerome Brunet <jbrunet@baylibre.com> | |
5 | ||
6 | #include <linux/clk.h> | |
7 | #include <linux/module.h> | |
8 | #include <linux/of_platform.h> | |
9 | #include <sound/pcm_params.h> | |
10 | #include <sound/soc.h> | |
11 | #include <sound/soc-dai.h> | |
12 | ||
13 | #include "axg-tdm.h" | |
14 | ||
59c6a3a4 JB |
15 | /* Maximum bit clock frequency according the datasheets */ |
16 | #define MAX_SCLK 100000000 /* Hz */ | |
17 | ||
d60e4f1e JB |
18 | enum { |
19 | TDM_IFACE_PAD, | |
20 | TDM_IFACE_LOOPBACK, | |
21 | }; | |
22 | ||
23 | static unsigned int axg_tdm_slots_total(u32 *mask) | |
24 | { | |
25 | unsigned int slots = 0; | |
26 | int i; | |
27 | ||
28 | if (!mask) | |
29 | return 0; | |
30 | ||
31 | /* Count the total number of slots provided by all 4 lanes */ | |
32 | for (i = 0; i < AXG_TDM_NUM_LANES; i++) | |
33 | slots += hweight32(mask[i]); | |
34 | ||
35 | return slots; | |
36 | } | |
37 | ||
38 | int axg_tdm_set_tdm_slots(struct snd_soc_dai *dai, u32 *tx_mask, | |
39 | u32 *rx_mask, unsigned int slots, | |
40 | unsigned int slot_width) | |
41 | { | |
42 | struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); | |
c765ceda KM |
43 | struct axg_tdm_stream *tx = snd_soc_dai_dma_data_get_playback(dai); |
44 | struct axg_tdm_stream *rx = snd_soc_dai_dma_data_get_capture(dai); | |
d60e4f1e | 45 | unsigned int tx_slots, rx_slots; |
302df269 | 46 | unsigned int fmt = 0; |
d60e4f1e JB |
47 | |
48 | tx_slots = axg_tdm_slots_total(tx_mask); | |
49 | rx_slots = axg_tdm_slots_total(rx_mask); | |
50 | ||
51 | /* We should at least have a slot for a valid interface */ | |
52 | if (!tx_slots && !rx_slots) { | |
53 | dev_err(dai->dev, "interface has no slot\n"); | |
54 | return -EINVAL; | |
55 | } | |
56 | ||
d60e4f1e JB |
57 | iface->slots = slots; |
58 | ||
59 | switch (slot_width) { | |
60 | case 0: | |
302df269 | 61 | slot_width = 32; |
df561f66 | 62 | fallthrough; |
d60e4f1e | 63 | case 32: |
302df269 | 64 | fmt |= SNDRV_PCM_FMTBIT_S32_LE; |
df561f66 | 65 | fallthrough; |
302df269 JB |
66 | case 24: |
67 | fmt |= SNDRV_PCM_FMTBIT_S24_LE; | |
68 | fmt |= SNDRV_PCM_FMTBIT_S20_LE; | |
df561f66 | 69 | fallthrough; |
302df269 JB |
70 | case 16: |
71 | fmt |= SNDRV_PCM_FMTBIT_S16_LE; | |
df561f66 | 72 | fallthrough; |
302df269 JB |
73 | case 8: |
74 | fmt |= SNDRV_PCM_FMTBIT_S8; | |
d60e4f1e JB |
75 | break; |
76 | default: | |
77 | dev_err(dai->dev, "unsupported slot width: %d\n", slot_width); | |
78 | return -EINVAL; | |
79 | } | |
80 | ||
302df269 JB |
81 | iface->slot_width = slot_width; |
82 | ||
83 | /* Amend the dai driver and let dpcm merge do its job */ | |
84 | if (tx) { | |
85 | tx->mask = tx_mask; | |
86 | dai->driver->playback.channels_max = tx_slots; | |
87 | dai->driver->playback.formats = fmt; | |
88 | } | |
89 | ||
90 | if (rx) { | |
91 | rx->mask = rx_mask; | |
92 | dai->driver->capture.channels_max = rx_slots; | |
93 | dai->driver->capture.formats = fmt; | |
94 | } | |
95 | ||
d60e4f1e JB |
96 | return 0; |
97 | } | |
98 | EXPORT_SYMBOL_GPL(axg_tdm_set_tdm_slots); | |
99 | ||
100 | static int axg_tdm_iface_set_sysclk(struct snd_soc_dai *dai, int clk_id, | |
101 | unsigned int freq, int dir) | |
102 | { | |
103 | struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); | |
104 | int ret = -ENOTSUPP; | |
105 | ||
106 | if (dir == SND_SOC_CLOCK_OUT && clk_id == 0) { | |
107 | if (!iface->mclk) { | |
108 | dev_warn(dai->dev, "master clock not provided\n"); | |
109 | } else { | |
110 | ret = clk_set_rate(iface->mclk, freq); | |
111 | if (!ret) | |
112 | iface->mclk_rate = freq; | |
113 | } | |
114 | } | |
115 | ||
116 | return ret; | |
117 | } | |
118 | ||
119 | static int axg_tdm_iface_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) | |
120 | { | |
121 | struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); | |
122 | ||
f60442bf CK |
123 | switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { |
124 | case SND_SOC_DAIFMT_BP_FP: | |
6878ba91 JB |
125 | if (!iface->mclk) { |
126 | dev_err(dai->dev, "cpu clock master: mclk missing\n"); | |
127 | return -ENODEV; | |
128 | } | |
129 | break; | |
130 | ||
f60442bf | 131 | case SND_SOC_DAIFMT_BC_FC: |
6878ba91 JB |
132 | break; |
133 | ||
f60442bf CK |
134 | case SND_SOC_DAIFMT_BP_FC: |
135 | case SND_SOC_DAIFMT_BC_FP: | |
48bbec09 | 136 | dev_err(dai->dev, "only BP_FP and BC_FC are supported\n"); |
df561f66 | 137 | fallthrough; |
6878ba91 | 138 | default: |
d60e4f1e JB |
139 | return -EINVAL; |
140 | } | |
141 | ||
d60e4f1e JB |
142 | iface->fmt = fmt; |
143 | return 0; | |
144 | } | |
145 | ||
146 | static int axg_tdm_iface_startup(struct snd_pcm_substream *substream, | |
147 | struct snd_soc_dai *dai) | |
148 | { | |
149 | struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); | |
150 | struct axg_tdm_stream *ts = | |
151 | snd_soc_dai_get_dma_data(dai, substream); | |
152 | int ret; | |
153 | ||
154 | if (!axg_tdm_slots_total(ts->mask)) { | |
155 | dev_err(dai->dev, "interface has not slots\n"); | |
156 | return -EINVAL; | |
157 | } | |
158 | ||
1f79aab5 | 159 | if (snd_soc_component_active(dai->component)) { |
59c6a3a4 | 160 | /* Apply component wide rate symmetry */ |
d60e4f1e JB |
161 | ret = snd_pcm_hw_constraint_single(substream->runtime, |
162 | SNDRV_PCM_HW_PARAM_RATE, | |
163 | iface->rate); | |
59c6a3a4 JB |
164 | |
165 | } else { | |
166 | /* Limit rate according to the slot number and width */ | |
167 | unsigned int max_rate = | |
168 | MAX_SCLK / (iface->slots * iface->slot_width); | |
169 | ret = snd_pcm_hw_constraint_minmax(substream->runtime, | |
170 | SNDRV_PCM_HW_PARAM_RATE, | |
171 | 0, max_rate); | |
d60e4f1e JB |
172 | } |
173 | ||
59c6a3a4 JB |
174 | if (ret < 0) |
175 | dev_err(dai->dev, "can't set iface rate constraint\n"); | |
176 | else | |
177 | ret = 0; | |
178 | ||
179 | return ret; | |
d60e4f1e JB |
180 | } |
181 | ||
182 | static int axg_tdm_iface_set_stream(struct snd_pcm_substream *substream, | |
183 | struct snd_pcm_hw_params *params, | |
184 | struct snd_soc_dai *dai) | |
185 | { | |
186 | struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); | |
187 | struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream); | |
188 | unsigned int channels = params_channels(params); | |
189 | unsigned int width = params_width(params); | |
190 | ||
191 | /* Save rate and sample_bits for component symmetry */ | |
192 | iface->rate = params_rate(params); | |
193 | ||
194 | /* Make sure this interface can cope with the stream */ | |
195 | if (axg_tdm_slots_total(ts->mask) < channels) { | |
196 | dev_err(dai->dev, "not enough slots for channels\n"); | |
197 | return -EINVAL; | |
198 | } | |
199 | ||
200 | if (iface->slot_width < width) { | |
201 | dev_err(dai->dev, "incompatible slots width for stream\n"); | |
202 | return -EINVAL; | |
203 | } | |
204 | ||
205 | /* Save the parameter for tdmout/tdmin widgets */ | |
206 | ts->physical_width = params_physical_width(params); | |
207 | ts->width = params_width(params); | |
208 | ts->channels = params_channels(params); | |
209 | ||
210 | return 0; | |
211 | } | |
212 | ||
213 | static int axg_tdm_iface_set_lrclk(struct snd_soc_dai *dai, | |
214 | struct snd_pcm_hw_params *params) | |
215 | { | |
216 | struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); | |
217 | unsigned int ratio_num; | |
218 | int ret; | |
219 | ||
220 | ret = clk_set_rate(iface->lrclk, params_rate(params)); | |
221 | if (ret) { | |
222 | dev_err(dai->dev, "setting sample clock failed: %d\n", ret); | |
223 | return ret; | |
224 | } | |
225 | ||
226 | switch (iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | |
227 | case SND_SOC_DAIFMT_I2S: | |
228 | case SND_SOC_DAIFMT_LEFT_J: | |
229 | case SND_SOC_DAIFMT_RIGHT_J: | |
230 | /* 50% duty cycle ratio */ | |
231 | ratio_num = 1; | |
232 | break; | |
233 | ||
234 | case SND_SOC_DAIFMT_DSP_A: | |
235 | case SND_SOC_DAIFMT_DSP_B: | |
236 | /* | |
237 | * A zero duty cycle ratio will result in setting the mininum | |
238 | * ratio possible which, for this clock, is 1 cycle of the | |
239 | * parent bclk clock high and the rest low, This is exactly | |
240 | * what we want here. | |
241 | */ | |
242 | ratio_num = 0; | |
243 | break; | |
244 | ||
245 | default: | |
246 | return -EINVAL; | |
247 | } | |
248 | ||
249 | ret = clk_set_duty_cycle(iface->lrclk, ratio_num, 2); | |
250 | if (ret) { | |
251 | dev_err(dai->dev, | |
252 | "setting sample clock duty cycle failed: %d\n", ret); | |
253 | return ret; | |
254 | } | |
255 | ||
256 | /* Set sample clock inversion */ | |
257 | ret = clk_set_phase(iface->lrclk, | |
258 | axg_tdm_lrclk_invert(iface->fmt) ? 180 : 0); | |
259 | if (ret) { | |
260 | dev_err(dai->dev, | |
261 | "setting sample clock phase failed: %d\n", ret); | |
262 | return ret; | |
263 | } | |
264 | ||
265 | return 0; | |
266 | } | |
267 | ||
268 | static int axg_tdm_iface_set_sclk(struct snd_soc_dai *dai, | |
269 | struct snd_pcm_hw_params *params) | |
270 | { | |
271 | struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); | |
272 | unsigned long srate; | |
273 | int ret; | |
274 | ||
275 | srate = iface->slots * iface->slot_width * params_rate(params); | |
276 | ||
277 | if (!iface->mclk_rate) { | |
e3741a8d JB |
278 | /* If no specific mclk is requested, default to bit clock * 2 */ |
279 | clk_set_rate(iface->mclk, 2 * srate); | |
d60e4f1e JB |
280 | } else { |
281 | /* Check if we can actually get the bit clock from mclk */ | |
282 | if (iface->mclk_rate % srate) { | |
283 | dev_err(dai->dev, | |
284 | "can't derive sclk %lu from mclk %lu\n", | |
285 | srate, iface->mclk_rate); | |
286 | return -EINVAL; | |
287 | } | |
288 | } | |
289 | ||
290 | ret = clk_set_rate(iface->sclk, srate); | |
291 | if (ret) { | |
292 | dev_err(dai->dev, "setting bit clock failed: %d\n", ret); | |
293 | return ret; | |
294 | } | |
295 | ||
296 | /* Set the bit clock inversion */ | |
297 | ret = clk_set_phase(iface->sclk, | |
298 | axg_tdm_sclk_invert(iface->fmt) ? 0 : 180); | |
299 | if (ret) { | |
300 | dev_err(dai->dev, "setting bit clock phase failed: %d\n", ret); | |
301 | return ret; | |
302 | } | |
303 | ||
304 | return ret; | |
305 | } | |
306 | ||
307 | static int axg_tdm_iface_hw_params(struct snd_pcm_substream *substream, | |
308 | struct snd_pcm_hw_params *params, | |
309 | struct snd_soc_dai *dai) | |
310 | { | |
311 | struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); | |
a5a89037 | 312 | struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream); |
d60e4f1e JB |
313 | int ret; |
314 | ||
315 | switch (iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | |
316 | case SND_SOC_DAIFMT_I2S: | |
317 | case SND_SOC_DAIFMT_LEFT_J: | |
318 | case SND_SOC_DAIFMT_RIGHT_J: | |
319 | if (iface->slots > 2) { | |
320 | dev_err(dai->dev, "bad slot number for format: %d\n", | |
321 | iface->slots); | |
322 | return -EINVAL; | |
323 | } | |
324 | break; | |
325 | ||
05113483 JB |
326 | case SND_SOC_DAIFMT_DSP_A: |
327 | case SND_SOC_DAIFMT_DSP_B: | |
d60e4f1e JB |
328 | break; |
329 | ||
330 | default: | |
331 | dev_err(dai->dev, "unsupported dai format\n"); | |
332 | return -EINVAL; | |
333 | } | |
334 | ||
335 | ret = axg_tdm_iface_set_stream(substream, params, dai); | |
336 | if (ret) | |
337 | return ret; | |
338 | ||
f60442bf CK |
339 | if ((iface->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) == |
340 | SND_SOC_DAIFMT_BP_FP) { | |
d60e4f1e JB |
341 | ret = axg_tdm_iface_set_sclk(dai, params); |
342 | if (ret) | |
343 | return ret; | |
344 | ||
345 | ret = axg_tdm_iface_set_lrclk(dai, params); | |
346 | if (ret) | |
347 | return ret; | |
348 | } | |
349 | ||
a5a89037 JB |
350 | ret = axg_tdm_stream_set_cont_clocks(ts, iface->fmt); |
351 | if (ret) | |
352 | dev_err(dai->dev, "failed to apply continuous clock setting\n"); | |
353 | ||
354 | return ret; | |
355 | } | |
356 | ||
357 | static int axg_tdm_iface_hw_free(struct snd_pcm_substream *substream, | |
358 | struct snd_soc_dai *dai) | |
359 | { | |
360 | struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream); | |
361 | ||
362 | return axg_tdm_stream_set_cont_clocks(ts, 0); | |
d60e4f1e JB |
363 | } |
364 | ||
f949ed45 JB |
365 | static int axg_tdm_iface_trigger(struct snd_pcm_substream *substream, |
366 | int cmd, | |
d60e4f1e JB |
367 | struct snd_soc_dai *dai) |
368 | { | |
f949ed45 JB |
369 | struct axg_tdm_stream *ts = |
370 | snd_soc_dai_get_dma_data(dai, substream); | |
d60e4f1e | 371 | |
f949ed45 JB |
372 | switch (cmd) { |
373 | case SNDRV_PCM_TRIGGER_START: | |
374 | case SNDRV_PCM_TRIGGER_RESUME: | |
375 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | |
376 | axg_tdm_stream_start(ts); | |
377 | break; | |
378 | case SNDRV_PCM_TRIGGER_SUSPEND: | |
379 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | |
380 | case SNDRV_PCM_TRIGGER_STOP: | |
381 | axg_tdm_stream_stop(ts); | |
382 | break; | |
383 | default: | |
384 | return -EINVAL; | |
385 | } | |
d60e4f1e JB |
386 | |
387 | return 0; | |
388 | } | |
389 | ||
d60e4f1e JB |
390 | static int axg_tdm_iface_remove_dai(struct snd_soc_dai *dai) |
391 | { | |
c765ceda | 392 | int stream; |
d60e4f1e | 393 | |
c765ceda KM |
394 | for_each_pcm_streams(stream) { |
395 | struct axg_tdm_stream *ts = snd_soc_dai_dma_data_get(dai, stream); | |
396 | ||
397 | if (ts) | |
398 | axg_tdm_stream_free(ts); | |
399 | } | |
d60e4f1e JB |
400 | |
401 | return 0; | |
402 | } | |
403 | ||
404 | static int axg_tdm_iface_probe_dai(struct snd_soc_dai *dai) | |
405 | { | |
406 | struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); | |
c765ceda | 407 | int stream; |
d60e4f1e | 408 | |
c765ceda KM |
409 | for_each_pcm_streams(stream) { |
410 | struct axg_tdm_stream *ts; | |
411 | ||
412 | if (!snd_soc_dai_get_widget(dai, stream)) | |
413 | continue; | |
d60e4f1e | 414 | |
c765ceda KM |
415 | ts = axg_tdm_stream_alloc(iface); |
416 | if (!ts) { | |
d60e4f1e JB |
417 | axg_tdm_iface_remove_dai(dai); |
418 | return -ENOMEM; | |
419 | } | |
c765ceda | 420 | snd_soc_dai_dma_data_set(dai, stream, ts); |
d60e4f1e JB |
421 | } |
422 | ||
423 | return 0; | |
424 | } | |
425 | ||
426 | static const struct snd_soc_dai_ops axg_tdm_iface_ops = { | |
2d3155a9 KM |
427 | .probe = axg_tdm_iface_probe_dai, |
428 | .remove = axg_tdm_iface_remove_dai, | |
d60e4f1e | 429 | .set_sysclk = axg_tdm_iface_set_sysclk, |
eee6b5b9 | 430 | .set_fmt = axg_tdm_iface_set_fmt, |
d60e4f1e JB |
431 | .startup = axg_tdm_iface_startup, |
432 | .hw_params = axg_tdm_iface_hw_params, | |
a5a89037 | 433 | .hw_free = axg_tdm_iface_hw_free, |
f949ed45 | 434 | .trigger = axg_tdm_iface_trigger, |
d60e4f1e JB |
435 | }; |
436 | ||
437 | /* TDM Backend DAIs */ | |
438 | static const struct snd_soc_dai_driver axg_tdm_iface_dai_drv[] = { | |
439 | [TDM_IFACE_PAD] = { | |
440 | .name = "TDM Pad", | |
441 | .playback = { | |
442 | .stream_name = "Playback", | |
443 | .channels_min = 1, | |
444 | .channels_max = AXG_TDM_CHANNEL_MAX, | |
445 | .rates = AXG_TDM_RATES, | |
446 | .formats = AXG_TDM_FORMATS, | |
447 | }, | |
448 | .capture = { | |
449 | .stream_name = "Capture", | |
450 | .channels_min = 1, | |
451 | .channels_max = AXG_TDM_CHANNEL_MAX, | |
452 | .rates = AXG_TDM_RATES, | |
453 | .formats = AXG_TDM_FORMATS, | |
454 | }, | |
455 | .id = TDM_IFACE_PAD, | |
456 | .ops = &axg_tdm_iface_ops, | |
d60e4f1e JB |
457 | }, |
458 | [TDM_IFACE_LOOPBACK] = { | |
459 | .name = "TDM Loopback", | |
460 | .capture = { | |
461 | .stream_name = "Loopback", | |
462 | .channels_min = 1, | |
463 | .channels_max = AXG_TDM_CHANNEL_MAX, | |
464 | .rates = AXG_TDM_RATES, | |
465 | .formats = AXG_TDM_FORMATS, | |
466 | }, | |
467 | .id = TDM_IFACE_LOOPBACK, | |
468 | .ops = &axg_tdm_iface_ops, | |
d60e4f1e JB |
469 | }, |
470 | }; | |
471 | ||
472 | static int axg_tdm_iface_set_bias_level(struct snd_soc_component *component, | |
473 | enum snd_soc_bias_level level) | |
474 | { | |
475 | struct axg_tdm_iface *iface = snd_soc_component_get_drvdata(component); | |
476 | enum snd_soc_bias_level now = | |
477 | snd_soc_component_get_bias_level(component); | |
478 | int ret = 0; | |
479 | ||
480 | switch (level) { | |
481 | case SND_SOC_BIAS_PREPARE: | |
482 | if (now == SND_SOC_BIAS_STANDBY) | |
483 | ret = clk_prepare_enable(iface->mclk); | |
484 | break; | |
485 | ||
486 | case SND_SOC_BIAS_STANDBY: | |
487 | if (now == SND_SOC_BIAS_PREPARE) | |
488 | clk_disable_unprepare(iface->mclk); | |
489 | break; | |
490 | ||
491 | case SND_SOC_BIAS_OFF: | |
492 | case SND_SOC_BIAS_ON: | |
493 | break; | |
494 | } | |
495 | ||
496 | return ret; | |
497 | } | |
498 | ||
671ee4db JB |
499 | static const struct snd_soc_dapm_widget axg_tdm_iface_dapm_widgets[] = { |
500 | SND_SOC_DAPM_SIGGEN("Playback Signal"), | |
501 | }; | |
502 | ||
503 | static const struct snd_soc_dapm_route axg_tdm_iface_dapm_routes[] = { | |
504 | { "Loopback", NULL, "Playback Signal" }, | |
505 | }; | |
506 | ||
d60e4f1e | 507 | static const struct snd_soc_component_driver axg_tdm_iface_component_drv = { |
671ee4db JB |
508 | .dapm_widgets = axg_tdm_iface_dapm_widgets, |
509 | .num_dapm_widgets = ARRAY_SIZE(axg_tdm_iface_dapm_widgets), | |
510 | .dapm_routes = axg_tdm_iface_dapm_routes, | |
511 | .num_dapm_routes = ARRAY_SIZE(axg_tdm_iface_dapm_routes), | |
512 | .set_bias_level = axg_tdm_iface_set_bias_level, | |
d60e4f1e JB |
513 | }; |
514 | ||
515 | static const struct of_device_id axg_tdm_iface_of_match[] = { | |
516 | { .compatible = "amlogic,axg-tdm-iface", }, | |
517 | {} | |
518 | }; | |
519 | MODULE_DEVICE_TABLE(of, axg_tdm_iface_of_match); | |
520 | ||
521 | static int axg_tdm_iface_probe(struct platform_device *pdev) | |
522 | { | |
523 | struct device *dev = &pdev->dev; | |
524 | struct snd_soc_dai_driver *dai_drv; | |
525 | struct axg_tdm_iface *iface; | |
65d4d725 | 526 | int i; |
d60e4f1e JB |
527 | |
528 | iface = devm_kzalloc(dev, sizeof(*iface), GFP_KERNEL); | |
529 | if (!iface) | |
530 | return -ENOMEM; | |
531 | platform_set_drvdata(pdev, iface); | |
532 | ||
533 | /* | |
534 | * Duplicate dai driver: depending on the slot masks configuration | |
535 | * We'll change the number of channel provided by DAI stream, so dpcm | |
536 | * channel merge can be done properly | |
537 | */ | |
538 | dai_drv = devm_kcalloc(dev, ARRAY_SIZE(axg_tdm_iface_dai_drv), | |
539 | sizeof(*dai_drv), GFP_KERNEL); | |
540 | if (!dai_drv) | |
541 | return -ENOMEM; | |
542 | ||
543 | for (i = 0; i < ARRAY_SIZE(axg_tdm_iface_dai_drv); i++) | |
544 | memcpy(&dai_drv[i], &axg_tdm_iface_dai_drv[i], | |
545 | sizeof(*dai_drv)); | |
546 | ||
547 | /* Bit clock provided on the pad */ | |
548 | iface->sclk = devm_clk_get(dev, "sclk"); | |
2ff4e003 KM |
549 | if (IS_ERR(iface->sclk)) |
550 | return dev_err_probe(dev, PTR_ERR(iface->sclk), "failed to get sclk\n"); | |
d60e4f1e JB |
551 | |
552 | /* Sample clock provided on the pad */ | |
553 | iface->lrclk = devm_clk_get(dev, "lrclk"); | |
2ff4e003 KM |
554 | if (IS_ERR(iface->lrclk)) |
555 | return dev_err_probe(dev, PTR_ERR(iface->lrclk), "failed to get lrclk\n"); | |
d60e4f1e JB |
556 | |
557 | /* | |
558 | * mclk maybe be missing when the cpu dai is in slave mode and | |
559 | * the codec does not require it to provide a master clock. | |
560 | * At this point, ignore the error if mclk is missing. We'll | |
561 | * throw an error if the cpu dai is master and mclk is missing | |
562 | */ | |
65d4d725 CJ |
563 | iface->mclk = devm_clk_get_optional(dev, "mclk"); |
564 | if (IS_ERR(iface->mclk)) | |
565 | return dev_err_probe(dev, PTR_ERR(iface->mclk), "failed to get mclk\n"); | |
d60e4f1e JB |
566 | |
567 | return devm_snd_soc_register_component(dev, | |
568 | &axg_tdm_iface_component_drv, dai_drv, | |
569 | ARRAY_SIZE(axg_tdm_iface_dai_drv)); | |
570 | } | |
571 | ||
572 | static struct platform_driver axg_tdm_iface_pdrv = { | |
573 | .probe = axg_tdm_iface_probe, | |
574 | .driver = { | |
575 | .name = "axg-tdm-iface", | |
576 | .of_match_table = axg_tdm_iface_of_match, | |
577 | }, | |
578 | }; | |
579 | module_platform_driver(axg_tdm_iface_pdrv); | |
580 | ||
581 | MODULE_DESCRIPTION("Amlogic AXG TDM interface driver"); | |
582 | MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); | |
583 | MODULE_LICENSE("GPL v2"); |