diff options
Diffstat (limited to 'sound/soc/omap/omap-twl4030.c')
-rw-r--r-- | sound/soc/omap/omap-twl4030.c | 204 |
1 files changed, 202 insertions, 2 deletions
diff --git a/sound/soc/omap/omap-twl4030.c b/sound/soc/omap/omap-twl4030.c index 4541d28b5314..fd98509d0f49 100644 --- a/sound/soc/omap/omap-twl4030.c +++ b/sound/soc/omap/omap-twl4030.c | |||
@@ -11,6 +11,8 @@ | |||
11 | * omap3evm (Author: Anuj Aggarwal <anuj.aggarwal@ti.com>) | 11 | * omap3evm (Author: Anuj Aggarwal <anuj.aggarwal@ti.com>) |
12 | * overo (Author: Steve Sakoman <steve@sakoman.com>) | 12 | * overo (Author: Steve Sakoman <steve@sakoman.com>) |
13 | * igep0020 (Author: Enric Balletbo i Serra <eballetbo@iseebcn.com>) | 13 | * igep0020 (Author: Enric Balletbo i Serra <eballetbo@iseebcn.com>) |
14 | * zoom2 (Author: Misael Lopez Cruz <misael.lopez@ti.com>) | ||
15 | * sdp3430 (Author: Misael Lopez Cruz <misael.lopez@ti.com>) | ||
14 | * | 16 | * |
15 | * This program is free software; you can redistribute it and/or | 17 | * This program is free software; you can redistribute it and/or |
16 | * modify it under the terms of the GNU General Public License | 18 | * modify it under the terms of the GNU General Public License |
@@ -32,14 +34,22 @@ | |||
32 | #include <linux/platform_data/omap-twl4030.h> | 34 | #include <linux/platform_data/omap-twl4030.h> |
33 | #include <linux/module.h> | 35 | #include <linux/module.h> |
34 | #include <linux/of.h> | 36 | #include <linux/of.h> |
37 | #include <linux/gpio.h> | ||
38 | #include <linux/of_gpio.h> | ||
35 | 39 | ||
36 | #include <sound/core.h> | 40 | #include <sound/core.h> |
37 | #include <sound/pcm.h> | 41 | #include <sound/pcm.h> |
38 | #include <sound/soc.h> | 42 | #include <sound/soc.h> |
43 | #include <sound/jack.h> | ||
39 | 44 | ||
40 | #include "omap-mcbsp.h" | 45 | #include "omap-mcbsp.h" |
41 | #include "omap-pcm.h" | 46 | #include "omap-pcm.h" |
42 | 47 | ||
48 | struct omap_twl4030 { | ||
49 | int jack_detect; /* board can detect jack events */ | ||
50 | struct snd_soc_jack hs_jack; | ||
51 | }; | ||
52 | |||
43 | static int omap_twl4030_hw_params(struct snd_pcm_substream *substream, | 53 | static int omap_twl4030_hw_params(struct snd_pcm_substream *substream, |
44 | struct snd_pcm_hw_params *params) | 54 | struct snd_pcm_hw_params *params) |
45 | { | 55 | { |
@@ -87,17 +97,164 @@ static struct snd_soc_ops omap_twl4030_ops = { | |||
87 | .hw_params = omap_twl4030_hw_params, | 97 | .hw_params = omap_twl4030_hw_params, |
88 | }; | 98 | }; |
89 | 99 | ||
100 | static const struct snd_soc_dapm_widget dapm_widgets[] = { | ||
101 | SND_SOC_DAPM_SPK("Earpiece Spk", NULL), | ||
102 | SND_SOC_DAPM_SPK("Handsfree Spk", NULL), | ||
103 | SND_SOC_DAPM_HP("Headset Stereophone", NULL), | ||
104 | SND_SOC_DAPM_SPK("Ext Spk", NULL), | ||
105 | SND_SOC_DAPM_SPK("Carkit Spk", NULL), | ||
106 | |||
107 | SND_SOC_DAPM_MIC("Main Mic", NULL), | ||
108 | SND_SOC_DAPM_MIC("Sub Mic", NULL), | ||
109 | SND_SOC_DAPM_MIC("Headset Mic", NULL), | ||
110 | SND_SOC_DAPM_MIC("Carkit Mic", NULL), | ||
111 | SND_SOC_DAPM_MIC("Digital0 Mic", NULL), | ||
112 | SND_SOC_DAPM_MIC("Digital1 Mic", NULL), | ||
113 | SND_SOC_DAPM_LINE("Line In", NULL), | ||
114 | }; | ||
115 | |||
116 | static const struct snd_soc_dapm_route audio_map[] = { | ||
117 | /* Headset Stereophone: HSOL, HSOR */ | ||
118 | {"Headset Stereophone", NULL, "HSOL"}, | ||
119 | {"Headset Stereophone", NULL, "HSOR"}, | ||
120 | /* External Speakers: HFL, HFR */ | ||
121 | {"Handsfree Spk", NULL, "HFL"}, | ||
122 | {"Handsfree Spk", NULL, "HFR"}, | ||
123 | /* External Speakers: PredrivL, PredrivR */ | ||
124 | {"Ext Spk", NULL, "PREDRIVEL"}, | ||
125 | {"Ext Spk", NULL, "PREDRIVER"}, | ||
126 | /* Carkit speakers: CARKITL, CARKITR */ | ||
127 | {"Carkit Spk", NULL, "CARKITL"}, | ||
128 | {"Carkit Spk", NULL, "CARKITR"}, | ||
129 | /* Earpiece */ | ||
130 | {"Earpiece Spk", NULL, "EARPIECE"}, | ||
131 | |||
132 | /* External Mics: MAINMIC, SUBMIC with bias */ | ||
133 | {"MAINMIC", NULL, "Main Mic"}, | ||
134 | {"Main Mic", NULL, "Mic Bias 1"}, | ||
135 | {"SUBMIC", NULL, "Sub Mic"}, | ||
136 | {"Sub Mic", NULL, "Mic Bias 2"}, | ||
137 | /* Headset Mic: HSMIC with bias */ | ||
138 | {"HSMIC", NULL, "Headset Mic"}, | ||
139 | {"Headset Mic", NULL, "Headset Mic Bias"}, | ||
140 | /* Digital Mics: DIGIMIC0, DIGIMIC1 with bias */ | ||
141 | {"DIGIMIC0", NULL, "Digital0 Mic"}, | ||
142 | {"Digital0 Mic", NULL, "Mic Bias 1"}, | ||
143 | {"DIGIMIC1", NULL, "Digital1 Mic"}, | ||
144 | {"Digital1 Mic", NULL, "Mic Bias 2"}, | ||
145 | /* Carkit In: CARKITMIC */ | ||
146 | {"CARKITMIC", NULL, "Carkit Mic"}, | ||
147 | /* Aux In: AUXL, AUXR */ | ||
148 | {"AUXL", NULL, "Line In"}, | ||
149 | {"AUXR", NULL, "Line In"}, | ||
150 | }; | ||
151 | |||
152 | /* Headset jack detection DAPM pins */ | ||
153 | static struct snd_soc_jack_pin hs_jack_pins[] = { | ||
154 | { | ||
155 | .pin = "Headset Mic", | ||
156 | .mask = SND_JACK_MICROPHONE, | ||
157 | }, | ||
158 | { | ||
159 | .pin = "Headset Stereophone", | ||
160 | .mask = SND_JACK_HEADPHONE, | ||
161 | }, | ||
162 | }; | ||
163 | |||
164 | /* Headset jack detection gpios */ | ||
165 | static struct snd_soc_jack_gpio hs_jack_gpios[] = { | ||
166 | { | ||
167 | .name = "hsdet-gpio", | ||
168 | .report = SND_JACK_HEADSET, | ||
169 | .debounce_time = 200, | ||
170 | }, | ||
171 | }; | ||
172 | |||
173 | static inline void twl4030_disconnect_pin(struct snd_soc_dapm_context *dapm, | ||
174 | int connected, char *pin) | ||
175 | { | ||
176 | if (!connected) | ||
177 | snd_soc_dapm_disable_pin(dapm, pin); | ||
178 | } | ||
179 | |||
180 | static int omap_twl4030_init(struct snd_soc_pcm_runtime *rtd) | ||
181 | { | ||
182 | struct snd_soc_codec *codec = rtd->codec; | ||
183 | struct snd_soc_card *card = codec->card; | ||
184 | struct snd_soc_dapm_context *dapm = &codec->dapm; | ||
185 | struct omap_tw4030_pdata *pdata = dev_get_platdata(card->dev); | ||
186 | struct omap_twl4030 *priv = snd_soc_card_get_drvdata(card); | ||
187 | int ret = 0; | ||
188 | |||
189 | /* Headset jack detection only if it is supported */ | ||
190 | if (priv->jack_detect > 0) { | ||
191 | hs_jack_gpios[0].gpio = priv->jack_detect; | ||
192 | |||
193 | ret = snd_soc_jack_new(codec, "Headset Jack", SND_JACK_HEADSET, | ||
194 | &priv->hs_jack); | ||
195 | if (ret) | ||
196 | return ret; | ||
197 | |||
198 | ret = snd_soc_jack_add_pins(&priv->hs_jack, | ||
199 | ARRAY_SIZE(hs_jack_pins), | ||
200 | hs_jack_pins); | ||
201 | if (ret) | ||
202 | return ret; | ||
203 | |||
204 | ret = snd_soc_jack_add_gpios(&priv->hs_jack, | ||
205 | ARRAY_SIZE(hs_jack_gpios), | ||
206 | hs_jack_gpios); | ||
207 | if (ret) | ||
208 | return ret; | ||
209 | } | ||
210 | |||
211 | /* | ||
212 | * NULL pdata means we booted with DT. In this case the routing is | ||
213 | * provided and the card is fully routed, no need to mark pins. | ||
214 | */ | ||
215 | if (!pdata || !pdata->custom_routing) | ||
216 | return ret; | ||
217 | |||
218 | /* Disable not connected paths if not used */ | ||
219 | twl4030_disconnect_pin(dapm, pdata->has_ear, "Earpiece Spk"); | ||
220 | twl4030_disconnect_pin(dapm, pdata->has_hf, "Handsfree Spk"); | ||
221 | twl4030_disconnect_pin(dapm, pdata->has_hs, "Headset Stereophone"); | ||
222 | twl4030_disconnect_pin(dapm, pdata->has_predriv, "Ext Spk"); | ||
223 | twl4030_disconnect_pin(dapm, pdata->has_carkit, "Carkit Spk"); | ||
224 | |||
225 | twl4030_disconnect_pin(dapm, pdata->has_mainmic, "Main Mic"); | ||
226 | twl4030_disconnect_pin(dapm, pdata->has_submic, "Sub Mic"); | ||
227 | twl4030_disconnect_pin(dapm, pdata->has_hsmic, "Headset Mic"); | ||
228 | twl4030_disconnect_pin(dapm, pdata->has_carkitmic, "Carkit Mic"); | ||
229 | twl4030_disconnect_pin(dapm, pdata->has_digimic0, "Digital0 Mic"); | ||
230 | twl4030_disconnect_pin(dapm, pdata->has_digimic1, "Digital1 Mic"); | ||
231 | twl4030_disconnect_pin(dapm, pdata->has_linein, "Line In"); | ||
232 | |||
233 | return ret; | ||
234 | } | ||
235 | |||
90 | /* Digital audio interface glue - connects codec <--> CPU */ | 236 | /* Digital audio interface glue - connects codec <--> CPU */ |
91 | static struct snd_soc_dai_link omap_twl4030_dai_links[] = { | 237 | static struct snd_soc_dai_link omap_twl4030_dai_links[] = { |
92 | { | 238 | { |
93 | .name = "TWL4030", | 239 | .name = "TWL4030 HiFi", |
94 | .stream_name = "TWL4030", | 240 | .stream_name = "TWL4030 HiFi", |
95 | .cpu_dai_name = "omap-mcbsp.2", | 241 | .cpu_dai_name = "omap-mcbsp.2", |
96 | .codec_dai_name = "twl4030-hifi", | 242 | .codec_dai_name = "twl4030-hifi", |
97 | .platform_name = "omap-pcm-audio", | 243 | .platform_name = "omap-pcm-audio", |
98 | .codec_name = "twl4030-codec", | 244 | .codec_name = "twl4030-codec", |
245 | .init = omap_twl4030_init, | ||
99 | .ops = &omap_twl4030_ops, | 246 | .ops = &omap_twl4030_ops, |
100 | }, | 247 | }, |
248 | { | ||
249 | .name = "TWL4030 Voice", | ||
250 | .stream_name = "TWL4030 Voice", | ||
251 | .cpu_dai_name = "omap-mcbsp.3", | ||
252 | .codec_dai_name = "twl4030-voice", | ||
253 | .platform_name = "omap-pcm-audio", | ||
254 | .codec_name = "twl4030-codec", | ||
255 | .dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF | | ||
256 | SND_SOC_DAIFMT_CBM_CFM, | ||
257 | }, | ||
101 | }; | 258 | }; |
102 | 259 | ||
103 | /* Audio machine driver */ | 260 | /* Audio machine driver */ |
@@ -105,6 +262,11 @@ static struct snd_soc_card omap_twl4030_card = { | |||
105 | .owner = THIS_MODULE, | 262 | .owner = THIS_MODULE, |
106 | .dai_link = omap_twl4030_dai_links, | 263 | .dai_link = omap_twl4030_dai_links, |
107 | .num_links = ARRAY_SIZE(omap_twl4030_dai_links), | 264 | .num_links = ARRAY_SIZE(omap_twl4030_dai_links), |
265 | |||
266 | .dapm_widgets = dapm_widgets, | ||
267 | .num_dapm_widgets = ARRAY_SIZE(dapm_widgets), | ||
268 | .dapm_routes = audio_map, | ||
269 | .num_dapm_routes = ARRAY_SIZE(audio_map), | ||
108 | }; | 270 | }; |
109 | 271 | ||
110 | static int omap_twl4030_probe(struct platform_device *pdev) | 272 | static int omap_twl4030_probe(struct platform_device *pdev) |
@@ -112,12 +274,18 @@ static int omap_twl4030_probe(struct platform_device *pdev) | |||
112 | struct omap_tw4030_pdata *pdata = dev_get_platdata(&pdev->dev); | 274 | struct omap_tw4030_pdata *pdata = dev_get_platdata(&pdev->dev); |
113 | struct device_node *node = pdev->dev.of_node; | 275 | struct device_node *node = pdev->dev.of_node; |
114 | struct snd_soc_card *card = &omap_twl4030_card; | 276 | struct snd_soc_card *card = &omap_twl4030_card; |
277 | struct omap_twl4030 *priv; | ||
115 | int ret = 0; | 278 | int ret = 0; |
116 | 279 | ||
117 | card->dev = &pdev->dev; | 280 | card->dev = &pdev->dev; |
118 | 281 | ||
282 | priv = devm_kzalloc(&pdev->dev, sizeof(struct omap_twl4030), GFP_KERNEL); | ||
283 | if (priv == NULL) | ||
284 | return -ENOMEM; | ||
285 | |||
119 | if (node) { | 286 | if (node) { |
120 | struct device_node *dai_node; | 287 | struct device_node *dai_node; |
288 | struct property *prop; | ||
121 | 289 | ||
122 | if (snd_soc_of_parse_card_name(card, "ti,model")) { | 290 | if (snd_soc_of_parse_card_name(card, "ti,model")) { |
123 | dev_err(&pdev->dev, "Card name is not provided\n"); | 291 | dev_err(&pdev->dev, "Card name is not provided\n"); |
@@ -132,6 +300,27 @@ static int omap_twl4030_probe(struct platform_device *pdev) | |||
132 | omap_twl4030_dai_links[0].cpu_dai_name = NULL; | 300 | omap_twl4030_dai_links[0].cpu_dai_name = NULL; |
133 | omap_twl4030_dai_links[0].cpu_of_node = dai_node; | 301 | omap_twl4030_dai_links[0].cpu_of_node = dai_node; |
134 | 302 | ||
303 | dai_node = of_parse_phandle(node, "ti,mcbsp-voice", 0); | ||
304 | if (!dai_node) { | ||
305 | card->num_links = 1; | ||
306 | } else { | ||
307 | omap_twl4030_dai_links[1].cpu_dai_name = NULL; | ||
308 | omap_twl4030_dai_links[1].cpu_of_node = dai_node; | ||
309 | } | ||
310 | |||
311 | priv->jack_detect = of_get_named_gpio(node, | ||
312 | "ti,jack-det-gpio", 0); | ||
313 | |||
314 | /* Optional: audio routing can be provided */ | ||
315 | prop = of_find_property(node, "ti,audio-routing", NULL); | ||
316 | if (prop) { | ||
317 | ret = snd_soc_of_parse_audio_routing(card, | ||
318 | "ti,audio-routing"); | ||
319 | if (ret) | ||
320 | return ret; | ||
321 | |||
322 | card->fully_routed = 1; | ||
323 | } | ||
135 | } else if (pdata) { | 324 | } else if (pdata) { |
136 | if (pdata->card_name) { | 325 | if (pdata->card_name) { |
137 | card->name = pdata->card_name; | 326 | card->name = pdata->card_name; |
@@ -139,11 +328,17 @@ static int omap_twl4030_probe(struct platform_device *pdev) | |||
139 | dev_err(&pdev->dev, "Card name is not provided\n"); | 328 | dev_err(&pdev->dev, "Card name is not provided\n"); |
140 | return -ENODEV; | 329 | return -ENODEV; |
141 | } | 330 | } |
331 | |||
332 | if (!pdata->voice_connected) | ||
333 | card->num_links = 1; | ||
334 | |||
335 | priv->jack_detect = pdata->jack_detect; | ||
142 | } else { | 336 | } else { |
143 | dev_err(&pdev->dev, "Missing pdata\n"); | 337 | dev_err(&pdev->dev, "Missing pdata\n"); |
144 | return -ENODEV; | 338 | return -ENODEV; |
145 | } | 339 | } |
146 | 340 | ||
341 | snd_soc_card_set_drvdata(card, priv); | ||
147 | ret = snd_soc_register_card(card); | 342 | ret = snd_soc_register_card(card); |
148 | if (ret) { | 343 | if (ret) { |
149 | dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", | 344 | dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", |
@@ -157,7 +352,12 @@ static int omap_twl4030_probe(struct platform_device *pdev) | |||
157 | static int omap_twl4030_remove(struct platform_device *pdev) | 352 | static int omap_twl4030_remove(struct platform_device *pdev) |
158 | { | 353 | { |
159 | struct snd_soc_card *card = platform_get_drvdata(pdev); | 354 | struct snd_soc_card *card = platform_get_drvdata(pdev); |
355 | struct omap_twl4030 *priv = snd_soc_card_get_drvdata(card); | ||
160 | 356 | ||
357 | if (priv->jack_detect > 0) | ||
358 | snd_soc_jack_free_gpios(&priv->hs_jack, | ||
359 | ARRAY_SIZE(hs_jack_gpios), | ||
360 | hs_jack_gpios); | ||
161 | snd_soc_unregister_card(card); | 361 | snd_soc_unregister_card(card); |
162 | 362 | ||
163 | return 0; | 363 | return 0; |