1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * mt7986-wm8960.c -- MT7986-WM8960 ALSA SoC machine driver 4 * 5 * Copyright (c) 2023 MediaTek Inc. 6 * Authors: Vic Wu <vic.wu@mediatek.com> 7 * Maso Huang <maso.huang@mediatek.com> 8 */ 9 10 #include <linux/module.h> 11 #include <sound/soc.h> 12 13 #include "mt7986-afe-common.h" 14 15 struct mt7986_wm8960_priv { 16 struct device_node *platform_node; 17 struct device_node *codec_node; 18 }; 19 20 static const struct snd_soc_dapm_widget mt7986_wm8960_widgets[] = { 21 SND_SOC_DAPM_HP("Headphone", NULL), 22 SND_SOC_DAPM_MIC("AMIC", NULL), 23 }; 24 25 static const struct snd_kcontrol_new mt7986_wm8960_controls[] = { 26 SOC_DAPM_PIN_SWITCH("Headphone"), 27 SOC_DAPM_PIN_SWITCH("AMIC"), 28 }; 29 30 SND_SOC_DAILINK_DEFS(playback, 31 DAILINK_COMP_ARRAY(COMP_CPU("DL1")), 32 DAILINK_COMP_ARRAY(COMP_DUMMY()), 33 DAILINK_COMP_ARRAY(COMP_EMPTY())); 34 35 SND_SOC_DAILINK_DEFS(capture, 36 DAILINK_COMP_ARRAY(COMP_CPU("UL1")), 37 DAILINK_COMP_ARRAY(COMP_DUMMY()), 38 DAILINK_COMP_ARRAY(COMP_EMPTY())); 39 40 SND_SOC_DAILINK_DEFS(codec, 41 DAILINK_COMP_ARRAY(COMP_CPU("ETDM")), 42 DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8960-hifi")), 43 DAILINK_COMP_ARRAY(COMP_EMPTY())); 44 45 static struct snd_soc_dai_link mt7986_wm8960_dai_links[] = { 46 /* FE */ 47 { 48 .name = "wm8960-playback", 49 .stream_name = "wm8960-playback", 50 .trigger = {SND_SOC_DPCM_TRIGGER_POST, 51 SND_SOC_DPCM_TRIGGER_POST}, 52 .dynamic = 1, 53 .dpcm_playback = 1, 54 SND_SOC_DAILINK_REG(playback), 55 }, 56 { 57 .name = "wm8960-capture", 58 .stream_name = "wm8960-capture", 59 .trigger = {SND_SOC_DPCM_TRIGGER_POST, 60 SND_SOC_DPCM_TRIGGER_POST}, 61 .dynamic = 1, 62 .dpcm_capture = 1, 63 SND_SOC_DAILINK_REG(capture), 64 }, 65 /* BE */ 66 { 67 .name = "wm8960-codec", 68 .no_pcm = 1, 69 .dai_fmt = SND_SOC_DAIFMT_I2S | 70 SND_SOC_DAIFMT_NB_NF | 71 SND_SOC_DAIFMT_CBS_CFS | 72 SND_SOC_DAIFMT_GATED, 73 .dpcm_playback = 1, 74 .dpcm_capture = 1, 75 SND_SOC_DAILINK_REG(codec), 76 }, 77 }; 78 79 static struct snd_soc_card mt7986_wm8960_card = { 80 .name = "mt7986-wm8960", 81 .owner = THIS_MODULE, 82 .dai_link = mt7986_wm8960_dai_links, 83 .num_links = ARRAY_SIZE(mt7986_wm8960_dai_links), 84 .controls = mt7986_wm8960_controls, 85 .num_controls = ARRAY_SIZE(mt7986_wm8960_controls), 86 .dapm_widgets = mt7986_wm8960_widgets, 87 .num_dapm_widgets = ARRAY_SIZE(mt7986_wm8960_widgets), 88 }; 89 90 static int mt7986_wm8960_machine_probe(struct platform_device *pdev) 91 { 92 struct snd_soc_card *card = &mt7986_wm8960_card; 93 struct snd_soc_dai_link *dai_link; 94 struct device_node *platform, *codec; 95 struct mt7986_wm8960_priv *priv; 96 int ret, i; 97 98 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 99 if (!priv) 100 return -ENOMEM; 101 102 platform = of_get_child_by_name(pdev->dev.of_node, "platform"); 103 104 if (platform) { 105 priv->platform_node = of_parse_phandle(platform, "sound-dai", 0); 106 of_node_put(platform); 107 108 if (!priv->platform_node) { 109 dev_err(&pdev->dev, "Failed to parse platform/sound-dai property\n"); 110 return -EINVAL; 111 } 112 } else { 113 dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); 114 return -EINVAL; 115 } 116 117 for_each_card_prelinks(card, i, dai_link) { 118 if (dai_link->platforms->name) 119 continue; 120 dai_link->platforms->of_node = priv->platform_node; 121 } 122 123 card->dev = &pdev->dev; 124 125 codec = of_get_child_by_name(pdev->dev.of_node, "codec"); 126 127 if (codec) { 128 priv->codec_node = of_parse_phandle(codec, "sound-dai", 0); 129 of_node_put(codec); 130 131 if (!priv->codec_node) { 132 of_node_put(priv->platform_node); 133 dev_err(&pdev->dev, "Failed to parse codec/sound-dai property\n"); 134 return -EINVAL; 135 } 136 } else { 137 of_node_put(priv->platform_node); 138 dev_err(&pdev->dev, "Property 'codec' missing or invalid\n"); 139 return -EINVAL; 140 } 141 142 for_each_card_prelinks(card, i, dai_link) { 143 if (dai_link->codecs->name) 144 continue; 145 dai_link->codecs->of_node = priv->codec_node; 146 } 147 148 ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); 149 if (ret) { 150 dev_err(&pdev->dev, "Failed to parse audio-routing: %d\n", ret); 151 goto err_of_node_put; 152 } 153 154 ret = devm_snd_soc_register_card(&pdev->dev, card); 155 if (ret) { 156 dev_err(&pdev->dev, "%s snd_soc_register_card fail: %d\n", __func__, ret); 157 goto err_of_node_put; 158 } 159 160 err_of_node_put: 161 of_node_put(priv->codec_node); 162 of_node_put(priv->platform_node); 163 return ret; 164 } 165 166 static void mt7986_wm8960_machine_remove(struct platform_device *pdev) 167 { 168 struct snd_soc_card *card = platform_get_drvdata(pdev); 169 struct mt7986_wm8960_priv *priv = snd_soc_card_get_drvdata(card); 170 171 of_node_put(priv->codec_node); 172 of_node_put(priv->platform_node); 173 } 174 175 static const struct of_device_id mt7986_wm8960_machine_dt_match[] = { 176 {.compatible = "mediatek,mt7986-wm8960-sound"}, 177 { /* sentinel */ } 178 }; 179 MODULE_DEVICE_TABLE(of, mt7986_wm8960_machine_dt_match); 180 181 static struct platform_driver mt7986_wm8960_machine = { 182 .driver = { 183 .name = "mt7986-wm8960", 184 .of_match_table = mt7986_wm8960_machine_dt_match, 185 }, 186 .probe = mt7986_wm8960_machine_probe, 187 .remove_new = mt7986_wm8960_machine_remove, 188 }; 189 190 module_platform_driver(mt7986_wm8960_machine); 191 192 /* Module information */ 193 MODULE_DESCRIPTION("MT7986 WM8960 ALSA SoC machine driver"); 194 MODULE_AUTHOR("Vic Wu <vic.wu@mediatek.com>"); 195 MODULE_LICENSE("GPL"); 196 MODULE_ALIAS("mt7986 wm8960 soc card"); 197