linux/sound/soc/atmel/playpaq_wm8510.c
<<
>>
Prefs
   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,
 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