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