]>
Commit | Line | Data |
---|---|---|
cc8f70f5 DO |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * tegra_asoc_machine.c - Universal ASoC machine driver for NVIDIA Tegra boards. | |
4 | */ | |
5 | ||
8c1b3b15 | 6 | #include <linux/clk.h> |
cc8f70f5 DO |
7 | #include <linux/export.h> |
8 | #include <linux/gpio/consumer.h> | |
9 | #include <linux/module.h> | |
10 | #include <linux/of.h> | |
11 | #include <linux/of_device.h> | |
12 | #include <linux/platform_device.h> | |
13 | #include <linux/slab.h> | |
14 | ||
15 | #include <sound/core.h> | |
16 | #include <sound/jack.h> | |
17 | #include <sound/pcm.h> | |
18 | #include <sound/pcm_params.h> | |
19 | #include <sound/soc.h> | |
20 | ||
21 | #include "tegra_asoc_machine.h" | |
22 | ||
23 | /* Headphones Jack */ | |
24 | ||
25 | static struct snd_soc_jack tegra_machine_hp_jack; | |
26 | ||
27 | static struct snd_soc_jack_pin tegra_machine_hp_jack_pins[] = { | |
28 | { .pin = "Headphone", .mask = SND_JACK_HEADPHONE }, | |
29 | { .pin = "Headphones", .mask = SND_JACK_HEADPHONE }, | |
30 | }; | |
31 | ||
32 | static struct snd_soc_jack_gpio tegra_machine_hp_jack_gpio = { | |
33 | .name = "Headphones detection", | |
34 | .report = SND_JACK_HEADPHONE, | |
35 | .debounce_time = 150, | |
36 | }; | |
37 | ||
38 | /* Headset Jack */ | |
39 | ||
40 | static struct snd_soc_jack tegra_machine_headset_jack; | |
41 | ||
42 | static struct snd_soc_jack_pin tegra_machine_headset_jack_pins[] = { | |
43 | { .pin = "Headset Mic", .mask = SND_JACK_MICROPHONE }, | |
44 | { .pin = "Headset Stereophone", .mask = SND_JACK_HEADPHONE }, | |
45 | }; | |
46 | ||
47 | static struct snd_soc_jack_gpio tegra_machine_headset_jack_gpio = { | |
48 | .name = "Headset detection", | |
49 | .report = SND_JACK_HEADSET, | |
50 | .debounce_time = 150, | |
51 | }; | |
52 | ||
53 | /* Mic Jack */ | |
54 | ||
55 | static struct snd_soc_jack tegra_machine_mic_jack; | |
56 | ||
57 | static struct snd_soc_jack_pin tegra_machine_mic_jack_pins[] = { | |
58 | { .pin = "Mic Jack", .mask = SND_JACK_MICROPHONE }, | |
59 | { .pin = "Headset Mic", .mask = SND_JACK_MICROPHONE }, | |
60 | }; | |
61 | ||
62 | static struct snd_soc_jack_gpio tegra_machine_mic_jack_gpio = { | |
63 | .name = "Mic detection", | |
64 | .report = SND_JACK_MICROPHONE, | |
65 | .debounce_time = 150, | |
66 | }; | |
67 | ||
68 | static int tegra_machine_event(struct snd_soc_dapm_widget *w, | |
69 | struct snd_kcontrol *k, int event) | |
70 | { | |
71 | struct snd_soc_dapm_context *dapm = w->dapm; | |
72 | struct tegra_machine *machine = snd_soc_card_get_drvdata(dapm->card); | |
73 | ||
74 | if (!strcmp(w->name, "Int Spk") || !strcmp(w->name, "Speakers")) | |
75 | gpiod_set_value_cansleep(machine->gpiod_spkr_en, | |
76 | SND_SOC_DAPM_EVENT_ON(event)); | |
77 | ||
78 | if (!strcmp(w->name, "Mic Jack")) | |
79 | gpiod_set_value_cansleep(machine->gpiod_ext_mic_en, | |
80 | SND_SOC_DAPM_EVENT_ON(event)); | |
81 | ||
82 | if (!strcmp(w->name, "Int Mic")) | |
83 | gpiod_set_value_cansleep(machine->gpiod_int_mic_en, | |
84 | SND_SOC_DAPM_EVENT_ON(event)); | |
85 | ||
86 | if (!strcmp(w->name, "Headphone") || !strcmp(w->name, "Headphone Jack")) | |
87 | gpiod_set_value_cansleep(machine->gpiod_hp_mute, | |
88 | !SND_SOC_DAPM_EVENT_ON(event)); | |
89 | ||
90 | return 0; | |
91 | } | |
92 | ||
93 | static const struct snd_soc_dapm_widget tegra_machine_dapm_widgets[] = { | |
94 | SND_SOC_DAPM_HP("Headphone Jack", tegra_machine_event), | |
95 | SND_SOC_DAPM_HP("Headphone", tegra_machine_event), | |
96 | SND_SOC_DAPM_HP("Headset Stereophone", NULL), | |
97 | SND_SOC_DAPM_HP("Headphones", NULL), | |
98 | SND_SOC_DAPM_SPK("Speakers", tegra_machine_event), | |
99 | SND_SOC_DAPM_SPK("Int Spk", tegra_machine_event), | |
100 | SND_SOC_DAPM_MIC("Int Mic", tegra_machine_event), | |
101 | SND_SOC_DAPM_MIC("Mic Jack", tegra_machine_event), | |
102 | SND_SOC_DAPM_MIC("Internal Mic 1", NULL), | |
103 | SND_SOC_DAPM_MIC("Internal Mic 2", NULL), | |
104 | SND_SOC_DAPM_MIC("Headset Mic", NULL), | |
105 | SND_SOC_DAPM_MIC("Digital Mic", NULL), | |
106 | SND_SOC_DAPM_MIC("Mic", NULL), | |
107 | SND_SOC_DAPM_LINE("Line In Jack", NULL), | |
108 | SND_SOC_DAPM_LINE("Line In", NULL), | |
109 | SND_SOC_DAPM_LINE("LineIn", NULL), | |
110 | }; | |
111 | ||
112 | static const struct snd_kcontrol_new tegra_machine_controls[] = { | |
113 | SOC_DAPM_PIN_SWITCH("Speakers"), | |
114 | SOC_DAPM_PIN_SWITCH("Int Spk"), | |
115 | SOC_DAPM_PIN_SWITCH("Int Mic"), | |
116 | SOC_DAPM_PIN_SWITCH("Headset Mic"), | |
117 | SOC_DAPM_PIN_SWITCH("Internal Mic 1"), | |
118 | SOC_DAPM_PIN_SWITCH("Internal Mic 2"), | |
119 | }; | |
120 | ||
121 | int tegra_asoc_machine_init(struct snd_soc_pcm_runtime *rtd) | |
122 | { | |
123 | struct snd_soc_card *card = rtd->card; | |
124 | struct tegra_machine *machine = snd_soc_card_get_drvdata(card); | |
125 | int err; | |
126 | ||
127 | if (machine->gpiod_hp_det && machine->asoc->add_hp_jack) { | |
128 | err = snd_soc_card_jack_new(card, "Headphones Jack", | |
129 | SND_JACK_HEADPHONE, | |
130 | &tegra_machine_hp_jack, | |
131 | tegra_machine_hp_jack_pins, | |
132 | ARRAY_SIZE(tegra_machine_hp_jack_pins)); | |
133 | if (err) { | |
134 | dev_err(rtd->dev, | |
135 | "Headphones Jack creation failed: %d\n", err); | |
136 | return err; | |
137 | } | |
138 | ||
139 | tegra_machine_hp_jack_gpio.desc = machine->gpiod_hp_det; | |
140 | ||
141 | err = snd_soc_jack_add_gpios(&tegra_machine_hp_jack, 1, | |
142 | &tegra_machine_hp_jack_gpio); | |
143 | if (err) | |
144 | dev_err(rtd->dev, "HP GPIOs not added: %d\n", err); | |
145 | } | |
146 | ||
147 | if (machine->gpiod_hp_det && machine->asoc->add_headset_jack) { | |
148 | err = snd_soc_card_jack_new(card, "Headset Jack", | |
149 | SND_JACK_HEADSET, | |
150 | &tegra_machine_headset_jack, | |
151 | tegra_machine_headset_jack_pins, | |
152 | ARRAY_SIZE(tegra_machine_headset_jack_pins)); | |
153 | if (err) { | |
154 | dev_err(rtd->dev, | |
155 | "Headset Jack creation failed: %d\n", err); | |
156 | return err; | |
157 | } | |
158 | ||
159 | tegra_machine_headset_jack_gpio.desc = machine->gpiod_hp_det; | |
160 | ||
161 | err = snd_soc_jack_add_gpios(&tegra_machine_headset_jack, 1, | |
162 | &tegra_machine_headset_jack_gpio); | |
163 | if (err) | |
164 | dev_err(rtd->dev, "Headset GPIOs not added: %d\n", err); | |
165 | } | |
166 | ||
167 | if (machine->gpiod_mic_det && machine->asoc->add_mic_jack) { | |
168 | err = snd_soc_card_jack_new(rtd->card, "Mic Jack", | |
169 | SND_JACK_MICROPHONE, | |
170 | &tegra_machine_mic_jack, | |
171 | tegra_machine_mic_jack_pins, | |
172 | ARRAY_SIZE(tegra_machine_mic_jack_pins)); | |
173 | if (err) { | |
174 | dev_err(rtd->dev, "Mic Jack creation failed: %d\n", err); | |
175 | return err; | |
176 | } | |
177 | ||
178 | tegra_machine_mic_jack_gpio.desc = machine->gpiod_mic_det; | |
179 | ||
180 | err = snd_soc_jack_add_gpios(&tegra_machine_mic_jack, 1, | |
181 | &tegra_machine_mic_jack_gpio); | |
182 | if (err) | |
183 | dev_err(rtd->dev, "Mic GPIOs not added: %d\n", err); | |
184 | } | |
185 | ||
186 | return 0; | |
187 | } | |
188 | EXPORT_SYMBOL_GPL(tegra_asoc_machine_init); | |
189 | ||
190 | static unsigned int tegra_machine_mclk_rate_128(unsigned int srate) | |
191 | { | |
192 | return 128 * srate; | |
193 | } | |
194 | ||
195 | static unsigned int tegra_machine_mclk_rate_256(unsigned int srate) | |
196 | { | |
197 | return 256 * srate; | |
198 | } | |
199 | ||
200 | static unsigned int tegra_machine_mclk_rate_512(unsigned int srate) | |
201 | { | |
202 | return 512 * srate; | |
203 | } | |
204 | ||
205 | static unsigned int tegra_machine_mclk_rate_12mhz(unsigned int srate) | |
206 | { | |
207 | unsigned int mclk; | |
208 | ||
209 | switch (srate) { | |
210 | case 8000: | |
211 | case 16000: | |
212 | case 24000: | |
213 | case 32000: | |
214 | case 48000: | |
215 | case 64000: | |
216 | case 96000: | |
217 | mclk = 12288000; | |
218 | break; | |
219 | case 11025: | |
220 | case 22050: | |
221 | case 44100: | |
222 | case 88200: | |
223 | mclk = 11289600; | |
224 | break; | |
225 | default: | |
226 | mclk = 12000000; | |
227 | break; | |
228 | } | |
229 | ||
230 | return mclk; | |
231 | } | |
232 | ||
233 | static int tegra_machine_hw_params(struct snd_pcm_substream *substream, | |
234 | struct snd_pcm_hw_params *params) | |
235 | { | |
236 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
237 | struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); | |
238 | struct snd_soc_card *card = rtd->card; | |
239 | struct tegra_machine *machine = snd_soc_card_get_drvdata(card); | |
240 | unsigned int srate = params_rate(params); | |
241 | unsigned int mclk = machine->asoc->mclk_rate(srate); | |
242 | unsigned int clk_id = machine->asoc->mclk_id; | |
8c1b3b15 | 243 | unsigned int new_baseclock; |
cc8f70f5 DO |
244 | int err; |
245 | ||
8c1b3b15 DO |
246 | switch (srate) { |
247 | case 11025: | |
248 | case 22050: | |
249 | case 44100: | |
250 | case 88200: | |
251 | if (of_machine_is_compatible("nvidia,tegra20")) | |
252 | new_baseclock = 56448000; | |
253 | else if (of_machine_is_compatible("nvidia,tegra30")) | |
254 | new_baseclock = 564480000; | |
255 | else | |
256 | new_baseclock = 282240000; | |
257 | break; | |
258 | case 8000: | |
259 | case 16000: | |
260 | case 32000: | |
261 | case 48000: | |
262 | case 64000: | |
263 | case 96000: | |
264 | if (of_machine_is_compatible("nvidia,tegra20")) | |
265 | new_baseclock = 73728000; | |
266 | else if (of_machine_is_compatible("nvidia,tegra30")) | |
267 | new_baseclock = 552960000; | |
268 | else | |
269 | new_baseclock = 368640000; | |
270 | break; | |
271 | default: | |
272 | dev_err(card->dev, "Invalid sound rate: %u\n", srate); | |
273 | return -EINVAL; | |
274 | } | |
275 | ||
276 | if (new_baseclock != machine->set_baseclock || | |
277 | mclk != machine->set_mclk) { | |
278 | machine->set_baseclock = 0; | |
279 | machine->set_mclk = 0; | |
280 | ||
281 | clk_disable_unprepare(machine->clk_cdev1); | |
282 | ||
283 | err = clk_set_rate(machine->clk_pll_a, new_baseclock); | |
284 | if (err) { | |
285 | dev_err(card->dev, "Can't set pll_a rate: %d\n", err); | |
286 | return err; | |
287 | } | |
288 | ||
289 | err = clk_set_rate(machine->clk_pll_a_out0, mclk); | |
290 | if (err) { | |
291 | dev_err(card->dev, "Can't set pll_a_out0 rate: %d\n", err); | |
292 | return err; | |
293 | } | |
294 | ||
295 | /* Don't set cdev1/extern1 rate; it's locked to pll_a_out0 */ | |
296 | ||
297 | err = clk_prepare_enable(machine->clk_cdev1); | |
298 | if (err) { | |
299 | dev_err(card->dev, "Can't enable cdev1: %d\n", err); | |
300 | return err; | |
301 | } | |
302 | ||
303 | machine->set_baseclock = new_baseclock; | |
304 | machine->set_mclk = mclk; | |
cc8f70f5 DO |
305 | } |
306 | ||
307 | err = snd_soc_dai_set_sysclk(codec_dai, clk_id, mclk, SND_SOC_CLOCK_IN); | |
308 | if (err < 0) { | |
309 | dev_err(card->dev, "codec_dai clock not set: %d\n", err); | |
310 | return err; | |
311 | } | |
312 | ||
313 | return 0; | |
314 | } | |
315 | ||
316 | static struct snd_soc_ops tegra_machine_snd_ops = { | |
317 | .hw_params = tegra_machine_hw_params, | |
318 | }; | |
319 | ||
320 | static void tegra_machine_node_release(void *of_node) | |
321 | { | |
322 | of_node_put(of_node); | |
323 | } | |
324 | ||
325 | static struct device_node * | |
326 | tegra_machine_parse_phandle(struct device *dev, const char *name) | |
327 | { | |
328 | struct device_node *np; | |
329 | int err; | |
330 | ||
331 | np = of_parse_phandle(dev->of_node, name, 0); | |
332 | if (!np) { | |
333 | dev_err(dev, "Property '%s' missing or invalid\n", name); | |
334 | return ERR_PTR(-EINVAL); | |
335 | } | |
336 | ||
337 | err = devm_add_action_or_reset(dev, tegra_machine_node_release, np); | |
338 | if (err) | |
339 | return ERR_PTR(err); | |
340 | ||
341 | return np; | |
342 | } | |
343 | ||
298c4bf4 DO |
344 | static void tegra_machine_unregister_codec(void *pdev) |
345 | { | |
346 | platform_device_unregister(pdev); | |
347 | } | |
348 | ||
349 | static int tegra_machine_register_codec(struct device *dev, const char *name) | |
350 | { | |
351 | struct platform_device *pdev; | |
352 | int err; | |
353 | ||
354 | if (!name) | |
355 | return 0; | |
356 | ||
357 | pdev = platform_device_register_simple(name, -1, NULL, 0); | |
358 | if (IS_ERR(pdev)) | |
359 | return PTR_ERR(pdev); | |
360 | ||
361 | err = devm_add_action_or_reset(dev, tegra_machine_unregister_codec, | |
362 | pdev); | |
363 | if (err) | |
364 | return err; | |
365 | ||
366 | return 0; | |
367 | } | |
368 | ||
cc8f70f5 DO |
369 | int tegra_asoc_machine_probe(struct platform_device *pdev) |
370 | { | |
298c4bf4 | 371 | struct device_node *np_codec, *np_i2s, *np_ac97; |
cc8f70f5 DO |
372 | const struct tegra_asoc_data *asoc; |
373 | struct device *dev = &pdev->dev; | |
374 | struct tegra_machine *machine; | |
375 | struct snd_soc_card *card; | |
376 | struct gpio_desc *gpiod; | |
377 | int err; | |
378 | ||
379 | machine = devm_kzalloc(dev, sizeof(*machine), GFP_KERNEL); | |
380 | if (!machine) | |
381 | return -ENOMEM; | |
382 | ||
383 | asoc = of_device_get_match_data(dev); | |
384 | card = asoc->card; | |
385 | card->dev = dev; | |
386 | ||
387 | machine->asoc = asoc; | |
388 | machine->mic_jack = &tegra_machine_mic_jack; | |
389 | machine->hp_jack_gpio = &tegra_machine_hp_jack_gpio; | |
390 | snd_soc_card_set_drvdata(card, machine); | |
391 | ||
392 | gpiod = devm_gpiod_get_optional(dev, "nvidia,hp-mute", GPIOD_OUT_HIGH); | |
393 | machine->gpiod_hp_mute = gpiod; | |
394 | if (IS_ERR(gpiod)) | |
395 | return PTR_ERR(gpiod); | |
396 | ||
397 | gpiod = devm_gpiod_get_optional(dev, "nvidia,hp-det", GPIOD_IN); | |
398 | machine->gpiod_hp_det = gpiod; | |
399 | if (IS_ERR(gpiod)) | |
400 | return PTR_ERR(gpiod); | |
401 | ||
402 | gpiod = devm_gpiod_get_optional(dev, "nvidia,mic-det", GPIOD_IN); | |
403 | machine->gpiod_mic_det = gpiod; | |
404 | if (IS_ERR(gpiod)) | |
405 | return PTR_ERR(gpiod); | |
406 | ||
407 | gpiod = devm_gpiod_get_optional(dev, "nvidia,spkr-en", GPIOD_OUT_LOW); | |
408 | machine->gpiod_spkr_en = gpiod; | |
409 | if (IS_ERR(gpiod)) | |
410 | return PTR_ERR(gpiod); | |
411 | ||
412 | gpiod = devm_gpiod_get_optional(dev, "nvidia,int-mic-en", GPIOD_OUT_LOW); | |
413 | machine->gpiod_int_mic_en = gpiod; | |
414 | if (IS_ERR(gpiod)) | |
415 | return PTR_ERR(gpiod); | |
416 | ||
417 | gpiod = devm_gpiod_get_optional(dev, "nvidia,ext-mic-en", GPIOD_OUT_LOW); | |
418 | machine->gpiod_ext_mic_en = gpiod; | |
419 | if (IS_ERR(gpiod)) | |
420 | return PTR_ERR(gpiod); | |
421 | ||
422 | err = snd_soc_of_parse_card_name(card, "nvidia,model"); | |
423 | if (err) | |
424 | return err; | |
425 | ||
426 | if (!card->dapm_routes) { | |
427 | err = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); | |
428 | if (err) | |
429 | return err; | |
430 | } | |
431 | ||
298c4bf4 DO |
432 | if (asoc->set_ac97) { |
433 | err = tegra_machine_register_codec(dev, asoc->codec_dev_name); | |
434 | if (err) | |
435 | return err; | |
436 | ||
437 | np_ac97 = tegra_machine_parse_phandle(dev, "nvidia,ac97-controller"); | |
438 | if (IS_ERR(np_ac97)) | |
439 | return PTR_ERR(np_ac97); | |
cc8f70f5 | 440 | |
298c4bf4 DO |
441 | card->dai_link->cpus->of_node = np_ac97; |
442 | card->dai_link->platforms->of_node = np_ac97; | |
443 | } else { | |
444 | np_codec = tegra_machine_parse_phandle(dev, "nvidia,audio-codec"); | |
445 | if (IS_ERR(np_codec)) | |
446 | return PTR_ERR(np_codec); | |
cc8f70f5 | 447 | |
298c4bf4 DO |
448 | np_i2s = tegra_machine_parse_phandle(dev, "nvidia,i2s-controller"); |
449 | if (IS_ERR(np_i2s)) | |
450 | return PTR_ERR(np_i2s); | |
451 | ||
452 | card->dai_link->cpus->of_node = np_i2s; | |
453 | card->dai_link->codecs->of_node = np_codec; | |
454 | card->dai_link->platforms->of_node = np_i2s; | |
455 | } | |
cc8f70f5 DO |
456 | |
457 | if (asoc->add_common_controls) { | |
458 | card->controls = tegra_machine_controls; | |
459 | card->num_controls = ARRAY_SIZE(tegra_machine_controls); | |
460 | } | |
461 | ||
462 | if (asoc->add_common_dapm_widgets) { | |
463 | card->dapm_widgets = tegra_machine_dapm_widgets; | |
464 | card->num_dapm_widgets = ARRAY_SIZE(tegra_machine_dapm_widgets); | |
465 | } | |
466 | ||
467 | if (asoc->add_common_snd_ops) | |
468 | card->dai_link->ops = &tegra_machine_snd_ops; | |
469 | ||
470 | if (!card->owner) | |
471 | card->owner = THIS_MODULE; | |
472 | if (!card->driver_name) | |
473 | card->driver_name = "tegra"; | |
474 | ||
8c1b3b15 DO |
475 | machine->clk_pll_a = devm_clk_get(dev, "pll_a"); |
476 | if (IS_ERR(machine->clk_pll_a)) { | |
477 | dev_err(dev, "Can't retrieve clk pll_a\n"); | |
478 | return PTR_ERR(machine->clk_pll_a); | |
479 | } | |
480 | ||
481 | machine->clk_pll_a_out0 = devm_clk_get(dev, "pll_a_out0"); | |
482 | if (IS_ERR(machine->clk_pll_a_out0)) { | |
483 | dev_err(dev, "Can't retrieve clk pll_a_out0\n"); | |
484 | return PTR_ERR(machine->clk_pll_a_out0); | |
485 | } | |
486 | ||
487 | machine->clk_cdev1 = devm_clk_get(dev, "mclk"); | |
488 | if (IS_ERR(machine->clk_cdev1)) { | |
489 | dev_err(dev, "Can't retrieve clk cdev1\n"); | |
490 | return PTR_ERR(machine->clk_cdev1); | |
491 | } | |
492 | ||
493 | /* | |
494 | * If clock parents are not set in DT, configure here to use clk_out_1 | |
495 | * as mclk and extern1 as parent for Tegra30 and higher. | |
496 | */ | |
497 | if (!of_find_property(dev->of_node, "assigned-clock-parents", NULL) && | |
498 | !of_machine_is_compatible("nvidia,tegra20")) { | |
499 | struct clk *clk_out_1, *clk_extern1; | |
500 | ||
501 | dev_warn(dev, "Configuring clocks for a legacy device-tree\n"); | |
502 | dev_warn(dev, "Please update DT to use assigned-clock-parents\n"); | |
503 | ||
504 | clk_extern1 = devm_clk_get(dev, "extern1"); | |
505 | if (IS_ERR(clk_extern1)) { | |
506 | dev_err(dev, "Can't retrieve clk extern1\n"); | |
507 | return PTR_ERR(clk_extern1); | |
508 | } | |
509 | ||
510 | err = clk_set_parent(clk_extern1, machine->clk_pll_a_out0); | |
511 | if (err < 0) { | |
512 | dev_err(dev, "Set parent failed for clk extern1\n"); | |
513 | return err; | |
514 | } | |
515 | ||
516 | clk_out_1 = devm_clk_get(dev, "pmc_clk_out_1"); | |
517 | if (IS_ERR(clk_out_1)) { | |
518 | dev_err(dev, "Can't retrieve pmc_clk_out_1\n"); | |
519 | return PTR_ERR(clk_out_1); | |
520 | } | |
521 | ||
522 | err = clk_set_parent(clk_out_1, clk_extern1); | |
523 | if (err < 0) { | |
524 | dev_err(dev, "Set parent failed for pmc_clk_out_1\n"); | |
525 | return err; | |
526 | } | |
527 | ||
528 | machine->clk_cdev1 = clk_out_1; | |
529 | } | |
cc8f70f5 DO |
530 | |
531 | if (asoc->set_ac97) { | |
8c1b3b15 DO |
532 | /* |
533 | * AC97 rate is fixed at 24.576MHz and is used for both the | |
534 | * host controller and the external codec | |
535 | */ | |
536 | err = clk_set_rate(machine->clk_pll_a, 73728000); | |
537 | if (err) { | |
538 | dev_err(dev, "Can't set pll_a rate: %d\n", err); | |
cc8f70f5 | 539 | return err; |
8c1b3b15 DO |
540 | } |
541 | ||
542 | err = clk_set_rate(machine->clk_pll_a_out0, 24576000); | |
543 | if (err) { | |
544 | dev_err(dev, "Can't set pll_a_out0 rate: %d\n", err); | |
545 | return err; | |
546 | } | |
547 | ||
548 | machine->set_baseclock = 73728000; | |
549 | machine->set_mclk = 24576000; | |
550 | } | |
551 | ||
552 | /* | |
553 | * FIXME: There is some unknown dependency between audio MCLK disable | |
554 | * and suspend-resume functionality on Tegra30, although audio MCLK is | |
555 | * only needed for audio. | |
556 | */ | |
557 | err = clk_prepare_enable(machine->clk_cdev1); | |
558 | if (err) { | |
559 | dev_err(dev, "Can't enable cdev1: %d\n", err); | |
560 | return err; | |
cc8f70f5 DO |
561 | } |
562 | ||
563 | err = devm_snd_soc_register_card(dev, card); | |
564 | if (err) | |
565 | return err; | |
566 | ||
567 | return 0; | |
568 | } | |
569 | EXPORT_SYMBOL_GPL(tegra_asoc_machine_probe); | |
570 | ||
571 | /* WM8753 machine */ | |
572 | ||
573 | SND_SOC_DAILINK_DEFS(wm8753_hifi, | |
574 | DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
575 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8753-hifi")), | |
576 | DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
577 | ||
578 | static struct snd_soc_dai_link tegra_wm8753_dai = { | |
579 | .name = "WM8753", | |
580 | .stream_name = "WM8753 PCM", | |
581 | .dai_fmt = SND_SOC_DAIFMT_I2S | | |
582 | SND_SOC_DAIFMT_NB_NF | | |
583 | SND_SOC_DAIFMT_CBS_CFS, | |
584 | SND_SOC_DAILINK_REG(wm8753_hifi), | |
585 | }; | |
586 | ||
587 | static struct snd_soc_card snd_soc_tegra_wm8753 = { | |
c16aab8d | 588 | .components = "codec:wm8753", |
cc8f70f5 DO |
589 | .dai_link = &tegra_wm8753_dai, |
590 | .num_links = 1, | |
591 | .fully_routed = true, | |
592 | }; | |
593 | ||
594 | static const struct tegra_asoc_data tegra_wm8753_data = { | |
595 | .mclk_rate = tegra_machine_mclk_rate_12mhz, | |
596 | .card = &snd_soc_tegra_wm8753, | |
597 | .add_common_dapm_widgets = true, | |
598 | .add_common_snd_ops = true, | |
599 | }; | |
600 | ||
601 | /* WM9712 machine */ | |
602 | ||
603 | static int tegra_wm9712_init(struct snd_soc_pcm_runtime *rtd) | |
604 | { | |
605 | return snd_soc_dapm_force_enable_pin(&rtd->card->dapm, "Mic Bias"); | |
606 | } | |
607 | ||
608 | SND_SOC_DAILINK_DEFS(wm9712_hifi, | |
609 | DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
610 | DAILINK_COMP_ARRAY(COMP_CODEC("wm9712-codec", "wm9712-hifi")), | |
611 | DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
612 | ||
613 | static struct snd_soc_dai_link tegra_wm9712_dai = { | |
614 | .name = "AC97 HiFi", | |
615 | .stream_name = "AC97 HiFi", | |
616 | .init = tegra_wm9712_init, | |
617 | SND_SOC_DAILINK_REG(wm9712_hifi), | |
618 | }; | |
619 | ||
620 | static struct snd_soc_card snd_soc_tegra_wm9712 = { | |
c16aab8d | 621 | .components = "codec:wm9712", |
cc8f70f5 DO |
622 | .dai_link = &tegra_wm9712_dai, |
623 | .num_links = 1, | |
624 | .fully_routed = true, | |
625 | }; | |
626 | ||
627 | static const struct tegra_asoc_data tegra_wm9712_data = { | |
628 | .card = &snd_soc_tegra_wm9712, | |
629 | .add_common_dapm_widgets = true, | |
298c4bf4 | 630 | .codec_dev_name = "wm9712-codec", |
cc8f70f5 DO |
631 | .set_ac97 = true, |
632 | }; | |
633 | ||
634 | /* MAX98090 machine */ | |
635 | ||
636 | SND_SOC_DAILINK_DEFS(max98090_hifi, | |
637 | DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
638 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "HiFi")), | |
639 | DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
640 | ||
641 | static struct snd_soc_dai_link tegra_max98090_dai = { | |
642 | .name = "max98090", | |
643 | .stream_name = "max98090 PCM", | |
644 | .init = tegra_asoc_machine_init, | |
645 | .dai_fmt = SND_SOC_DAIFMT_I2S | | |
646 | SND_SOC_DAIFMT_NB_NF | | |
647 | SND_SOC_DAIFMT_CBS_CFS, | |
648 | SND_SOC_DAILINK_REG(max98090_hifi), | |
649 | }; | |
650 | ||
651 | static struct snd_soc_card snd_soc_tegra_max98090 = { | |
652 | .components = "codec:max98090", | |
653 | .dai_link = &tegra_max98090_dai, | |
654 | .num_links = 1, | |
655 | .fully_routed = true, | |
656 | }; | |
657 | ||
658 | static const struct tegra_asoc_data tegra_max98090_data = { | |
659 | .mclk_rate = tegra_machine_mclk_rate_12mhz, | |
660 | .card = &snd_soc_tegra_max98090, | |
661 | .add_common_dapm_widgets = true, | |
662 | .add_common_controls = true, | |
663 | .add_common_snd_ops = true, | |
664 | .add_mic_jack = true, | |
665 | .add_hp_jack = true, | |
666 | }; | |
667 | ||
668 | /* SGTL5000 machine */ | |
669 | ||
670 | SND_SOC_DAILINK_DEFS(sgtl5000_hifi, | |
671 | DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
672 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "sgtl5000")), | |
673 | DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
674 | ||
675 | static struct snd_soc_dai_link tegra_sgtl5000_dai = { | |
676 | .name = "sgtl5000", | |
677 | .stream_name = "HiFi", | |
678 | .dai_fmt = SND_SOC_DAIFMT_I2S | | |
679 | SND_SOC_DAIFMT_NB_NF | | |
680 | SND_SOC_DAIFMT_CBS_CFS, | |
681 | SND_SOC_DAILINK_REG(sgtl5000_hifi), | |
682 | }; | |
683 | ||
684 | static struct snd_soc_card snd_soc_tegra_sgtl5000 = { | |
c16aab8d | 685 | .components = "codec:sgtl5000", |
cc8f70f5 DO |
686 | .dai_link = &tegra_sgtl5000_dai, |
687 | .num_links = 1, | |
688 | .fully_routed = true, | |
689 | }; | |
690 | ||
691 | static const struct tegra_asoc_data tegra_sgtl5000_data = { | |
692 | .mclk_rate = tegra_machine_mclk_rate_12mhz, | |
693 | .card = &snd_soc_tegra_sgtl5000, | |
694 | .add_common_dapm_widgets = true, | |
695 | .add_common_snd_ops = true, | |
696 | }; | |
697 | ||
698 | /* TLV320AIC23 machine */ | |
699 | ||
700 | static const struct snd_soc_dapm_widget trimslice_dapm_widgets[] = { | |
701 | SND_SOC_DAPM_HP("Line Out", NULL), | |
702 | SND_SOC_DAPM_LINE("Line In", NULL), | |
703 | }; | |
704 | ||
705 | static const struct snd_soc_dapm_route trimslice_audio_map[] = { | |
706 | {"Line Out", NULL, "LOUT"}, | |
707 | {"Line Out", NULL, "ROUT"}, | |
708 | ||
709 | {"LLINEIN", NULL, "Line In"}, | |
710 | {"RLINEIN", NULL, "Line In"}, | |
711 | }; | |
712 | ||
713 | SND_SOC_DAILINK_DEFS(tlv320aic23_hifi, | |
714 | DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
715 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "tlv320aic23-hifi")), | |
716 | DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
717 | ||
718 | static struct snd_soc_dai_link tegra_tlv320aic23_dai = { | |
719 | .name = "TLV320AIC23", | |
720 | .stream_name = "AIC23", | |
721 | .dai_fmt = SND_SOC_DAIFMT_I2S | | |
722 | SND_SOC_DAIFMT_NB_NF | | |
723 | SND_SOC_DAIFMT_CBS_CFS, | |
724 | SND_SOC_DAILINK_REG(tlv320aic23_hifi), | |
725 | }; | |
726 | ||
727 | static struct snd_soc_card snd_soc_tegra_trimslice = { | |
5f172dc2 | 728 | .name = "tegra-trimslice", |
c16aab8d | 729 | .components = "codec:tlv320aic23", |
cc8f70f5 DO |
730 | .dai_link = &tegra_tlv320aic23_dai, |
731 | .num_links = 1, | |
732 | .dapm_widgets = trimslice_dapm_widgets, | |
733 | .num_dapm_widgets = ARRAY_SIZE(trimslice_dapm_widgets), | |
734 | .dapm_routes = trimslice_audio_map, | |
735 | .num_dapm_routes = ARRAY_SIZE(trimslice_audio_map), | |
736 | .fully_routed = true, | |
737 | }; | |
738 | ||
739 | static const struct tegra_asoc_data tegra_trimslice_data = { | |
740 | .mclk_rate = tegra_machine_mclk_rate_128, | |
741 | .card = &snd_soc_tegra_trimslice, | |
742 | .add_common_snd_ops = true, | |
743 | }; | |
744 | ||
745 | /* RT5677 machine */ | |
746 | ||
747 | static int tegra_rt5677_init(struct snd_soc_pcm_runtime *rtd) | |
748 | { | |
749 | struct snd_soc_card *card = rtd->card; | |
750 | int err; | |
751 | ||
752 | err = tegra_asoc_machine_init(rtd); | |
753 | if (err) | |
754 | return err; | |
755 | ||
756 | snd_soc_dapm_force_enable_pin(&card->dapm, "MICBIAS1"); | |
757 | ||
758 | return 0; | |
759 | } | |
760 | ||
761 | SND_SOC_DAILINK_DEFS(rt5677_aif1, | |
762 | DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
763 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "rt5677-aif1")), | |
764 | DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
765 | ||
766 | static struct snd_soc_dai_link tegra_rt5677_dai = { | |
767 | .name = "RT5677", | |
768 | .stream_name = "RT5677 PCM", | |
769 | .init = tegra_rt5677_init, | |
770 | .dai_fmt = SND_SOC_DAIFMT_I2S | | |
771 | SND_SOC_DAIFMT_NB_NF | | |
772 | SND_SOC_DAIFMT_CBS_CFS, | |
773 | SND_SOC_DAILINK_REG(rt5677_aif1), | |
774 | }; | |
775 | ||
776 | static struct snd_soc_card snd_soc_tegra_rt5677 = { | |
c16aab8d | 777 | .components = "codec:rt5677", |
cc8f70f5 DO |
778 | .dai_link = &tegra_rt5677_dai, |
779 | .num_links = 1, | |
780 | .fully_routed = true, | |
781 | }; | |
782 | ||
783 | static const struct tegra_asoc_data tegra_rt5677_data = { | |
784 | .mclk_rate = tegra_machine_mclk_rate_256, | |
785 | .card = &snd_soc_tegra_rt5677, | |
786 | .add_common_dapm_widgets = true, | |
787 | .add_common_controls = true, | |
788 | .add_common_snd_ops = true, | |
789 | .add_mic_jack = true, | |
790 | .add_hp_jack = true, | |
791 | }; | |
792 | ||
793 | /* RT5640 machine */ | |
794 | ||
795 | SND_SOC_DAILINK_DEFS(rt5640_aif1, | |
796 | DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
797 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "rt5640-aif1")), | |
798 | DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
799 | ||
800 | static struct snd_soc_dai_link tegra_rt5640_dai = { | |
801 | .name = "RT5640", | |
802 | .stream_name = "RT5640 PCM", | |
803 | .init = tegra_asoc_machine_init, | |
804 | .dai_fmt = SND_SOC_DAIFMT_I2S | | |
805 | SND_SOC_DAIFMT_NB_NF | | |
806 | SND_SOC_DAIFMT_CBS_CFS, | |
807 | SND_SOC_DAILINK_REG(rt5640_aif1), | |
808 | }; | |
809 | ||
810 | static struct snd_soc_card snd_soc_tegra_rt5640 = { | |
c16aab8d | 811 | .components = "codec:rt5640", |
cc8f70f5 DO |
812 | .dai_link = &tegra_rt5640_dai, |
813 | .num_links = 1, | |
814 | .fully_routed = true, | |
815 | }; | |
816 | ||
817 | static const struct tegra_asoc_data tegra_rt5640_data = { | |
818 | .mclk_rate = tegra_machine_mclk_rate_256, | |
819 | .card = &snd_soc_tegra_rt5640, | |
820 | .add_common_dapm_widgets = true, | |
821 | .add_common_controls = true, | |
822 | .add_common_snd_ops = true, | |
823 | .add_hp_jack = true, | |
824 | }; | |
825 | ||
826 | /* RT5632 machine */ | |
827 | ||
828 | SND_SOC_DAILINK_DEFS(rt5632_hifi, | |
829 | DAILINK_COMP_ARRAY(COMP_EMPTY()), | |
830 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "alc5632-hifi")), | |
831 | DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
832 | ||
833 | static struct snd_soc_dai_link tegra_rt5632_dai = { | |
834 | .name = "ALC5632", | |
835 | .stream_name = "ALC5632 PCM", | |
836 | .init = tegra_rt5677_init, | |
837 | .dai_fmt = SND_SOC_DAIFMT_I2S | | |
838 | SND_SOC_DAIFMT_NB_NF | | |
839 | SND_SOC_DAIFMT_CBS_CFS, | |
840 | SND_SOC_DAILINK_REG(rt5632_hifi), | |
841 | }; | |
842 | ||
843 | static struct snd_soc_card snd_soc_tegra_rt5632 = { | |
c16aab8d | 844 | .components = "codec:rt5632", |
cc8f70f5 DO |
845 | .dai_link = &tegra_rt5632_dai, |
846 | .num_links = 1, | |
847 | .fully_routed = true, | |
848 | }; | |
849 | ||
850 | static const struct tegra_asoc_data tegra_rt5632_data = { | |
851 | .mclk_rate = tegra_machine_mclk_rate_512, | |
852 | .card = &snd_soc_tegra_rt5632, | |
853 | .add_common_dapm_widgets = true, | |
854 | .add_common_controls = true, | |
855 | .add_common_snd_ops = true, | |
856 | .add_headset_jack = true, | |
857 | }; | |
858 | ||
859 | static const struct of_device_id tegra_machine_of_match[] = { | |
860 | { .compatible = "nvidia,tegra-audio-trimslice", .data = &tegra_trimslice_data }, | |
861 | { .compatible = "nvidia,tegra-audio-max98090", .data = &tegra_max98090_data }, | |
862 | { .compatible = "nvidia,tegra-audio-sgtl5000", .data = &tegra_sgtl5000_data }, | |
863 | { .compatible = "nvidia,tegra-audio-wm9712", .data = &tegra_wm9712_data }, | |
864 | { .compatible = "nvidia,tegra-audio-wm8753", .data = &tegra_wm8753_data }, | |
865 | { .compatible = "nvidia,tegra-audio-rt5677", .data = &tegra_rt5677_data }, | |
866 | { .compatible = "nvidia,tegra-audio-rt5640", .data = &tegra_rt5640_data }, | |
867 | { .compatible = "nvidia,tegra-audio-alc5632", .data = &tegra_rt5632_data }, | |
868 | {}, | |
869 | }; | |
870 | MODULE_DEVICE_TABLE(of, tegra_machine_of_match); | |
871 | ||
872 | static struct platform_driver tegra_asoc_machine_driver = { | |
873 | .driver = { | |
874 | .name = "tegra-audio", | |
875 | .of_match_table = tegra_machine_of_match, | |
876 | .pm = &snd_soc_pm_ops, | |
877 | }, | |
878 | .probe = tegra_asoc_machine_probe, | |
879 | }; | |
880 | module_platform_driver(tegra_asoc_machine_driver); | |
881 | ||
882 | MODULE_AUTHOR("Anatol Pomozov <anatol@google.com>"); | |
883 | MODULE_AUTHOR("Andrey Danin <danindrey@mail.ru>"); | |
884 | MODULE_AUTHOR("Dmitry Osipenko <digetx@gmail.com>"); | |
885 | MODULE_AUTHOR("Ion Agorria <ion@agorria.com>"); | |
886 | MODULE_AUTHOR("Leon Romanovsky <leon@leon.nu>"); | |
887 | MODULE_AUTHOR("Lucas Stach <dev@lynxeye.de>"); | |
888 | MODULE_AUTHOR("Marc Dietrich <marvin24@gmx.de>"); | |
889 | MODULE_AUTHOR("Marcel Ziswiler <marcel@ziswiler.com>"); | |
890 | MODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>"); | |
891 | MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); | |
892 | MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>"); | |
893 | MODULE_DESCRIPTION("Tegra machine ASoC driver"); | |
894 | MODULE_LICENSE("GPL"); |