13a280ed1SRyder Lee // SPDX-License-Identifier: GPL-2.0 20d1d7a66SGarlic Tseng /* 30d1d7a66SGarlic Tseng * mt8173-max98090.c -- MT8173 MAX98090 ALSA SoC machine driver 40d1d7a66SGarlic Tseng * 50d1d7a66SGarlic Tseng * Copyright (c) 2015 MediaTek Inc. 60d1d7a66SGarlic Tseng * Author: Koro Chen <koro.chen@mediatek.com> 70d1d7a66SGarlic Tseng */ 80d1d7a66SGarlic Tseng 90d1d7a66SGarlic Tseng #include <linux/module.h> 100d1d7a66SGarlic Tseng #include <sound/soc.h> 110d1d7a66SGarlic Tseng #include <sound/jack.h> 120d1d7a66SGarlic Tseng #include <linux/gpio.h> 130d1d7a66SGarlic Tseng #include "../../codecs/max98090.h" 140d1d7a66SGarlic Tseng 150d1d7a66SGarlic Tseng static struct snd_soc_jack mt8173_max98090_jack; 160d1d7a66SGarlic Tseng 170d1d7a66SGarlic Tseng static struct snd_soc_jack_pin mt8173_max98090_jack_pins[] = { 180d1d7a66SGarlic Tseng { 190d1d7a66SGarlic Tseng .pin = "Headphone", 200d1d7a66SGarlic Tseng .mask = SND_JACK_HEADPHONE, 210d1d7a66SGarlic Tseng }, 220d1d7a66SGarlic Tseng { 230d1d7a66SGarlic Tseng .pin = "Headset Mic", 240d1d7a66SGarlic Tseng .mask = SND_JACK_MICROPHONE, 250d1d7a66SGarlic Tseng }, 260d1d7a66SGarlic Tseng }; 270d1d7a66SGarlic Tseng 280d1d7a66SGarlic Tseng static const struct snd_soc_dapm_widget mt8173_max98090_widgets[] = { 290d1d7a66SGarlic Tseng SND_SOC_DAPM_SPK("Speaker", NULL), 300d1d7a66SGarlic Tseng SND_SOC_DAPM_MIC("Int Mic", NULL), 310d1d7a66SGarlic Tseng SND_SOC_DAPM_HP("Headphone", NULL), 320d1d7a66SGarlic Tseng SND_SOC_DAPM_MIC("Headset Mic", NULL), 330d1d7a66SGarlic Tseng }; 340d1d7a66SGarlic Tseng 350d1d7a66SGarlic Tseng static const struct snd_soc_dapm_route mt8173_max98090_routes[] = { 360d1d7a66SGarlic Tseng {"Speaker", NULL, "SPKL"}, 370d1d7a66SGarlic Tseng {"Speaker", NULL, "SPKR"}, 380d1d7a66SGarlic Tseng {"DMICL", NULL, "Int Mic"}, 390d1d7a66SGarlic Tseng {"Headphone", NULL, "HPL"}, 400d1d7a66SGarlic Tseng {"Headphone", NULL, "HPR"}, 410d1d7a66SGarlic Tseng {"Headset Mic", NULL, "MICBIAS"}, 420d1d7a66SGarlic Tseng {"IN34", NULL, "Headset Mic"}, 430d1d7a66SGarlic Tseng }; 440d1d7a66SGarlic Tseng 450d1d7a66SGarlic Tseng static const struct snd_kcontrol_new mt8173_max98090_controls[] = { 460d1d7a66SGarlic Tseng SOC_DAPM_PIN_SWITCH("Speaker"), 470d1d7a66SGarlic Tseng SOC_DAPM_PIN_SWITCH("Int Mic"), 480d1d7a66SGarlic Tseng SOC_DAPM_PIN_SWITCH("Headphone"), 490d1d7a66SGarlic Tseng SOC_DAPM_PIN_SWITCH("Headset Mic"), 500d1d7a66SGarlic Tseng }; 510d1d7a66SGarlic Tseng 520d1d7a66SGarlic Tseng static int mt8173_max98090_hw_params(struct snd_pcm_substream *substream, 530d1d7a66SGarlic Tseng struct snd_pcm_hw_params *params) 540d1d7a66SGarlic Tseng { 550d1d7a66SGarlic Tseng struct snd_soc_pcm_runtime *rtd = substream->private_data; 560d1d7a66SGarlic Tseng struct snd_soc_dai *codec_dai = rtd->codec_dai; 570d1d7a66SGarlic Tseng 580d1d7a66SGarlic Tseng return snd_soc_dai_set_sysclk(codec_dai, 0, params_rate(params) * 256, 590d1d7a66SGarlic Tseng SND_SOC_CLOCK_IN); 600d1d7a66SGarlic Tseng } 610d1d7a66SGarlic Tseng 62424dfbf2SBhumika Goyal static const struct snd_soc_ops mt8173_max98090_ops = { 630d1d7a66SGarlic Tseng .hw_params = mt8173_max98090_hw_params, 640d1d7a66SGarlic Tseng }; 650d1d7a66SGarlic Tseng 660d1d7a66SGarlic Tseng static int mt8173_max98090_init(struct snd_soc_pcm_runtime *runtime) 670d1d7a66SGarlic Tseng { 680d1d7a66SGarlic Tseng int ret; 690d1d7a66SGarlic Tseng struct snd_soc_card *card = runtime->card; 704c66b9d1SKuninori Morimoto struct snd_soc_component *component = runtime->codec_dai->component; 710d1d7a66SGarlic Tseng 720d1d7a66SGarlic Tseng /* enable jack detection */ 730d1d7a66SGarlic Tseng ret = snd_soc_card_jack_new(card, "Headphone", SND_JACK_HEADPHONE, 74b1b9e0d3SEnric Balletbo i Serra &mt8173_max98090_jack, 75b1b9e0d3SEnric Balletbo i Serra mt8173_max98090_jack_pins, 76b1b9e0d3SEnric Balletbo i Serra ARRAY_SIZE(mt8173_max98090_jack_pins)); 770d1d7a66SGarlic Tseng if (ret) { 78b1b9e0d3SEnric Balletbo i Serra dev_err(card->dev, "Can't create a new Jack %d\n", ret); 790d1d7a66SGarlic Tseng return ret; 800d1d7a66SGarlic Tseng } 810d1d7a66SGarlic Tseng 824c66b9d1SKuninori Morimoto return max98090_mic_detect(component, &mt8173_max98090_jack); 830d1d7a66SGarlic Tseng } 840d1d7a66SGarlic Tseng 850d1d7a66SGarlic Tseng /* Digital audio interface glue - connects codec <---> CPU */ 860d1d7a66SGarlic Tseng static struct snd_soc_dai_link mt8173_max98090_dais[] = { 870d1d7a66SGarlic Tseng /* Front End DAI links */ 880d1d7a66SGarlic Tseng { 890d1d7a66SGarlic Tseng .name = "MAX98090 Playback", 900d1d7a66SGarlic Tseng .stream_name = "MAX98090 Playback", 910d1d7a66SGarlic Tseng .cpu_dai_name = "DL1", 920d1d7a66SGarlic Tseng .codec_name = "snd-soc-dummy", 930d1d7a66SGarlic Tseng .codec_dai_name = "snd-soc-dummy-dai", 940d1d7a66SGarlic Tseng .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, 950d1d7a66SGarlic Tseng .dynamic = 1, 960d1d7a66SGarlic Tseng .dpcm_playback = 1, 970d1d7a66SGarlic Tseng }, 980d1d7a66SGarlic Tseng { 990d1d7a66SGarlic Tseng .name = "MAX98090 Capture", 1000d1d7a66SGarlic Tseng .stream_name = "MAX98090 Capture", 1010d1d7a66SGarlic Tseng .cpu_dai_name = "VUL", 1020d1d7a66SGarlic Tseng .codec_name = "snd-soc-dummy", 1030d1d7a66SGarlic Tseng .codec_dai_name = "snd-soc-dummy-dai", 1040d1d7a66SGarlic Tseng .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, 1050d1d7a66SGarlic Tseng .dynamic = 1, 1060d1d7a66SGarlic Tseng .dpcm_capture = 1, 1070d1d7a66SGarlic Tseng }, 1080d1d7a66SGarlic Tseng /* Back End DAI links */ 1090d1d7a66SGarlic Tseng { 1100d1d7a66SGarlic Tseng .name = "Codec", 1110d1d7a66SGarlic Tseng .cpu_dai_name = "I2S", 1120d1d7a66SGarlic Tseng .no_pcm = 1, 1130d1d7a66SGarlic Tseng .codec_dai_name = "HiFi", 1140d1d7a66SGarlic Tseng .init = mt8173_max98090_init, 1150d1d7a66SGarlic Tseng .ops = &mt8173_max98090_ops, 1160d1d7a66SGarlic Tseng .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 1170d1d7a66SGarlic Tseng SND_SOC_DAIFMT_CBS_CFS, 1180d1d7a66SGarlic Tseng .dpcm_playback = 1, 1190d1d7a66SGarlic Tseng .dpcm_capture = 1, 1200d1d7a66SGarlic Tseng }, 1210d1d7a66SGarlic Tseng }; 1220d1d7a66SGarlic Tseng 1230d1d7a66SGarlic Tseng static struct snd_soc_card mt8173_max98090_card = { 1240d1d7a66SGarlic Tseng .name = "mt8173-max98090", 1250d1d7a66SGarlic Tseng .owner = THIS_MODULE, 1260d1d7a66SGarlic Tseng .dai_link = mt8173_max98090_dais, 1270d1d7a66SGarlic Tseng .num_links = ARRAY_SIZE(mt8173_max98090_dais), 1280d1d7a66SGarlic Tseng .controls = mt8173_max98090_controls, 1290d1d7a66SGarlic Tseng .num_controls = ARRAY_SIZE(mt8173_max98090_controls), 1300d1d7a66SGarlic Tseng .dapm_widgets = mt8173_max98090_widgets, 1310d1d7a66SGarlic Tseng .num_dapm_widgets = ARRAY_SIZE(mt8173_max98090_widgets), 1320d1d7a66SGarlic Tseng .dapm_routes = mt8173_max98090_routes, 1330d1d7a66SGarlic Tseng .num_dapm_routes = ARRAY_SIZE(mt8173_max98090_routes), 1340d1d7a66SGarlic Tseng }; 1350d1d7a66SGarlic Tseng 1360d1d7a66SGarlic Tseng static int mt8173_max98090_dev_probe(struct platform_device *pdev) 1370d1d7a66SGarlic Tseng { 1380d1d7a66SGarlic Tseng struct snd_soc_card *card = &mt8173_max98090_card; 1390d1d7a66SGarlic Tseng struct device_node *codec_node, *platform_node; 1400d1d7a66SGarlic Tseng int ret, i; 1410d1d7a66SGarlic Tseng 1420d1d7a66SGarlic Tseng platform_node = of_parse_phandle(pdev->dev.of_node, 1430d1d7a66SGarlic Tseng "mediatek,platform", 0); 1440d1d7a66SGarlic Tseng if (!platform_node) { 1450d1d7a66SGarlic Tseng dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); 1460d1d7a66SGarlic Tseng return -EINVAL; 1470d1d7a66SGarlic Tseng } 1480d1d7a66SGarlic Tseng for (i = 0; i < card->num_links; i++) { 1490d1d7a66SGarlic Tseng if (mt8173_max98090_dais[i].platform_name) 1500d1d7a66SGarlic Tseng continue; 1510d1d7a66SGarlic Tseng mt8173_max98090_dais[i].platform_of_node = platform_node; 1520d1d7a66SGarlic Tseng } 1530d1d7a66SGarlic Tseng 1540d1d7a66SGarlic Tseng codec_node = of_parse_phandle(pdev->dev.of_node, 1550d1d7a66SGarlic Tseng "mediatek,audio-codec", 0); 1560d1d7a66SGarlic Tseng if (!codec_node) { 1570d1d7a66SGarlic Tseng dev_err(&pdev->dev, 1580d1d7a66SGarlic Tseng "Property 'audio-codec' missing or invalid\n"); 1590d1d7a66SGarlic Tseng return -EINVAL; 1600d1d7a66SGarlic Tseng } 1610d1d7a66SGarlic Tseng for (i = 0; i < card->num_links; i++) { 1620d1d7a66SGarlic Tseng if (mt8173_max98090_dais[i].codec_name) 1630d1d7a66SGarlic Tseng continue; 1640d1d7a66SGarlic Tseng mt8173_max98090_dais[i].codec_of_node = codec_node; 1650d1d7a66SGarlic Tseng } 1660d1d7a66SGarlic Tseng card->dev = &pdev->dev; 1670d1d7a66SGarlic Tseng 1680d1d7a66SGarlic Tseng ret = devm_snd_soc_register_card(&pdev->dev, card); 1690d1d7a66SGarlic Tseng if (ret) 1700d1d7a66SGarlic Tseng dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", 1710d1d7a66SGarlic Tseng __func__, ret); 1720d1d7a66SGarlic Tseng return ret; 1730d1d7a66SGarlic Tseng } 1740d1d7a66SGarlic Tseng 1750d1d7a66SGarlic Tseng static const struct of_device_id mt8173_max98090_dt_match[] = { 1760d1d7a66SGarlic Tseng { .compatible = "mediatek,mt8173-max98090", }, 1770d1d7a66SGarlic Tseng { } 1780d1d7a66SGarlic Tseng }; 1790d1d7a66SGarlic Tseng MODULE_DEVICE_TABLE(of, mt8173_max98090_dt_match); 1800d1d7a66SGarlic Tseng 1810d1d7a66SGarlic Tseng static struct platform_driver mt8173_max98090_driver = { 1820d1d7a66SGarlic Tseng .driver = { 1830d1d7a66SGarlic Tseng .name = "mt8173-max98090", 1840d1d7a66SGarlic Tseng .of_match_table = mt8173_max98090_dt_match, 1850d1d7a66SGarlic Tseng #ifdef CONFIG_PM 1860d1d7a66SGarlic Tseng .pm = &snd_soc_pm_ops, 1870d1d7a66SGarlic Tseng #endif 1880d1d7a66SGarlic Tseng }, 1890d1d7a66SGarlic Tseng .probe = mt8173_max98090_dev_probe, 1900d1d7a66SGarlic Tseng }; 1910d1d7a66SGarlic Tseng 1920d1d7a66SGarlic Tseng module_platform_driver(mt8173_max98090_driver); 1930d1d7a66SGarlic Tseng 1940d1d7a66SGarlic Tseng /* Module information */ 1950d1d7a66SGarlic Tseng MODULE_DESCRIPTION("MT8173 MAX98090 ALSA SoC machine driver"); 1960d1d7a66SGarlic Tseng MODULE_AUTHOR("Koro Chen <koro.chen@mediatek.com>"); 1970d1d7a66SGarlic Tseng MODULE_LICENSE("GPL v2"); 1980d1d7a66SGarlic Tseng MODULE_ALIAS("platform:mt8173-max98090"); 1990d1d7a66SGarlic Tseng 200