omap-twl4030.c 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. // SPDX-License-Identifier: GPL-2.0-only
  2. /*
  3. * omap-twl4030.c -- SoC audio for TI SoC based boards with twl4030 codec
  4. *
  5. * Copyright (C) 2012 Texas Instruments Incorporated - https://www.ti.com
  6. * All rights reserved.
  7. *
  8. * Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
  9. *
  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>)
  17. */
  18. #include <linux/platform_device.h>
  19. #include <linux/platform_data/omap-twl4030.h>
  20. #include <linux/module.h>
  21. #include <linux/of.h>
  22. #include <linux/gpio.h>
  23. #include <linux/of_gpio.h>
  24. #include <sound/core.h>
  25. #include <sound/pcm.h>
  26. #include <sound/soc.h>
  27. #include <sound/jack.h>
  28. #include "omap-mcbsp.h"
  29. struct omap_twl4030 {
  30. int jack_detect; /* board can detect jack events */
  31. struct snd_soc_jack hs_jack;
  32. };
  33. static int omap_twl4030_hw_params(struct snd_pcm_substream *substream,
  34. struct snd_pcm_hw_params *params)
  35. {
  36. struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
  37. unsigned int fmt;
  38. switch (params_channels(params)) {
  39. case 2: /* Stereo I2S mode */
  40. fmt = SND_SOC_DAIFMT_I2S |
  41. SND_SOC_DAIFMT_NB_NF |
  42. SND_SOC_DAIFMT_CBM_CFM;
  43. break;
  44. case 4: /* Four channel TDM mode */
  45. fmt = SND_SOC_DAIFMT_DSP_A |
  46. SND_SOC_DAIFMT_IB_NF |
  47. SND_SOC_DAIFMT_CBM_CFM;
  48. break;
  49. default:
  50. return -EINVAL;
  51. }
  52. return snd_soc_runtime_set_dai_fmt(rtd, fmt);
  53. }
  54. static const struct snd_soc_ops omap_twl4030_ops = {
  55. .hw_params = omap_twl4030_hw_params,
  56. };
  57. static const struct snd_soc_dapm_widget dapm_widgets[] = {
  58. SND_SOC_DAPM_SPK("Earpiece Spk", NULL),
  59. SND_SOC_DAPM_SPK("Handsfree Spk", NULL),
  60. SND_SOC_DAPM_HP("Headset Stereophone", NULL),
  61. SND_SOC_DAPM_SPK("Ext Spk", NULL),
  62. SND_SOC_DAPM_SPK("Carkit Spk", NULL),
  63. SND_SOC_DAPM_MIC("Main Mic", NULL),
  64. SND_SOC_DAPM_MIC("Sub Mic", NULL),
  65. SND_SOC_DAPM_MIC("Headset Mic", NULL),
  66. SND_SOC_DAPM_MIC("Carkit Mic", NULL),
  67. SND_SOC_DAPM_MIC("Digital0 Mic", NULL),
  68. SND_SOC_DAPM_MIC("Digital1 Mic", NULL),
  69. SND_SOC_DAPM_LINE("Line In", NULL),
  70. };
  71. static const struct snd_soc_dapm_route audio_map[] = {
  72. /* Headset Stereophone: HSOL, HSOR */
  73. {"Headset Stereophone", NULL, "HSOL"},
  74. {"Headset Stereophone", NULL, "HSOR"},
  75. /* External Speakers: HFL, HFR */
  76. {"Handsfree Spk", NULL, "HFL"},
  77. {"Handsfree Spk", NULL, "HFR"},
  78. /* External Speakers: PredrivL, PredrivR */
  79. {"Ext Spk", NULL, "PREDRIVEL"},
  80. {"Ext Spk", NULL, "PREDRIVER"},
  81. /* Carkit speakers: CARKITL, CARKITR */
  82. {"Carkit Spk", NULL, "CARKITL"},
  83. {"Carkit Spk", NULL, "CARKITR"},
  84. /* Earpiece */
  85. {"Earpiece Spk", NULL, "EARPIECE"},
  86. /* External Mics: MAINMIC, SUBMIC with bias */
  87. {"MAINMIC", NULL, "Main Mic"},
  88. {"Main Mic", NULL, "Mic Bias 1"},
  89. {"SUBMIC", NULL, "Sub Mic"},
  90. {"Sub Mic", NULL, "Mic Bias 2"},
  91. /* Headset Mic: HSMIC with bias */
  92. {"HSMIC", NULL, "Headset Mic"},
  93. {"Headset Mic", NULL, "Headset Mic Bias"},
  94. /* Digital Mics: DIGIMIC0, DIGIMIC1 with bias */
  95. {"DIGIMIC0", NULL, "Digital0 Mic"},
  96. {"Digital0 Mic", NULL, "Mic Bias 1"},
  97. {"DIGIMIC1", NULL, "Digital1 Mic"},
  98. {"Digital1 Mic", NULL, "Mic Bias 2"},
  99. /* Carkit In: CARKITMIC */
  100. {"CARKITMIC", NULL, "Carkit Mic"},
  101. /* Aux In: AUXL, AUXR */
  102. {"AUXL", NULL, "Line In"},
  103. {"AUXR", NULL, "Line In"},
  104. };
  105. /* Headset jack detection DAPM pins */
  106. static struct snd_soc_jack_pin hs_jack_pins[] = {
  107. {
  108. .pin = "Headset Mic",
  109. .mask = SND_JACK_MICROPHONE,
  110. },
  111. {
  112. .pin = "Headset Stereophone",
  113. .mask = SND_JACK_HEADPHONE,
  114. },
  115. };
  116. /* Headset jack detection gpios */
  117. static struct snd_soc_jack_gpio hs_jack_gpios[] = {
  118. {
  119. .name = "hsdet-gpio",
  120. .report = SND_JACK_HEADSET,
  121. .debounce_time = 200,
  122. },
  123. };
  124. static inline void twl4030_disconnect_pin(struct snd_soc_dapm_context *dapm,
  125. int connected, char *pin)
  126. {
  127. if (!connected)
  128. snd_soc_dapm_disable_pin(dapm, pin);
  129. }
  130. static int omap_twl4030_init(struct snd_soc_pcm_runtime *rtd)
  131. {
  132. struct snd_soc_card *card = rtd->card;
  133. struct snd_soc_dapm_context *dapm = &card->dapm;
  134. struct omap_tw4030_pdata *pdata = dev_get_platdata(card->dev);
  135. struct omap_twl4030 *priv = snd_soc_card_get_drvdata(card);
  136. int ret = 0;
  137. /* Headset jack detection only if it is supported */
  138. if (priv->jack_detect > 0) {
  139. hs_jack_gpios[0].gpio = priv->jack_detect;
  140. ret = snd_soc_card_jack_new(rtd->card, "Headset Jack",
  141. SND_JACK_HEADSET, &priv->hs_jack,
  142. hs_jack_pins,
  143. ARRAY_SIZE(hs_jack_pins));
  144. if (ret)
  145. return ret;
  146. ret = snd_soc_jack_add_gpios(&priv->hs_jack,
  147. ARRAY_SIZE(hs_jack_gpios),
  148. hs_jack_gpios);
  149. if (ret)
  150. return ret;
  151. }
  152. /*
  153. * NULL pdata means we booted with DT. In this case the routing is
  154. * provided and the card is fully routed, no need to mark pins.
  155. */
  156. if (!pdata || !pdata->custom_routing)
  157. return ret;
  158. /* Disable not connected paths if not used */
  159. twl4030_disconnect_pin(dapm, pdata->has_ear, "Earpiece Spk");
  160. twl4030_disconnect_pin(dapm, pdata->has_hf, "Handsfree Spk");
  161. twl4030_disconnect_pin(dapm, pdata->has_hs, "Headset Stereophone");
  162. twl4030_disconnect_pin(dapm, pdata->has_predriv, "Ext Spk");
  163. twl4030_disconnect_pin(dapm, pdata->has_carkit, "Carkit Spk");
  164. twl4030_disconnect_pin(dapm, pdata->has_mainmic, "Main Mic");
  165. twl4030_disconnect_pin(dapm, pdata->has_submic, "Sub Mic");
  166. twl4030_disconnect_pin(dapm, pdata->has_hsmic, "Headset Mic");
  167. twl4030_disconnect_pin(dapm, pdata->has_carkitmic, "Carkit Mic");
  168. twl4030_disconnect_pin(dapm, pdata->has_digimic0, "Digital0 Mic");
  169. twl4030_disconnect_pin(dapm, pdata->has_digimic1, "Digital1 Mic");
  170. twl4030_disconnect_pin(dapm, pdata->has_linein, "Line In");
  171. return ret;
  172. }
  173. /* Digital audio interface glue - connects codec <--> CPU */
  174. SND_SOC_DAILINK_DEFS(hifi,
  175. DAILINK_COMP_ARRAY(COMP_CPU("omap-mcbsp.2")),
  176. DAILINK_COMP_ARRAY(COMP_CODEC("twl4030-codec", "twl4030-hifi")),
  177. DAILINK_COMP_ARRAY(COMP_PLATFORM("omap-mcbsp.2")));
  178. SND_SOC_DAILINK_DEFS(voice,
  179. DAILINK_COMP_ARRAY(COMP_CPU("omap-mcbsp.3")),
  180. DAILINK_COMP_ARRAY(COMP_CODEC("twl4030-codec", "twl4030-voice")),
  181. DAILINK_COMP_ARRAY(COMP_PLATFORM("omap-mcbsp.3")));
  182. static struct snd_soc_dai_link omap_twl4030_dai_links[] = {
  183. {
  184. .name = "TWL4030 HiFi",
  185. .stream_name = "TWL4030 HiFi",
  186. .init = omap_twl4030_init,
  187. .ops = &omap_twl4030_ops,
  188. SND_SOC_DAILINK_REG(hifi),
  189. },
  190. {
  191. .name = "TWL4030 Voice",
  192. .stream_name = "TWL4030 Voice",
  193. .dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF |
  194. SND_SOC_DAIFMT_CBM_CFM,
  195. SND_SOC_DAILINK_REG(voice),
  196. },
  197. };
  198. /* Audio machine driver */
  199. static struct snd_soc_card omap_twl4030_card = {
  200. .owner = THIS_MODULE,
  201. .dai_link = omap_twl4030_dai_links,
  202. .num_links = ARRAY_SIZE(omap_twl4030_dai_links),
  203. .dapm_widgets = dapm_widgets,
  204. .num_dapm_widgets = ARRAY_SIZE(dapm_widgets),
  205. .dapm_routes = audio_map,
  206. .num_dapm_routes = ARRAY_SIZE(audio_map),
  207. };
  208. static int omap_twl4030_probe(struct platform_device *pdev)
  209. {
  210. struct omap_tw4030_pdata *pdata = dev_get_platdata(&pdev->dev);
  211. struct device_node *node = pdev->dev.of_node;
  212. struct snd_soc_card *card = &omap_twl4030_card;
  213. struct omap_twl4030 *priv;
  214. int ret = 0;
  215. card->dev = &pdev->dev;
  216. priv = devm_kzalloc(&pdev->dev, sizeof(struct omap_twl4030), GFP_KERNEL);
  217. if (priv == NULL)
  218. return -ENOMEM;
  219. if (node) {
  220. struct device_node *dai_node;
  221. struct property *prop;
  222. if (snd_soc_of_parse_card_name(card, "ti,model")) {
  223. dev_err(&pdev->dev, "Card name is not provided\n");
  224. return -ENODEV;
  225. }
  226. dai_node = of_parse_phandle(node, "ti,mcbsp", 0);
  227. if (!dai_node) {
  228. dev_err(&pdev->dev, "McBSP node is not provided\n");
  229. return -EINVAL;
  230. }
  231. omap_twl4030_dai_links[0].cpus->dai_name = NULL;
  232. omap_twl4030_dai_links[0].cpus->of_node = dai_node;
  233. omap_twl4030_dai_links[0].platforms->name = NULL;
  234. omap_twl4030_dai_links[0].platforms->of_node = dai_node;
  235. dai_node = of_parse_phandle(node, "ti,mcbsp-voice", 0);
  236. if (!dai_node) {
  237. card->num_links = 1;
  238. } else {
  239. omap_twl4030_dai_links[1].cpus->dai_name = NULL;
  240. omap_twl4030_dai_links[1].cpus->of_node = dai_node;
  241. omap_twl4030_dai_links[1].platforms->name = NULL;
  242. omap_twl4030_dai_links[1].platforms->of_node = dai_node;
  243. }
  244. priv->jack_detect = of_get_named_gpio(node,
  245. "ti,jack-det-gpio", 0);
  246. /* Optional: audio routing can be provided */
  247. prop = of_find_property(node, "ti,audio-routing", NULL);
  248. if (prop) {
  249. ret = snd_soc_of_parse_audio_routing(card,
  250. "ti,audio-routing");
  251. if (ret)
  252. return ret;
  253. card->fully_routed = 1;
  254. }
  255. } else if (pdata) {
  256. if (pdata->card_name) {
  257. card->name = pdata->card_name;
  258. } else {
  259. dev_err(&pdev->dev, "Card name is not provided\n");
  260. return -ENODEV;
  261. }
  262. if (!pdata->voice_connected)
  263. card->num_links = 1;
  264. priv->jack_detect = pdata->jack_detect;
  265. } else {
  266. dev_err(&pdev->dev, "Missing pdata\n");
  267. return -ENODEV;
  268. }
  269. snd_soc_card_set_drvdata(card, priv);
  270. ret = devm_snd_soc_register_card(&pdev->dev, card);
  271. if (ret) {
  272. dev_err(&pdev->dev, "devm_snd_soc_register_card() failed: %d\n",
  273. ret);
  274. return ret;
  275. }
  276. return 0;
  277. }
  278. static const struct of_device_id omap_twl4030_of_match[] = {
  279. {.compatible = "ti,omap-twl4030", },
  280. { },
  281. };
  282. MODULE_DEVICE_TABLE(of, omap_twl4030_of_match);
  283. static struct platform_driver omap_twl4030_driver = {
  284. .driver = {
  285. .name = "omap-twl4030",
  286. .pm = &snd_soc_pm_ops,
  287. .of_match_table = omap_twl4030_of_match,
  288. },
  289. .probe = omap_twl4030_probe,
  290. };
  291. module_platform_driver(omap_twl4030_driver);
  292. MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
  293. MODULE_DESCRIPTION("ALSA SoC for TI SoC based boards with twl4030 codec");
  294. MODULE_LICENSE("GPL");
  295. MODULE_ALIAS("platform:omap-twl4030");