1 // SPDX-License-Identifier: GPL-2.0-only
3 * omap-twl4030.c -- SoC audio for TI SoC based boards with twl4030 codec
5 * Copyright (C) 2012 Texas Instruments Incorporated - https://www.ti.com
8 * Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
10 * This driver replaces the following machine drivers:
11 * omap3beagle (Author: Steve Sakoman <steve@sakoman.com>)
12 * omap3evm (Author: Anuj Aggarwal <anuj.aggarwal@ti.com>)
13 * overo (Author: Steve Sakoman <steve@sakoman.com>)
14 * igep0020 (Author: Enric Balletbo i Serra <eballetbo@iseebcn.com>)
15 * zoom2 (Author: Misael Lopez Cruz <misael.lopez@ti.com>)
16 * sdp3430 (Author: Misael Lopez Cruz <misael.lopez@ti.com>)
19 #include <linux/platform_device.h>
20 #include <linux/platform_data/omap-twl4030.h>
21 #include <linux/module.h>
24 #include <sound/core.h>
25 #include <sound/pcm.h>
26 #include <sound/soc.h>
27 #include <sound/jack.h>
29 #include "omap-mcbsp.h"
32 struct snd_soc_jack hs_jack
;
35 static int omap_twl4030_hw_params(struct snd_pcm_substream
*substream
,
36 struct snd_pcm_hw_params
*params
)
38 struct snd_soc_pcm_runtime
*rtd
= snd_soc_substream_to_rtd(substream
);
41 switch (params_channels(params
)) {
42 case 2: /* Stereo I2S mode */
43 fmt
= SND_SOC_DAIFMT_I2S
|
44 SND_SOC_DAIFMT_NB_NF
|
45 SND_SOC_DAIFMT_CBM_CFM
;
47 case 4: /* Four channel TDM mode */
48 fmt
= SND_SOC_DAIFMT_DSP_A
|
49 SND_SOC_DAIFMT_IB_NF
|
50 SND_SOC_DAIFMT_CBM_CFM
;
56 return snd_soc_runtime_set_dai_fmt(rtd
, fmt
);
59 static const struct snd_soc_ops omap_twl4030_ops
= {
60 .hw_params
= omap_twl4030_hw_params
,
63 static const struct snd_soc_dapm_widget dapm_widgets
[] = {
64 SND_SOC_DAPM_SPK("Earpiece Spk", NULL
),
65 SND_SOC_DAPM_SPK("Handsfree Spk", NULL
),
66 SND_SOC_DAPM_HP("Headset Stereophone", NULL
),
67 SND_SOC_DAPM_SPK("Ext Spk", NULL
),
68 SND_SOC_DAPM_SPK("Carkit Spk", NULL
),
70 SND_SOC_DAPM_MIC("Main Mic", NULL
),
71 SND_SOC_DAPM_MIC("Sub Mic", NULL
),
72 SND_SOC_DAPM_MIC("Headset Mic", NULL
),
73 SND_SOC_DAPM_MIC("Carkit Mic", NULL
),
74 SND_SOC_DAPM_MIC("Digital0 Mic", NULL
),
75 SND_SOC_DAPM_MIC("Digital1 Mic", NULL
),
76 SND_SOC_DAPM_LINE("Line In", NULL
),
79 static const struct snd_soc_dapm_route audio_map
[] = {
80 /* Headset Stereophone: HSOL, HSOR */
81 {"Headset Stereophone", NULL
, "HSOL"},
82 {"Headset Stereophone", NULL
, "HSOR"},
83 /* External Speakers: HFL, HFR */
84 {"Handsfree Spk", NULL
, "HFL"},
85 {"Handsfree Spk", NULL
, "HFR"},
86 /* External Speakers: PredrivL, PredrivR */
87 {"Ext Spk", NULL
, "PREDRIVEL"},
88 {"Ext Spk", NULL
, "PREDRIVER"},
89 /* Carkit speakers: CARKITL, CARKITR */
90 {"Carkit Spk", NULL
, "CARKITL"},
91 {"Carkit Spk", NULL
, "CARKITR"},
93 {"Earpiece Spk", NULL
, "EARPIECE"},
95 /* External Mics: MAINMIC, SUBMIC with bias */
96 {"MAINMIC", NULL
, "Main Mic"},
97 {"Main Mic", NULL
, "Mic Bias 1"},
98 {"SUBMIC", NULL
, "Sub Mic"},
99 {"Sub Mic", NULL
, "Mic Bias 2"},
100 /* Headset Mic: HSMIC with bias */
101 {"HSMIC", NULL
, "Headset Mic"},
102 {"Headset Mic", NULL
, "Headset Mic Bias"},
103 /* Digital Mics: DIGIMIC0, DIGIMIC1 with bias */
104 {"DIGIMIC0", NULL
, "Digital0 Mic"},
105 {"Digital0 Mic", NULL
, "Mic Bias 1"},
106 {"DIGIMIC1", NULL
, "Digital1 Mic"},
107 {"Digital1 Mic", NULL
, "Mic Bias 2"},
108 /* Carkit In: CARKITMIC */
109 {"CARKITMIC", NULL
, "Carkit Mic"},
110 /* Aux In: AUXL, AUXR */
111 {"AUXL", NULL
, "Line In"},
112 {"AUXR", NULL
, "Line In"},
115 /* Headset jack detection DAPM pins */
116 static struct snd_soc_jack_pin hs_jack_pins
[] = {
118 .pin
= "Headset Mic",
119 .mask
= SND_JACK_MICROPHONE
,
122 .pin
= "Headset Stereophone",
123 .mask
= SND_JACK_HEADPHONE
,
127 /* Headset jack detection gpios */
128 static struct snd_soc_jack_gpio hs_jack_gpios
[] = {
130 .name
= "ti,jack-det",
131 .report
= SND_JACK_HEADSET
,
132 .debounce_time
= 200,
136 static inline void twl4030_disconnect_pin(struct snd_soc_dapm_context
*dapm
,
137 int connected
, char *pin
)
140 snd_soc_dapm_disable_pin(dapm
, pin
);
143 static int omap_twl4030_init(struct snd_soc_pcm_runtime
*rtd
)
145 struct snd_soc_card
*card
= rtd
->card
;
146 struct snd_soc_dapm_context
*dapm
= &card
->dapm
;
147 struct omap_tw4030_pdata
*pdata
= dev_get_platdata(card
->dev
);
148 struct omap_twl4030
*priv
= snd_soc_card_get_drvdata(card
);
152 * This is a bit of a hack, but the GPIO is optional so we
153 * only want to add the jack detection if the GPIO is there.
155 if (of_property_present(card
->dev
->of_node
, "ti,jack-det-gpio")) {
156 hs_jack_gpios
[0].gpiod_dev
= card
->dev
;
157 hs_jack_gpios
[0].idx
= 0;
159 ret
= snd_soc_card_jack_new_pins(rtd
->card
, "Headset Jack",
161 &priv
->hs_jack
, hs_jack_pins
,
162 ARRAY_SIZE(hs_jack_pins
));
166 ret
= snd_soc_jack_add_gpios(&priv
->hs_jack
,
167 ARRAY_SIZE(hs_jack_gpios
),
174 * NULL pdata means we booted with DT. In this case the routing is
175 * provided and the card is fully routed, no need to mark pins.
177 if (!pdata
|| !pdata
->custom_routing
)
180 /* Disable not connected paths if not used */
181 twl4030_disconnect_pin(dapm
, pdata
->has_ear
, "Earpiece Spk");
182 twl4030_disconnect_pin(dapm
, pdata
->has_hf
, "Handsfree Spk");
183 twl4030_disconnect_pin(dapm
, pdata
->has_hs
, "Headset Stereophone");
184 twl4030_disconnect_pin(dapm
, pdata
->has_predriv
, "Ext Spk");
185 twl4030_disconnect_pin(dapm
, pdata
->has_carkit
, "Carkit Spk");
187 twl4030_disconnect_pin(dapm
, pdata
->has_mainmic
, "Main Mic");
188 twl4030_disconnect_pin(dapm
, pdata
->has_submic
, "Sub Mic");
189 twl4030_disconnect_pin(dapm
, pdata
->has_hsmic
, "Headset Mic");
190 twl4030_disconnect_pin(dapm
, pdata
->has_carkitmic
, "Carkit Mic");
191 twl4030_disconnect_pin(dapm
, pdata
->has_digimic0
, "Digital0 Mic");
192 twl4030_disconnect_pin(dapm
, pdata
->has_digimic1
, "Digital1 Mic");
193 twl4030_disconnect_pin(dapm
, pdata
->has_linein
, "Line In");
198 /* Digital audio interface glue - connects codec <--> CPU */
199 SND_SOC_DAILINK_DEFS(hifi
,
200 DAILINK_COMP_ARRAY(COMP_CPU("omap-mcbsp.2")),
201 DAILINK_COMP_ARRAY(COMP_CODEC("twl4030-codec", "twl4030-hifi")),
202 DAILINK_COMP_ARRAY(COMP_PLATFORM("omap-mcbsp.2")));
204 SND_SOC_DAILINK_DEFS(voice
,
205 DAILINK_COMP_ARRAY(COMP_CPU("omap-mcbsp.3")),
206 DAILINK_COMP_ARRAY(COMP_CODEC("twl4030-codec", "twl4030-voice")),
207 DAILINK_COMP_ARRAY(COMP_PLATFORM("omap-mcbsp.3")));
209 static struct snd_soc_dai_link omap_twl4030_dai_links
[] = {
211 .name
= "TWL4030 HiFi",
212 .stream_name
= "TWL4030 HiFi",
213 .init
= omap_twl4030_init
,
214 .ops
= &omap_twl4030_ops
,
215 SND_SOC_DAILINK_REG(hifi
),
218 .name
= "TWL4030 Voice",
219 .stream_name
= "TWL4030 Voice",
220 .dai_fmt
= SND_SOC_DAIFMT_DSP_A
| SND_SOC_DAIFMT_IB_NF
|
221 SND_SOC_DAIFMT_CBM_CFM
,
222 SND_SOC_DAILINK_REG(voice
),
226 /* Audio machine driver */
227 static struct snd_soc_card omap_twl4030_card
= {
228 .owner
= THIS_MODULE
,
229 .dai_link
= omap_twl4030_dai_links
,
230 .num_links
= ARRAY_SIZE(omap_twl4030_dai_links
),
232 .dapm_widgets
= dapm_widgets
,
233 .num_dapm_widgets
= ARRAY_SIZE(dapm_widgets
),
234 .dapm_routes
= audio_map
,
235 .num_dapm_routes
= ARRAY_SIZE(audio_map
),
238 static int omap_twl4030_probe(struct platform_device
*pdev
)
240 struct omap_tw4030_pdata
*pdata
= dev_get_platdata(&pdev
->dev
);
241 struct device_node
*node
= pdev
->dev
.of_node
;
242 struct snd_soc_card
*card
= &omap_twl4030_card
;
243 struct omap_twl4030
*priv
;
246 card
->dev
= &pdev
->dev
;
248 priv
= devm_kzalloc(&pdev
->dev
, sizeof(struct omap_twl4030
), GFP_KERNEL
);
253 struct device_node
*dai_node
;
254 struct property
*prop
;
256 if (snd_soc_of_parse_card_name(card
, "ti,model")) {
257 dev_err(&pdev
->dev
, "Card name is not provided\n");
261 dai_node
= of_parse_phandle(node
, "ti,mcbsp", 0);
263 dev_err(&pdev
->dev
, "McBSP node is not provided\n");
266 omap_twl4030_dai_links
[0].cpus
->dai_name
= NULL
;
267 omap_twl4030_dai_links
[0].cpus
->of_node
= dai_node
;
269 omap_twl4030_dai_links
[0].platforms
->name
= NULL
;
270 omap_twl4030_dai_links
[0].platforms
->of_node
= dai_node
;
272 dai_node
= of_parse_phandle(node
, "ti,mcbsp-voice", 0);
276 omap_twl4030_dai_links
[1].cpus
->dai_name
= NULL
;
277 omap_twl4030_dai_links
[1].cpus
->of_node
= dai_node
;
279 omap_twl4030_dai_links
[1].platforms
->name
= NULL
;
280 omap_twl4030_dai_links
[1].platforms
->of_node
= dai_node
;
283 /* Optional: audio routing can be provided */
284 prop
= of_find_property(node
, "ti,audio-routing", NULL
);
286 ret
= snd_soc_of_parse_audio_routing(card
,
291 card
->fully_routed
= 1;
294 if (pdata
->card_name
) {
295 card
->name
= pdata
->card_name
;
297 dev_err(&pdev
->dev
, "Card name is not provided\n");
301 if (!pdata
->voice_connected
)
304 dev_err(&pdev
->dev
, "Missing pdata\n");
308 snd_soc_card_set_drvdata(card
, priv
);
309 ret
= devm_snd_soc_register_card(&pdev
->dev
, card
);
311 dev_err(&pdev
->dev
, "devm_snd_soc_register_card() failed: %d\n",
319 static const struct of_device_id omap_twl4030_of_match
[] = {
320 {.compatible
= "ti,omap-twl4030", },
323 MODULE_DEVICE_TABLE(of
, omap_twl4030_of_match
);
325 static struct platform_driver omap_twl4030_driver
= {
327 .name
= "omap-twl4030",
328 .pm
= &snd_soc_pm_ops
,
329 .of_match_table
= omap_twl4030_of_match
,
331 .probe
= omap_twl4030_probe
,
334 module_platform_driver(omap_twl4030_driver
);
336 MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
337 MODULE_DESCRIPTION("ALSA SoC for TI SoC based boards with twl4030 codec");
338 MODULE_LICENSE("GPL");
339 MODULE_ALIAS("platform:omap-twl4030");