// SPDX-License-Identifier: GPL-2.0-only /* * omap-twl4030.c -- SoC audio for TI SoC based boards with twl4030 codec * * Copyright (C) 2012 Texas Instruments Incorporated - https://www.ti.com * All rights reserved. * * Author: Peter Ujfalusi * * This driver replaces the following machine drivers: * omap3beagle (Author: Steve Sakoman ) * omap3evm (Author: Anuj Aggarwal ) * overo (Author: Steve Sakoman ) * igep0020 (Author: Enric Balletbo i Serra ) * zoom2 (Author: Misael Lopez Cruz ) * sdp3430 (Author: Misael Lopez Cruz ) */ #include #include #include #include #include #include #include #include "omap-mcbsp.h" struct omap_twl4030 { struct snd_soc_jack_gpio hs_jack_gpio; struct snd_soc_jack hs_jack; }; static int omap_twl4030_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); unsigned int fmt; switch (params_channels(params)) { case 2: /* Stereo I2S mode */ fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBP_CFP; break; case 4: /* Four channel TDM mode */ fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_CBP_CFP; break; default: return -EINVAL; } return snd_soc_runtime_set_dai_fmt(rtd, fmt); } static const struct snd_soc_ops omap_twl4030_ops = { .hw_params = omap_twl4030_hw_params, }; static const struct snd_soc_dapm_widget dapm_widgets[] = { SND_SOC_DAPM_SPK("Earpiece Spk", NULL), SND_SOC_DAPM_SPK("Handsfree Spk", NULL), SND_SOC_DAPM_HP("Headset Stereophone", NULL), SND_SOC_DAPM_SPK("Ext Spk", NULL), SND_SOC_DAPM_SPK("Carkit Spk", NULL), SND_SOC_DAPM_MIC("Main Mic", NULL), SND_SOC_DAPM_MIC("Sub Mic", NULL), SND_SOC_DAPM_MIC("Headset Mic", NULL), SND_SOC_DAPM_MIC("Carkit Mic", NULL), SND_SOC_DAPM_MIC("Digital0 Mic", NULL), SND_SOC_DAPM_MIC("Digital1 Mic", NULL), SND_SOC_DAPM_LINE("Line In", NULL), }; static const struct snd_soc_dapm_route audio_map[] = { /* Headset Stereophone: HSOL, HSOR */ {"Headset Stereophone", NULL, "HSOL"}, {"Headset Stereophone", NULL, "HSOR"}, /* External Speakers: HFL, HFR */ {"Handsfree Spk", NULL, "HFL"}, {"Handsfree Spk", NULL, "HFR"}, /* External Speakers: PredrivL, PredrivR */ {"Ext Spk", NULL, "PREDRIVEL"}, {"Ext Spk", NULL, "PREDRIVER"}, /* Carkit speakers: CARKITL, CARKITR */ {"Carkit Spk", NULL, "CARKITL"}, {"Carkit Spk", NULL, "CARKITR"}, /* Earpiece */ {"Earpiece Spk", NULL, "EARPIECE"}, /* External Mics: MAINMIC, SUBMIC with bias */ {"MAINMIC", NULL, "Main Mic"}, {"Main Mic", NULL, "Mic Bias 1"}, {"SUBMIC", NULL, "Sub Mic"}, {"Sub Mic", NULL, "Mic Bias 2"}, /* Headset Mic: HSMIC with bias */ {"HSMIC", NULL, "Headset Mic"}, {"Headset Mic", NULL, "Headset Mic Bias"}, /* Digital Mics: DIGIMIC0, DIGIMIC1 with bias */ {"DIGIMIC0", NULL, "Digital0 Mic"}, {"Digital0 Mic", NULL, "Mic Bias 1"}, {"DIGIMIC1", NULL, "Digital1 Mic"}, {"Digital1 Mic", NULL, "Mic Bias 2"}, /* Carkit In: CARKITMIC */ {"CARKITMIC", NULL, "Carkit Mic"}, /* Aux In: AUXL, AUXR */ {"AUXL", NULL, "Line In"}, {"AUXR", NULL, "Line In"}, }; /* Headset jack detection DAPM pins */ static struct snd_soc_jack_pin hs_jack_pins[] = { { .pin = "Headset Mic", .mask = SND_JACK_MICROPHONE, }, { .pin = "Headset Stereophone", .mask = SND_JACK_HEADPHONE, }, }; static int omap_twl4030_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_card *card = rtd->card; struct omap_twl4030 *priv = snd_soc_card_get_drvdata(card); int ret; /* * This is a bit of a hack, but the GPIO is optional so we * only want to add the jack detection if the GPIO is there. */ if (of_property_present(card->dev->of_node, "ti,jack-det-gpio")) { ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", SND_JACK_HEADSET, &priv->hs_jack, hs_jack_pins, ARRAY_SIZE(hs_jack_pins)); if (ret) return ret; priv->hs_jack_gpio.name = "ti,jack-det"; priv->hs_jack_gpio.report = SND_JACK_HEADSET; priv->hs_jack_gpio.debounce_time = 200; priv->hs_jack_gpio.gpiod_dev = card->dev; priv->hs_jack_gpio.idx = 0; ret = snd_soc_jack_add_gpios(&priv->hs_jack, 1, &priv->hs_jack_gpio); if (ret) return ret; } return 0; } /* Digital audio interface glue - connects codec <--> CPU */ SND_SOC_DAILINK_DEFS(hifi, DAILINK_COMP_ARRAY(COMP_CPU("omap-mcbsp.2")), DAILINK_COMP_ARRAY(COMP_CODEC("twl4030-codec", "twl4030-hifi")), DAILINK_COMP_ARRAY(COMP_PLATFORM("omap-mcbsp.2"))); SND_SOC_DAILINK_DEFS(voice, DAILINK_COMP_ARRAY(COMP_CPU("omap-mcbsp.3")), DAILINK_COMP_ARRAY(COMP_CODEC("twl4030-codec", "twl4030-voice")), DAILINK_COMP_ARRAY(COMP_PLATFORM("omap-mcbsp.3"))); static struct snd_soc_dai_link omap_twl4030_dai_links[] = { { .name = "TWL4030 HiFi", .stream_name = "TWL4030 HiFi", .init = omap_twl4030_init, .ops = &omap_twl4030_ops, SND_SOC_DAILINK_REG(hifi), }, { .name = "TWL4030 Voice", .stream_name = "TWL4030 Voice", .dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_CBP_CFP, SND_SOC_DAILINK_REG(voice), }, }; /* Audio machine driver */ static struct snd_soc_card omap_twl4030_card = { .owner = THIS_MODULE, .dai_link = omap_twl4030_dai_links, .num_links = ARRAY_SIZE(omap_twl4030_dai_links), .dapm_widgets = dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(dapm_widgets), .dapm_routes = audio_map, .num_dapm_routes = ARRAY_SIZE(audio_map), }; static int omap_twl4030_probe(struct platform_device *pdev) { struct snd_soc_card *card = &omap_twl4030_card; struct device_node *node, *dai_node; struct omap_twl4030 *priv; struct property *prop; int ret; node = pdev->dev.of_node; if (!node) return -ENODEV; card->dev = &pdev->dev; priv = devm_kzalloc(&pdev->dev, sizeof(struct omap_twl4030), GFP_KERNEL); if (priv == NULL) return -ENOMEM; ret = snd_soc_of_parse_card_name(card, "ti,model"); if (ret) return ret; if (!card->name) { dev_err(&pdev->dev, "Card name is not provided\n"); return -ENODEV; } dai_node = of_parse_phandle(node, "ti,mcbsp", 0); if (!dai_node) { dev_err(&pdev->dev, "McBSP node is not provided\n"); return -EINVAL; } omap_twl4030_dai_links[0].cpus->dai_name = NULL; omap_twl4030_dai_links[0].cpus->of_node = dai_node; omap_twl4030_dai_links[0].platforms->name = NULL; omap_twl4030_dai_links[0].platforms->of_node = dai_node; dai_node = of_parse_phandle(node, "ti,mcbsp-voice", 0); if (!dai_node) { card->num_links = 1; } else { omap_twl4030_dai_links[1].cpus->dai_name = NULL; omap_twl4030_dai_links[1].cpus->of_node = dai_node; omap_twl4030_dai_links[1].platforms->name = NULL; omap_twl4030_dai_links[1].platforms->of_node = dai_node; } /* Optional: audio routing can be provided */ prop = of_find_property(node, "ti,audio-routing", NULL); if (prop) { ret = snd_soc_of_parse_audio_routing(card, "ti,audio-routing"); if (ret) return ret; card->fully_routed = 1; } snd_soc_card_set_drvdata(card, priv); ret = devm_snd_soc_register_card(&pdev->dev, card); if (ret) return dev_err_probe(&pdev->dev, ret, "devm_snd_soc_register_card() failed\n"); return 0; } static const struct of_device_id omap_twl4030_of_match[] = { {.compatible = "ti,omap-twl4030", }, { }, }; MODULE_DEVICE_TABLE(of, omap_twl4030_of_match); static struct platform_driver omap_twl4030_driver = { .driver = { .name = "omap-twl4030", .pm = &snd_soc_pm_ops, .of_match_table = omap_twl4030_of_match, }, .probe = omap_twl4030_probe, }; module_platform_driver(omap_twl4030_driver); MODULE_AUTHOR("Peter Ujfalusi "); MODULE_DESCRIPTION("ALSA SoC for TI SoC based boards with twl4030 codec"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:omap-twl4030");