1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com |
4 | * Author: Peter Ujfalusi <peter.ujfalusi@ti.com> |
5 | */ |
6 | |
7 | #include <linux/clk.h> |
8 | #include <linux/module.h> |
9 | #include <linux/of.h> |
10 | #include <linux/platform_device.h> |
11 | |
12 | #include <sound/core.h> |
13 | #include <sound/pcm.h> |
14 | #include <sound/pcm_params.h> |
15 | #include <sound/soc.h> |
16 | |
17 | #include "davinci-mcasp.h" |
18 | |
19 | /* |
20 | * Maximum number of configuration entries for prefixes: |
21 | * CPB: 2 (mcasp10 + codec) |
22 | * IVI: 3 (mcasp0 + 2x codec) |
23 | */ |
24 | #define J721E_CODEC_CONF_COUNT 5 |
25 | |
26 | enum j721e_audio_domain_id { |
27 | J721E_AUDIO_DOMAIN_CPB = 0, |
28 | J721E_AUDIO_DOMAIN_IVI, |
29 | J721E_AUDIO_DOMAIN_LAST, |
30 | }; |
31 | |
32 | #define J721E_CLK_PARENT_48000 0 |
33 | #define J721E_CLK_PARENT_44100 1 |
34 | |
35 | #define J721E_MAX_CLK_HSDIV 128 |
36 | #define PCM1368A_MAX_SYSCLK 36864000 |
37 | |
38 | #define J721E_DAI_FMT (SND_SOC_DAIFMT_RIGHT_J | \ |
39 | SND_SOC_DAIFMT_NB_NF | \ |
40 | SND_SOC_DAIFMT_CBS_CFS) |
41 | |
42 | enum j721e_board_type { |
43 | J721E_BOARD_CPB = 1, |
44 | J721E_BOARD_CPB_IVI, |
45 | }; |
46 | |
47 | struct j721e_audio_match_data { |
48 | enum j721e_board_type board_type; |
49 | int num_links; |
50 | unsigned int pll_rates[2]; |
51 | }; |
52 | |
53 | static unsigned int ratios_for_pcm3168a[] = { |
54 | 256, |
55 | 512, |
56 | 768, |
57 | }; |
58 | |
59 | struct j721e_audio_clocks { |
60 | struct clk *target; |
61 | struct clk *parent[2]; |
62 | }; |
63 | |
64 | struct j721e_audio_domain { |
65 | struct j721e_audio_clocks codec; |
66 | struct j721e_audio_clocks mcasp; |
67 | int parent_clk_id; |
68 | |
69 | int active; |
70 | unsigned int active_link; |
71 | unsigned int rate; |
72 | }; |
73 | |
74 | struct j721e_priv { |
75 | struct device *dev; |
76 | struct snd_soc_card card; |
77 | struct snd_soc_dai_link *dai_links; |
78 | struct snd_soc_codec_conf codec_conf[J721E_CODEC_CONF_COUNT]; |
79 | struct snd_interval rate_range; |
80 | const struct j721e_audio_match_data *match_data; |
81 | u32 pll_rates[2]; |
82 | unsigned int hsdiv_rates[2]; |
83 | |
84 | struct j721e_audio_domain audio_domains[J721E_AUDIO_DOMAIN_LAST]; |
85 | |
86 | struct mutex mutex; |
87 | }; |
88 | |
89 | static const struct snd_soc_dapm_widget j721e_cpb_dapm_widgets[] = { |
90 | SND_SOC_DAPM_HP("CPB Stereo HP 1" , NULL), |
91 | SND_SOC_DAPM_HP("CPB Stereo HP 2" , NULL), |
92 | SND_SOC_DAPM_HP("CPB Stereo HP 3" , NULL), |
93 | SND_SOC_DAPM_LINE("CPB Line Out" , NULL), |
94 | SND_SOC_DAPM_MIC("CPB Stereo Mic 1" , NULL), |
95 | SND_SOC_DAPM_MIC("CPB Stereo Mic 2" , NULL), |
96 | SND_SOC_DAPM_LINE("CPB Line In" , NULL), |
97 | }; |
98 | |
99 | static const struct snd_soc_dapm_route j721e_cpb_dapm_routes[] = { |
100 | {"CPB Stereo HP 1" , NULL, "codec-1 AOUT1L" }, |
101 | {"CPB Stereo HP 1" , NULL, "codec-1 AOUT1R" }, |
102 | {"CPB Stereo HP 2" , NULL, "codec-1 AOUT2L" }, |
103 | {"CPB Stereo HP 2" , NULL, "codec-1 AOUT2R" }, |
104 | {"CPB Stereo HP 3" , NULL, "codec-1 AOUT3L" }, |
105 | {"CPB Stereo HP 3" , NULL, "codec-1 AOUT3R" }, |
106 | {"CPB Line Out" , NULL, "codec-1 AOUT4L" }, |
107 | {"CPB Line Out" , NULL, "codec-1 AOUT4R" }, |
108 | |
109 | {"codec-1 AIN1L" , NULL, "CPB Stereo Mic 1" }, |
110 | {"codec-1 AIN1R" , NULL, "CPB Stereo Mic 1" }, |
111 | {"codec-1 AIN2L" , NULL, "CPB Stereo Mic 2" }, |
112 | {"codec-1 AIN2R" , NULL, "CPB Stereo Mic 2" }, |
113 | {"codec-1 AIN3L" , NULL, "CPB Line In" }, |
114 | {"codec-1 AIN3R" , NULL, "CPB Line In" }, |
115 | }; |
116 | |
117 | static const struct snd_soc_dapm_widget j721e_ivi_codec_a_dapm_widgets[] = { |
118 | SND_SOC_DAPM_LINE("IVI A Line Out 1" , NULL), |
119 | SND_SOC_DAPM_LINE("IVI A Line Out 2" , NULL), |
120 | SND_SOC_DAPM_LINE("IVI A Line Out 3" , NULL), |
121 | SND_SOC_DAPM_LINE("IVI A Line Out 4" , NULL), |
122 | SND_SOC_DAPM_MIC("IVI A Stereo Mic 1" , NULL), |
123 | SND_SOC_DAPM_MIC("IVI A Stereo Mic 2" , NULL), |
124 | SND_SOC_DAPM_LINE("IVI A Line In" , NULL), |
125 | }; |
126 | |
127 | static const struct snd_soc_dapm_route j721e_codec_a_dapm_routes[] = { |
128 | {"IVI A Line Out 1" , NULL, "codec-a AOUT1L" }, |
129 | {"IVI A Line Out 1" , NULL, "codec-a AOUT1R" }, |
130 | {"IVI A Line Out 2" , NULL, "codec-a AOUT2L" }, |
131 | {"IVI A Line Out 2" , NULL, "codec-a AOUT2R" }, |
132 | {"IVI A Line Out 3" , NULL, "codec-a AOUT3L" }, |
133 | {"IVI A Line Out 3" , NULL, "codec-a AOUT3R" }, |
134 | {"IVI A Line Out 4" , NULL, "codec-a AOUT4L" }, |
135 | {"IVI A Line Out 4" , NULL, "codec-a AOUT4R" }, |
136 | |
137 | {"codec-a AIN1L" , NULL, "IVI A Stereo Mic 1" }, |
138 | {"codec-a AIN1R" , NULL, "IVI A Stereo Mic 1" }, |
139 | {"codec-a AIN2L" , NULL, "IVI A Stereo Mic 2" }, |
140 | {"codec-a AIN2R" , NULL, "IVI A Stereo Mic 2" }, |
141 | {"codec-a AIN3L" , NULL, "IVI A Line In" }, |
142 | {"codec-a AIN3R" , NULL, "IVI A Line In" }, |
143 | }; |
144 | |
145 | static const struct snd_soc_dapm_widget j721e_ivi_codec_b_dapm_widgets[] = { |
146 | SND_SOC_DAPM_LINE("IVI B Line Out 1" , NULL), |
147 | SND_SOC_DAPM_LINE("IVI B Line Out 2" , NULL), |
148 | SND_SOC_DAPM_LINE("IVI B Line Out 3" , NULL), |
149 | SND_SOC_DAPM_LINE("IVI B Line Out 4" , NULL), |
150 | SND_SOC_DAPM_MIC("IVI B Stereo Mic 1" , NULL), |
151 | SND_SOC_DAPM_MIC("IVI B Stereo Mic 2" , NULL), |
152 | SND_SOC_DAPM_LINE("IVI B Line In" , NULL), |
153 | }; |
154 | |
155 | static const struct snd_soc_dapm_route j721e_codec_b_dapm_routes[] = { |
156 | {"IVI B Line Out 1" , NULL, "codec-b AOUT1L" }, |
157 | {"IVI B Line Out 1" , NULL, "codec-b AOUT1R" }, |
158 | {"IVI B Line Out 2" , NULL, "codec-b AOUT2L" }, |
159 | {"IVI B Line Out 2" , NULL, "codec-b AOUT2R" }, |
160 | {"IVI B Line Out 3" , NULL, "codec-b AOUT3L" }, |
161 | {"IVI B Line Out 3" , NULL, "codec-b AOUT3R" }, |
162 | {"IVI B Line Out 4" , NULL, "codec-b AOUT4L" }, |
163 | {"IVI B Line Out 4" , NULL, "codec-b AOUT4R" }, |
164 | |
165 | {"codec-b AIN1L" , NULL, "IVI B Stereo Mic 1" }, |
166 | {"codec-b AIN1R" , NULL, "IVI B Stereo Mic 1" }, |
167 | {"codec-b AIN2L" , NULL, "IVI B Stereo Mic 2" }, |
168 | {"codec-b AIN2R" , NULL, "IVI B Stereo Mic 2" }, |
169 | {"codec-b AIN3L" , NULL, "IVI B Line In" }, |
170 | {"codec-b AIN3R" , NULL, "IVI B Line In" }, |
171 | }; |
172 | |
173 | static int j721e_configure_refclk(struct j721e_priv *priv, |
174 | unsigned int audio_domain, unsigned int rate) |
175 | { |
176 | struct j721e_audio_domain *domain = &priv->audio_domains[audio_domain]; |
177 | unsigned int scki; |
178 | int ret = -EINVAL; |
179 | int i, clk_id; |
180 | |
181 | if (!(rate % 8000) && priv->pll_rates[J721E_CLK_PARENT_48000]) |
182 | clk_id = J721E_CLK_PARENT_48000; |
183 | else if (!(rate % 11025) && priv->pll_rates[J721E_CLK_PARENT_44100]) |
184 | clk_id = J721E_CLK_PARENT_44100; |
185 | else |
186 | return ret; |
187 | |
188 | for (i = 0; i < ARRAY_SIZE(ratios_for_pcm3168a); i++) { |
189 | scki = ratios_for_pcm3168a[i] * rate; |
190 | |
191 | if (priv->pll_rates[clk_id] / scki <= J721E_MAX_CLK_HSDIV) { |
192 | ret = 0; |
193 | break; |
194 | } |
195 | } |
196 | |
197 | if (ret) { |
198 | dev_err(priv->dev, "No valid clock configuration for %u Hz\n" , |
199 | rate); |
200 | return ret; |
201 | } |
202 | |
203 | if (domain->parent_clk_id == -1 || priv->hsdiv_rates[domain->parent_clk_id] != scki) { |
204 | dev_dbg(priv->dev, |
205 | "domain%u configuration for %u Hz: %s, %dxFS (SCKI: %u Hz)\n" , |
206 | audio_domain, rate, |
207 | clk_id == J721E_CLK_PARENT_48000 ? "PLL4" : "PLL15" , |
208 | ratios_for_pcm3168a[i], scki); |
209 | |
210 | if (domain->parent_clk_id != clk_id) { |
211 | ret = clk_set_parent(clk: domain->codec.target, |
212 | parent: domain->codec.parent[clk_id]); |
213 | if (ret) |
214 | return ret; |
215 | |
216 | ret = clk_set_parent(clk: domain->mcasp.target, |
217 | parent: domain->mcasp.parent[clk_id]); |
218 | if (ret) |
219 | return ret; |
220 | |
221 | domain->parent_clk_id = clk_id; |
222 | } |
223 | |
224 | ret = clk_set_rate(clk: domain->codec.target, rate: scki); |
225 | if (ret) { |
226 | dev_err(priv->dev, "codec set rate failed for %u Hz\n" , |
227 | scki); |
228 | return ret; |
229 | } |
230 | |
231 | ret = clk_set_rate(clk: domain->mcasp.target, rate: scki); |
232 | if (!ret) { |
233 | priv->hsdiv_rates[domain->parent_clk_id] = scki; |
234 | } else { |
235 | dev_err(priv->dev, "mcasp set rate failed for %u Hz\n" , |
236 | scki); |
237 | return ret; |
238 | } |
239 | } |
240 | |
241 | return ret; |
242 | } |
243 | |
244 | static int j721e_rule_rate(struct snd_pcm_hw_params *params, |
245 | struct snd_pcm_hw_rule *rule) |
246 | { |
247 | struct snd_interval *t = rule->private; |
248 | |
249 | return snd_interval_refine(i: hw_param_interval(params, var: rule->var), v: t); |
250 | } |
251 | |
252 | static int j721e_audio_startup(struct snd_pcm_substream *substream) |
253 | { |
254 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
255 | struct j721e_priv *priv = snd_soc_card_get_drvdata(card: rtd->card); |
256 | unsigned int domain_id = rtd->dai_link->id; |
257 | struct j721e_audio_domain *domain = &priv->audio_domains[domain_id]; |
258 | struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); |
259 | struct snd_soc_dai *codec_dai; |
260 | unsigned int active_rate; |
261 | int ret = 0; |
262 | int i; |
263 | |
264 | mutex_lock(&priv->mutex); |
265 | |
266 | domain->active++; |
267 | |
268 | for (i = 0; i < J721E_AUDIO_DOMAIN_LAST; i++) { |
269 | active_rate = priv->audio_domains[i].rate; |
270 | if (active_rate) |
271 | break; |
272 | } |
273 | |
274 | if (active_rate) |
275 | ret = snd_pcm_hw_constraint_single(runtime: substream->runtime, |
276 | SNDRV_PCM_HW_PARAM_RATE, |
277 | val: active_rate); |
278 | else |
279 | ret = snd_pcm_hw_rule_add(runtime: substream->runtime, cond: 0, |
280 | SNDRV_PCM_HW_PARAM_RATE, |
281 | func: j721e_rule_rate, private: &priv->rate_range, |
282 | SNDRV_PCM_HW_PARAM_RATE, -1); |
283 | |
284 | |
285 | if (ret) |
286 | goto out; |
287 | |
288 | /* Reset TDM slots to 32 */ |
289 | ret = snd_soc_dai_set_tdm_slot(dai: cpu_dai, tx_mask: 0x3, rx_mask: 0x3, slots: 2, slot_width: 32); |
290 | if (ret && ret != -ENOTSUPP) |
291 | goto out; |
292 | |
293 | for_each_rtd_codec_dais(rtd, i, codec_dai) { |
294 | ret = snd_soc_dai_set_tdm_slot(dai: codec_dai, tx_mask: 0x3, rx_mask: 0x3, slots: 2, slot_width: 32); |
295 | if (ret && ret != -ENOTSUPP) |
296 | goto out; |
297 | } |
298 | |
299 | if (ret == -ENOTSUPP) |
300 | ret = 0; |
301 | out: |
302 | if (ret) |
303 | domain->active--; |
304 | mutex_unlock(lock: &priv->mutex); |
305 | |
306 | return ret; |
307 | } |
308 | |
309 | static int j721e_audio_hw_params(struct snd_pcm_substream *substream, |
310 | struct snd_pcm_hw_params *params) |
311 | { |
312 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
313 | struct snd_soc_card *card = rtd->card; |
314 | struct j721e_priv *priv = snd_soc_card_get_drvdata(card); |
315 | unsigned int domain_id = rtd->dai_link->id; |
316 | struct j721e_audio_domain *domain = &priv->audio_domains[domain_id]; |
317 | struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); |
318 | struct snd_soc_dai *codec_dai; |
319 | unsigned int sysclk_rate; |
320 | int slot_width = 32; |
321 | int ret; |
322 | int i; |
323 | |
324 | mutex_lock(&priv->mutex); |
325 | |
326 | if (domain->rate && domain->rate != params_rate(p: params)) { |
327 | ret = -EINVAL; |
328 | goto out; |
329 | } |
330 | |
331 | if (params_width(p: params) == 16) |
332 | slot_width = 16; |
333 | |
334 | ret = snd_soc_dai_set_tdm_slot(dai: cpu_dai, tx_mask: 0x3, rx_mask: 0x3, slots: 2, slot_width); |
335 | if (ret && ret != -ENOTSUPP) |
336 | goto out; |
337 | |
338 | for_each_rtd_codec_dais(rtd, i, codec_dai) { |
339 | ret = snd_soc_dai_set_tdm_slot(dai: codec_dai, tx_mask: 0x3, rx_mask: 0x3, slots: 2, |
340 | slot_width); |
341 | if (ret && ret != -ENOTSUPP) |
342 | goto out; |
343 | } |
344 | |
345 | ret = j721e_configure_refclk(priv, audio_domain: domain_id, rate: params_rate(p: params)); |
346 | if (ret) |
347 | goto out; |
348 | |
349 | sysclk_rate = priv->hsdiv_rates[domain->parent_clk_id]; |
350 | for_each_rtd_codec_dais(rtd, i, codec_dai) { |
351 | ret = snd_soc_dai_set_sysclk(dai: codec_dai, clk_id: 0, freq: sysclk_rate, |
352 | SND_SOC_CLOCK_IN); |
353 | if (ret && ret != -ENOTSUPP) { |
354 | dev_err(priv->dev, |
355 | "codec set_sysclk failed for %u Hz\n" , |
356 | sysclk_rate); |
357 | goto out; |
358 | } |
359 | } |
360 | |
361 | ret = snd_soc_dai_set_sysclk(dai: cpu_dai, MCASP_CLK_HCLK_AUXCLK, |
362 | freq: sysclk_rate, SND_SOC_CLOCK_IN); |
363 | |
364 | if (ret && ret != -ENOTSUPP) { |
365 | dev_err(priv->dev, "mcasp set_sysclk failed for %u Hz\n" , |
366 | sysclk_rate); |
367 | } else { |
368 | domain->rate = params_rate(p: params); |
369 | ret = 0; |
370 | } |
371 | |
372 | out: |
373 | mutex_unlock(lock: &priv->mutex); |
374 | return ret; |
375 | } |
376 | |
377 | static void j721e_audio_shutdown(struct snd_pcm_substream *substream) |
378 | { |
379 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
380 | struct j721e_priv *priv = snd_soc_card_get_drvdata(card: rtd->card); |
381 | unsigned int domain_id = rtd->dai_link->id; |
382 | struct j721e_audio_domain *domain = &priv->audio_domains[domain_id]; |
383 | |
384 | mutex_lock(&priv->mutex); |
385 | |
386 | domain->active--; |
387 | if (!domain->active) { |
388 | domain->rate = 0; |
389 | domain->active_link = 0; |
390 | } |
391 | |
392 | mutex_unlock(lock: &priv->mutex); |
393 | } |
394 | |
395 | static const struct snd_soc_ops j721e_audio_ops = { |
396 | .startup = j721e_audio_startup, |
397 | .hw_params = j721e_audio_hw_params, |
398 | .shutdown = j721e_audio_shutdown, |
399 | }; |
400 | |
401 | static int j721e_audio_init(struct snd_soc_pcm_runtime *rtd) |
402 | { |
403 | struct j721e_priv *priv = snd_soc_card_get_drvdata(card: rtd->card); |
404 | unsigned int domain_id = rtd->dai_link->id; |
405 | struct j721e_audio_domain *domain = &priv->audio_domains[domain_id]; |
406 | struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); |
407 | struct snd_soc_dai *codec_dai; |
408 | unsigned int sysclk_rate; |
409 | int i, ret; |
410 | |
411 | /* Set up initial clock configuration */ |
412 | ret = j721e_configure_refclk(priv, audio_domain: domain_id, rate: 48000); |
413 | if (ret) |
414 | return ret; |
415 | |
416 | sysclk_rate = priv->hsdiv_rates[domain->parent_clk_id]; |
417 | for_each_rtd_codec_dais(rtd, i, codec_dai) { |
418 | ret = snd_soc_dai_set_sysclk(dai: codec_dai, clk_id: 0, freq: sysclk_rate, |
419 | SND_SOC_CLOCK_IN); |
420 | if (ret && ret != -ENOTSUPP) |
421 | return ret; |
422 | } |
423 | |
424 | ret = snd_soc_dai_set_sysclk(dai: cpu_dai, MCASP_CLK_HCLK_AUXCLK, |
425 | freq: sysclk_rate, SND_SOC_CLOCK_IN); |
426 | if (ret && ret != -ENOTSUPP) |
427 | return ret; |
428 | |
429 | /* Set initial tdm slots */ |
430 | ret = snd_soc_dai_set_tdm_slot(dai: cpu_dai, tx_mask: 0x3, rx_mask: 0x3, slots: 2, slot_width: 32); |
431 | if (ret && ret != -ENOTSUPP) |
432 | return ret; |
433 | |
434 | for_each_rtd_codec_dais(rtd, i, codec_dai) { |
435 | ret = snd_soc_dai_set_tdm_slot(dai: codec_dai, tx_mask: 0x3, rx_mask: 0x3, slots: 2, slot_width: 32); |
436 | if (ret && ret != -ENOTSUPP) |
437 | return ret; |
438 | } |
439 | |
440 | return 0; |
441 | } |
442 | |
443 | static int j721e_audio_init_ivi(struct snd_soc_pcm_runtime *rtd) |
444 | { |
445 | struct snd_soc_dapm_context *dapm = &rtd->card->dapm; |
446 | |
447 | snd_soc_dapm_new_controls(dapm, widget: j721e_ivi_codec_a_dapm_widgets, |
448 | ARRAY_SIZE(j721e_ivi_codec_a_dapm_widgets)); |
449 | snd_soc_dapm_add_routes(dapm, route: j721e_codec_a_dapm_routes, |
450 | ARRAY_SIZE(j721e_codec_a_dapm_routes)); |
451 | snd_soc_dapm_new_controls(dapm, widget: j721e_ivi_codec_b_dapm_widgets, |
452 | ARRAY_SIZE(j721e_ivi_codec_b_dapm_widgets)); |
453 | snd_soc_dapm_add_routes(dapm, route: j721e_codec_b_dapm_routes, |
454 | ARRAY_SIZE(j721e_codec_b_dapm_routes)); |
455 | |
456 | return j721e_audio_init(rtd); |
457 | } |
458 | |
459 | static int j721e_get_clocks(struct device *dev, |
460 | struct j721e_audio_clocks *clocks, char *prefix) |
461 | { |
462 | struct clk *parent; |
463 | char *clk_name; |
464 | int ret; |
465 | |
466 | clocks->target = devm_clk_get(dev, id: prefix); |
467 | if (IS_ERR(ptr: clocks->target)) |
468 | return dev_err_probe(dev, err: PTR_ERR(ptr: clocks->target), |
469 | fmt: "failed to acquire %s\n" , prefix); |
470 | |
471 | clk_name = kasprintf(GFP_KERNEL, fmt: "%s-48000" , prefix); |
472 | if (clk_name) { |
473 | parent = devm_clk_get(dev, id: clk_name); |
474 | kfree(objp: clk_name); |
475 | if (IS_ERR(ptr: parent)) { |
476 | ret = PTR_ERR(ptr: parent); |
477 | if (ret == -EPROBE_DEFER) |
478 | return ret; |
479 | |
480 | dev_dbg(dev, "no 48KHz parent for %s: %d\n" , prefix, ret); |
481 | parent = NULL; |
482 | } |
483 | clocks->parent[J721E_CLK_PARENT_48000] = parent; |
484 | } else { |
485 | return -ENOMEM; |
486 | } |
487 | |
488 | clk_name = kasprintf(GFP_KERNEL, fmt: "%s-44100" , prefix); |
489 | if (clk_name) { |
490 | parent = devm_clk_get(dev, id: clk_name); |
491 | kfree(objp: clk_name); |
492 | if (IS_ERR(ptr: parent)) { |
493 | ret = PTR_ERR(ptr: parent); |
494 | if (ret == -EPROBE_DEFER) |
495 | return ret; |
496 | |
497 | dev_dbg(dev, "no 44.1KHz parent for %s: %d\n" , prefix, ret); |
498 | parent = NULL; |
499 | } |
500 | clocks->parent[J721E_CLK_PARENT_44100] = parent; |
501 | } else { |
502 | return -ENOMEM; |
503 | } |
504 | |
505 | if (!clocks->parent[J721E_CLK_PARENT_44100] && |
506 | !clocks->parent[J721E_CLK_PARENT_48000]) { |
507 | dev_err(dev, "At least one parent clock is needed for %s\n" , |
508 | prefix); |
509 | return -EINVAL; |
510 | } |
511 | |
512 | return 0; |
513 | } |
514 | |
515 | static const struct j721e_audio_match_data j721e_cpb_data = { |
516 | .board_type = J721E_BOARD_CPB, |
517 | .num_links = 2, /* CPB pcm3168a */ |
518 | .pll_rates = { |
519 | [J721E_CLK_PARENT_44100] = 1083801600, /* PLL15 */ |
520 | [J721E_CLK_PARENT_48000] = 1179648000, /* PLL4 */ |
521 | }, |
522 | }; |
523 | |
524 | static const struct j721e_audio_match_data j721e_cpb_ivi_data = { |
525 | .board_type = J721E_BOARD_CPB_IVI, |
526 | .num_links = 4, /* CPB pcm3168a + 2x pcm3168a on IVI */ |
527 | .pll_rates = { |
528 | [J721E_CLK_PARENT_44100] = 1083801600, /* PLL15 */ |
529 | [J721E_CLK_PARENT_48000] = 1179648000, /* PLL4 */ |
530 | }, |
531 | }; |
532 | |
533 | static const struct j721e_audio_match_data j7200_cpb_data = { |
534 | .board_type = J721E_BOARD_CPB, |
535 | .num_links = 2, /* CPB pcm3168a */ |
536 | .pll_rates = { |
537 | [J721E_CLK_PARENT_48000] = 2359296000u, /* PLL4 */ |
538 | }, |
539 | }; |
540 | |
541 | static const struct of_device_id j721e_audio_of_match[] = { |
542 | { |
543 | .compatible = "ti,j721e-cpb-audio" , |
544 | .data = &j721e_cpb_data, |
545 | }, { |
546 | .compatible = "ti,j721e-cpb-ivi-audio" , |
547 | .data = &j721e_cpb_ivi_data, |
548 | }, { |
549 | .compatible = "ti,j7200-cpb-audio" , |
550 | .data = &j7200_cpb_data, |
551 | }, |
552 | { }, |
553 | }; |
554 | MODULE_DEVICE_TABLE(of, j721e_audio_of_match); |
555 | |
556 | static int j721e_calculate_rate_range(struct j721e_priv *priv) |
557 | { |
558 | const struct j721e_audio_match_data *match_data = priv->match_data; |
559 | struct j721e_audio_clocks *domain_clocks; |
560 | unsigned int min_rate, max_rate, pll_rate; |
561 | struct clk *pll; |
562 | |
563 | domain_clocks = &priv->audio_domains[J721E_AUDIO_DOMAIN_CPB].mcasp; |
564 | |
565 | pll = clk_get_parent(clk: domain_clocks->parent[J721E_CLK_PARENT_44100]); |
566 | if (IS_ERR_OR_NULL(ptr: pll)) { |
567 | priv->pll_rates[J721E_CLK_PARENT_44100] = |
568 | match_data->pll_rates[J721E_CLK_PARENT_44100]; |
569 | } else { |
570 | priv->pll_rates[J721E_CLK_PARENT_44100] = clk_get_rate(clk: pll); |
571 | clk_put(clk: pll); |
572 | } |
573 | |
574 | pll = clk_get_parent(clk: domain_clocks->parent[J721E_CLK_PARENT_48000]); |
575 | if (IS_ERR_OR_NULL(ptr: pll)) { |
576 | priv->pll_rates[J721E_CLK_PARENT_48000] = |
577 | match_data->pll_rates[J721E_CLK_PARENT_48000]; |
578 | } else { |
579 | priv->pll_rates[J721E_CLK_PARENT_48000] = clk_get_rate(clk: pll); |
580 | clk_put(clk: pll); |
581 | } |
582 | |
583 | if (!priv->pll_rates[J721E_CLK_PARENT_44100] && |
584 | !priv->pll_rates[J721E_CLK_PARENT_48000]) { |
585 | dev_err(priv->dev, "At least one PLL is needed\n" ); |
586 | return -EINVAL; |
587 | } |
588 | |
589 | if (priv->pll_rates[J721E_CLK_PARENT_44100]) |
590 | pll_rate = priv->pll_rates[J721E_CLK_PARENT_44100]; |
591 | else |
592 | pll_rate = priv->pll_rates[J721E_CLK_PARENT_48000]; |
593 | |
594 | min_rate = pll_rate / J721E_MAX_CLK_HSDIV; |
595 | min_rate /= ratios_for_pcm3168a[ARRAY_SIZE(ratios_for_pcm3168a) - 1]; |
596 | |
597 | if (priv->pll_rates[J721E_CLK_PARENT_48000]) |
598 | pll_rate = priv->pll_rates[J721E_CLK_PARENT_48000]; |
599 | else |
600 | pll_rate = priv->pll_rates[J721E_CLK_PARENT_44100]; |
601 | |
602 | if (pll_rate > PCM1368A_MAX_SYSCLK) |
603 | pll_rate = PCM1368A_MAX_SYSCLK; |
604 | |
605 | max_rate = pll_rate / ratios_for_pcm3168a[0]; |
606 | |
607 | snd_interval_any(i: &priv->rate_range); |
608 | priv->rate_range.min = min_rate; |
609 | priv->rate_range.max = max_rate; |
610 | |
611 | return 0; |
612 | } |
613 | |
614 | static int j721e_soc_probe_cpb(struct j721e_priv *priv, int *link_idx, |
615 | int *conf_idx) |
616 | { |
617 | struct device_node *node = priv->dev->of_node; |
618 | struct snd_soc_dai_link_component *compnent; |
619 | struct device_node *dai_node, *codec_node; |
620 | struct j721e_audio_domain *domain; |
621 | int comp_count, comp_idx; |
622 | int ret; |
623 | |
624 | dai_node = of_parse_phandle(np: node, phandle_name: "ti,cpb-mcasp" , index: 0); |
625 | if (!dai_node) { |
626 | dev_err(priv->dev, "CPB McASP node is not provided\n" ); |
627 | return -EINVAL; |
628 | } |
629 | |
630 | codec_node = of_parse_phandle(np: node, phandle_name: "ti,cpb-codec" , index: 0); |
631 | if (!codec_node) { |
632 | dev_err(priv->dev, "CPB codec node is not provided\n" ); |
633 | ret = -EINVAL; |
634 | goto put_dai_node; |
635 | } |
636 | |
637 | domain = &priv->audio_domains[J721E_AUDIO_DOMAIN_CPB]; |
638 | ret = j721e_get_clocks(dev: priv->dev, clocks: &domain->codec, prefix: "cpb-codec-scki" ); |
639 | if (ret) |
640 | goto put_codec_node; |
641 | |
642 | ret = j721e_get_clocks(dev: priv->dev, clocks: &domain->mcasp, prefix: "cpb-mcasp-auxclk" ); |
643 | if (ret) |
644 | goto put_codec_node; |
645 | |
646 | /* |
647 | * Common Processor Board, two links |
648 | * Link 1: McASP10 -> pcm3168a_1 DAC |
649 | * Link 2: McASP10 <- pcm3168a_1 ADC |
650 | */ |
651 | comp_count = 6; |
652 | compnent = devm_kcalloc(dev: priv->dev, n: comp_count, size: sizeof(*compnent), |
653 | GFP_KERNEL); |
654 | if (!compnent) { |
655 | ret = -ENOMEM; |
656 | goto put_codec_node; |
657 | } |
658 | |
659 | comp_idx = 0; |
660 | priv->dai_links[*link_idx].cpus = &compnent[comp_idx++]; |
661 | priv->dai_links[*link_idx].num_cpus = 1; |
662 | priv->dai_links[*link_idx].codecs = &compnent[comp_idx++]; |
663 | priv->dai_links[*link_idx].num_codecs = 1; |
664 | priv->dai_links[*link_idx].platforms = &compnent[comp_idx++]; |
665 | priv->dai_links[*link_idx].num_platforms = 1; |
666 | |
667 | priv->dai_links[*link_idx].name = "CPB PCM3168A Playback" ; |
668 | priv->dai_links[*link_idx].stream_name = "CPB PCM3168A Analog" ; |
669 | priv->dai_links[*link_idx].cpus->of_node = dai_node; |
670 | priv->dai_links[*link_idx].platforms->of_node = dai_node; |
671 | priv->dai_links[*link_idx].codecs->of_node = codec_node; |
672 | priv->dai_links[*link_idx].codecs->dai_name = "pcm3168a-dac" ; |
673 | priv->dai_links[*link_idx].playback_only = 1; |
674 | priv->dai_links[*link_idx].id = J721E_AUDIO_DOMAIN_CPB; |
675 | priv->dai_links[*link_idx].dai_fmt = J721E_DAI_FMT; |
676 | priv->dai_links[*link_idx].init = j721e_audio_init; |
677 | priv->dai_links[*link_idx].ops = &j721e_audio_ops; |
678 | (*link_idx)++; |
679 | |
680 | priv->dai_links[*link_idx].cpus = &compnent[comp_idx++]; |
681 | priv->dai_links[*link_idx].num_cpus = 1; |
682 | priv->dai_links[*link_idx].codecs = &compnent[comp_idx++]; |
683 | priv->dai_links[*link_idx].num_codecs = 1; |
684 | priv->dai_links[*link_idx].platforms = &compnent[comp_idx++]; |
685 | priv->dai_links[*link_idx].num_platforms = 1; |
686 | |
687 | priv->dai_links[*link_idx].name = "CPB PCM3168A Capture" ; |
688 | priv->dai_links[*link_idx].stream_name = "CPB PCM3168A Analog" ; |
689 | priv->dai_links[*link_idx].cpus->of_node = dai_node; |
690 | priv->dai_links[*link_idx].platforms->of_node = dai_node; |
691 | priv->dai_links[*link_idx].codecs->of_node = codec_node; |
692 | priv->dai_links[*link_idx].codecs->dai_name = "pcm3168a-adc" ; |
693 | priv->dai_links[*link_idx].capture_only = 1; |
694 | priv->dai_links[*link_idx].id = J721E_AUDIO_DOMAIN_CPB; |
695 | priv->dai_links[*link_idx].dai_fmt = J721E_DAI_FMT; |
696 | priv->dai_links[*link_idx].init = j721e_audio_init; |
697 | priv->dai_links[*link_idx].ops = &j721e_audio_ops; |
698 | (*link_idx)++; |
699 | |
700 | priv->codec_conf[*conf_idx].dlc.of_node = codec_node; |
701 | priv->codec_conf[*conf_idx].name_prefix = "codec-1" ; |
702 | (*conf_idx)++; |
703 | priv->codec_conf[*conf_idx].dlc.of_node = dai_node; |
704 | priv->codec_conf[*conf_idx].name_prefix = "McASP10" ; |
705 | (*conf_idx)++; |
706 | |
707 | return 0; |
708 | |
709 | put_codec_node: |
710 | of_node_put(node: codec_node); |
711 | put_dai_node: |
712 | of_node_put(node: dai_node); |
713 | return ret; |
714 | } |
715 | |
716 | static int j721e_soc_probe_ivi(struct j721e_priv *priv, int *link_idx, |
717 | int *conf_idx) |
718 | { |
719 | struct device_node *node = priv->dev->of_node; |
720 | struct snd_soc_dai_link_component *compnent; |
721 | struct device_node *dai_node, *codeca_node, *codecb_node; |
722 | struct j721e_audio_domain *domain; |
723 | int comp_count, comp_idx; |
724 | int ret; |
725 | |
726 | if (priv->match_data->board_type != J721E_BOARD_CPB_IVI) |
727 | return 0; |
728 | |
729 | dai_node = of_parse_phandle(np: node, phandle_name: "ti,ivi-mcasp" , index: 0); |
730 | if (!dai_node) { |
731 | dev_err(priv->dev, "IVI McASP node is not provided\n" ); |
732 | return -EINVAL; |
733 | } |
734 | |
735 | codeca_node = of_parse_phandle(np: node, phandle_name: "ti,ivi-codec-a" , index: 0); |
736 | if (!codeca_node) { |
737 | dev_err(priv->dev, "IVI codec-a node is not provided\n" ); |
738 | ret = -EINVAL; |
739 | goto put_dai_node; |
740 | } |
741 | |
742 | codecb_node = of_parse_phandle(np: node, phandle_name: "ti,ivi-codec-b" , index: 0); |
743 | if (!codecb_node) { |
744 | dev_warn(priv->dev, "IVI codec-b node is not provided\n" ); |
745 | ret = 0; |
746 | goto put_codeca_node; |
747 | } |
748 | |
749 | domain = &priv->audio_domains[J721E_AUDIO_DOMAIN_IVI]; |
750 | ret = j721e_get_clocks(dev: priv->dev, clocks: &domain->codec, prefix: "ivi-codec-scki" ); |
751 | if (ret) |
752 | goto put_codecb_node; |
753 | |
754 | ret = j721e_get_clocks(dev: priv->dev, clocks: &domain->mcasp, prefix: "ivi-mcasp-auxclk" ); |
755 | if (ret) |
756 | goto put_codecb_node; |
757 | |
758 | /* |
759 | * IVI extension, two links |
760 | * Link 1: McASP0 -> pcm3168a_a DAC |
761 | * \> pcm3168a_b DAC |
762 | * Link 2: McASP0 <- pcm3168a_a ADC |
763 | * \ pcm3168a_b ADC |
764 | */ |
765 | comp_count = 8; |
766 | compnent = devm_kcalloc(dev: priv->dev, n: comp_count, size: sizeof(*compnent), |
767 | GFP_KERNEL); |
768 | if (!compnent) { |
769 | ret = -ENOMEM; |
770 | goto put_codecb_node; |
771 | } |
772 | |
773 | comp_idx = 0; |
774 | priv->dai_links[*link_idx].cpus = &compnent[comp_idx++]; |
775 | priv->dai_links[*link_idx].num_cpus = 1; |
776 | priv->dai_links[*link_idx].platforms = &compnent[comp_idx++]; |
777 | priv->dai_links[*link_idx].num_platforms = 1; |
778 | priv->dai_links[*link_idx].codecs = &compnent[comp_idx]; |
779 | priv->dai_links[*link_idx].num_codecs = 2; |
780 | comp_idx += 2; |
781 | |
782 | priv->dai_links[*link_idx].name = "IVI 2xPCM3168A Playback" ; |
783 | priv->dai_links[*link_idx].stream_name = "IVI 2xPCM3168A Analog" ; |
784 | priv->dai_links[*link_idx].cpus->of_node = dai_node; |
785 | priv->dai_links[*link_idx].platforms->of_node = dai_node; |
786 | priv->dai_links[*link_idx].codecs[0].of_node = codeca_node; |
787 | priv->dai_links[*link_idx].codecs[0].dai_name = "pcm3168a-dac" ; |
788 | priv->dai_links[*link_idx].codecs[1].of_node = codecb_node; |
789 | priv->dai_links[*link_idx].codecs[1].dai_name = "pcm3168a-dac" ; |
790 | priv->dai_links[*link_idx].playback_only = 1; |
791 | priv->dai_links[*link_idx].id = J721E_AUDIO_DOMAIN_IVI; |
792 | priv->dai_links[*link_idx].dai_fmt = J721E_DAI_FMT; |
793 | priv->dai_links[*link_idx].init = j721e_audio_init_ivi; |
794 | priv->dai_links[*link_idx].ops = &j721e_audio_ops; |
795 | (*link_idx)++; |
796 | |
797 | priv->dai_links[*link_idx].cpus = &compnent[comp_idx++]; |
798 | priv->dai_links[*link_idx].num_cpus = 1; |
799 | priv->dai_links[*link_idx].platforms = &compnent[comp_idx++]; |
800 | priv->dai_links[*link_idx].num_platforms = 1; |
801 | priv->dai_links[*link_idx].codecs = &compnent[comp_idx]; |
802 | priv->dai_links[*link_idx].num_codecs = 2; |
803 | |
804 | priv->dai_links[*link_idx].name = "IVI 2xPCM3168A Capture" ; |
805 | priv->dai_links[*link_idx].stream_name = "IVI 2xPCM3168A Analog" ; |
806 | priv->dai_links[*link_idx].cpus->of_node = dai_node; |
807 | priv->dai_links[*link_idx].platforms->of_node = dai_node; |
808 | priv->dai_links[*link_idx].codecs[0].of_node = codeca_node; |
809 | priv->dai_links[*link_idx].codecs[0].dai_name = "pcm3168a-adc" ; |
810 | priv->dai_links[*link_idx].codecs[1].of_node = codecb_node; |
811 | priv->dai_links[*link_idx].codecs[1].dai_name = "pcm3168a-adc" ; |
812 | priv->dai_links[*link_idx].capture_only = 1; |
813 | priv->dai_links[*link_idx].id = J721E_AUDIO_DOMAIN_IVI; |
814 | priv->dai_links[*link_idx].dai_fmt = J721E_DAI_FMT; |
815 | priv->dai_links[*link_idx].init = j721e_audio_init; |
816 | priv->dai_links[*link_idx].ops = &j721e_audio_ops; |
817 | (*link_idx)++; |
818 | |
819 | priv->codec_conf[*conf_idx].dlc.of_node = codeca_node; |
820 | priv->codec_conf[*conf_idx].name_prefix = "codec-a" ; |
821 | (*conf_idx)++; |
822 | |
823 | priv->codec_conf[*conf_idx].dlc.of_node = codecb_node; |
824 | priv->codec_conf[*conf_idx].name_prefix = "codec-b" ; |
825 | (*conf_idx)++; |
826 | |
827 | priv->codec_conf[*conf_idx].dlc.of_node = dai_node; |
828 | priv->codec_conf[*conf_idx].name_prefix = "McASP0" ; |
829 | (*conf_idx)++; |
830 | |
831 | return 0; |
832 | |
833 | |
834 | put_codecb_node: |
835 | of_node_put(node: codecb_node); |
836 | put_codeca_node: |
837 | of_node_put(node: codeca_node); |
838 | put_dai_node: |
839 | of_node_put(node: dai_node); |
840 | return ret; |
841 | } |
842 | |
843 | static int j721e_soc_probe(struct platform_device *pdev) |
844 | { |
845 | struct device_node *node = pdev->dev.of_node; |
846 | struct snd_soc_card *card; |
847 | const struct of_device_id *match; |
848 | struct j721e_priv *priv; |
849 | int link_cnt, conf_cnt, ret, i; |
850 | |
851 | if (!node) { |
852 | dev_err(&pdev->dev, "of node is missing.\n" ); |
853 | return -ENODEV; |
854 | } |
855 | |
856 | match = of_match_node(matches: j721e_audio_of_match, node); |
857 | if (!match) { |
858 | dev_err(&pdev->dev, "No compatible match found\n" ); |
859 | return -ENODEV; |
860 | } |
861 | |
862 | priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
863 | if (!priv) |
864 | return -ENOMEM; |
865 | |
866 | priv->match_data = match->data; |
867 | |
868 | priv->dai_links = devm_kcalloc(dev: &pdev->dev, n: priv->match_data->num_links, |
869 | size: sizeof(*priv->dai_links), GFP_KERNEL); |
870 | if (!priv->dai_links) |
871 | return -ENOMEM; |
872 | |
873 | for (i = 0; i < J721E_AUDIO_DOMAIN_LAST; i++) |
874 | priv->audio_domains[i].parent_clk_id = -1; |
875 | |
876 | priv->dev = &pdev->dev; |
877 | card = &priv->card; |
878 | card->dev = &pdev->dev; |
879 | card->owner = THIS_MODULE; |
880 | card->dapm_widgets = j721e_cpb_dapm_widgets; |
881 | card->num_dapm_widgets = ARRAY_SIZE(j721e_cpb_dapm_widgets); |
882 | card->dapm_routes = j721e_cpb_dapm_routes; |
883 | card->num_dapm_routes = ARRAY_SIZE(j721e_cpb_dapm_routes); |
884 | card->fully_routed = 1; |
885 | |
886 | if (snd_soc_of_parse_card_name(card, propname: "model" )) { |
887 | dev_err(&pdev->dev, "Card name is not provided\n" ); |
888 | return -ENODEV; |
889 | } |
890 | |
891 | link_cnt = 0; |
892 | conf_cnt = 0; |
893 | ret = j721e_soc_probe_cpb(priv, link_idx: &link_cnt, conf_idx: &conf_cnt); |
894 | if (ret) |
895 | return ret; |
896 | |
897 | ret = j721e_soc_probe_ivi(priv, link_idx: &link_cnt, conf_idx: &conf_cnt); |
898 | if (ret) |
899 | return ret; |
900 | |
901 | card->dai_link = priv->dai_links; |
902 | card->num_links = link_cnt; |
903 | |
904 | card->codec_conf = priv->codec_conf; |
905 | card->num_configs = conf_cnt; |
906 | |
907 | ret = j721e_calculate_rate_range(priv); |
908 | if (ret) |
909 | return ret; |
910 | |
911 | snd_soc_card_set_drvdata(card, data: priv); |
912 | |
913 | mutex_init(&priv->mutex); |
914 | ret = devm_snd_soc_register_card(dev: &pdev->dev, card); |
915 | if (ret) |
916 | dev_err(&pdev->dev, "devm_snd_soc_register_card() failed: %d\n" , |
917 | ret); |
918 | |
919 | return ret; |
920 | } |
921 | |
922 | static struct platform_driver j721e_soc_driver = { |
923 | .driver = { |
924 | .name = "j721e-audio" , |
925 | .pm = &snd_soc_pm_ops, |
926 | .of_match_table = j721e_audio_of_match, |
927 | }, |
928 | .probe = j721e_soc_probe, |
929 | }; |
930 | |
931 | module_platform_driver(j721e_soc_driver); |
932 | |
933 | MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>" ); |
934 | MODULE_DESCRIPTION("ASoC machine driver for j721e Common Processor Board" ); |
935 | MODULE_LICENSE("GPL v2" ); |
936 | |