1 // SPDX-License-Identifier: GPL-2.0+ 2 // 3 // Tobermory 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/wm8962.h" 14 15 static int sample_rate = 44100; 16 17 static int tobermory_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 *codec_dai; 23 int ret; 24 25 rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); 26 codec_dai = rtd->codec_dai; 27 28 if (dapm->dev != codec_dai->dev) 29 return 0; 30 31 switch (level) { 32 case SND_SOC_BIAS_PREPARE: 33 if (dapm->bias_level == SND_SOC_BIAS_STANDBY) { 34 ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, 35 WM8962_FLL_MCLK, 32768, 36 sample_rate * 512); 37 if (ret < 0) 38 pr_err("Failed to start FLL: %d\n", ret); 39 40 ret = snd_soc_dai_set_sysclk(codec_dai, 41 WM8962_SYSCLK_FLL, 42 sample_rate * 512, 43 SND_SOC_CLOCK_IN); 44 if (ret < 0) { 45 pr_err("Failed to set SYSCLK: %d\n", ret); 46 snd_soc_dai_set_pll(codec_dai, WM8962_FLL, 47 0, 0, 0); 48 return ret; 49 } 50 } 51 break; 52 53 default: 54 break; 55 } 56 57 return 0; 58 } 59 60 static int tobermory_set_bias_level_post(struct snd_soc_card *card, 61 struct snd_soc_dapm_context *dapm, 62 enum snd_soc_bias_level level) 63 { 64 struct snd_soc_pcm_runtime *rtd; 65 struct snd_soc_dai *codec_dai; 66 int ret; 67 68 rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); 69 codec_dai = rtd->codec_dai; 70 71 if (dapm->dev != codec_dai->dev) 72 return 0; 73 74 switch (level) { 75 case SND_SOC_BIAS_STANDBY: 76 ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK, 77 32768, SND_SOC_CLOCK_IN); 78 if (ret < 0) { 79 pr_err("Failed to switch away from FLL: %d\n", ret); 80 return ret; 81 } 82 83 ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, 84 0, 0, 0); 85 if (ret < 0) { 86 pr_err("Failed to stop FLL: %d\n", ret); 87 return ret; 88 } 89 break; 90 91 default: 92 break; 93 } 94 95 dapm->bias_level = level; 96 97 return 0; 98 } 99 100 static int tobermory_hw_params(struct snd_pcm_substream *substream, 101 struct snd_pcm_hw_params *params) 102 { 103 sample_rate = params_rate(params); 104 105 return 0; 106 } 107 108 static struct snd_soc_ops tobermory_ops = { 109 .hw_params = tobermory_hw_params, 110 }; 111 112 static struct snd_soc_dai_link tobermory_dai[] = { 113 { 114 .name = "CPU", 115 .stream_name = "CPU", 116 .cpu_dai_name = "samsung-i2s.0", 117 .codec_dai_name = "wm8962", 118 .platform_name = "samsung-i2s.0", 119 .codec_name = "wm8962.1-001a", 120 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF 121 | SND_SOC_DAIFMT_CBM_CFM, 122 .ops = &tobermory_ops, 123 }, 124 }; 125 126 static const struct snd_kcontrol_new controls[] = { 127 SOC_DAPM_PIN_SWITCH("Main Speaker"), 128 SOC_DAPM_PIN_SWITCH("DMIC"), 129 }; 130 131 static struct snd_soc_dapm_widget widgets[] = { 132 SND_SOC_DAPM_HP("Headphone", NULL), 133 SND_SOC_DAPM_MIC("Headset Mic", NULL), 134 135 SND_SOC_DAPM_MIC("DMIC", NULL), 136 SND_SOC_DAPM_MIC("AMIC", NULL), 137 138 SND_SOC_DAPM_SPK("Main Speaker", NULL), 139 }; 140 141 static struct snd_soc_dapm_route audio_paths[] = { 142 { "Headphone", NULL, "HPOUTL" }, 143 { "Headphone", NULL, "HPOUTR" }, 144 145 { "Main Speaker", NULL, "SPKOUTL" }, 146 { "Main Speaker", NULL, "SPKOUTR" }, 147 148 { "Headset Mic", NULL, "MICBIAS" }, 149 { "IN4L", NULL, "Headset Mic" }, 150 { "IN4R", NULL, "Headset Mic" }, 151 152 { "AMIC", NULL, "MICBIAS" }, 153 { "IN1L", NULL, "AMIC" }, 154 { "IN1R", NULL, "AMIC" }, 155 156 { "DMIC", NULL, "MICBIAS" }, 157 { "DMICDAT", NULL, "DMIC" }, 158 }; 159 160 static struct snd_soc_jack tobermory_headset; 161 162 /* Headset jack detection DAPM pins */ 163 static struct snd_soc_jack_pin tobermory_headset_pins[] = { 164 { 165 .pin = "Headset Mic", 166 .mask = SND_JACK_MICROPHONE, 167 }, 168 { 169 .pin = "Headphone", 170 .mask = SND_JACK_MICROPHONE, 171 }, 172 }; 173 174 static int tobermory_late_probe(struct snd_soc_card *card) 175 { 176 struct snd_soc_pcm_runtime *rtd; 177 struct snd_soc_component *component; 178 struct snd_soc_dai *codec_dai; 179 int ret; 180 181 rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); 182 component = rtd->codec_dai->component; 183 codec_dai = rtd->codec_dai; 184 185 ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK, 186 32768, SND_SOC_CLOCK_IN); 187 if (ret < 0) 188 return ret; 189 190 ret = snd_soc_card_jack_new(card, "Headset", SND_JACK_HEADSET | 191 SND_JACK_BTN_0, &tobermory_headset, 192 tobermory_headset_pins, 193 ARRAY_SIZE(tobermory_headset_pins)); 194 if (ret) 195 return ret; 196 197 wm8962_mic_detect(component, &tobermory_headset); 198 199 return 0; 200 } 201 202 static struct snd_soc_card tobermory = { 203 .name = "Tobermory", 204 .owner = THIS_MODULE, 205 .dai_link = tobermory_dai, 206 .num_links = ARRAY_SIZE(tobermory_dai), 207 208 .set_bias_level = tobermory_set_bias_level, 209 .set_bias_level_post = tobermory_set_bias_level_post, 210 211 .controls = controls, 212 .num_controls = ARRAY_SIZE(controls), 213 .dapm_widgets = widgets, 214 .num_dapm_widgets = ARRAY_SIZE(widgets), 215 .dapm_routes = audio_paths, 216 .num_dapm_routes = ARRAY_SIZE(audio_paths), 217 .fully_routed = true, 218 219 .late_probe = tobermory_late_probe, 220 }; 221 222 static int tobermory_probe(struct platform_device *pdev) 223 { 224 struct snd_soc_card *card = &tobermory; 225 int ret; 226 227 card->dev = &pdev->dev; 228 229 ret = devm_snd_soc_register_card(&pdev->dev, card); 230 if (ret) 231 dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", 232 ret); 233 234 return ret; 235 } 236 237 static struct platform_driver tobermory_driver = { 238 .driver = { 239 .name = "tobermory", 240 .pm = &snd_soc_pm_ops, 241 }, 242 .probe = tobermory_probe, 243 }; 244 245 module_platform_driver(tobermory_driver); 246 247 MODULE_DESCRIPTION("Tobermory audio support"); 248 MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); 249 MODULE_LICENSE("GPL"); 250 MODULE_ALIAS("platform:tobermory"); 251