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