Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * omap-twl4030.c  --  SoC audio for TI SoC based boards with twl4030 codec
0004  *
0005  * Copyright (C) 2012 Texas Instruments Incorporated - https://www.ti.com
0006  * All rights reserved.
0007  *
0008  * Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
0009  *
0010  * This driver replaces the following machine drivers:
0011  * omap3beagle (Author: Steve Sakoman <steve@sakoman.com>)
0012  * omap3evm (Author: Anuj Aggarwal <anuj.aggarwal@ti.com>)
0013  * overo (Author: Steve Sakoman <steve@sakoman.com>)
0014  * igep0020 (Author: Enric Balletbo i Serra <eballetbo@iseebcn.com>)
0015  * zoom2 (Author: Misael Lopez Cruz <misael.lopez@ti.com>)
0016  * sdp3430 (Author: Misael Lopez Cruz <misael.lopez@ti.com>)
0017  */
0018 
0019 #include <linux/platform_device.h>
0020 #include <linux/platform_data/omap-twl4030.h>
0021 #include <linux/module.h>
0022 #include <linux/of.h>
0023 #include <linux/gpio.h>
0024 #include <linux/of_gpio.h>
0025 
0026 #include <sound/core.h>
0027 #include <sound/pcm.h>
0028 #include <sound/soc.h>
0029 #include <sound/jack.h>
0030 
0031 #include "omap-mcbsp.h"
0032 
0033 struct omap_twl4030 {
0034     int jack_detect;    /* board can detect jack events */
0035     struct snd_soc_jack hs_jack;
0036 };
0037 
0038 static int omap_twl4030_hw_params(struct snd_pcm_substream *substream,
0039     struct snd_pcm_hw_params *params)
0040 {
0041     struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
0042     unsigned int fmt;
0043 
0044     switch (params_channels(params)) {
0045     case 2: /* Stereo I2S mode */
0046         fmt =   SND_SOC_DAIFMT_I2S |
0047             SND_SOC_DAIFMT_NB_NF |
0048             SND_SOC_DAIFMT_CBM_CFM;
0049         break;
0050     case 4: /* Four channel TDM mode */
0051         fmt =   SND_SOC_DAIFMT_DSP_A |
0052             SND_SOC_DAIFMT_IB_NF |
0053             SND_SOC_DAIFMT_CBM_CFM;
0054         break;
0055     default:
0056         return -EINVAL;
0057     }
0058 
0059     return snd_soc_runtime_set_dai_fmt(rtd, fmt);
0060 }
0061 
0062 static const struct snd_soc_ops omap_twl4030_ops = {
0063     .hw_params = omap_twl4030_hw_params,
0064 };
0065 
0066 static const struct snd_soc_dapm_widget dapm_widgets[] = {
0067     SND_SOC_DAPM_SPK("Earpiece Spk", NULL),
0068     SND_SOC_DAPM_SPK("Handsfree Spk", NULL),
0069     SND_SOC_DAPM_HP("Headset Stereophone", NULL),
0070     SND_SOC_DAPM_SPK("Ext Spk", NULL),
0071     SND_SOC_DAPM_SPK("Carkit Spk", NULL),
0072 
0073     SND_SOC_DAPM_MIC("Main Mic", NULL),
0074     SND_SOC_DAPM_MIC("Sub Mic", NULL),
0075     SND_SOC_DAPM_MIC("Headset Mic", NULL),
0076     SND_SOC_DAPM_MIC("Carkit Mic", NULL),
0077     SND_SOC_DAPM_MIC("Digital0 Mic", NULL),
0078     SND_SOC_DAPM_MIC("Digital1 Mic", NULL),
0079     SND_SOC_DAPM_LINE("Line In", NULL),
0080 };
0081 
0082 static const struct snd_soc_dapm_route audio_map[] = {
0083     /* Headset Stereophone:  HSOL, HSOR */
0084     {"Headset Stereophone", NULL, "HSOL"},
0085     {"Headset Stereophone", NULL, "HSOR"},
0086     /* External Speakers: HFL, HFR */
0087     {"Handsfree Spk", NULL, "HFL"},
0088     {"Handsfree Spk", NULL, "HFR"},
0089     /* External Speakers: PredrivL, PredrivR */
0090     {"Ext Spk", NULL, "PREDRIVEL"},
0091     {"Ext Spk", NULL, "PREDRIVER"},
0092     /* Carkit speakers:  CARKITL, CARKITR */
0093     {"Carkit Spk", NULL, "CARKITL"},
0094     {"Carkit Spk", NULL, "CARKITR"},
0095     /* Earpiece */
0096     {"Earpiece Spk", NULL, "EARPIECE"},
0097 
0098     /* External Mics: MAINMIC, SUBMIC with bias */
0099     {"MAINMIC", NULL, "Main Mic"},
0100     {"Main Mic", NULL, "Mic Bias 1"},
0101     {"SUBMIC", NULL, "Sub Mic"},
0102     {"Sub Mic", NULL, "Mic Bias 2"},
0103     /* Headset Mic: HSMIC with bias */
0104     {"HSMIC", NULL, "Headset Mic"},
0105     {"Headset Mic", NULL, "Headset Mic Bias"},
0106     /* Digital Mics: DIGIMIC0, DIGIMIC1 with bias */
0107     {"DIGIMIC0", NULL, "Digital0 Mic"},
0108     {"Digital0 Mic", NULL, "Mic Bias 1"},
0109     {"DIGIMIC1", NULL, "Digital1 Mic"},
0110     {"Digital1 Mic", NULL, "Mic Bias 2"},
0111     /* Carkit In: CARKITMIC */
0112     {"CARKITMIC", NULL, "Carkit Mic"},
0113     /* Aux In: AUXL, AUXR */
0114     {"AUXL", NULL, "Line In"},
0115     {"AUXR", NULL, "Line In"},
0116 };
0117 
0118 /* Headset jack detection DAPM pins */
0119 static struct snd_soc_jack_pin hs_jack_pins[] = {
0120     {
0121         .pin = "Headset Mic",
0122         .mask = SND_JACK_MICROPHONE,
0123     },
0124     {
0125         .pin = "Headset Stereophone",
0126         .mask = SND_JACK_HEADPHONE,
0127     },
0128 };
0129 
0130 /* Headset jack detection gpios */
0131 static struct snd_soc_jack_gpio hs_jack_gpios[] = {
0132     {
0133         .name = "hsdet-gpio",
0134         .report = SND_JACK_HEADSET,
0135         .debounce_time = 200,
0136     },
0137 };
0138 
0139 static inline void twl4030_disconnect_pin(struct snd_soc_dapm_context *dapm,
0140                       int connected, char *pin)
0141 {
0142     if (!connected)
0143         snd_soc_dapm_disable_pin(dapm, pin);
0144 }
0145 
0146 static int omap_twl4030_init(struct snd_soc_pcm_runtime *rtd)
0147 {
0148     struct snd_soc_card *card = rtd->card;
0149     struct snd_soc_dapm_context *dapm = &card->dapm;
0150     struct omap_tw4030_pdata *pdata = dev_get_platdata(card->dev);
0151     struct omap_twl4030 *priv = snd_soc_card_get_drvdata(card);
0152     int ret = 0;
0153 
0154     /* Headset jack detection only if it is supported */
0155     if (priv->jack_detect > 0) {
0156         hs_jack_gpios[0].gpio = priv->jack_detect;
0157 
0158         ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack",
0159                          SND_JACK_HEADSET,
0160                          &priv->hs_jack, hs_jack_pins,
0161                          ARRAY_SIZE(hs_jack_pins));
0162         if (ret)
0163             return ret;
0164 
0165         ret = snd_soc_jack_add_gpios(&priv->hs_jack,
0166                          ARRAY_SIZE(hs_jack_gpios),
0167                          hs_jack_gpios);
0168         if (ret)
0169             return ret;
0170     }
0171 
0172     /*
0173      * NULL pdata means we booted with DT. In this case the routing is
0174      * provided and the card is fully routed, no need to mark pins.
0175      */
0176     if (!pdata || !pdata->custom_routing)
0177         return ret;
0178 
0179     /* Disable not connected paths if not used */
0180     twl4030_disconnect_pin(dapm, pdata->has_ear, "Earpiece Spk");
0181     twl4030_disconnect_pin(dapm, pdata->has_hf, "Handsfree Spk");
0182     twl4030_disconnect_pin(dapm, pdata->has_hs, "Headset Stereophone");
0183     twl4030_disconnect_pin(dapm, pdata->has_predriv, "Ext Spk");
0184     twl4030_disconnect_pin(dapm, pdata->has_carkit, "Carkit Spk");
0185 
0186     twl4030_disconnect_pin(dapm, pdata->has_mainmic, "Main Mic");
0187     twl4030_disconnect_pin(dapm, pdata->has_submic, "Sub Mic");
0188     twl4030_disconnect_pin(dapm, pdata->has_hsmic, "Headset Mic");
0189     twl4030_disconnect_pin(dapm, pdata->has_carkitmic, "Carkit Mic");
0190     twl4030_disconnect_pin(dapm, pdata->has_digimic0, "Digital0 Mic");
0191     twl4030_disconnect_pin(dapm, pdata->has_digimic1, "Digital1 Mic");
0192     twl4030_disconnect_pin(dapm, pdata->has_linein, "Line In");
0193 
0194     return ret;
0195 }
0196 
0197 /* Digital audio interface glue - connects codec <--> CPU */
0198 SND_SOC_DAILINK_DEFS(hifi,
0199     DAILINK_COMP_ARRAY(COMP_CPU("omap-mcbsp.2")),
0200     DAILINK_COMP_ARRAY(COMP_CODEC("twl4030-codec", "twl4030-hifi")),
0201     DAILINK_COMP_ARRAY(COMP_PLATFORM("omap-mcbsp.2")));
0202 
0203 SND_SOC_DAILINK_DEFS(voice,
0204     DAILINK_COMP_ARRAY(COMP_CPU("omap-mcbsp.3")),
0205     DAILINK_COMP_ARRAY(COMP_CODEC("twl4030-codec", "twl4030-voice")),
0206     DAILINK_COMP_ARRAY(COMP_PLATFORM("omap-mcbsp.3")));
0207 
0208 static struct snd_soc_dai_link omap_twl4030_dai_links[] = {
0209     {
0210         .name = "TWL4030 HiFi",
0211         .stream_name = "TWL4030 HiFi",
0212         .init = omap_twl4030_init,
0213         .ops = &omap_twl4030_ops,
0214         SND_SOC_DAILINK_REG(hifi),
0215     },
0216     {
0217         .name = "TWL4030 Voice",
0218         .stream_name = "TWL4030 Voice",
0219         .dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF |
0220                SND_SOC_DAIFMT_CBM_CFM,
0221         SND_SOC_DAILINK_REG(voice),
0222     },
0223 };
0224 
0225 /* Audio machine driver */
0226 static struct snd_soc_card omap_twl4030_card = {
0227     .owner = THIS_MODULE,
0228     .dai_link = omap_twl4030_dai_links,
0229     .num_links = ARRAY_SIZE(omap_twl4030_dai_links),
0230 
0231     .dapm_widgets = dapm_widgets,
0232     .num_dapm_widgets = ARRAY_SIZE(dapm_widgets),
0233     .dapm_routes = audio_map,
0234     .num_dapm_routes = ARRAY_SIZE(audio_map),
0235 };
0236 
0237 static int omap_twl4030_probe(struct platform_device *pdev)
0238 {
0239     struct omap_tw4030_pdata *pdata = dev_get_platdata(&pdev->dev);
0240     struct device_node *node = pdev->dev.of_node;
0241     struct snd_soc_card *card = &omap_twl4030_card;
0242     struct omap_twl4030 *priv;
0243     int ret = 0;
0244 
0245     card->dev = &pdev->dev;
0246 
0247     priv = devm_kzalloc(&pdev->dev, sizeof(struct omap_twl4030), GFP_KERNEL);
0248     if (priv == NULL)
0249         return -ENOMEM;
0250 
0251     if (node) {
0252         struct device_node *dai_node;
0253         struct property *prop;
0254 
0255         if (snd_soc_of_parse_card_name(card, "ti,model")) {
0256             dev_err(&pdev->dev, "Card name is not provided\n");
0257             return -ENODEV;
0258         }
0259 
0260         dai_node = of_parse_phandle(node, "ti,mcbsp", 0);
0261         if (!dai_node) {
0262             dev_err(&pdev->dev, "McBSP node is not provided\n");
0263             return -EINVAL;
0264         }
0265         omap_twl4030_dai_links[0].cpus->dai_name  = NULL;
0266         omap_twl4030_dai_links[0].cpus->of_node = dai_node;
0267 
0268         omap_twl4030_dai_links[0].platforms->name  = NULL;
0269         omap_twl4030_dai_links[0].platforms->of_node = dai_node;
0270 
0271         dai_node = of_parse_phandle(node, "ti,mcbsp-voice", 0);
0272         if (!dai_node) {
0273             card->num_links = 1;
0274         } else {
0275             omap_twl4030_dai_links[1].cpus->dai_name  = NULL;
0276             omap_twl4030_dai_links[1].cpus->of_node = dai_node;
0277 
0278             omap_twl4030_dai_links[1].platforms->name  = NULL;
0279             omap_twl4030_dai_links[1].platforms->of_node = dai_node;
0280         }
0281 
0282         priv->jack_detect = of_get_named_gpio(node,
0283                               "ti,jack-det-gpio", 0);
0284 
0285         /* Optional: audio routing can be provided */
0286         prop = of_find_property(node, "ti,audio-routing", NULL);
0287         if (prop) {
0288             ret = snd_soc_of_parse_audio_routing(card,
0289                                 "ti,audio-routing");
0290             if (ret)
0291                 return ret;
0292 
0293             card->fully_routed = 1;
0294         }
0295     } else if (pdata) {
0296         if (pdata->card_name) {
0297             card->name = pdata->card_name;
0298         } else {
0299             dev_err(&pdev->dev, "Card name is not provided\n");
0300             return -ENODEV;
0301         }
0302 
0303         if (!pdata->voice_connected)
0304             card->num_links = 1;
0305 
0306         priv->jack_detect = pdata->jack_detect;
0307     } else {
0308         dev_err(&pdev->dev, "Missing pdata\n");
0309         return -ENODEV;
0310     }
0311 
0312     snd_soc_card_set_drvdata(card, priv);
0313     ret = devm_snd_soc_register_card(&pdev->dev, card);
0314     if (ret) {
0315         dev_err(&pdev->dev, "devm_snd_soc_register_card() failed: %d\n",
0316             ret);
0317         return ret;
0318     }
0319 
0320     return 0;
0321 }
0322 
0323 static const struct of_device_id omap_twl4030_of_match[] = {
0324     {.compatible = "ti,omap-twl4030", },
0325     { },
0326 };
0327 MODULE_DEVICE_TABLE(of, omap_twl4030_of_match);
0328 
0329 static struct platform_driver omap_twl4030_driver = {
0330     .driver = {
0331         .name = "omap-twl4030",
0332         .pm = &snd_soc_pm_ops,
0333         .of_match_table = omap_twl4030_of_match,
0334     },
0335     .probe = omap_twl4030_probe,
0336 };
0337 
0338 module_platform_driver(omap_twl4030_driver);
0339 
0340 MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
0341 MODULE_DESCRIPTION("ALSA SoC for TI SoC based boards with twl4030 codec");
0342 MODULE_LICENSE("GPL");
0343 MODULE_ALIAS("platform:omap-twl4030");