linux/sound/soc/samsung/littlemill.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2//
   3// Littlemill audio support
   4//
   5// Copyright 2011 Wolfson Microelectronics
   6
   7#include <sound/soc.h>
   8#include <sound/soc-dapm.h>
   9#include <sound/jack.h>
  10#include <linux/gpio.h>
  11#include <linux/module.h>
  12
  13#include "../codecs/wm8994.h"
  14
  15static int sample_rate = 44100;
  16
  17static int littlemill_set_bias_level(struct snd_soc_card *card,
  18                                          struct snd_soc_dapm_context *dapm,
  19                                          enum snd_soc_bias_level level)
  20{
  21        struct snd_soc_pcm_runtime *rtd;
  22        struct snd_soc_dai *aif1_dai;
  23        int ret;
  24
  25        rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name);
  26        aif1_dai = rtd->codec_dai;
  27
  28        if (dapm->dev != aif1_dai->dev)
  29                return 0;
  30
  31        switch (level) {
  32        case SND_SOC_BIAS_PREPARE:
  33                /*
  34                 * If we've not already clocked things via hw_params()
  35                 * then do so now, otherwise these are noops.
  36                 */
  37                if (dapm->bias_level == SND_SOC_BIAS_STANDBY) {
  38                        ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1,
  39                                                  WM8994_FLL_SRC_MCLK2, 32768,
  40                                                  sample_rate * 512);
  41                        if (ret < 0) {
  42                                pr_err("Failed to start FLL: %d\n", ret);
  43                                return ret;
  44                        }
  45
  46                        ret = snd_soc_dai_set_sysclk(aif1_dai,
  47                                                     WM8994_SYSCLK_FLL1,
  48                                                     sample_rate * 512,
  49                                                     SND_SOC_CLOCK_IN);
  50                        if (ret < 0) {
  51                                pr_err("Failed to set SYSCLK: %d\n", ret);
  52                                return ret;
  53                        }
  54                }
  55                break;
  56
  57        default:
  58                break;
  59        }
  60
  61        return 0;
  62}
  63
  64static int littlemill_set_bias_level_post(struct snd_soc_card *card,
  65                                               struct snd_soc_dapm_context *dapm,
  66                                               enum snd_soc_bias_level level)
  67{
  68        struct snd_soc_pcm_runtime *rtd;
  69        struct snd_soc_dai *aif1_dai;
  70        int ret;
  71
  72        rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name);
  73        aif1_dai = rtd->codec_dai;
  74
  75        if (dapm->dev != aif1_dai->dev)
  76                return 0;
  77
  78        switch (level) {
  79        case SND_SOC_BIAS_STANDBY:
  80                ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2,
  81                                             32768, SND_SOC_CLOCK_IN);
  82                if (ret < 0) {
  83                        pr_err("Failed to switch away from FLL1: %d\n", ret);
  84                        return ret;
  85                }
  86
  87                ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1,
  88                                          0, 0, 0);
  89                if (ret < 0) {
  90                        pr_err("Failed to stop FLL1: %d\n", ret);
  91                        return ret;
  92                }
  93                break;
  94
  95        default:
  96                break;
  97        }
  98
  99        dapm->bias_level = level;
 100
 101        return 0;
 102}
 103
 104static int littlemill_hw_params(struct snd_pcm_substream *substream,
 105                                struct snd_pcm_hw_params *params)
 106{
 107        struct snd_soc_pcm_runtime *rtd = substream->private_data;
 108        struct snd_soc_dai *codec_dai = rtd->codec_dai;
 109        int ret;
 110
 111        sample_rate = params_rate(params);
 112
 113        ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1,
 114                                  WM8994_FLL_SRC_MCLK2, 32768,
 115                                  sample_rate * 512);
 116        if (ret < 0) {
 117                pr_err("Failed to start FLL: %d\n", ret);
 118                return ret;
 119        }
 120
 121        ret = snd_soc_dai_set_sysclk(codec_dai,
 122                                     WM8994_SYSCLK_FLL1,
 123                                     sample_rate * 512,
 124                                     SND_SOC_CLOCK_IN);
 125        if (ret < 0) {
 126                pr_err("Failed to set SYSCLK: %d\n", ret);
 127                return ret;
 128        }
 129
 130        return 0;
 131}
 132
 133static struct snd_soc_ops littlemill_ops = {
 134        .hw_params = littlemill_hw_params,
 135};
 136
 137static const struct snd_soc_pcm_stream baseband_params = {
 138        .formats = SNDRV_PCM_FMTBIT_S32_LE,
 139        .rate_min = 8000,
 140        .rate_max = 8000,
 141        .channels_min = 2,
 142        .channels_max = 2,
 143};
 144
 145static struct snd_soc_dai_link littlemill_dai[] = {
 146        {
 147                .name = "CPU",
 148                .stream_name = "CPU",
 149                .cpu_dai_name = "samsung-i2s.0",
 150                .codec_dai_name = "wm8994-aif1",
 151                .platform_name = "samsung-i2s.0",
 152                .codec_name = "wm8994-codec",
 153                .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
 154                                | SND_SOC_DAIFMT_CBM_CFM,
 155                .ops = &littlemill_ops,
 156        },
 157        {
 158                .name = "Baseband",
 159                .stream_name = "Baseband",
 160                .cpu_dai_name = "wm8994-aif2",
 161                .codec_dai_name = "wm1250-ev1",
 162                .codec_name = "wm1250-ev1.1-0027",
 163                .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
 164                                | SND_SOC_DAIFMT_CBM_CFM,
 165                .ignore_suspend = 1,
 166                .params = &baseband_params,
 167        },
 168};
 169
 170static int bbclk_ev(struct snd_soc_dapm_widget *w,
 171                    struct snd_kcontrol *kcontrol, int event)
 172{
 173        struct snd_soc_card *card = w->dapm->card;
 174        struct snd_soc_pcm_runtime *rtd;
 175        struct snd_soc_dai *aif2_dai;
 176        int ret;
 177
 178        rtd = snd_soc_get_pcm_runtime(card, card->dai_link[1].name);
 179        aif2_dai = rtd->cpu_dai;
 180
 181        switch (event) {
 182        case SND_SOC_DAPM_PRE_PMU:
 183                ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2,
 184                                          WM8994_FLL_SRC_BCLK, 64 * 8000,
 185                                          8000 * 256);
 186                if (ret < 0) {
 187                        pr_err("Failed to start FLL: %d\n", ret);
 188                        return ret;
 189                }
 190
 191                ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_FLL2,
 192                                             8000 * 256,
 193                                             SND_SOC_CLOCK_IN);
 194                if (ret < 0) {
 195                        pr_err("Failed to set SYSCLK: %d\n", ret);
 196                        return ret;
 197                }
 198                break;
 199        case SND_SOC_DAPM_POST_PMD:
 200                ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_MCLK2,
 201                                             32768, SND_SOC_CLOCK_IN);
 202                if (ret < 0) {
 203                        pr_err("Failed to switch away from FLL2: %d\n", ret);
 204                        return ret;
 205                }
 206
 207                ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2,
 208                                          0, 0, 0);
 209                if (ret < 0) {
 210                        pr_err("Failed to stop FLL2: %d\n", ret);
 211                        return ret;
 212                }
 213                break;
 214        default:
 215                return -EINVAL;
 216        }
 217
 218        return 0;
 219}
 220
 221static const struct snd_kcontrol_new controls[] = {
 222        SOC_DAPM_PIN_SWITCH("WM1250 Input"),
 223        SOC_DAPM_PIN_SWITCH("WM1250 Output"),
 224};
 225
 226static struct snd_soc_dapm_widget widgets[] = {
 227        SND_SOC_DAPM_HP("Headphone", NULL),
 228
 229        SND_SOC_DAPM_MIC("AMIC", NULL),
 230        SND_SOC_DAPM_MIC("DMIC", NULL),
 231
 232        SND_SOC_DAPM_SUPPLY_S("Baseband Clock", -1, SND_SOC_NOPM, 0, 0,
 233                              bbclk_ev,
 234                              SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
 235};
 236
 237static struct snd_soc_dapm_route audio_paths[] = {
 238        { "Headphone", NULL, "HPOUT1L" },
 239        { "Headphone", NULL, "HPOUT1R" },
 240
 241        { "AMIC", NULL, "MICBIAS1" },   /* Default for AMICBIAS jumper */
 242        { "IN1LN", NULL, "AMIC" },
 243
 244        { "DMIC", NULL, "MICBIAS2" },   /* Default for DMICBIAS jumper */
 245        { "DMIC1DAT", NULL, "DMIC" },
 246        { "DMIC2DAT", NULL, "DMIC" },
 247
 248        { "AIF2CLK", NULL, "Baseband Clock" },
 249};
 250
 251static struct snd_soc_jack littlemill_headset;
 252
 253static int littlemill_late_probe(struct snd_soc_card *card)
 254{
 255        struct snd_soc_pcm_runtime *rtd;
 256        struct snd_soc_component *component;
 257        struct snd_soc_dai *aif1_dai;
 258        struct snd_soc_dai *aif2_dai;
 259        int ret;
 260
 261        rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name);
 262        component = rtd->codec_dai->component;
 263        aif1_dai = rtd->codec_dai;
 264
 265        rtd = snd_soc_get_pcm_runtime(card, card->dai_link[1].name);
 266        aif2_dai = rtd->cpu_dai;
 267
 268        ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2,
 269                                     32768, SND_SOC_CLOCK_IN);
 270        if (ret < 0)
 271                return ret;
 272
 273        ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_MCLK2,
 274                                     32768, SND_SOC_CLOCK_IN);
 275        if (ret < 0)
 276                return ret;
 277
 278        ret = snd_soc_card_jack_new(card, "Headset",
 279                                    SND_JACK_HEADSET | SND_JACK_MECHANICAL |
 280                                    SND_JACK_BTN_0 | SND_JACK_BTN_1 |
 281                                    SND_JACK_BTN_2 | SND_JACK_BTN_3 |
 282                                    SND_JACK_BTN_4 | SND_JACK_BTN_5,
 283                                    &littlemill_headset, NULL, 0);
 284        if (ret)
 285                return ret;
 286
 287        /* This will check device compatibility itself */
 288        wm8958_mic_detect(component, &littlemill_headset, NULL, NULL, NULL, NULL);
 289
 290        /* As will this */
 291        wm8994_mic_detect(component, &littlemill_headset, 1);
 292
 293        return 0;
 294}
 295
 296static struct snd_soc_card littlemill = {
 297        .name = "Littlemill",
 298        .owner = THIS_MODULE,
 299        .dai_link = littlemill_dai,
 300        .num_links = ARRAY_SIZE(littlemill_dai),
 301
 302        .set_bias_level = littlemill_set_bias_level,
 303        .set_bias_level_post = littlemill_set_bias_level_post,
 304
 305        .controls = controls,
 306        .num_controls = ARRAY_SIZE(controls),
 307        .dapm_widgets = widgets,
 308        .num_dapm_widgets = ARRAY_SIZE(widgets),
 309        .dapm_routes = audio_paths,
 310        .num_dapm_routes = ARRAY_SIZE(audio_paths),
 311
 312        .late_probe = littlemill_late_probe,
 313};
 314
 315static int littlemill_probe(struct platform_device *pdev)
 316{
 317        struct snd_soc_card *card = &littlemill;
 318        int ret;
 319
 320        card->dev = &pdev->dev;
 321
 322        ret = devm_snd_soc_register_card(&pdev->dev, card);
 323        if (ret)
 324                dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
 325                        ret);
 326
 327        return ret;
 328}
 329
 330static struct platform_driver littlemill_driver = {
 331        .driver = {
 332                .name = "littlemill",
 333                .pm = &snd_soc_pm_ops,
 334        },
 335        .probe = littlemill_probe,
 336};
 337
 338module_platform_driver(littlemill_driver);
 339
 340MODULE_DESCRIPTION("Littlemill audio support");
 341MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
 342MODULE_LICENSE("GPL");
 343MODULE_ALIAS("platform:littlemill");
 344