1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * mtk-soundcard-driver.c -- MediaTek soundcard driver common 4 * 5 * Copyright (c) 2022 MediaTek Inc. 6 * Author: Trevor Wu <trevor.wu@mediatek.com> 7 */ 8 9 #include <linux/module.h> 10 #include <linux/of.h> 11 #include <sound/soc.h> 12 13 #include "mtk-dsp-sof-common.h" 14 #include "mtk-soc-card.h" 15 #include "mtk-soundcard-driver.h" 16 17 static int set_card_codec_info(struct snd_soc_card *card, 18 struct device_node *sub_node, 19 struct snd_soc_dai_link *dai_link) 20 { 21 struct device *dev = card->dev; 22 struct device_node *codec_node; 23 int ret; 24 25 codec_node = of_get_child_by_name(sub_node, "codec"); 26 if (!codec_node) { 27 dev_dbg(dev, "%s no specified codec: setting dummy.\n", dai_link->name); 28 29 dai_link->codecs = &snd_soc_dummy_dlc; 30 dai_link->num_codecs = 1; 31 dai_link->dynamic = 1; 32 return 0; 33 } 34 35 /* set card codec info */ 36 ret = snd_soc_of_get_dai_link_codecs(dev, codec_node, dai_link); 37 38 of_node_put(codec_node); 39 40 if (ret < 0) 41 return dev_err_probe(dev, ret, "%s: codec dai not found\n", 42 dai_link->name); 43 44 return 0; 45 } 46 47 static int set_dailink_daifmt(struct snd_soc_card *card, 48 struct device_node *sub_node, 49 struct snd_soc_dai_link *dai_link) 50 { 51 unsigned int daifmt; 52 const char *str; 53 int ret; 54 struct { 55 char *name; 56 unsigned int val; 57 } of_clk_table[] = { 58 { "cpu", SND_SOC_DAIFMT_CBC_CFC }, 59 { "codec", SND_SOC_DAIFMT_CBP_CFP }, 60 }; 61 62 daifmt = snd_soc_daifmt_parse_format(sub_node, NULL); 63 if (daifmt) { 64 dai_link->dai_fmt &= SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK; 65 dai_link->dai_fmt |= daifmt; 66 } 67 68 /* 69 * check "mediatek,clk-provider = xxx" 70 * SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK area 71 */ 72 ret = of_property_read_string(sub_node, "mediatek,clk-provider", &str); 73 if (ret == 0) { 74 int i; 75 76 for (i = 0; i < ARRAY_SIZE(of_clk_table); i++) { 77 if (strcmp(str, of_clk_table[i].name) == 0) { 78 dai_link->dai_fmt &= ~SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK; 79 dai_link->dai_fmt |= of_clk_table[i].val; 80 break; 81 } 82 } 83 } 84 85 return 0; 86 } 87 88 int parse_dai_link_info(struct snd_soc_card *card) 89 { 90 struct device *dev = card->dev; 91 struct device_node *sub_node; 92 struct snd_soc_dai_link *dai_link; 93 const char *dai_link_name; 94 int ret, i; 95 96 /* Loop over all the dai link sub nodes */ 97 for_each_available_child_of_node(dev->of_node, sub_node) { 98 if (of_property_read_string(sub_node, "link-name", 99 &dai_link_name)) { 100 of_node_put(sub_node); 101 return -EINVAL; 102 } 103 104 for_each_card_prelinks(card, i, dai_link) { 105 if (!strcmp(dai_link_name, dai_link->name)) 106 break; 107 } 108 109 if (i >= card->num_links) { 110 of_node_put(sub_node); 111 return -EINVAL; 112 } 113 114 ret = set_card_codec_info(card, sub_node, dai_link); 115 if (ret < 0) { 116 of_node_put(sub_node); 117 return ret; 118 } 119 120 ret = set_dailink_daifmt(card, sub_node, dai_link); 121 if (ret < 0) { 122 of_node_put(sub_node); 123 return ret; 124 } 125 } 126 127 return 0; 128 } 129 EXPORT_SYMBOL_GPL(parse_dai_link_info); 130 131 void clean_card_reference(struct snd_soc_card *card) 132 { 133 struct snd_soc_dai_link *dai_link; 134 int i; 135 136 /* release codec reference gotten by set_card_codec_info */ 137 for_each_card_prelinks(card, i, dai_link) 138 snd_soc_of_put_dai_link_codecs(dai_link); 139 } 140 EXPORT_SYMBOL_GPL(clean_card_reference); 141 142 int mtk_soundcard_startup(struct snd_pcm_substream *substream, 143 enum mtk_pcm_constraint_type ctype) 144 { 145 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); 146 struct mtk_soc_card_data *soc_card = snd_soc_card_get_drvdata(rtd->card); 147 const struct mtk_pcm_constraints_data *mpc = &soc_card->card_data->pcm_constraints[ctype]; 148 int ret; 149 150 if (unlikely(!mpc)) 151 return -EINVAL; 152 153 ret = snd_pcm_hw_constraint_list(substream->runtime, 0, 154 SNDRV_PCM_HW_PARAM_RATE, 155 mpc->rates); 156 if (ret < 0) { 157 dev_err(rtd->dev, "hw_constraint_list rate failed\n"); 158 return ret; 159 } 160 161 ret = snd_pcm_hw_constraint_list(substream->runtime, 0, 162 SNDRV_PCM_HW_PARAM_CHANNELS, 163 mpc->channels); 164 if (ret < 0) { 165 dev_err(rtd->dev, "hw_constraint_list channel failed\n"); 166 return ret; 167 } 168 169 return 0; 170 } 171 EXPORT_SYMBOL_GPL(mtk_soundcard_startup); 172 173 static int mtk_soundcard_playback_startup(struct snd_pcm_substream *substream) 174 { 175 return mtk_soundcard_startup(substream, MTK_CONSTRAINT_PLAYBACK); 176 } 177 178 const struct snd_soc_ops mtk_soundcard_common_playback_ops = { 179 .startup = mtk_soundcard_playback_startup, 180 }; 181 EXPORT_SYMBOL_GPL(mtk_soundcard_common_playback_ops); 182 183 static int mtk_soundcard_capture_startup(struct snd_pcm_substream *substream) 184 { 185 return mtk_soundcard_startup(substream, MTK_CONSTRAINT_CAPTURE); 186 } 187 188 const struct snd_soc_ops mtk_soundcard_common_capture_ops = { 189 .startup = mtk_soundcard_capture_startup, 190 }; 191 EXPORT_SYMBOL_GPL(mtk_soundcard_common_capture_ops); 192 193 int mtk_soundcard_common_probe(struct platform_device *pdev) 194 { 195 struct device_node *platform_node, *adsp_node; 196 const struct mtk_soundcard_pdata *pdata; 197 struct mtk_soc_card_data *soc_card_data; 198 struct snd_soc_dai_link *orig_dai_link, *dai_link; 199 struct snd_soc_jack *jacks; 200 struct snd_soc_card *card; 201 int i, orig_num_links, ret; 202 bool needs_legacy_probe; 203 204 pdata = device_get_match_data(&pdev->dev); 205 if (!pdata) 206 return -EINVAL; 207 208 card = pdata->card_data->card; 209 card->dev = &pdev->dev; 210 orig_dai_link = card->dai_link; 211 orig_num_links = card->num_links; 212 213 ret = snd_soc_of_parse_card_name(card, "model"); 214 if (ret) 215 return ret; 216 217 if (!card->name) { 218 if (!pdata->card_name) 219 return -EINVAL; 220 221 card->name = pdata->card_name; 222 } 223 224 needs_legacy_probe = !of_property_read_bool(pdev->dev.of_node, "audio-routing"); 225 if (needs_legacy_probe) { 226 /* 227 * If we have no .soc_probe() callback there's no way of using 228 * any legacy probe mechanism, as that cannot not be generic. 229 */ 230 if (!pdata->soc_probe) 231 return -EINVAL; 232 233 dev_info_once(&pdev->dev, "audio-routing not found: using legacy probe\n"); 234 } else { 235 ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); 236 if (ret) 237 return ret; 238 } 239 240 soc_card_data = devm_kzalloc(&pdev->dev, sizeof(*soc_card_data), GFP_KERNEL); 241 if (!soc_card_data) 242 return -ENOMEM; 243 244 soc_card_data->card_data = pdata->card_data; 245 246 jacks = devm_kcalloc(card->dev, soc_card_data->card_data->num_jacks, 247 sizeof(*jacks), GFP_KERNEL); 248 if (!jacks) 249 return -ENOMEM; 250 251 soc_card_data->card_data->jacks = jacks; 252 253 platform_node = of_parse_phandle(pdev->dev.of_node, "mediatek,platform", 0); 254 if (!platform_node) 255 return dev_err_probe(&pdev->dev, -EINVAL, 256 "Property mediatek,platform missing or invalid\n"); 257 258 /* Check if this SoC has an Audio DSP */ 259 if (pdata->sof_priv) 260 adsp_node = of_parse_phandle(pdev->dev.of_node, "mediatek,adsp", 0); 261 else 262 adsp_node = NULL; 263 264 if (adsp_node) { 265 if (of_property_read_bool(pdev->dev.of_node, "mediatek,dai-link")) { 266 ret = mtk_sof_dailink_parse_of(card, pdev->dev.of_node, 267 "mediatek,dai-link", 268 card->dai_link, card->num_links); 269 if (ret) { 270 of_node_put(adsp_node); 271 of_node_put(platform_node); 272 return dev_err_probe(&pdev->dev, ret, 273 "Cannot parse mediatek,dai-link\n"); 274 } 275 } 276 277 soc_card_data->sof_priv = pdata->sof_priv; 278 card->probe = mtk_sof_card_probe; 279 card->late_probe = mtk_sof_card_late_probe; 280 if (!card->topology_shortname_created) { 281 snprintf(card->topology_shortname, 32, "sof-%s", card->name); 282 card->topology_shortname_created = true; 283 } 284 card->name = card->topology_shortname; 285 } 286 287 /* 288 * Regardless of whether the ADSP is wanted and/or present in a machine 289 * specific device tree or not and regardless of whether any AFE_SOF 290 * link is present, we have to make sure that the platforms->of_node 291 * is not NULL, and set to either ADSP (adsp_node) or AFE (platform_node). 292 */ 293 for_each_card_prelinks(card, i, dai_link) { 294 if (adsp_node && !strncmp(dai_link->name, "AFE_SOF", strlen("AFE_SOF"))) 295 dai_link->platforms->of_node = adsp_node; 296 else if (!dai_link->platforms->name && !dai_link->platforms->of_node) 297 dai_link->platforms->of_node = platform_node; 298 } 299 300 if (!needs_legacy_probe) { 301 ret = parse_dai_link_info(card); 302 if (ret) 303 goto err_restore_dais; 304 } else { 305 if (adsp_node) 306 of_node_put(adsp_node); 307 of_node_put(platform_node); 308 } 309 310 if (pdata->soc_probe) { 311 ret = pdata->soc_probe(soc_card_data, needs_legacy_probe); 312 if (ret) { 313 if (!needs_legacy_probe) 314 clean_card_reference(card); 315 goto err_restore_dais; 316 } 317 } 318 snd_soc_card_set_drvdata(card, soc_card_data); 319 320 ret = devm_snd_soc_register_card(&pdev->dev, card); 321 322 if (!needs_legacy_probe) 323 clean_card_reference(card); 324 325 if (ret) { 326 dev_err_probe(&pdev->dev, ret, "Cannot register card\n"); 327 goto err_restore_dais; 328 } 329 330 return 0; 331 332 err_restore_dais: 333 card->dai_link = orig_dai_link; 334 card->num_links = orig_num_links; 335 return ret; 336 } 337 EXPORT_SYMBOL_GPL(mtk_soundcard_common_probe); 338
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.