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