1 /* 2 * Bells audio support 3 * 4 * Copyright 2012 Wolfson Microelectronics 5 * 6 * This program is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU General Public License as published by the 8 * Free Software Foundation; either version 2 of the License, or (at your 9 * option) any later version. 10 */ 11 12 #include <sound/soc.h> 13 #include <sound/soc-dapm.h> 14 #include <sound/jack.h> 15 #include <linux/gpio.h> 16 #include <linux/module.h> 17 18 #include "../codecs/wm5102.h" 19 #include "../codecs/wm9081.h" 20 21 /* 22 * 44.1kHz based clocks for the SYSCLK domain, use a very high clock 23 * to allow all the DSP functionality to be enabled if desired. 24 */ 25 #define SYSCLK_RATE (44100 * 1024) 26 27 /* 48kHz based clocks for the ASYNC domain */ 28 #define ASYNCCLK_RATE (48000 * 512) 29 30 /* BCLK2 is fixed at this currently */ 31 #define BCLK2_RATE (64 * 8000) 32 33 /* 34 * Expect a 24.576MHz crystal if one is fitted (the driver will function 35 * if this is not fitted). 36 */ 37 #define MCLK_RATE 24576000 38 39 #define WM9081_AUDIO_RATE 44100 40 #define WM9081_MCLK_RATE (WM9081_AUDIO_RATE * 256) 41 42 static int bells_set_bias_level(struct snd_soc_card *card, 43 struct snd_soc_dapm_context *dapm, 44 enum snd_soc_bias_level level) 45 { 46 struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai; 47 struct snd_soc_codec *codec = codec_dai->codec; 48 int ret; 49 50 if (dapm->dev != codec_dai->dev) 51 return 0; 52 53 switch (level) { 54 case SND_SOC_BIAS_PREPARE: 55 if (dapm->bias_level == SND_SOC_BIAS_STANDBY) { 56 ret = snd_soc_codec_set_pll(codec, WM5102_FLL1, 57 ARIZONA_FLL_SRC_MCLK1, 58 MCLK_RATE, 59 SYSCLK_RATE); 60 if (ret < 0) 61 pr_err("Failed to start FLL: %d\n", ret); 62 63 ret = snd_soc_codec_set_pll(codec, WM5102_FLL2, 64 ARIZONA_FLL_SRC_AIF2BCLK, 65 BCLK2_RATE, 66 ASYNCCLK_RATE); 67 if (ret < 0) 68 pr_err("Failed to start FLL: %d\n", ret); 69 } 70 break; 71 72 default: 73 break; 74 } 75 76 return 0; 77 } 78 79 static int bells_set_bias_level_post(struct snd_soc_card *card, 80 struct snd_soc_dapm_context *dapm, 81 enum snd_soc_bias_level level) 82 { 83 struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai; 84 struct snd_soc_codec *codec = codec_dai->codec; 85 int ret; 86 87 if (dapm->dev != codec_dai->dev) 88 return 0; 89 90 switch (level) { 91 case SND_SOC_BIAS_STANDBY: 92 ret = snd_soc_codec_set_pll(codec, WM5102_FLL1, 0, 0, 0); 93 if (ret < 0) { 94 pr_err("Failed to stop FLL: %d\n", ret); 95 return ret; 96 } 97 98 ret = snd_soc_codec_set_pll(codec, WM5102_FLL2, 0, 0, 0); 99 if (ret < 0) { 100 pr_err("Failed to stop FLL: %d\n", ret); 101 return ret; 102 } 103 break; 104 105 default: 106 break; 107 } 108 109 dapm->bias_level = level; 110 111 return 0; 112 } 113 114 static int bells_late_probe(struct snd_soc_card *card) 115 { 116 struct snd_soc_codec *codec = card->rtd[0].codec; 117 struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai; 118 struct snd_soc_dai *aif2_dai = card->rtd[1].cpu_dai; 119 struct snd_soc_dai *aif3_dai = card->rtd[2].cpu_dai; 120 struct snd_soc_dai *wm9081_dai = card->rtd[2].codec_dai; 121 int ret; 122 123 ret = snd_soc_dai_set_sysclk(aif1_dai, ARIZONA_CLK_SYSCLK, 0, 0); 124 if (ret != 0) { 125 dev_err(aif1_dai->dev, "Failed to set AIF1 clock: %d\n", ret); 126 return ret; 127 } 128 129 ret = snd_soc_dai_set_sysclk(aif2_dai, ARIZONA_CLK_ASYNCCLK, 0, 0); 130 if (ret != 0) { 131 dev_err(aif2_dai->dev, "Failed to set AIF2 clock: %d\n", ret); 132 return ret; 133 } 134 135 ret = snd_soc_dai_set_sysclk(aif3_dai, ARIZONA_CLK_SYSCLK, 0, 0); 136 if (ret != 0) { 137 dev_err(aif1_dai->dev, "Failed to set AIF1 clock: %d\n", ret); 138 return ret; 139 } 140 141 ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_SYSCLK, 142 ARIZONA_CLK_SRC_FLL1, SYSCLK_RATE, 143 SND_SOC_CLOCK_IN); 144 if (ret != 0) { 145 dev_err(codec->dev, "Failed to set SYSCLK: %d\n", ret); 146 return ret; 147 } 148 149 ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_OPCLK, 0, 150 WM9081_MCLK_RATE, SND_SOC_CLOCK_OUT); 151 if (ret != 0) { 152 dev_err(codec->dev, "Failed to set OPCLK: %d\n", ret); 153 return ret; 154 } 155 156 ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_ASYNCCLK, 157 ARIZONA_CLK_SRC_FLL2, ASYNCCLK_RATE, 158 SND_SOC_CLOCK_IN); 159 if (ret != 0) { 160 dev_err(codec->dev, "Failed to set SYSCLK: %d\n", ret); 161 return ret; 162 } 163 164 ret = snd_soc_codec_set_sysclk(wm9081_dai->codec, WM9081_SYSCLK_MCLK, 165 0, WM9081_MCLK_RATE, 0); 166 if (ret != 0) { 167 dev_err(wm9081_dai->dev, "Failed to set MCLK: %d\n", ret); 168 return ret; 169 } 170 171 return 0; 172 } 173 174 static const struct snd_soc_pcm_stream baseband_params = { 175 .formats = SNDRV_PCM_FMTBIT_S32_LE, 176 .rate_min = 8000, 177 .rate_max = 8000, 178 .channels_min = 2, 179 .channels_max = 2, 180 }; 181 182 static const struct snd_soc_pcm_stream sub_params = { 183 .formats = SNDRV_PCM_FMTBIT_S32_LE, 184 .rate_min = WM9081_AUDIO_RATE, 185 .rate_max = WM9081_AUDIO_RATE, 186 .channels_min = 2, 187 .channels_max = 2, 188 }; 189 190 static struct snd_soc_dai_link bells_dai_wm5102[] = { 191 { 192 .name = "CPU", 193 .stream_name = "CPU", 194 .cpu_dai_name = "samsung-i2s.0", 195 .codec_dai_name = "wm5102-aif1", 196 .platform_name = "samsung-audio", 197 .codec_name = "wm5102-codec", 198 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF 199 | SND_SOC_DAIFMT_CBM_CFM, 200 }, 201 { 202 .name = "Baseband", 203 .stream_name = "Baseband", 204 .cpu_dai_name = "wm5102-aif2", 205 .codec_dai_name = "wm1250-ev1", 206 .codec_name = "wm1250-ev1.1-0027", 207 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF 208 | SND_SOC_DAIFMT_CBM_CFM, 209 .ignore_suspend = 1, 210 .params = &baseband_params, 211 }, 212 { 213 .name = "Sub", 214 .stream_name = "Sub", 215 .cpu_dai_name = "wm5110-aif3", 216 .codec_dai_name = "wm9081-hifi", 217 .codec_name = "wm9081.1-006c", 218 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF 219 | SND_SOC_DAIFMT_CBS_CFS, 220 .ignore_suspend = 1, 221 .params = &sub_params, 222 }, 223 }; 224 225 static struct snd_soc_dai_link bells_dai_wm5110[] = { 226 { 227 .name = "CPU", 228 .stream_name = "CPU", 229 .cpu_dai_name = "samsung-i2s.0", 230 .codec_dai_name = "wm5110-aif1", 231 .platform_name = "samsung-audio", 232 .codec_name = "wm5110-codec", 233 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF 234 | SND_SOC_DAIFMT_CBM_CFM, 235 }, 236 { 237 .name = "Baseband", 238 .stream_name = "Baseband", 239 .cpu_dai_name = "wm5110-aif2", 240 .codec_dai_name = "wm1250-ev1", 241 .codec_name = "wm1250-ev1.1-0027", 242 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF 243 | SND_SOC_DAIFMT_CBM_CFM, 244 .ignore_suspend = 1, 245 .params = &baseband_params, 246 }, 247 { 248 .name = "Sub", 249 .stream_name = "Sub", 250 .cpu_dai_name = "wm5110-aif3", 251 .codec_dai_name = "wm9081-hifi", 252 .codec_name = "wm9081.1-006c", 253 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF 254 | SND_SOC_DAIFMT_CBS_CFS, 255 .ignore_suspend = 1, 256 .params = &sub_params, 257 }, 258 }; 259 260 static struct snd_soc_codec_conf bells_codec_conf[] = { 261 { 262 .dev_name = "wm9081.1-006c", 263 .name_prefix = "Sub", 264 }, 265 }; 266 267 static struct snd_soc_dapm_route bells_routes[] = { 268 { "Sub CLK_SYS", NULL, "OPCLK" }, 269 }; 270 271 static struct snd_soc_card bells_cards[] = { 272 { 273 .name = "Bells WM5102", 274 .owner = THIS_MODULE, 275 .dai_link = bells_dai_wm5102, 276 .num_links = ARRAY_SIZE(bells_dai_wm5102), 277 .codec_conf = bells_codec_conf, 278 .num_configs = ARRAY_SIZE(bells_codec_conf), 279 280 .late_probe = bells_late_probe, 281 282 .dapm_routes = bells_routes, 283 .num_dapm_routes = ARRAY_SIZE(bells_routes), 284 285 .set_bias_level = bells_set_bias_level, 286 .set_bias_level_post = bells_set_bias_level_post, 287 }, 288 { 289 .name = "Bells WM5110", 290 .owner = THIS_MODULE, 291 .dai_link = bells_dai_wm5110, 292 .num_links = ARRAY_SIZE(bells_dai_wm5110), 293 .codec_conf = bells_codec_conf, 294 .num_configs = ARRAY_SIZE(bells_codec_conf), 295 296 .late_probe = bells_late_probe, 297 298 .dapm_routes = bells_routes, 299 .num_dapm_routes = ARRAY_SIZE(bells_routes), 300 301 .set_bias_level = bells_set_bias_level, 302 .set_bias_level_post = bells_set_bias_level_post, 303 }, 304 }; 305 306 307 static __devinit int bells_probe(struct platform_device *pdev) 308 { 309 int ret; 310 311 bells_cards[pdev->id].dev = &pdev->dev; 312 313 ret = snd_soc_register_card(&bells_cards[pdev->id]); 314 if (ret) { 315 dev_err(&pdev->dev, 316 "snd_soc_register_card(%s) failed: %d\n", 317 bells_cards[pdev->id].name, ret); 318 return ret; 319 } 320 321 return 0; 322 } 323 324 static int __devexit bells_remove(struct platform_device *pdev) 325 { 326 snd_soc_unregister_card(&bells_cards[pdev->id]); 327 328 return 0; 329 } 330 331 static struct platform_driver bells_driver = { 332 .driver = { 333 .name = "bells", 334 .owner = THIS_MODULE, 335 .pm = &snd_soc_pm_ops, 336 }, 337 .probe = bells_probe, 338 .remove = __devexit_p(bells_remove), 339 }; 340 341 module_platform_driver(bells_driver); 342 343 MODULE_DESCRIPTION("Bells audio support"); 344 MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); 345 MODULE_LICENSE("GPL"); 346 MODULE_ALIAS("platform:bells"); 347