1 /* 2 * atmel_wm8904 - Atmel ASoC driver for boards with WM8904 codec. 3 * 4 * Copyright (C) 2012 Atmel 5 * 6 * Author: Bo Shen <voice.shen@atmel.com> 7 * 8 * GPLv2 or later 9 */ 10 11 #include <linux/clk.h> 12 #include <linux/module.h> 13 #include <linux/of.h> 14 #include <linux/of_device.h> 15 #include <linux/pinctrl/consumer.h> 16 17 #include <sound/soc.h> 18 19 #include "../codecs/wm8904.h" 20 #include "atmel_ssc_dai.h" 21 22 #define MCLK_RATE 32768 23 24 static struct clk *mclk; 25 26 static const struct snd_soc_dapm_widget atmel_asoc_wm8904_dapm_widgets[] = { 27 SND_SOC_DAPM_HP("Headphone Jack", NULL), 28 SND_SOC_DAPM_MIC("Mic", NULL), 29 SND_SOC_DAPM_LINE("Line In Jack", NULL), 30 }; 31 32 static int atmel_asoc_wm8904_hw_params(struct snd_pcm_substream *substream, 33 struct snd_pcm_hw_params *params) 34 { 35 struct snd_soc_pcm_runtime *rtd = substream->private_data; 36 struct snd_soc_dai *codec_dai = rtd->codec_dai; 37 int ret; 38 39 ret = snd_soc_dai_set_pll(codec_dai, WM8904_FLL_MCLK, WM8904_FLL_MCLK, 40 32768, params_rate(params) * 256); 41 if (ret < 0) { 42 pr_err("%s - failed to set wm8904 codec PLL.", __func__); 43 return ret; 44 } 45 46 /* 47 * As here wm8904 use FLL output as its system clock 48 * so calling set_sysclk won't care freq parameter 49 * then we pass 0 50 */ 51 ret = snd_soc_dai_set_sysclk(codec_dai, WM8904_CLK_FLL, 52 0, SND_SOC_CLOCK_IN); 53 if (ret < 0) { 54 pr_err("%s -failed to set wm8904 SYSCLK\n", __func__); 55 return ret; 56 } 57 58 return 0; 59 } 60 61 static struct snd_soc_ops atmel_asoc_wm8904_ops = { 62 .hw_params = atmel_asoc_wm8904_hw_params, 63 }; 64 65 static int atmel_set_bias_level(struct snd_soc_card *card, 66 struct snd_soc_dapm_context *dapm, 67 enum snd_soc_bias_level level) 68 { 69 if (dapm->bias_level == SND_SOC_BIAS_STANDBY) { 70 switch (level) { 71 case SND_SOC_BIAS_PREPARE: 72 clk_prepare_enable(mclk); 73 break; 74 case SND_SOC_BIAS_OFF: 75 clk_disable_unprepare(mclk); 76 break; 77 default: 78 break; 79 } 80 } 81 82 return 0; 83 }; 84 85 static struct snd_soc_dai_link atmel_asoc_wm8904_dailink = { 86 .name = "WM8904", 87 .stream_name = "WM8904 PCM", 88 .codec_dai_name = "wm8904-hifi", 89 .dai_fmt = SND_SOC_DAIFMT_I2S 90 | SND_SOC_DAIFMT_NB_NF 91 | SND_SOC_DAIFMT_CBM_CFM, 92 .ops = &atmel_asoc_wm8904_ops, 93 }; 94 95 static struct snd_soc_card atmel_asoc_wm8904_card = { 96 .name = "atmel_asoc_wm8904", 97 .owner = THIS_MODULE, 98 .set_bias_level = atmel_set_bias_level, 99 .dai_link = &atmel_asoc_wm8904_dailink, 100 .num_links = 1, 101 .dapm_widgets = atmel_asoc_wm8904_dapm_widgets, 102 .num_dapm_widgets = ARRAY_SIZE(atmel_asoc_wm8904_dapm_widgets), 103 .fully_routed = true, 104 }; 105 106 static int atmel_asoc_wm8904_dt_init(struct platform_device *pdev) 107 { 108 struct device_node *np = pdev->dev.of_node; 109 struct device_node *codec_np, *cpu_np; 110 struct snd_soc_card *card = &atmel_asoc_wm8904_card; 111 struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink; 112 int ret; 113 114 if (!np) { 115 dev_err(&pdev->dev, "only device tree supported\n"); 116 return -EINVAL; 117 } 118 119 ret = snd_soc_of_parse_card_name(card, "atmel,model"); 120 if (ret) { 121 dev_err(&pdev->dev, "failed to parse card name\n"); 122 return ret; 123 } 124 125 ret = snd_soc_of_parse_audio_routing(card, "atmel,audio-routing"); 126 if (ret) { 127 dev_err(&pdev->dev, "failed to parse audio routing\n"); 128 return ret; 129 } 130 131 cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0); 132 if (!cpu_np) { 133 dev_err(&pdev->dev, "failed to get dai and pcm info\n"); 134 ret = -EINVAL; 135 return ret; 136 } 137 dailink->cpu_of_node = cpu_np; 138 dailink->platform_of_node = cpu_np; 139 of_node_put(cpu_np); 140 141 codec_np = of_parse_phandle(np, "atmel,audio-codec", 0); 142 if (!codec_np) { 143 dev_err(&pdev->dev, "failed to get codec info\n"); 144 ret = -EINVAL; 145 return ret; 146 } 147 dailink->codec_of_node = codec_np; 148 of_node_put(codec_np); 149 150 return 0; 151 } 152 153 static int atmel_asoc_wm8904_probe(struct platform_device *pdev) 154 { 155 struct snd_soc_card *card = &atmel_asoc_wm8904_card; 156 struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink; 157 struct clk *clk_src; 158 struct pinctrl *pinctrl; 159 int id, ret; 160 161 pinctrl = devm_pinctrl_get_select_default(&pdev->dev); 162 if (IS_ERR(pinctrl)) { 163 dev_err(&pdev->dev, "failed to request pinctrl\n"); 164 return PTR_ERR(pinctrl); 165 } 166 167 card->dev = &pdev->dev; 168 ret = atmel_asoc_wm8904_dt_init(pdev); 169 if (ret) { 170 dev_err(&pdev->dev, "failed to init dt info\n"); 171 return ret; 172 } 173 174 id = of_alias_get_id((struct device_node *)dailink->cpu_of_node, "ssc"); 175 ret = atmel_ssc_set_audio(id); 176 if (ret != 0) { 177 dev_err(&pdev->dev, "failed to set SSC %d for audio\n", id); 178 return ret; 179 } 180 181 mclk = clk_get(NULL, "pck0"); 182 if (IS_ERR(mclk)) { 183 dev_err(&pdev->dev, "failed to get pck0\n"); 184 ret = PTR_ERR(mclk); 185 goto err_set_audio; 186 } 187 188 clk_src = clk_get(NULL, "clk32k"); 189 if (IS_ERR(clk_src)) { 190 dev_err(&pdev->dev, "failed to get clk32k\n"); 191 ret = PTR_ERR(clk_src); 192 goto err_set_audio; 193 } 194 195 ret = clk_set_parent(mclk, clk_src); 196 clk_put(clk_src); 197 if (ret != 0) { 198 dev_err(&pdev->dev, "failed to set MCLK parent\n"); 199 goto err_set_audio; 200 } 201 202 dev_info(&pdev->dev, "setting pck0 to %dHz\n", MCLK_RATE); 203 clk_set_rate(mclk, MCLK_RATE); 204 205 ret = snd_soc_register_card(card); 206 if (ret) { 207 dev_err(&pdev->dev, "snd_soc_register_card failed\n"); 208 goto err_set_audio; 209 } 210 211 return 0; 212 213 err_set_audio: 214 atmel_ssc_put_audio(id); 215 return ret; 216 } 217 218 static int atmel_asoc_wm8904_remove(struct platform_device *pdev) 219 { 220 struct snd_soc_card *card = platform_get_drvdata(pdev); 221 struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink; 222 int id; 223 224 id = of_alias_get_id((struct device_node *)dailink->cpu_of_node, "ssc"); 225 226 snd_soc_unregister_card(card); 227 atmel_ssc_put_audio(id); 228 229 return 0; 230 } 231 232 #ifdef CONFIG_OF 233 static const struct of_device_id atmel_asoc_wm8904_dt_ids[] = { 234 { .compatible = "atmel,asoc-wm8904", }, 235 { } 236 }; 237 #endif 238 239 static struct platform_driver atmel_asoc_wm8904_driver = { 240 .driver = { 241 .name = "atmel-wm8904-audio", 242 .owner = THIS_MODULE, 243 .of_match_table = of_match_ptr(atmel_asoc_wm8904_dt_ids), 244 }, 245 .probe = atmel_asoc_wm8904_probe, 246 .remove = atmel_asoc_wm8904_remove, 247 }; 248 249 module_platform_driver(atmel_asoc_wm8904_driver); 250 251 /* Module information */ 252 MODULE_AUTHOR("Bo Shen <voice.shen@atmel.com>"); 253 MODULE_DESCRIPTION("ALSA SoC machine driver for Atmel EK with WM8904 codec"); 254 MODULE_LICENSE("GPL"); 255