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