1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * rx51.c -- SoC audio for Nokia RX-51 |
4 | * |
5 | * Copyright (C) 2008 - 2009 Nokia Corporation |
6 | * |
7 | * Contact: Peter Ujfalusi <peter.ujfalusi@ti.com> |
8 | * Eduardo Valentin <eduardo.valentin@nokia.com> |
9 | * Jarkko Nikula <jarkko.nikula@bitmer.com> |
10 | */ |
11 | |
12 | #include <linux/delay.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/gpio/consumer.h> |
15 | #include <linux/module.h> |
16 | #include <sound/core.h> |
17 | #include <sound/jack.h> |
18 | #include <sound/pcm.h> |
19 | #include <sound/soc.h> |
20 | #include <linux/platform_data/asoc-ti-mcbsp.h> |
21 | |
22 | #include <asm/mach-types.h> |
23 | |
24 | #include "omap-mcbsp.h" |
25 | |
26 | enum { |
27 | RX51_JACK_DISABLED, |
28 | RX51_JACK_TVOUT, /* tv-out with stereo output */ |
29 | RX51_JACK_HP, /* headphone: stereo output, no mic */ |
30 | RX51_JACK_HS, /* headset: stereo output with mic */ |
31 | }; |
32 | |
33 | struct rx51_audio_pdata { |
34 | struct gpio_desc *tvout_selection_gpio; |
35 | struct gpio_desc *eci_sw_gpio; |
36 | struct gpio_desc *speaker_amp_gpio; |
37 | }; |
38 | |
39 | static int rx51_spk_func; |
40 | static int rx51_dmic_func; |
41 | static int rx51_jack_func; |
42 | |
43 | static void rx51_ext_control(struct snd_soc_dapm_context *dapm) |
44 | { |
45 | struct snd_soc_card *card = dapm->card; |
46 | struct rx51_audio_pdata *pdata = snd_soc_card_get_drvdata(card); |
47 | int hp = 0, hs = 0, tvout = 0; |
48 | |
49 | switch (rx51_jack_func) { |
50 | case RX51_JACK_TVOUT: |
51 | tvout = 1; |
52 | hp = 1; |
53 | break; |
54 | case RX51_JACK_HS: |
55 | hs = 1; |
56 | fallthrough; |
57 | case RX51_JACK_HP: |
58 | hp = 1; |
59 | break; |
60 | } |
61 | |
62 | snd_soc_dapm_mutex_lock(dapm); |
63 | |
64 | if (rx51_spk_func) |
65 | snd_soc_dapm_enable_pin_unlocked(dapm, pin: "Ext Spk" ); |
66 | else |
67 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "Ext Spk" ); |
68 | if (rx51_dmic_func) |
69 | snd_soc_dapm_enable_pin_unlocked(dapm, pin: "DMic" ); |
70 | else |
71 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "DMic" ); |
72 | if (hp) |
73 | snd_soc_dapm_enable_pin_unlocked(dapm, pin: "Headphone Jack" ); |
74 | else |
75 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "Headphone Jack" ); |
76 | if (hs) |
77 | snd_soc_dapm_enable_pin_unlocked(dapm, pin: "HS Mic" ); |
78 | else |
79 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "HS Mic" ); |
80 | |
81 | gpiod_set_value(desc: pdata->tvout_selection_gpio, value: tvout); |
82 | |
83 | snd_soc_dapm_sync_unlocked(dapm); |
84 | |
85 | snd_soc_dapm_mutex_unlock(dapm); |
86 | } |
87 | |
88 | static int rx51_startup(struct snd_pcm_substream *substream) |
89 | { |
90 | struct snd_pcm_runtime *runtime = substream->runtime; |
91 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
92 | struct snd_soc_card *card = rtd->card; |
93 | |
94 | snd_pcm_hw_constraint_single(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, val: 2); |
95 | rx51_ext_control(dapm: &card->dapm); |
96 | |
97 | return 0; |
98 | } |
99 | |
100 | static int rx51_hw_params(struct snd_pcm_substream *substream, |
101 | struct snd_pcm_hw_params *params) |
102 | { |
103 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
104 | struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); |
105 | |
106 | /* Set the codec system clock for DAC and ADC */ |
107 | return snd_soc_dai_set_sysclk(dai: codec_dai, clk_id: 0, freq: 19200000, |
108 | SND_SOC_CLOCK_IN); |
109 | } |
110 | |
111 | static const struct snd_soc_ops rx51_ops = { |
112 | .startup = rx51_startup, |
113 | .hw_params = rx51_hw_params, |
114 | }; |
115 | |
116 | static int rx51_get_spk(struct snd_kcontrol *kcontrol, |
117 | struct snd_ctl_elem_value *ucontrol) |
118 | { |
119 | ucontrol->value.enumerated.item[0] = rx51_spk_func; |
120 | |
121 | return 0; |
122 | } |
123 | |
124 | static int rx51_set_spk(struct snd_kcontrol *kcontrol, |
125 | struct snd_ctl_elem_value *ucontrol) |
126 | { |
127 | struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); |
128 | |
129 | if (rx51_spk_func == ucontrol->value.enumerated.item[0]) |
130 | return 0; |
131 | |
132 | rx51_spk_func = ucontrol->value.enumerated.item[0]; |
133 | rx51_ext_control(dapm: &card->dapm); |
134 | |
135 | return 1; |
136 | } |
137 | |
138 | static int rx51_spk_event(struct snd_soc_dapm_widget *w, |
139 | struct snd_kcontrol *k, int event) |
140 | { |
141 | struct snd_soc_dapm_context *dapm = w->dapm; |
142 | struct snd_soc_card *card = dapm->card; |
143 | struct rx51_audio_pdata *pdata = snd_soc_card_get_drvdata(card); |
144 | |
145 | gpiod_set_raw_value_cansleep(desc: pdata->speaker_amp_gpio, |
146 | value: !!SND_SOC_DAPM_EVENT_ON(event)); |
147 | |
148 | return 0; |
149 | } |
150 | |
151 | static int rx51_get_input(struct snd_kcontrol *kcontrol, |
152 | struct snd_ctl_elem_value *ucontrol) |
153 | { |
154 | ucontrol->value.enumerated.item[0] = rx51_dmic_func; |
155 | |
156 | return 0; |
157 | } |
158 | |
159 | static int rx51_set_input(struct snd_kcontrol *kcontrol, |
160 | struct snd_ctl_elem_value *ucontrol) |
161 | { |
162 | struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); |
163 | |
164 | if (rx51_dmic_func == ucontrol->value.enumerated.item[0]) |
165 | return 0; |
166 | |
167 | rx51_dmic_func = ucontrol->value.enumerated.item[0]; |
168 | rx51_ext_control(dapm: &card->dapm); |
169 | |
170 | return 1; |
171 | } |
172 | |
173 | static int rx51_get_jack(struct snd_kcontrol *kcontrol, |
174 | struct snd_ctl_elem_value *ucontrol) |
175 | { |
176 | ucontrol->value.enumerated.item[0] = rx51_jack_func; |
177 | |
178 | return 0; |
179 | } |
180 | |
181 | static int rx51_set_jack(struct snd_kcontrol *kcontrol, |
182 | struct snd_ctl_elem_value *ucontrol) |
183 | { |
184 | struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); |
185 | |
186 | if (rx51_jack_func == ucontrol->value.enumerated.item[0]) |
187 | return 0; |
188 | |
189 | rx51_jack_func = ucontrol->value.enumerated.item[0]; |
190 | rx51_ext_control(dapm: &card->dapm); |
191 | |
192 | return 1; |
193 | } |
194 | |
195 | static struct snd_soc_jack rx51_av_jack; |
196 | |
197 | static struct snd_soc_jack_gpio rx51_av_jack_gpios[] = { |
198 | { |
199 | .name = "jack-detection" , |
200 | .report = SND_JACK_HEADSET, |
201 | .invert = 1, |
202 | .debounce_time = 200, |
203 | }, |
204 | }; |
205 | |
206 | static const struct snd_soc_dapm_widget aic34_dapm_widgets[] = { |
207 | SND_SOC_DAPM_SPK("Ext Spk" , rx51_spk_event), |
208 | SND_SOC_DAPM_MIC("DMic" , NULL), |
209 | SND_SOC_DAPM_HP("Headphone Jack" , NULL), |
210 | SND_SOC_DAPM_MIC("HS Mic" , NULL), |
211 | SND_SOC_DAPM_LINE("FM Transmitter" , NULL), |
212 | SND_SOC_DAPM_SPK("Earphone" , NULL), |
213 | }; |
214 | |
215 | static const struct snd_soc_dapm_route audio_map[] = { |
216 | {"Ext Spk" , NULL, "HPLOUT" }, |
217 | {"Ext Spk" , NULL, "HPROUT" }, |
218 | {"Ext Spk" , NULL, "HPLCOM" }, |
219 | {"Ext Spk" , NULL, "HPRCOM" }, |
220 | {"FM Transmitter" , NULL, "LLOUT" }, |
221 | {"FM Transmitter" , NULL, "RLOUT" }, |
222 | |
223 | {"Headphone Jack" , NULL, "TPA6130A2 HPLEFT" }, |
224 | {"Headphone Jack" , NULL, "TPA6130A2 HPRIGHT" }, |
225 | {"TPA6130A2 LEFTIN" , NULL, "LLOUT" }, |
226 | {"TPA6130A2 RIGHTIN" , NULL, "RLOUT" }, |
227 | |
228 | {"DMic Rate 64" , NULL, "DMic" }, |
229 | {"DMic" , NULL, "Mic Bias" }, |
230 | |
231 | {"b LINE2R" , NULL, "MONO_LOUT" }, |
232 | {"Earphone" , NULL, "b HPLOUT" }, |
233 | |
234 | {"LINE1L" , NULL, "HS Mic" }, |
235 | {"HS Mic" , NULL, "b Mic Bias" }, |
236 | }; |
237 | |
238 | static const char * const spk_function[] = {"Off" , "On" }; |
239 | static const char * const input_function[] = {"ADC" , "Digital Mic" }; |
240 | static const char * const jack_function[] = { |
241 | "Off" , "TV-OUT" , "Headphone" , "Headset" |
242 | }; |
243 | |
244 | static const struct soc_enum rx51_enum[] = { |
245 | SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spk_function), spk_function), |
246 | SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_function), input_function), |
247 | SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(jack_function), jack_function), |
248 | }; |
249 | |
250 | static const struct snd_kcontrol_new aic34_rx51_controls[] = { |
251 | SOC_ENUM_EXT("Speaker Function" , rx51_enum[0], |
252 | rx51_get_spk, rx51_set_spk), |
253 | SOC_ENUM_EXT("Input Select" , rx51_enum[1], |
254 | rx51_get_input, rx51_set_input), |
255 | SOC_ENUM_EXT("Jack Function" , rx51_enum[2], |
256 | rx51_get_jack, rx51_set_jack), |
257 | SOC_DAPM_PIN_SWITCH("FM Transmitter" ), |
258 | SOC_DAPM_PIN_SWITCH("Earphone" ), |
259 | }; |
260 | |
261 | static int rx51_aic34_init(struct snd_soc_pcm_runtime *rtd) |
262 | { |
263 | struct snd_soc_card *card = rtd->card; |
264 | int err; |
265 | |
266 | snd_soc_limit_volume(card, name: "TPA6130A2 Headphone Playback Volume" , max: 42); |
267 | |
268 | err = omap_mcbsp_st_add_controls(rtd, port_id: 2); |
269 | if (err < 0) { |
270 | dev_err(card->dev, "Failed to add MCBSP controls\n" ); |
271 | return err; |
272 | } |
273 | |
274 | /* AV jack detection */ |
275 | err = snd_soc_card_jack_new(card: rtd->card, id: "AV Jack" , |
276 | type: SND_JACK_HEADSET | SND_JACK_VIDEOOUT, |
277 | jack: &rx51_av_jack); |
278 | if (err) { |
279 | dev_err(card->dev, "Failed to add AV Jack\n" ); |
280 | return err; |
281 | } |
282 | |
283 | rx51_av_jack_gpios[0].gpiod_dev = card->dev; |
284 | /* Name is assigned in the struct */ |
285 | rx51_av_jack_gpios[0].idx = 0; |
286 | |
287 | err = snd_soc_jack_add_gpios(jack: &rx51_av_jack, |
288 | ARRAY_SIZE(rx51_av_jack_gpios), |
289 | gpios: rx51_av_jack_gpios); |
290 | if (err) { |
291 | dev_err(card->dev, "Failed to add GPIOs\n" ); |
292 | return err; |
293 | } |
294 | |
295 | return err; |
296 | } |
297 | |
298 | /* Digital audio interface glue - connects codec <--> CPU */ |
299 | SND_SOC_DAILINK_DEFS(aic34, |
300 | DAILINK_COMP_ARRAY(COMP_CPU("omap-mcbsp.2" )), |
301 | DAILINK_COMP_ARRAY(COMP_CODEC("tlv320aic3x-codec.2-0018" , |
302 | "tlv320aic3x-hifi" )), |
303 | DAILINK_COMP_ARRAY(COMP_PLATFORM("omap-mcbsp.2" ))); |
304 | |
305 | static struct snd_soc_dai_link rx51_dai[] = { |
306 | { |
307 | .name = "TLV320AIC34" , |
308 | .stream_name = "AIC34" , |
309 | .dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF | |
310 | SND_SOC_DAIFMT_CBM_CFM, |
311 | .init = rx51_aic34_init, |
312 | .ops = &rx51_ops, |
313 | SND_SOC_DAILINK_REG(aic34), |
314 | }, |
315 | }; |
316 | |
317 | static struct snd_soc_aux_dev rx51_aux_dev[] = { |
318 | { |
319 | .dlc = COMP_AUX("tlv320aic3x-codec.2-0019" ), |
320 | }, |
321 | { |
322 | .dlc = COMP_AUX("tpa6130a2.2-0060" ), |
323 | }, |
324 | }; |
325 | |
326 | static struct snd_soc_codec_conf rx51_codec_conf[] = { |
327 | { |
328 | .dlc = COMP_CODEC_CONF("tlv320aic3x-codec.2-0019" ), |
329 | .name_prefix = "b" , |
330 | }, |
331 | { |
332 | .dlc = COMP_CODEC_CONF("tpa6130a2.2-0060" ), |
333 | .name_prefix = "TPA6130A2" , |
334 | }, |
335 | }; |
336 | |
337 | /* Audio card */ |
338 | static struct snd_soc_card rx51_sound_card = { |
339 | .name = "RX-51" , |
340 | .owner = THIS_MODULE, |
341 | .dai_link = rx51_dai, |
342 | .num_links = ARRAY_SIZE(rx51_dai), |
343 | .aux_dev = rx51_aux_dev, |
344 | .num_aux_devs = ARRAY_SIZE(rx51_aux_dev), |
345 | .codec_conf = rx51_codec_conf, |
346 | .num_configs = ARRAY_SIZE(rx51_codec_conf), |
347 | .fully_routed = true, |
348 | |
349 | .controls = aic34_rx51_controls, |
350 | .num_controls = ARRAY_SIZE(aic34_rx51_controls), |
351 | .dapm_widgets = aic34_dapm_widgets, |
352 | .num_dapm_widgets = ARRAY_SIZE(aic34_dapm_widgets), |
353 | .dapm_routes = audio_map, |
354 | .num_dapm_routes = ARRAY_SIZE(audio_map), |
355 | }; |
356 | |
357 | static int rx51_soc_probe(struct platform_device *pdev) |
358 | { |
359 | struct rx51_audio_pdata *pdata; |
360 | struct device_node *np = pdev->dev.of_node; |
361 | struct snd_soc_card *card = &rx51_sound_card; |
362 | int err; |
363 | |
364 | if (!machine_is_nokia_rx51() && !of_machine_is_compatible(compat: "nokia,omap3-n900" )) |
365 | return -ENODEV; |
366 | |
367 | card->dev = &pdev->dev; |
368 | |
369 | if (np) { |
370 | struct device_node *dai_node; |
371 | |
372 | dai_node = of_parse_phandle(np, phandle_name: "nokia,cpu-dai" , index: 0); |
373 | if (!dai_node) { |
374 | dev_err(&pdev->dev, "McBSP node is not provided\n" ); |
375 | return -EINVAL; |
376 | } |
377 | rx51_dai[0].cpus->dai_name = NULL; |
378 | rx51_dai[0].platforms->name = NULL; |
379 | rx51_dai[0].cpus->of_node = dai_node; |
380 | rx51_dai[0].platforms->of_node = dai_node; |
381 | |
382 | dai_node = of_parse_phandle(np, phandle_name: "nokia,audio-codec" , index: 0); |
383 | if (!dai_node) { |
384 | dev_err(&pdev->dev, "Codec node is not provided\n" ); |
385 | return -EINVAL; |
386 | } |
387 | rx51_dai[0].codecs->name = NULL; |
388 | rx51_dai[0].codecs->of_node = dai_node; |
389 | |
390 | dai_node = of_parse_phandle(np, phandle_name: "nokia,audio-codec" , index: 1); |
391 | if (!dai_node) { |
392 | dev_err(&pdev->dev, "Auxiliary Codec node is not provided\n" ); |
393 | return -EINVAL; |
394 | } |
395 | rx51_aux_dev[0].dlc.name = NULL; |
396 | rx51_aux_dev[0].dlc.of_node = dai_node; |
397 | rx51_codec_conf[0].dlc.name = NULL; |
398 | rx51_codec_conf[0].dlc.of_node = dai_node; |
399 | |
400 | dai_node = of_parse_phandle(np, phandle_name: "nokia,headphone-amplifier" , index: 0); |
401 | if (!dai_node) { |
402 | dev_err(&pdev->dev, "Headphone amplifier node is not provided\n" ); |
403 | return -EINVAL; |
404 | } |
405 | rx51_aux_dev[1].dlc.name = NULL; |
406 | rx51_aux_dev[1].dlc.of_node = dai_node; |
407 | rx51_codec_conf[1].dlc.name = NULL; |
408 | rx51_codec_conf[1].dlc.of_node = dai_node; |
409 | } |
410 | |
411 | pdata = devm_kzalloc(dev: &pdev->dev, size: sizeof(*pdata), GFP_KERNEL); |
412 | if (pdata == NULL) |
413 | return -ENOMEM; |
414 | |
415 | snd_soc_card_set_drvdata(card, data: pdata); |
416 | |
417 | pdata->tvout_selection_gpio = devm_gpiod_get(dev: card->dev, |
418 | con_id: "tvout-selection" , |
419 | flags: GPIOD_OUT_LOW); |
420 | if (IS_ERR(ptr: pdata->tvout_selection_gpio)) { |
421 | dev_err(card->dev, "could not get tvout selection gpio\n" ); |
422 | return PTR_ERR(ptr: pdata->tvout_selection_gpio); |
423 | } |
424 | |
425 | pdata->eci_sw_gpio = devm_gpiod_get(dev: card->dev, con_id: "eci-switch" , |
426 | flags: GPIOD_OUT_HIGH); |
427 | if (IS_ERR(ptr: pdata->eci_sw_gpio)) { |
428 | dev_err(card->dev, "could not get eci switch gpio\n" ); |
429 | return PTR_ERR(ptr: pdata->eci_sw_gpio); |
430 | } |
431 | |
432 | pdata->speaker_amp_gpio = devm_gpiod_get(dev: card->dev, |
433 | con_id: "speaker-amplifier" , |
434 | flags: GPIOD_OUT_LOW); |
435 | if (IS_ERR(ptr: pdata->speaker_amp_gpio)) { |
436 | dev_err(card->dev, "could not get speaker enable gpio\n" ); |
437 | return PTR_ERR(ptr: pdata->speaker_amp_gpio); |
438 | } |
439 | |
440 | err = devm_snd_soc_register_card(dev: card->dev, card); |
441 | if (err) { |
442 | dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n" , err); |
443 | return err; |
444 | } |
445 | |
446 | return 0; |
447 | } |
448 | |
449 | #if defined(CONFIG_OF) |
450 | static const struct of_device_id rx51_audio_of_match[] = { |
451 | { .compatible = "nokia,n900-audio" , }, |
452 | {}, |
453 | }; |
454 | MODULE_DEVICE_TABLE(of, rx51_audio_of_match); |
455 | #endif |
456 | |
457 | static struct platform_driver rx51_soc_driver = { |
458 | .driver = { |
459 | .name = "rx51-audio" , |
460 | .of_match_table = of_match_ptr(rx51_audio_of_match), |
461 | }, |
462 | .probe = rx51_soc_probe, |
463 | }; |
464 | |
465 | module_platform_driver(rx51_soc_driver); |
466 | |
467 | MODULE_AUTHOR("Nokia Corporation" ); |
468 | MODULE_DESCRIPTION("ALSA SoC Nokia RX-51" ); |
469 | MODULE_LICENSE("GPL" ); |
470 | MODULE_ALIAS("platform:rx51-audio" ); |
471 | |