1/* sound/soc/at32/playpaq_wm8510.c 2 * ASoC machine driver for PlayPaq using WM8510 codec 3 * 4 * Copyright (C) 2008 Long Range Systems 5 * Geoffrey Wossum <gwossum@acm.org> 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License version 2 as 9 * published by the Free Software Foundation. 10 * 11 * This code is largely inspired by sound/soc/at91/eti_b1_wm8731.c 12 * 13 * NOTE: If you don't have the AT32 enhanced portmux configured (which 14 * isn't currently in the mainline or Atmel patched kernel), you will 15 * need to set the MCLK pin (PA30) to peripheral A in your board initialization 16 * code. Something like: 17 * at32_select_periph(GPIO_PIN_PA(30), GPIO_PERIPH_A, 0); 18 * 19 */ 20 21/* #define DEBUG */ 22 23#include <linux/module.h> 24#include <linux/moduleparam.h> 25#include <linux/kernel.h> 26#include <linux/errno.h> 27#include <linux/clk.h> 28#include <linux/timer.h> 29#include <linux/interrupt.h> 30#include <linux/platform_device.h> 31 32#include <sound/core.h> 33#include <sound/pcm.h> 34#include <sound/pcm_params.h> 35#include <sound/soc.h> 36#include <sound/soc-dapm.h> 37 38#include <mach/at32ap700x.h> 39#include <mach/portmux.h> 40 41#include "../codecs/wm8510.h" 42#include "atmel-pcm.h" 43#include "atmel_ssc_dai.h" 44 45 46/*-------------------------------------------------------------------------*\ 47 * constants 48\*-------------------------------------------------------------------------*/ 49#define MCLK_PIN GPIO_PIN_PA(30) 50#define MCLK_PERIPH GPIO_PERIPH_A 51 52 53/*-------------------------------------------------------------------------*\ 54 * data types 55\*-------------------------------------------------------------------------*/ 56/* SSC clocking data */ 57struct ssc_clock_data { 58 /* CMR div */ 59 unsigned int cmr_div; 60 61 /* Frame period (as needed by xCMR.PERIOD) */ 62 unsigned int period; 63 64 /* The SSC clock rate these settings where calculated for */ 65 unsigned long ssc_rate; 66}; 67 68 69/*-------------------------------------------------------------------------*\ 70 * module data 71\*-------------------------------------------------------------------------*/ 72static struct clk *_gclk0; 73static struct clk *_pll0; 74 75#define CODEC_CLK (_gclk0) 76 77 78/*-------------------------------------------------------------------------*\ 79 * Sound SOC operations 80\*-------------------------------------------------------------------------*/ 81#if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE 82static struct ssc_clock_data playpaq_wm8510_calc_ssc_clock( 83 struct snd_pcm_hw_params *params, 84 struct snd_soc_dai *cpu_dai) 85{ 86 struct at32_ssc_info *ssc_p = cpu_dai->private_data; 87 struct ssc_device *ssc = ssc_p->ssc; 88 struct ssc_clock_data cd; 89 unsigned int rate, width_bits, channels; 90 unsigned int bitrate, ssc_div; 91 unsigned actual_rate; 92 93 94 /* 95 * Figure out required bitrate 96 */ 97 rate = params_rate(params); 98 channels = params_channels(params); 99 width_bits = snd_pcm_format_physical_width(params_format(params)); 100 bitrate = rate * width_bits * channels; 101 102 103 /* 104 * Figure out required SSC divider and period for required bitrate 105 */ 106 cd.ssc_rate = clk_get_rate(ssc->clk); 107 ssc_div = cd.ssc_rate / bitrate; 108 cd.cmr_div = ssc_div / 2; 109 if (ssc_div & 1) { 110 /* round cmr_div up */ 111 cd.cmr_div++; 112 } 113 cd.period = width_bits - 1; 114 115 116 /* 117 * Find actual rate, compare to requested rate 118 */ 119 actual_rate = (cd.ssc_rate / (cd.cmr_div * 2)) / (2 * (cd.period + 1)); 120 pr_debug("playpaq_wm8510: Request rate = %u, actual rate = %u\n", 121 rate, actual_rate); 122 123 124 return cd; 125} 126#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */ 127 128 129 130static int playpaq_wm8510_hw_params(struct snd_pcm_substream *substream, 131 struct snd_pcm_hw_params *params) 132{ 133 struct snd_soc_pcm_runtime *rtd = substream->private_data; 134 struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; 135 struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; 136 struct at32_ssc_info *ssc_p = cpu_dai->private_data; 137 struct ssc_device *ssc = ssc_p->ssc; 138 unsigned int pll_out = 0, bclk = 0, mclk_div = 0; 139 int ret; 140 141 142 /* Due to difficulties with getting the correct clocks from the AT32's 143 * PLL0, we're going to let the CODEC be in charge of all the clocks 144 */ 145#if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE 146 const unsigned int fmt = (SND_SOC_DAIFMT_I2S | 147 SND_SOC_DAIFMT_NB_NF | 148 SND_SOC_DAIFMT_CBM_CFM); 149#else 150 struct ssc_clock_data cd; 151 const unsigned int fmt = (SND_SOC_DAIFMT_I2S | 152 SND_SOC_DAIFMT_NB_NF | 153 SND_SOC_DAIFMT_CBS_CFS); 154#endif 155 156 if (ssc == NULL) { 157 pr_warning("playpaq_wm8510_hw_params: ssc is NULL!\n"); 158 return -EINVAL; 159 } 160 161 162 /* 163 * Figure out PLL and BCLK dividers for WM8510 164 */ 165 switch (params_rate(params)) { 166 case 48000: 167 pll_out = 24576000; 168 mclk_div = WM8510_MCLKDIV_2; 169 bclk = WM8510_BCLKDIV_8; 170 break; 171 172 case 44100: 173 pll_out = 22579200; 174 mclk_div = WM8510_MCLKDIV_2; 175 bclk = WM8510_BCLKDIV_8; 176 break; 177 178 case 22050: 179 pll_out = 22579200; 180 mclk_div = WM8510_MCLKDIV_4; 181 bclk = WM8510_BCLKDIV_8; 182 break; 183 184 case 16000: 185 pll_out = 24576000; 186 mclk_div = WM8510_MCLKDIV_6; 187 bclk = WM8510_BCLKDIV_8; 188 break; 189 190 case 11025: 191 pll_out = 22579200; 192 mclk_div = WM8510_MCLKDIV_8; 193 bclk = WM8510_BCLKDIV_8; 194 break; 195 196 case 8000: 197 pll_out = 24576000; 198 mclk_div = WM8510_MCLKDIV_12; 199 bclk = WM8510_BCLKDIV_8; 200 break; 201 202 default: 203 pr_warning("playpaq_wm8510: Unsupported sample rate %d\n", 204 params_rate(params)); 205 return -EINVAL; 206 } 207 208 209 /* 210 * set CPU and CODEC DAI configuration 211 */ 212 ret = snd_soc_dai_set_fmt(codec_dai, fmt); 213 if (ret < 0) { 214 pr_warning("playpaq_wm8510: " 215 "Failed to set CODEC DAI format (%d)\n", 216 ret); 217 return ret; 218 } 219 ret = snd_soc_dai_set_fmt(cpu_dai, fmt); 220 if (ret < 0) { 221 pr_warning("playpaq_wm8510: " 222 "Failed to set CPU DAI format (%d)\n", 223 ret); 224 return ret; 225 } 226 227 228 /* 229 * Set CPU clock configuration 230 */ 231#if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE 232 cd = playpaq_wm8510_calc_ssc_clock(params, cpu_dai); 233 pr_debug("playpaq_wm8510: cmr_div = %d, period = %d\n", 234 cd.cmr_div, cd.period); 235 ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_CMR_DIV, cd.cmr_div); 236 if (ret < 0) { 237 pr_warning("playpaq_wm8510: Failed to set CPU CMR_DIV (%d)\n", 238 ret); 239 return ret; 240 } 241 ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_TCMR_PERIOD, 242 cd.period); 243 if (ret < 0) { 244 pr_warning("playpaq_wm8510: " 245 "Failed to set CPU transmit period (%d)\n", 246 ret); 247 return ret; 248 } 249#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */ 250 251 252 /* 253 * Set CODEC clock configuration 254 */ 255 pr_debug("playpaq_wm8510: " 256 "pll_in = %ld, pll_out = %u, bclk = %x, mclk = %x\n", 257 clk_get_rate(CODEC_CLK), pll_out, bclk, mclk_div); 258 259 260#if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE 261 ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_BCLKDIV, bclk); 262 if (ret < 0) { 263 pr_warning 264 ("playpaq_wm8510: Failed to set CODEC DAI BCLKDIV (%d)\n", 265 ret); 266 return ret; 267 } 268#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */ 269 270 271 ret = snd_soc_dai_set_pll(codec_dai, 0, 0, 272 clk_get_rate(CODEC_CLK), pll_out); 273 if (ret < 0) { 274 pr_warning("playpaq_wm8510: Failed to set CODEC DAI PLL (%d)\n", 275 ret); 276 return ret; 277 } 278 279 280 ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_MCLKDIV, mclk_div); 281 if (ret < 0) { 282 pr_warning("playpaq_wm8510: Failed to set CODEC MCLKDIV (%d)\n", 283 ret); 284 return ret; 285 } 286 287 288 return 0; 289} 290 291 292 293static struct snd_soc_ops playpaq_wm8510_ops = { 294 .hw_params = playpaq_wm8510_hw_params, 295}; 296 297 298 299static const struct snd_soc_dapm_widget playpaq_dapm_widgets[] = { 300 SND_SOC_DAPM_MIC("Int Mic", NULL), 301 SND_SOC_DAPM_SPK("Ext Spk", NULL), 302}; 303 304 305 306static const struct snd_soc_dapm_route intercon[] = { 307 /* speaker connected to SPKOUT */ 308 {"Ext Spk", NULL, "SPKOUTP"}, 309 {"Ext Spk", NULL, "SPKOUTN"}, 310 311 {"Mic Bias", NULL, "Int Mic"}, 312 {"MICN", NULL, "Mic Bias"}, 313 {"MICP", NULL, "Mic Bias"}, 314}; 315 316 317 318static int playpaq_wm8510_init(struct snd_soc_codec *codec) 319{ 320 int i; 321 322 /* 323 * Add DAPM widgets 324 */ 325 for (i = 0; i < ARRAY_SIZE(playpaq_dapm_widgets); i++) 326 snd_soc_dapm_new_control(codec, &playpaq_dapm_widgets[i]); 327 328 329 330 /* 331 * Setup audio path interconnects 332 */ 333 snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); 334 335 336 337 /* always connected pins */ 338 snd_soc_dapm_enable_pin(codec, "Int Mic"); 339 snd_soc_dapm_enable_pin(codec, "Ext Spk"); 340 snd_soc_dapm_sync(codec); 341 342 343 344 /* Make CSB show PLL rate */ 345 snd_soc_dai_set_clkdiv(codec->dai, WM8510_OPCLKDIV, 346 WM8510_OPCLKDIV_1 | 4); 347 348 return 0; 349} 350 351 352 353static struct snd_soc_dai_link playpaq_wm8510_dai = { 354 .name = "WM8510", 355 .stream_name = "WM8510 PCM", 356 .cpu_dai = &at32_ssc_dai[0], 357 .codec_dai = &wm8510_dai, 358 .init = playpaq_wm8510_init, 359 .ops = &playpaq_wm8510_ops, 360}; 361 362 363 364static struct snd_soc_card snd_soc_playpaq = { 365 .name = "LRS_PlayPaq_WM8510", 366 .platform = &at32_soc_platform, 367 .dai_link = &playpaq_wm8510_dai, 368 .num_links = 1, 369}; 370 371 372 373static struct wm8510_setup_data playpaq_wm8510_setup = { 374 .i2c_bus = 0, 375 .i2c_address = 0x1a, 376}; 377 378 379 380static struct snd_soc_device playpaq_wm8510_snd_devdata = { 381 .card = &snd_soc_playpaq, 382 .codec_dev = &soc_codec_dev_wm8510, 383 .codec_data = &playpaq_wm8510_setup, 384}; 385 386static struct platform_device *playpaq_snd_device; 387 388 389static int __init playpaq_asoc_init(void) 390{ 391 int ret = 0; 392 struct at32_ssc_info *ssc_p = playpaq_wm8510_dai.cpu_dai->private_data; 393 struct ssc_device *ssc = NULL; 394 395 396 /* 397 * Request SSC device 398 */ 399 ssc = ssc_request(0); 400 if (IS_ERR(ssc)) { 401 ret = PTR_ERR(ssc); 402 goto err_ssc; 403 } 404 ssc_p->ssc = ssc; 405 406 407 /* 408 * Configure MCLK for WM8510 409 */ 410 _gclk0 = clk_get(NULL, "gclk0"); 411 if (IS_ERR(_gclk0)) { 412 _gclk0 = NULL; 413 goto err_gclk0; 414 } 415 _pll0 = clk_get(NULL, "pll0"); 416 if (IS_ERR(_pll0)) { 417 _pll0 = NULL; 418 goto err_pll0; 419 } 420 if (clk_set_parent(_gclk0, _pll0)) { 421 pr_warning("snd-soc-playpaq: " 422 "Failed to set PLL0 as parent for DAC clock\n"); 423 goto err_set_clk; 424 } 425 clk_set_rate(CODEC_CLK, 12000000); 426 clk_enable(CODEC_CLK); 427 428#if defined CONFIG_AT32_ENHANCED_PORTMUX 429 at32_select_periph(MCLK_PIN, MCLK_PERIPH, 0); 430#endif 431 432 433 /* 434 * Create and register platform device 435 */ 436 playpaq_snd_device = platform_device_alloc("soc-audio", 0); 437 if (playpaq_snd_device == NULL) { 438 ret = -ENOMEM; 439 goto err_device_alloc; 440 } 441 442 platform_set_drvdata(playpaq_snd_device, &playpaq_wm8510_snd_devdata); 443 playpaq_wm8510_snd_devdata.dev = &playpaq_snd_device->dev; 444 445 ret = platform_device_add(playpaq_snd_device); 446 if (ret) { 447 pr_warning("playpaq_wm8510: platform_device_add failed (%d)\n", 448 ret); 449 goto err_device_add; 450 } 451 452 return 0; 453 454 455err_device_add: 456 if (playpaq_snd_device != NULL) { 457 platform_device_put(playpaq_snd_device); 458 playpaq_snd_device = NULL; 459 } 460err_device_alloc: 461err_set_clk: 462 if (_pll0 != NULL) { 463 clk_put(_pll0); 464 _pll0 = NULL; 465 } 466err_pll0: 467 if (_gclk0 != NULL) { 468 clk_put(_gclk0); 469 _gclk0 = NULL; 470 } 471err_gclk0: 472 ssc_free(ssc); 473err_ssc: 474 return ret; 475} 476 477 478static void __exit playpaq_asoc_exit(void) 479{ 480 struct at32_ssc_info *ssc_p = playpaq_wm8510_dai.cpu_dai->private_data; 481 struct ssc_device *ssc; 482 483 if (ssc_p != NULL) { 484 ssc = ssc_p->ssc; 485 if (ssc != NULL) 486 ssc_free(ssc); 487 ssc_p->ssc = NULL; 488 } 489 490 if (_gclk0 != NULL) { 491 clk_put(_gclk0); 492 _gclk0 = NULL; 493 } 494 if (_pll0 != NULL) { 495 clk_put(_pll0); 496 _pll0 = NULL; 497 } 498 499#if defined CONFIG_AT32_ENHANCED_PORTMUX 500 at32_free_pin(MCLK_PIN); 501#endif 502 503 platform_device_unregister(playpaq_snd_device); 504 playpaq_snd_device = NULL; 505} 506 507module_init(playpaq_asoc_init); 508module_exit(playpaq_asoc_exit); 509 510MODULE_AUTHOR("Geoffrey Wossum <gwossum@acm.org>"); 511MODULE_DESCRIPTION("ASoC machine driver for LRS PlayPaq"); 512MODULE_LICENSE("GPL"); 513