linux/sound/soc/samsung/bells.c
<<
>>
Prefs
   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
  38struct bells_drvdata {
  39        int sysclk_rate;
  40        int asyncclk_rate;
  41};
  42
  43static struct bells_drvdata wm2200_drvdata = {
  44        .sysclk_rate = 22579200,
  45};
  46
  47static struct bells_drvdata wm5102_drvdata = {
  48        .sysclk_rate = 45158400,
  49        .asyncclk_rate = 49152000,
  50};
  51
  52static struct bells_drvdata wm5110_drvdata = {
  53        .sysclk_rate = 135475200,
  54        .asyncclk_rate = 147456000,
  55};
  56
  57static 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
  98static 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
 137static 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
 214static 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
 222static 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
 230static 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
 254static 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
 300static 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
 346static struct snd_soc_codec_conf bells_codec_conf[] = {
 347        {
 348                .dev_name = "wm9081.1-006c",
 349                .name_prefix = "Sub",
 350        },
 351};
 352
 353static struct snd_soc_dapm_route bells_routes[] = {
 354        { "Sub CLK_SYS", NULL, "OPCLK" },
 355};
 356
 357static struct snd_soc_card bells_cards[] = {
 358        {
 359                .name = "Bells WM2200",
 360                .owner = THIS_MODULE,
 361                .dai_link = bells_dai_wm2200,
 362                .num_links = ARRAY_SIZE(bells_dai_wm2200),
 363                .codec_conf = bells_codec_conf,
 364                .num_configs = ARRAY_SIZE(bells_codec_conf),
 365
 366                .late_probe = bells_late_probe,
 367
 368                .dapm_routes = bells_routes,
 369                .num_dapm_routes = ARRAY_SIZE(bells_routes),
 370
 371                .set_bias_level = bells_set_bias_level,
 372                .set_bias_level_post = bells_set_bias_level_post,
 373
 374                .drvdata = &wm2200_drvdata,
 375        },
 376        {
 377                .name = "Bells WM5102",
 378                .owner = THIS_MODULE,
 379                .dai_link = bells_dai_wm5102,
 380                .num_links = ARRAY_SIZE(bells_dai_wm5102),
 381                .codec_conf = bells_codec_conf,
 382                .num_configs = ARRAY_SIZE(bells_codec_conf),
 383
 384                .late_probe = bells_late_probe,
 385
 386                .dapm_routes = bells_routes,
 387                .num_dapm_routes = ARRAY_SIZE(bells_routes),
 388
 389                .set_bias_level = bells_set_bias_level,
 390                .set_bias_level_post = bells_set_bias_level_post,
 391
 392                .drvdata = &wm5102_drvdata,
 393        },
 394        {
 395                .name = "Bells WM5110",
 396                .owner = THIS_MODULE,
 397                .dai_link = bells_dai_wm5110,
 398                .num_links = ARRAY_SIZE(bells_dai_wm5110),
 399                .codec_conf = bells_codec_conf,
 400                .num_configs = ARRAY_SIZE(bells_codec_conf),
 401
 402                .late_probe = bells_late_probe,
 403
 404                .dapm_routes = bells_routes,
 405                .num_dapm_routes = ARRAY_SIZE(bells_routes),
 406
 407                .set_bias_level = bells_set_bias_level,
 408                .set_bias_level_post = bells_set_bias_level_post,
 409
 410                .drvdata = &wm5110_drvdata,
 411        },
 412};
 413
 414
 415static int bells_probe(struct platform_device *pdev)
 416{
 417        int ret;
 418
 419        bells_cards[pdev->id].dev = &pdev->dev;
 420
 421        ret = snd_soc_register_card(&bells_cards[pdev->id]);
 422        if (ret) {
 423                dev_err(&pdev->dev,
 424                        "snd_soc_register_card(%s) failed: %d\n",
 425                        bells_cards[pdev->id].name, ret);
 426                return ret;
 427        }
 428
 429        return 0;
 430}
 431
 432static int bells_remove(struct platform_device *pdev)
 433{
 434        snd_soc_unregister_card(&bells_cards[pdev->id]);
 435
 436        return 0;
 437}
 438
 439static struct platform_driver bells_driver = {
 440        .driver = {
 441                .name = "bells",
 442                .owner = THIS_MODULE,
 443                .pm = &snd_soc_pm_ops,
 444        },
 445        .probe = bells_probe,
 446        .remove = bells_remove,
 447};
 448
 449module_platform_driver(bells_driver);
 450
 451MODULE_DESCRIPTION("Bells audio support");
 452MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
 453MODULE_LICENSE("GPL");
 454MODULE_ALIAS("platform:bells");
 455