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