]> git.openfabrics.org - ~shefty/rdma-dev.git/blob - sound/soc/omap/omap-abe-twl6040.c
Merge tag 'asoc-3.7' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound...
[~shefty/rdma-dev.git] / sound / soc / omap / omap-abe-twl6040.c
1 /*
2  * omap-abe-twl6040.c  --  SoC audio for TI OMAP based boards with ABE and
3  *                         twl6040 codec
4  *
5  * Author: Misael Lopez Cruz <misael.lopez@ti.com>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * version 2 as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19  * 02110-1301 USA
20  *
21  */
22
23 #include <linux/clk.h>
24 #include <linux/platform_device.h>
25 #include <linux/mfd/twl6040.h>
26 #include <linux/platform_data/omap-abe-twl6040.h>
27 #include <linux/module.h>
28 #include <linux/of.h>
29
30 #include <sound/core.h>
31 #include <sound/pcm.h>
32 #include <sound/soc.h>
33 #include <sound/jack.h>
34
35 #include "omap-dmic.h"
36 #include "omap-mcpdm.h"
37 #include "omap-pcm.h"
38 #include "../codecs/twl6040.h"
39
40 struct abe_twl6040 {
41         int     jack_detection; /* board can detect jack events */
42         int     mclk_freq;      /* MCLK frequency speed for twl6040 */
43
44         struct platform_device *dmic_codec_dev;
45 };
46
47 static int omap_abe_hw_params(struct snd_pcm_substream *substream,
48         struct snd_pcm_hw_params *params)
49 {
50         struct snd_soc_pcm_runtime *rtd = substream->private_data;
51         struct snd_soc_dai *codec_dai = rtd->codec_dai;
52         struct snd_soc_codec *codec = rtd->codec;
53         struct snd_soc_card *card = codec->card;
54         struct abe_twl6040 *priv = snd_soc_card_get_drvdata(card);
55         int clk_id, freq;
56         int ret;
57
58         clk_id = twl6040_get_clk_id(rtd->codec);
59         if (clk_id == TWL6040_SYSCLK_SEL_HPPLL)
60                 freq = priv->mclk_freq;
61         else if (clk_id == TWL6040_SYSCLK_SEL_LPPLL)
62                 freq = 32768;
63         else
64                 return -EINVAL;
65
66         /* set the codec mclk */
67         ret = snd_soc_dai_set_sysclk(codec_dai, clk_id, freq,
68                                 SND_SOC_CLOCK_IN);
69         if (ret) {
70                 printk(KERN_ERR "can't set codec system clock\n");
71                 return ret;
72         }
73         return ret;
74 }
75
76 static struct snd_soc_ops omap_abe_ops = {
77         .hw_params = omap_abe_hw_params,
78 };
79
80 static int omap_abe_dmic_hw_params(struct snd_pcm_substream *substream,
81         struct snd_pcm_hw_params *params)
82 {
83         struct snd_soc_pcm_runtime *rtd = substream->private_data;
84         struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
85         int ret = 0;
86
87         ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_DMIC_SYSCLK_PAD_CLKS,
88                                      19200000, SND_SOC_CLOCK_IN);
89         if (ret < 0) {
90                 printk(KERN_ERR "can't set DMIC cpu system clock\n");
91                 return ret;
92         }
93         ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_DMIC_ABE_DMIC_CLK, 2400000,
94                                      SND_SOC_CLOCK_OUT);
95         if (ret < 0) {
96                 printk(KERN_ERR "can't set DMIC output clock\n");
97                 return ret;
98         }
99         return 0;
100 }
101
102 static struct snd_soc_ops omap_abe_dmic_ops = {
103         .hw_params = omap_abe_dmic_hw_params,
104 };
105
106 /* Headset jack */
107 static struct snd_soc_jack hs_jack;
108
109 /*Headset jack detection DAPM pins */
110 static struct snd_soc_jack_pin hs_jack_pins[] = {
111         {
112                 .pin = "Headset Mic",
113                 .mask = SND_JACK_MICROPHONE,
114         },
115         {
116                 .pin = "Headset Stereophone",
117                 .mask = SND_JACK_HEADPHONE,
118         },
119 };
120
121 /* SDP4430 machine DAPM */
122 static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = {
123         /* Outputs */
124         SND_SOC_DAPM_HP("Headset Stereophone", NULL),
125         SND_SOC_DAPM_SPK("Earphone Spk", NULL),
126         SND_SOC_DAPM_SPK("Ext Spk", NULL),
127         SND_SOC_DAPM_LINE("Line Out", NULL),
128         SND_SOC_DAPM_SPK("Vibrator", NULL),
129
130         /* Inputs */
131         SND_SOC_DAPM_MIC("Headset Mic", NULL),
132         SND_SOC_DAPM_MIC("Main Handset Mic", NULL),
133         SND_SOC_DAPM_MIC("Sub Handset Mic", NULL),
134         SND_SOC_DAPM_LINE("Line In", NULL),
135
136         /* Digital microphones */
137         SND_SOC_DAPM_MIC("Digital Mic", NULL),
138 };
139
140 static const struct snd_soc_dapm_route audio_map[] = {
141         /* Routings for outputs */
142         {"Headset Stereophone", NULL, "HSOL"},
143         {"Headset Stereophone", NULL, "HSOR"},
144
145         {"Earphone Spk", NULL, "EP"},
146
147         {"Ext Spk", NULL, "HFL"},
148         {"Ext Spk", NULL, "HFR"},
149
150         {"Line Out", NULL, "AUXL"},
151         {"Line Out", NULL, "AUXR"},
152
153         {"Vibrator", NULL, "VIBRAL"},
154         {"Vibrator", NULL, "VIBRAR"},
155
156         /* Routings for inputs */
157         {"HSMIC", NULL, "Headset Mic"},
158         {"Headset Mic", NULL, "Headset Mic Bias"},
159
160         {"MAINMIC", NULL, "Main Handset Mic"},
161         {"Main Handset Mic", NULL, "Main Mic Bias"},
162
163         {"SUBMIC", NULL, "Sub Handset Mic"},
164         {"Sub Handset Mic", NULL, "Main Mic Bias"},
165
166         {"AFML", NULL, "Line In"},
167         {"AFMR", NULL, "Line In"},
168 };
169
170 static inline void twl6040_disconnect_pin(struct snd_soc_dapm_context *dapm,
171                                           int connected, char *pin)
172 {
173         if (!connected)
174                 snd_soc_dapm_disable_pin(dapm, pin);
175 }
176
177 static int omap_abe_twl6040_init(struct snd_soc_pcm_runtime *rtd)
178 {
179         struct snd_soc_codec *codec = rtd->codec;
180         struct snd_soc_card *card = codec->card;
181         struct snd_soc_dapm_context *dapm = &codec->dapm;
182         struct omap_abe_twl6040_data *pdata = dev_get_platdata(card->dev);
183         struct abe_twl6040 *priv = snd_soc_card_get_drvdata(card);
184         int hs_trim;
185         int ret = 0;
186
187         /*
188          * Configure McPDM offset cancellation based on the HSOTRIM value from
189          * twl6040.
190          */
191         hs_trim = twl6040_get_trim_value(codec, TWL6040_TRIM_HSOTRIM);
192         omap_mcpdm_configure_dn_offsets(rtd, TWL6040_HSF_TRIM_LEFT(hs_trim),
193                                         TWL6040_HSF_TRIM_RIGHT(hs_trim));
194
195         /* Headset jack detection only if it is supported */
196         if (priv->jack_detection) {
197                 ret = snd_soc_jack_new(codec, "Headset Jack",
198                                         SND_JACK_HEADSET, &hs_jack);
199                 if (ret)
200                         return ret;
201
202                 ret = snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins),
203                                         hs_jack_pins);
204                 twl6040_hs_jack_detect(codec, &hs_jack, SND_JACK_HEADSET);
205         }
206
207         /*
208          * NULL pdata means we booted with DT. In this case the routing is
209          * provided and the card is fully routed, no need to mark pins.
210          */
211         if (!pdata)
212                 return ret;
213
214         /* Disable not connected paths if not used */
215         twl6040_disconnect_pin(dapm, pdata->has_hs, "Headset Stereophone");
216         twl6040_disconnect_pin(dapm, pdata->has_hf, "Ext Spk");
217         twl6040_disconnect_pin(dapm, pdata->has_ep, "Earphone Spk");
218         twl6040_disconnect_pin(dapm, pdata->has_aux, "Line Out");
219         twl6040_disconnect_pin(dapm, pdata->has_vibra, "Vibrator");
220         twl6040_disconnect_pin(dapm, pdata->has_hsmic, "Headset Mic");
221         twl6040_disconnect_pin(dapm, pdata->has_mainmic, "Main Handset Mic");
222         twl6040_disconnect_pin(dapm, pdata->has_submic, "Sub Handset Mic");
223         twl6040_disconnect_pin(dapm, pdata->has_afm, "Line In");
224
225         return ret;
226 }
227
228 static const struct snd_soc_dapm_route dmic_audio_map[] = {
229         {"DMic", NULL, "Digital Mic"},
230         {"Digital Mic", NULL, "Digital Mic1 Bias"},
231 };
232
233 static int omap_abe_dmic_init(struct snd_soc_pcm_runtime *rtd)
234 {
235         struct snd_soc_codec *codec = rtd->codec;
236         struct snd_soc_dapm_context *dapm = &codec->dapm;
237
238         return snd_soc_dapm_add_routes(dapm, dmic_audio_map,
239                                 ARRAY_SIZE(dmic_audio_map));
240 }
241
242 /* Digital audio interface glue - connects codec <--> CPU */
243 static struct snd_soc_dai_link abe_twl6040_dai_links[] = {
244         {
245                 .name = "TWL6040",
246                 .stream_name = "TWL6040",
247                 .cpu_dai_name = "omap-mcpdm",
248                 .codec_dai_name = "twl6040-legacy",
249                 .platform_name = "omap-pcm-audio",
250                 .codec_name = "twl6040-codec",
251                 .init = omap_abe_twl6040_init,
252                 .ops = &omap_abe_ops,
253         },
254         {
255                 .name = "DMIC",
256                 .stream_name = "DMIC Capture",
257                 .cpu_dai_name = "omap-dmic",
258                 .codec_dai_name = "dmic-hifi",
259                 .platform_name = "omap-pcm-audio",
260                 .codec_name = "dmic-codec",
261                 .init = omap_abe_dmic_init,
262                 .ops = &omap_abe_dmic_ops,
263         },
264 };
265
266 /* Audio machine driver */
267 static struct snd_soc_card omap_abe_card = {
268         .owner = THIS_MODULE,
269
270         .dapm_widgets = twl6040_dapm_widgets,
271         .num_dapm_widgets = ARRAY_SIZE(twl6040_dapm_widgets),
272         .dapm_routes = audio_map,
273         .num_dapm_routes = ARRAY_SIZE(audio_map),
274 };
275
276 static __devinit int omap_abe_probe(struct platform_device *pdev)
277 {
278         struct omap_abe_twl6040_data *pdata = dev_get_platdata(&pdev->dev);
279         struct device_node *node = pdev->dev.of_node;
280         struct snd_soc_card *card = &omap_abe_card;
281         struct abe_twl6040 *priv;
282         int num_links = 0;
283         int ret = 0;
284
285         card->dev = &pdev->dev;
286
287         priv = devm_kzalloc(&pdev->dev, sizeof(struct abe_twl6040), GFP_KERNEL);
288         if (priv == NULL)
289                 return -ENOMEM;
290
291         priv->dmic_codec_dev = ERR_PTR(-EINVAL);
292
293         if (node) {
294                 struct device_node *dai_node;
295
296                 if (snd_soc_of_parse_card_name(card, "ti,model")) {
297                         dev_err(&pdev->dev, "Card name is not provided\n");
298                         return -ENODEV;
299                 }
300
301                 ret = snd_soc_of_parse_audio_routing(card,
302                                                 "ti,audio-routing");
303                 if (ret) {
304                         dev_err(&pdev->dev,
305                                 "Error while parsing DAPM routing\n");
306                         return ret;
307                 }
308
309                 dai_node = of_parse_phandle(node, "ti,mcpdm", 0);
310                 if (!dai_node) {
311                         dev_err(&pdev->dev, "McPDM node is not provided\n");
312                         return -EINVAL;
313                 }
314                 abe_twl6040_dai_links[0].cpu_dai_name  = NULL;
315                 abe_twl6040_dai_links[0].cpu_of_node = dai_node;
316
317                 dai_node = of_parse_phandle(node, "ti,dmic", 0);
318                 if (dai_node) {
319                         num_links = 2;
320                         abe_twl6040_dai_links[1].cpu_dai_name  = NULL;
321                         abe_twl6040_dai_links[1].cpu_of_node = dai_node;
322
323                         priv->dmic_codec_dev = platform_device_register_simple(
324                                                 "dmic-codec", -1, NULL, 0);
325                         if (IS_ERR(priv->dmic_codec_dev)) {
326                                 dev_err(&pdev->dev,
327                                         "Can't instantiate dmic-codec\n");
328                                 return PTR_ERR(priv->dmic_codec_dev);
329                         }
330                 } else {
331                         num_links = 1;
332                 }
333
334                 of_property_read_u32(node, "ti,jack-detection",
335                                      &priv->jack_detection);
336                 of_property_read_u32(node, "ti,mclk-freq",
337                                      &priv->mclk_freq);
338                 if (!priv->mclk_freq) {
339                         dev_err(&pdev->dev, "MCLK frequency not provided\n");
340                         ret = -EINVAL;
341                         goto err_unregister;
342                 }
343
344                 omap_abe_card.fully_routed = 1;
345         } else if (pdata) {
346                 if (pdata->card_name) {
347                         card->name = pdata->card_name;
348                 } else {
349                         dev_err(&pdev->dev, "Card name is not provided\n");
350                         return -ENODEV;
351                 }
352
353                 if (pdata->has_dmic)
354                         num_links = 2;
355                 else
356                         num_links = 1;
357
358                 priv->jack_detection = pdata->jack_detection;
359                 priv->mclk_freq = pdata->mclk_freq;
360         } else {
361                 dev_err(&pdev->dev, "Missing pdata\n");
362                 return -ENODEV;
363         }
364
365
366         if (!priv->mclk_freq) {
367                 dev_err(&pdev->dev, "MCLK frequency missing\n");
368                 ret = -ENODEV;
369                 goto err_unregister;
370         }
371
372         card->dai_link = abe_twl6040_dai_links;
373         card->num_links = num_links;
374
375         snd_soc_card_set_drvdata(card, priv);
376
377         ret = snd_soc_register_card(card);
378         if (ret) {
379                 dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
380                         ret);
381                 goto err_unregister;
382         }
383
384         return 0;
385
386 err_unregister:
387         if (!IS_ERR(priv->dmic_codec_dev))
388                 platform_device_unregister(priv->dmic_codec_dev);
389
390         return ret;
391 }
392
393 static int __devexit omap_abe_remove(struct platform_device *pdev)
394 {
395         struct snd_soc_card *card = platform_get_drvdata(pdev);
396         struct abe_twl6040 *priv = snd_soc_card_get_drvdata(card);
397
398         snd_soc_unregister_card(card);
399
400         if (!IS_ERR(priv->dmic_codec_dev))
401                 platform_device_unregister(priv->dmic_codec_dev);
402
403         return 0;
404 }
405
406 static const struct of_device_id omap_abe_of_match[] = {
407         {.compatible = "ti,abe-twl6040", },
408         { },
409 };
410 MODULE_DEVICE_TABLE(of, omap_abe_of_match);
411
412 static struct platform_driver omap_abe_driver = {
413         .driver = {
414                 .name = "omap-abe-twl6040",
415                 .owner = THIS_MODULE,
416                 .pm = &snd_soc_pm_ops,
417                 .of_match_table = omap_abe_of_match,
418         },
419         .probe = omap_abe_probe,
420         .remove = __devexit_p(omap_abe_remove),
421 };
422
423 module_platform_driver(omap_abe_driver);
424
425 MODULE_AUTHOR("Misael Lopez Cruz <misael.lopez@ti.com>");
426 MODULE_DESCRIPTION("ALSA SoC for OMAP boards with ABE and twl6040 codec");
427 MODULE_LICENSE("GPL");
428 MODULE_ALIAS("platform:omap-abe-twl6040");