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