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 /* BCLK2 is fixed at this currently */ 22 #define BCLK2_RATE (64 * 8000) 23 24 /* 25 * Expect a 24.576MHz crystal if one is fitted (the driver will function 26 * if this is not fitted). 27 */ 28 #define MCLK_RATE 24576000 29 30 #define SYS_AUDIO_RATE 44100 31 #define SYS_MCLK_RATE (SYS_AUDIO_RATE * 512) 32 33 #define DAI_AP_DSP 0 34 #define DAI_DSP_CODEC 1 35 #define DAI_CODEC_CP 2 36 #define DAI_CODEC_SUB 3 37 38 struct bells_drvdata { 39 int sysclk_rate; 40 int asyncclk_rate; 41 }; 42 43 static struct bells_drvdata wm2200_drvdata = { 44 .sysclk_rate = 22579200, 45 }; 46 47 static struct bells_drvdata wm5102_drvdata = { 48 .sysclk_rate = 45158400, 49 .asyncclk_rate = 49152000, 50 }; 51 52 static struct bells_drvdata wm5110_drvdata = { 53 .sysclk_rate = 135475200, 54 .asyncclk_rate = 147456000, 55 }; 56 57 static int bells_set_bias_level(struct snd_soc_card *card, 58 struct snd_soc_dapm_context *dapm, 59 enum snd_soc_bias_level level) 60 { 61 struct snd_soc_dai *codec_dai = card->rtd[DAI_DSP_CODEC].codec_dai; 62 struct snd_soc_codec *codec = codec_dai->codec; 63 struct bells_drvdata *bells = card->drvdata; 64 int ret; 65 66 if (dapm->dev != codec_dai->dev) 67 return 0; 68 69 switch (level) { 70 case SND_SOC_BIAS_PREPARE: 71 if (dapm->bias_level != SND_SOC_BIAS_STANDBY) 72 break; 73 74 ret = snd_soc_codec_set_pll(codec, WM5102_FLL1, 75 ARIZONA_FLL_SRC_MCLK1, 76 MCLK_RATE, 77 bells->sysclk_rate); 78 if (ret < 0) 79 pr_err("Failed to start FLL: %d\n", ret); 80 81 if (bells->asyncclk_rate) { 82 ret = snd_soc_codec_set_pll(codec, WM5102_FLL2, 83 ARIZONA_FLL_SRC_AIF2BCLK, 84 BCLK2_RATE, 85 bells->asyncclk_rate); 86 if (ret < 0) 87 pr_err("Failed to start FLL: %d\n", ret); 88 } 89 break; 90 91 default: 92 break; 93 } 94 95 return 0; 96 } 97 98 static int bells_set_bias_level_post(struct snd_soc_card *card, 99 struct snd_soc_dapm_context *dapm, 100 enum snd_soc_bias_level level) 101 { 102 struct snd_soc_dai *codec_dai = card->rtd[DAI_DSP_CODEC].codec_dai; 103 struct snd_soc_codec *codec = codec_dai->codec; 104 struct bells_drvdata *bells = card->drvdata; 105 int ret; 106 107 if (dapm->dev != codec_dai->dev) 108 return 0; 109 110 switch (level) { 111 case SND_SOC_BIAS_STANDBY: 112 ret = snd_soc_codec_set_pll(codec, WM5102_FLL1, 0, 0, 0); 113 if (ret < 0) { 114 pr_err("Failed to stop FLL: %d\n", ret); 115 return ret; 116 } 117 118 if (bells->asyncclk_rate) { 119 ret = snd_soc_codec_set_pll(codec, WM5102_FLL2, 120 0, 0, 0); 121 if (ret < 0) { 122 pr_err("Failed to stop FLL: %d\n", ret); 123 return ret; 124 } 125 } 126 break; 127 128 default: 129 break; 130 } 131 132 dapm->bias_level = level; 133 134 return 0; 135 } 136 137 static int bells_late_probe(struct snd_soc_card *card) 138 { 139 struct bells_drvdata *bells = card->drvdata; 140 struct snd_soc_codec *wm0010 = card->rtd[DAI_AP_DSP].codec; 141 struct snd_soc_codec *codec = card->rtd[DAI_DSP_CODEC].codec; 142 struct snd_soc_dai *aif1_dai = card->rtd[DAI_DSP_CODEC].codec_dai; 143 struct snd_soc_dai *aif2_dai; 144 struct snd_soc_dai *aif3_dai; 145 struct snd_soc_dai *wm9081_dai; 146 int ret; 147 148 ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_SYSCLK, 149 ARIZONA_CLK_SRC_FLL1, 150 bells->sysclk_rate, 151 SND_SOC_CLOCK_IN); 152 if (ret != 0) { 153 dev_err(codec->dev, "Failed to set SYSCLK: %d\n", ret); 154 return ret; 155 } 156 157 ret = snd_soc_codec_set_sysclk(wm0010, 0, 0, SYS_MCLK_RATE, 0); 158 if (ret != 0) { 159 dev_err(wm0010->dev, "Failed to set WM0010 clock: %d\n", ret); 160 return ret; 161 } 162 163 ret = snd_soc_dai_set_sysclk(aif1_dai, ARIZONA_CLK_SYSCLK, 0, 0); 164 if (ret != 0) 165 dev_err(aif1_dai->dev, "Failed to set AIF1 clock: %d\n", ret); 166 167 ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_OPCLK, 0, 168 SYS_MCLK_RATE, SND_SOC_CLOCK_OUT); 169 if (ret != 0) 170 dev_err(codec->dev, "Failed to set OPCLK: %d\n", ret); 171 172 if (card->num_rtd == DAI_CODEC_CP) 173 return 0; 174 175 ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_ASYNCCLK, 176 ARIZONA_CLK_SRC_FLL2, 177 bells->asyncclk_rate, 178 SND_SOC_CLOCK_IN); 179 if (ret != 0) { 180 dev_err(codec->dev, "Failed to set ASYNCCLK: %d\n", ret); 181 return ret; 182 } 183 184 aif2_dai = card->rtd[DAI_CODEC_CP].cpu_dai; 185 186 ret = snd_soc_dai_set_sysclk(aif2_dai, ARIZONA_CLK_ASYNCCLK, 0, 0); 187 if (ret != 0) { 188 dev_err(aif2_dai->dev, "Failed to set AIF2 clock: %d\n", ret); 189 return ret; 190 } 191 192 if (card->num_rtd == DAI_CODEC_SUB) 193 return 0; 194 195 aif3_dai = card->rtd[DAI_CODEC_SUB].cpu_dai; 196 wm9081_dai = card->rtd[DAI_CODEC_SUB].codec_dai; 197 198 ret = snd_soc_dai_set_sysclk(aif3_dai, ARIZONA_CLK_SYSCLK, 0, 0); 199 if (ret != 0) { 200 dev_err(aif1_dai->dev, "Failed to set AIF1 clock: %d\n", ret); 201 return ret; 202 } 203 204 ret = snd_soc_codec_set_sysclk(wm9081_dai->codec, WM9081_SYSCLK_MCLK, 205 0, SYS_MCLK_RATE, 0); 206 if (ret != 0) { 207 dev_err(wm9081_dai->dev, "Failed to set MCLK: %d\n", ret); 208 return ret; 209 } 210 211 return 0; 212 } 213 214 static const struct snd_soc_pcm_stream baseband_params = { 215 .formats = SNDRV_PCM_FMTBIT_S32_LE, 216 .rate_min = 8000, 217 .rate_max = 8000, 218 .channels_min = 2, 219 .channels_max = 2, 220 }; 221 222 static const struct snd_soc_pcm_stream sub_params = { 223 .formats = SNDRV_PCM_FMTBIT_S32_LE, 224 .rate_min = SYS_AUDIO_RATE, 225 .rate_max = SYS_AUDIO_RATE, 226 .channels_min = 2, 227 .channels_max = 2, 228 }; 229 230 static struct snd_soc_dai_link bells_dai_wm2200[] = { 231 { 232 .name = "CPU-DSP", 233 .stream_name = "CPU-DSP", 234 .cpu_dai_name = "samsung-i2s.0", 235 .codec_dai_name = "wm0010-sdi1", 236 .platform_name = "samsung-i2s.0", 237 .codec_name = "spi0.0", 238 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF 239 | SND_SOC_DAIFMT_CBM_CFM, 240 }, 241 { 242 .name = "DSP-CODEC", 243 .stream_name = "DSP-CODEC", 244 .cpu_dai_name = "wm0010-sdi2", 245 .codec_dai_name = "wm2200", 246 .codec_name = "wm2200.1-003a", 247 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF 248 | SND_SOC_DAIFMT_CBM_CFM, 249 .params = &sub_params, 250 .ignore_suspend = 1, 251 }, 252 }; 253 254 static struct snd_soc_dai_link bells_dai_wm5102[] = { 255 { 256 .name = "CPU-DSP", 257 .stream_name = "CPU-DSP", 258 .cpu_dai_name = "samsung-i2s.0", 259 .codec_dai_name = "wm0010-sdi1", 260 .platform_name = "samsung-i2s.0", 261 .codec_name = "spi0.0", 262 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF 263 | SND_SOC_DAIFMT_CBM_CFM, 264 }, 265 { 266 .name = "DSP-CODEC", 267 .stream_name = "DSP-CODEC", 268 .cpu_dai_name = "wm0010-sdi2", 269 .codec_dai_name = "wm5102-aif1", 270 .codec_name = "wm5102-codec", 271 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF 272 | SND_SOC_DAIFMT_CBM_CFM, 273 .params = &sub_params, 274 .ignore_suspend = 1, 275 }, 276 { 277 .name = "Baseband", 278 .stream_name = "Baseband", 279 .cpu_dai_name = "wm5102-aif2", 280 .codec_dai_name = "wm1250-ev1", 281 .codec_name = "wm1250-ev1.1-0027", 282 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF 283 | SND_SOC_DAIFMT_CBM_CFM, 284 .ignore_suspend = 1, 285 .params = &baseband_params, 286 }, 287 { 288 .name = "Sub", 289 .stream_name = "Sub", 290 .cpu_dai_name = "wm5102-aif3", 291 .codec_dai_name = "wm9081-hifi", 292 .codec_name = "wm9081.1-006c", 293 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF 294 | SND_SOC_DAIFMT_CBS_CFS, 295 .ignore_suspend = 1, 296 .params = &sub_params, 297 }, 298 }; 299 300 static struct snd_soc_dai_link bells_dai_wm5110[] = { 301 { 302 .name = "CPU-DSP", 303 .stream_name = "CPU-DSP", 304 .cpu_dai_name = "samsung-i2s.0", 305 .codec_dai_name = "wm0010-sdi1", 306 .platform_name = "samsung-i2s.0", 307 .codec_name = "spi0.0", 308 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF 309 | SND_SOC_DAIFMT_CBM_CFM, 310 }, 311 { 312 .name = "DSP-CODEC", 313 .stream_name = "DSP-CODEC", 314 .cpu_dai_name = "wm0010-sdi2", 315 .codec_dai_name = "wm5110-aif1", 316 .codec_name = "wm5110-codec", 317 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF 318 | SND_SOC_DAIFMT_CBM_CFM, 319 .params = &sub_params, 320 .ignore_suspend = 1, 321 }, 322 { 323 .name = "Baseband", 324 .stream_name = "Baseband", 325 .cpu_dai_name = "wm5110-aif2", 326 .codec_dai_name = "wm1250-ev1", 327 .codec_name = "wm1250-ev1.1-0027", 328 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF 329 | SND_SOC_DAIFMT_CBM_CFM, 330 .ignore_suspend = 1, 331 .params = &baseband_params, 332 }, 333 { 334 .name = "Sub", 335 .stream_name = "Sub", 336 .cpu_dai_name = "wm5110-aif3", 337 .codec_dai_name = "wm9081-hifi", 338 .codec_name = "wm9081.1-006c", 339 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF 340 | SND_SOC_DAIFMT_CBS_CFS, 341 .ignore_suspend = 1, 342 .params = &sub_params, 343 }, 344 }; 345 346 static struct snd_soc_codec_conf bells_codec_conf[] = { 347 { 348 .dev_name = "wm9081.1-006c", 349 .name_prefix = "Sub", 350 }, 351 }; 352 353 static struct snd_soc_dapm_widget bells_widgets[] = { 354 SND_SOC_DAPM_MIC("DMIC", NULL), 355 }; 356 357 static struct snd_soc_dapm_route bells_routes[] = { 358 { "Sub CLK_SYS", NULL, "OPCLK" }, 359 { "CLKIN", NULL, "OPCLK" }, 360 361 { "DMIC", NULL, "MICBIAS2" }, 362 { "IN2L", NULL, "DMIC" }, 363 { "IN2R", NULL, "DMIC" }, 364 }; 365 366 static struct snd_soc_card bells_cards[] = { 367 { 368 .name = "Bells WM2200", 369 .owner = THIS_MODULE, 370 .dai_link = bells_dai_wm2200, 371 .num_links = ARRAY_SIZE(bells_dai_wm2200), 372 .codec_conf = bells_codec_conf, 373 .num_configs = ARRAY_SIZE(bells_codec_conf), 374 375 .late_probe = bells_late_probe, 376 377 .dapm_widgets = bells_widgets, 378 .num_dapm_widgets = ARRAY_SIZE(bells_widgets), 379 .dapm_routes = bells_routes, 380 .num_dapm_routes = ARRAY_SIZE(bells_routes), 381 382 .set_bias_level = bells_set_bias_level, 383 .set_bias_level_post = bells_set_bias_level_post, 384 385 .drvdata = &wm2200_drvdata, 386 }, 387 { 388 .name = "Bells WM5102", 389 .owner = THIS_MODULE, 390 .dai_link = bells_dai_wm5102, 391 .num_links = ARRAY_SIZE(bells_dai_wm5102), 392 .codec_conf = bells_codec_conf, 393 .num_configs = ARRAY_SIZE(bells_codec_conf), 394 395 .late_probe = bells_late_probe, 396 397 .dapm_widgets = bells_widgets, 398 .num_dapm_widgets = ARRAY_SIZE(bells_widgets), 399 .dapm_routes = bells_routes, 400 .num_dapm_routes = ARRAY_SIZE(bells_routes), 401 402 .set_bias_level = bells_set_bias_level, 403 .set_bias_level_post = bells_set_bias_level_post, 404 405 .drvdata = &wm5102_drvdata, 406 }, 407 { 408 .name = "Bells WM5110", 409 .owner = THIS_MODULE, 410 .dai_link = bells_dai_wm5110, 411 .num_links = ARRAY_SIZE(bells_dai_wm5110), 412 .codec_conf = bells_codec_conf, 413 .num_configs = ARRAY_SIZE(bells_codec_conf), 414 415 .late_probe = bells_late_probe, 416 417 .dapm_widgets = bells_widgets, 418 .num_dapm_widgets = ARRAY_SIZE(bells_widgets), 419 .dapm_routes = bells_routes, 420 .num_dapm_routes = ARRAY_SIZE(bells_routes), 421 422 .set_bias_level = bells_set_bias_level, 423 .set_bias_level_post = bells_set_bias_level_post, 424 425 .drvdata = &wm5110_drvdata, 426 }, 427 }; 428 429 430 static int bells_probe(struct platform_device *pdev) 431 { 432 int ret; 433 434 bells_cards[pdev->id].dev = &pdev->dev; 435 436 ret = snd_soc_register_card(&bells_cards[pdev->id]); 437 if (ret) { 438 dev_err(&pdev->dev, 439 "snd_soc_register_card(%s) failed: %d\n", 440 bells_cards[pdev->id].name, ret); 441 return ret; 442 } 443 444 return 0; 445 } 446 447 static int bells_remove(struct platform_device *pdev) 448 { 449 snd_soc_unregister_card(&bells_cards[pdev->id]); 450 451 return 0; 452 } 453 454 static struct platform_driver bells_driver = { 455 .driver = { 456 .name = "bells", 457 .owner = THIS_MODULE, 458 .pm = &snd_soc_pm_ops, 459 }, 460 .probe = bells_probe, 461 .remove = bells_remove, 462 }; 463 464 module_platform_driver(bells_driver); 465 466 MODULE_DESCRIPTION("Bells audio support"); 467 MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); 468 MODULE_LICENSE("GPL"); 469 MODULE_ALIAS("platform:bells"); 470