1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * atmel_wm8904 - Atmel ASoC driver for boards with WM8904 codec. 4 * 5 * Copyright (C) 2012 Atmel 6 * 7 * Author: Bo Shen <voice.shen@atmel.com> 8 */ 9 10 #include <linux/clk.h> 11 #include <linux/module.h> 12 #include <linux/of.h> 13 14 #include <sound/soc.h> 15 16 #include "../codecs/wm8904.h" 17 #include "atmel_ssc_dai.h" 18 19 static const struct snd_soc_dapm_widget atmel_asoc_wm8904_dapm_widgets[] = { 20 SND_SOC_DAPM_HP("Headphone Jack", NULL), 21 SND_SOC_DAPM_MIC("Mic", NULL), 22 SND_SOC_DAPM_LINE("Line In Jack", NULL), 23 }; 24 25 static int atmel_asoc_wm8904_hw_params(struct snd_pcm_substream *substream, 26 struct snd_pcm_hw_params *params) 27 { 28 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); 29 struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); 30 int ret; 31 32 ret = snd_soc_dai_set_pll(codec_dai, WM8904_FLL_MCLK, WM8904_FLL_MCLK, 33 32768, params_rate(params) * 256); 34 if (ret < 0) { 35 pr_err("%s - failed to set wm8904 codec PLL.", __func__); 36 return ret; 37 } 38 39 /* 40 * As here wm8904 use FLL output as its system clock 41 * so calling set_sysclk won't care freq parameter 42 * then we pass 0 43 */ 44 ret = snd_soc_dai_set_sysclk(codec_dai, WM8904_CLK_FLL, 45 0, SND_SOC_CLOCK_IN); 46 if (ret < 0) { 47 pr_err("%s -failed to set wm8904 SYSCLK\n", __func__); 48 return ret; 49 } 50 51 return 0; 52 } 53 54 static const struct snd_soc_ops atmel_asoc_wm8904_ops = { 55 .hw_params = atmel_asoc_wm8904_hw_params, 56 }; 57 58 SND_SOC_DAILINK_DEFS(pcm, 59 DAILINK_COMP_ARRAY(COMP_EMPTY()), 60 DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8904-hifi")), 61 DAILINK_COMP_ARRAY(COMP_EMPTY())); 62 63 static struct snd_soc_dai_link atmel_asoc_wm8904_dailink = { 64 .name = "WM8904", 65 .stream_name = "WM8904 PCM", 66 .dai_fmt = SND_SOC_DAIFMT_I2S 67 | SND_SOC_DAIFMT_NB_NF 68 | SND_SOC_DAIFMT_CBP_CFP, 69 .ops = &atmel_asoc_wm8904_ops, 70 SND_SOC_DAILINK_REG(pcm), 71 }; 72 73 static struct snd_soc_card atmel_asoc_wm8904_card = { 74 .name = "atmel_asoc_wm8904", 75 .owner = THIS_MODULE, 76 .dai_link = &atmel_asoc_wm8904_dailink, 77 .num_links = 1, 78 .dapm_widgets = atmel_asoc_wm8904_dapm_widgets, 79 .num_dapm_widgets = ARRAY_SIZE(atmel_asoc_wm8904_dapm_widgets), 80 .fully_routed = true, 81 }; 82 83 static int atmel_asoc_wm8904_dt_init(struct platform_device *pdev) 84 { 85 struct device_node *np = pdev->dev.of_node; 86 struct device_node *codec_np, *cpu_np; 87 struct snd_soc_card *card = &atmel_asoc_wm8904_card; 88 struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink; 89 int ret; 90 91 if (!np) { 92 dev_err(&pdev->dev, "only device tree supported\n"); 93 return -EINVAL; 94 } 95 96 ret = snd_soc_of_parse_card_name(card, "atmel,model"); 97 if (ret) { 98 dev_err(&pdev->dev, "failed to parse card name\n"); 99 return ret; 100 } 101 102 ret = snd_soc_of_parse_audio_routing(card, "atmel,audio-routing"); 103 if (ret) { 104 dev_err(&pdev->dev, "failed to parse audio routing\n"); 105 return ret; 106 } 107 108 cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0); 109 if (!cpu_np) { 110 dev_err(&pdev->dev, "failed to get dai and pcm info\n"); 111 ret = -EINVAL; 112 return ret; 113 } 114 dailink->cpus->of_node = cpu_np; 115 dailink->platforms->of_node = cpu_np; 116 of_node_put(cpu_np); 117 118 codec_np = of_parse_phandle(np, "atmel,audio-codec", 0); 119 if (!codec_np) { 120 dev_err(&pdev->dev, "failed to get codec info\n"); 121 ret = -EINVAL; 122 return ret; 123 } 124 dailink->codecs->of_node = codec_np; 125 of_node_put(codec_np); 126 127 return 0; 128 } 129 130 static int atmel_asoc_wm8904_probe(struct platform_device *pdev) 131 { 132 struct snd_soc_card *card = &atmel_asoc_wm8904_card; 133 struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink; 134 int id, ret; 135 136 card->dev = &pdev->dev; 137 ret = atmel_asoc_wm8904_dt_init(pdev); 138 if (ret) { 139 dev_err(&pdev->dev, "failed to init dt info\n"); 140 return ret; 141 } 142 143 id = of_alias_get_id((struct device_node *)dailink->cpus->of_node, "ssc"); 144 ret = atmel_ssc_set_audio(id); 145 if (ret != 0) { 146 dev_err(&pdev->dev, "failed to set SSC %d for audio\n", id); 147 return ret; 148 } 149 150 ret = snd_soc_register_card(card); 151 if (ret) { 152 dev_err(&pdev->dev, "snd_soc_register_card failed\n"); 153 goto err_set_audio; 154 } 155 156 return 0; 157 158 err_set_audio: 159 atmel_ssc_put_audio(id); 160 return ret; 161 } 162 163 static void atmel_asoc_wm8904_remove(struct platform_device *pdev) 164 { 165 struct snd_soc_card *card = platform_get_drvdata(pdev); 166 struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink; 167 int id; 168 169 id = of_alias_get_id((struct device_node *)dailink->cpus->of_node, "ssc"); 170 171 snd_soc_unregister_card(card); 172 atmel_ssc_put_audio(id); 173 } 174 175 #ifdef CONFIG_OF 176 static const struct of_device_id atmel_asoc_wm8904_dt_ids[] = { 177 { .compatible = "atmel,asoc-wm8904", }, 178 { } 179 }; 180 MODULE_DEVICE_TABLE(of, atmel_asoc_wm8904_dt_ids); 181 #endif 182 183 static struct platform_driver atmel_asoc_wm8904_driver = { 184 .driver = { 185 .name = "atmel-wm8904-audio", 186 .of_match_table = of_match_ptr(atmel_asoc_wm8904_dt_ids), 187 .pm = &snd_soc_pm_ops, 188 }, 189 .probe = atmel_asoc_wm8904_probe, 190 .remove_new = atmel_asoc_wm8904_remove, 191 }; 192 193 module_platform_driver(atmel_asoc_wm8904_driver); 194 195 /* Module information */ 196 MODULE_AUTHOR("Bo Shen <voice.shen@atmel.com>"); 197 MODULE_DESCRIPTION("ALSA SoC machine driver for Atmel EK with WM8904 codec"); 198 MODULE_LICENSE("GPL"); 199
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.