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