Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0+
0002 //
0003 // Tobermory audio support
0004 //
0005 // Copyright 2011 Wolfson Microelectronics
0006 
0007 #include <sound/soc.h>
0008 #include <sound/soc-dapm.h>
0009 #include <sound/jack.h>
0010 #include <linux/gpio.h>
0011 #include <linux/module.h>
0012 
0013 #include "../codecs/wm8962.h"
0014 
0015 static int sample_rate = 44100;
0016 
0017 static int tobermory_set_bias_level(struct snd_soc_card *card,
0018                       struct snd_soc_dapm_context *dapm,
0019                       enum snd_soc_bias_level level)
0020 {
0021     struct snd_soc_pcm_runtime *rtd;
0022     struct snd_soc_dai *codec_dai;
0023     int ret;
0024 
0025     rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
0026     codec_dai = asoc_rtd_to_codec(rtd, 0);
0027 
0028     if (dapm->dev != codec_dai->dev)
0029         return 0;
0030 
0031     switch (level) {
0032     case SND_SOC_BIAS_PREPARE:
0033         if (dapm->bias_level == SND_SOC_BIAS_STANDBY) {
0034             ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
0035                           WM8962_FLL_MCLK, 32768,
0036                           sample_rate * 512);
0037             if (ret < 0)
0038                 pr_err("Failed to start FLL: %d\n", ret);
0039 
0040             ret = snd_soc_dai_set_sysclk(codec_dai,
0041                              WM8962_SYSCLK_FLL,
0042                              sample_rate * 512,
0043                              SND_SOC_CLOCK_IN);
0044             if (ret < 0) {
0045                 pr_err("Failed to set SYSCLK: %d\n", ret);
0046                 snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
0047                             0, 0, 0);
0048                 return ret;
0049             }
0050         }
0051         break;
0052 
0053     default:
0054         break;
0055     }
0056 
0057     return 0;
0058 }
0059 
0060 static int tobermory_set_bias_level_post(struct snd_soc_card *card,
0061                            struct snd_soc_dapm_context *dapm,
0062                            enum snd_soc_bias_level level)
0063 {
0064     struct snd_soc_pcm_runtime *rtd;
0065     struct snd_soc_dai *codec_dai;
0066     int ret;
0067 
0068     rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
0069     codec_dai = asoc_rtd_to_codec(rtd, 0);
0070 
0071     if (dapm->dev != codec_dai->dev)
0072         return 0;
0073 
0074     switch (level) {
0075     case SND_SOC_BIAS_STANDBY:
0076         ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
0077                          32768, SND_SOC_CLOCK_IN);
0078         if (ret < 0) {
0079             pr_err("Failed to switch away from FLL: %d\n", ret);
0080             return ret;
0081         }
0082 
0083         ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
0084                       0, 0, 0);
0085         if (ret < 0) {
0086             pr_err("Failed to stop FLL: %d\n", ret);
0087             return ret;
0088         }
0089         break;
0090 
0091     default:
0092         break;
0093     }
0094 
0095     dapm->bias_level = level;
0096 
0097     return 0;
0098 }
0099 
0100 static int tobermory_hw_params(struct snd_pcm_substream *substream,
0101                   struct snd_pcm_hw_params *params)
0102 {
0103     sample_rate = params_rate(params);
0104 
0105     return 0;
0106 }
0107 
0108 static const struct snd_soc_ops tobermory_ops = {
0109     .hw_params = tobermory_hw_params,
0110 };
0111 
0112 SND_SOC_DAILINK_DEFS(cpu,
0113     DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")),
0114     DAILINK_COMP_ARRAY(COMP_CODEC("wm8962.1-001a", "wm8962")),
0115     DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
0116 
0117 static struct snd_soc_dai_link tobermory_dai[] = {
0118     {
0119         .name = "CPU",
0120         .stream_name = "CPU",
0121         .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
0122                 | SND_SOC_DAIFMT_CBM_CFM,
0123         .ops = &tobermory_ops,
0124         SND_SOC_DAILINK_REG(cpu),
0125     },
0126 };
0127 
0128 static const struct snd_kcontrol_new controls[] = {
0129     SOC_DAPM_PIN_SWITCH("Main Speaker"),
0130     SOC_DAPM_PIN_SWITCH("DMIC"),
0131 };
0132 
0133 static const struct snd_soc_dapm_widget widgets[] = {
0134     SND_SOC_DAPM_HP("Headphone", NULL),
0135     SND_SOC_DAPM_MIC("Headset Mic", NULL),
0136 
0137     SND_SOC_DAPM_MIC("DMIC", NULL),
0138     SND_SOC_DAPM_MIC("AMIC", NULL),
0139 
0140     SND_SOC_DAPM_SPK("Main Speaker", NULL),
0141 };
0142 
0143 static const struct snd_soc_dapm_route audio_paths[] = {
0144     { "Headphone", NULL, "HPOUTL" },
0145     { "Headphone", NULL, "HPOUTR" },
0146 
0147     { "Main Speaker", NULL, "SPKOUTL" },
0148     { "Main Speaker", NULL, "SPKOUTR" },
0149 
0150     { "Headset Mic", NULL, "MICBIAS" },
0151     { "IN4L", NULL, "Headset Mic" },
0152     { "IN4R", NULL, "Headset Mic" },
0153 
0154     { "AMIC", NULL, "MICBIAS" },
0155     { "IN1L", NULL, "AMIC" },
0156     { "IN1R", NULL, "AMIC" },
0157 
0158     { "DMIC", NULL, "MICBIAS" },
0159     { "DMICDAT", NULL, "DMIC" },
0160 };
0161 
0162 static struct snd_soc_jack tobermory_headset;
0163 
0164 /* Headset jack detection DAPM pins */
0165 static struct snd_soc_jack_pin tobermory_headset_pins[] = {
0166     {
0167         .pin = "Headset Mic",
0168         .mask = SND_JACK_MICROPHONE,
0169     },
0170     {
0171         .pin = "Headphone",
0172         .mask = SND_JACK_MICROPHONE,
0173     },
0174 };
0175 
0176 static int tobermory_late_probe(struct snd_soc_card *card)
0177 {
0178     struct snd_soc_pcm_runtime *rtd;
0179     struct snd_soc_component *component;
0180     struct snd_soc_dai *codec_dai;
0181     int ret;
0182 
0183     rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
0184     component = asoc_rtd_to_codec(rtd, 0)->component;
0185     codec_dai = asoc_rtd_to_codec(rtd, 0);
0186 
0187     ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
0188                      32768, SND_SOC_CLOCK_IN);
0189     if (ret < 0)
0190         return ret;
0191 
0192     ret = snd_soc_card_jack_new_pins(card, "Headset", SND_JACK_HEADSET |
0193                      SND_JACK_BTN_0, &tobermory_headset,
0194                      tobermory_headset_pins,
0195                      ARRAY_SIZE(tobermory_headset_pins));
0196     if (ret)
0197         return ret;
0198 
0199     wm8962_mic_detect(component, &tobermory_headset);
0200 
0201     return 0;
0202 }
0203 
0204 static struct snd_soc_card tobermory = {
0205     .name = "Tobermory",
0206     .owner = THIS_MODULE,
0207     .dai_link = tobermory_dai,
0208     .num_links = ARRAY_SIZE(tobermory_dai),
0209 
0210     .set_bias_level = tobermory_set_bias_level,
0211     .set_bias_level_post = tobermory_set_bias_level_post,
0212 
0213     .controls = controls,
0214     .num_controls = ARRAY_SIZE(controls),
0215     .dapm_widgets = widgets,
0216     .num_dapm_widgets = ARRAY_SIZE(widgets),
0217     .dapm_routes = audio_paths,
0218     .num_dapm_routes = ARRAY_SIZE(audio_paths),
0219     .fully_routed = true,
0220 
0221     .late_probe = tobermory_late_probe,
0222 };
0223 
0224 static int tobermory_probe(struct platform_device *pdev)
0225 {
0226     struct snd_soc_card *card = &tobermory;
0227     int ret;
0228 
0229     card->dev = &pdev->dev;
0230 
0231     ret = devm_snd_soc_register_card(&pdev->dev, card);
0232     if (ret)
0233         dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n");
0234 
0235     return ret;
0236 }
0237 
0238 static struct platform_driver tobermory_driver = {
0239     .driver = {
0240         .name = "tobermory",
0241         .pm = &snd_soc_pm_ops,
0242     },
0243     .probe = tobermory_probe,
0244 };
0245 
0246 module_platform_driver(tobermory_driver);
0247 
0248 MODULE_DESCRIPTION("Tobermory audio support");
0249 MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
0250 MODULE_LICENSE("GPL");
0251 MODULE_ALIAS("platform:tobermory");