| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * tegra_soc.c -- SoC audio for tegra | |
| 3 * | |
| 4 * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. | |
| 5 * http://www.nvidia.com | |
| 6 * | |
| 7 * Copyright 2007 Wolfson Microelectronics PLC. | |
| 8 * Author: Graeme Gregory | |
| 9 * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com | |
| 10 * | |
| 11 * This program is free software; you can redistribute it and/or modify it | |
| 12 * under the terms of the GNU General Public License as published by the | |
| 13 * Free Software Foundation; either version 2 of the License, or (at your | |
| 14 * option) any later version. | |
| 15 * | |
| 16 */ | |
| 17 | |
| 18 #include "tegra_soc.h" | |
| 19 #include "../codecs/wm8903.h" | |
| 20 #include <mach/audio.h> | |
| 21 | |
| 22 static struct platform_device *tegra_snd_device; | |
| 23 static int tegra_jack_func; | |
| 24 static int tegra_spk_func; | |
| 25 | |
| 26 #define TEGRA_HP 0 | |
| 27 #define TEGRA_MIC 1 | |
| 28 #define TEGRA_LINE 2 | |
| 29 #define TEGRA_HEADSET 3 | |
| 30 #define TEGRA_HP_OFF 4 | |
| 31 #define TEGRA_SPK_ON 0 | |
| 32 #define TEGRA_SPK_OFF 1 | |
| 33 | |
| 34 /* codec register values */ | |
| 35 #define B07_INEMUTE 7 | |
| 36 #define B06_VOL_M3DB 6 | |
| 37 #define B00_IN_VOL 0 | |
| 38 #define B00_INR_ENA 0 | |
| 39 #define B01_INL_ENA 1 | |
| 40 #define R06_MICBIAS_CTRL_0 6 | |
| 41 #define B07_MICDET_HYST_ENA 7 | |
| 42 #define B04_MICDET_THR 4 | |
| 43 #define B02_MICSHORT_THR 2 | |
| 44 #define B01_MICDET_ENA 1 | |
| 45 #define B00_MICBIAS_ENA 0 | |
| 46 #define B15_DRC_ENA 15 | |
| 47 #define B03_DACL_ENA 3 | |
| 48 #define B02_DACR_ENA 2 | |
| 49 #define B01_ADCL_ENA 1 | |
| 50 #define B00_ADCR_ENA 0 | |
| 51 #define B06_IN_CM_ENA 6 | |
| 52 #define B04_IP_SEL_N 4 | |
| 53 #define B02_IP_SEL_P 2 | |
| 54 #define B00_MODE 0 | |
| 55 #define B06_AIF_ADCL 7 | |
| 56 #define B06_AIF_ADCR 6 | |
| 57 #define B05_ADC_HPF_CUT 5 | |
| 58 #define B04_ADC_HPF_ENA 4 | |
| 59 #define B01_ADCL_DATINV 1 | |
| 60 #define B00_ADCR_DATINV 0 | |
| 61 #define R20_SIDETONE_CTRL 32 | |
| 62 #define R29_DRC_1 41 | |
| 63 #define SET_REG_VAL(r, m, l, v) (((r)&(~((m)<<(l))))|(((v)&(m))<<(l))) | |
| 64 | |
| 65 | |
| 66 static void tegra_ext_control(struct snd_soc_codec *codec) | |
| 67 { | |
| 68 /* set up jack connection */ | |
| 69 switch (tegra_jack_func) { | |
| 70 case TEGRA_HP: | |
| 71 /* set = unmute headphone */ | |
| 72 snd_soc_dapm_enable_pin(codec, "Mic Jack"); | |
| 73 snd_soc_dapm_disable_pin(codec, "Line Jack"); | |
| 74 snd_soc_dapm_enable_pin(codec, "Headphone Jack"); | |
| 75 snd_soc_dapm_disable_pin(codec, "Headset Jack"); | |
| 76 break; | |
| 77 case TEGRA_MIC: | |
| 78 /* reset = mute headphone */ | |
| 79 snd_soc_dapm_enable_pin(codec, "Mic Jack"); | |
| 80 snd_soc_dapm_disable_pin(codec, "Line Jack"); | |
| 81 snd_soc_dapm_disable_pin(codec, "Headphone Jack"); | |
| 82 snd_soc_dapm_disable_pin(codec, "Headset Jack"); | |
| 83 break; | |
| 84 case TEGRA_LINE: | |
| 85 snd_soc_dapm_disable_pin(codec, "Mic Jack"); | |
| 86 snd_soc_dapm_enable_pin(codec, "Line Jack"); | |
| 87 snd_soc_dapm_disable_pin(codec, "Headphone Jack"); | |
| 88 snd_soc_dapm_disable_pin(codec, "Headset Jack"); | |
| 89 break; | |
| 90 case TEGRA_HEADSET: | |
| 91 snd_soc_dapm_enable_pin(codec, "Mic Jack"); | |
| 92 snd_soc_dapm_disable_pin(codec, "Line Jack"); | |
| 93 snd_soc_dapm_disable_pin(codec, "Headphone Jack"); | |
| 94 snd_soc_dapm_enable_pin(codec, "Headset Jack"); | |
| 95 break; | |
| 96 } | |
| 97 | |
| 98 if (tegra_spk_func == TEGRA_SPK_ON) | |
| 99 snd_soc_dapm_enable_pin(codec, "Ext Spk"); | |
| 100 else | |
| 101 snd_soc_dapm_disable_pin(codec, "Ext Spk"); | |
| 102 | |
| 103 /* signal a DAPM event */ | |
| 104 snd_soc_dapm_sync(codec); | |
| 105 } | |
| 106 | |
| 107 static int tegra_hifi_hw_params(struct snd_pcm_substream *substream, | |
| 108 struct snd_pcm_hw_params *params) | |
| 109 { | |
| 110 struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
| 111 struct snd_soc_dai *codec_dai = rtd->codec_dai; | |
| 112 struct snd_soc_dai *cpu_dai = rtd->cpu_dai; | |
| 113 int err; | |
| 114 struct snd_soc_codec *codec = codec_dai->codec; | |
| 115 int ctrl_reg = 0; | |
| 116 | |
| 117 err = snd_soc_dai_set_fmt(codec_dai, | |
| 118 SND_SOC_DAIFMT_I2S | \ | |
| 119 SND_SOC_DAIFMT_NB_NF | \ | |
| 120 SND_SOC_DAIFMT_CBS_CFS); | |
| 121 if (err < 0) { | |
| 122 printk(KERN_ERR "codec_dai fmt not set\n"); | |
| 123 return err; | |
| 124 } | |
| 125 | |
| 126 err = snd_soc_dai_set_fmt(cpu_dai, | |
| 127 SND_SOC_DAIFMT_I2S | \ | |
| 128 SND_SOC_DAIFMT_NB_NF | \ | |
| 129 SND_SOC_DAIFMT_CBS_CFS); | |
| 130 if (err < 0) { | |
| 131 printk(KERN_ERR "cpu_dai fmt not set\n"); | |
| 132 return err; | |
| 133 } | |
| 134 err = snd_soc_dai_set_sysclk(codec_dai, 0, I2S_CLK, SND_SOC_CLOCK_IN); | |
| 135 | |
| 136 if (err < 0) { | |
| 137 printk(KERN_ERR "codec_dai clock not set\n"); | |
| 138 return err; | |
| 139 } | |
| 140 err = snd_soc_dai_set_sysclk(cpu_dai, 0, I2S_CLK, SND_SOC_CLOCK_IN); | |
| 141 | |
| 142 if (err < 0) { | |
| 143 printk(KERN_ERR "cpu_dai clock not set\n"); | |
| 144 return err; | |
| 145 } | |
| 146 | |
| 147 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | |
| 148 return 0; | |
| 149 | |
| 150 snd_soc_write(codec, WM8903_ANALOGUE_LEFT_INPUT_0, 0x7); | |
| 151 snd_soc_write(codec, WM8903_ANALOGUE_RIGHT_INPUT_0, 0x7); | |
| 152 | |
| 153 /* Mic Bias enable */ | |
| 154 ctrl_reg = (0x1 << B00_MICBIAS_ENA) | (0x1 << B01_MICDET_ENA); | |
| 155 snd_soc_write(codec, WM8903_MIC_BIAS_CONTROL_0, ctrl_reg); | |
| 156 | |
| 157 /* Enable DRC */ | |
| 158 ctrl_reg = snd_soc_read(codec, WM8903_DRC_0); | |
| 159 ctrl_reg |= (1 << B15_DRC_ENA); | |
| 160 snd_soc_write(codec, WM8903_DRC_0, ctrl_reg); | |
| 161 | |
| 162 /* Single Ended Mic */ | |
| 163 ctrl_reg = (0x0 << B06_IN_CM_ENA) | (0x0 << B00_MODE) | | |
| 164 (0x0 << B04_IP_SEL_N) | (0x1 << B02_IP_SEL_P); | |
| 165 /* Mic Setting */ | |
| 166 snd_soc_write(codec, WM8903_ANALOGUE_LEFT_INPUT_1, ctrl_reg); | |
| 167 snd_soc_write(codec, WM8903_ANALOGUE_RIGHT_INPUT_1, ctrl_reg); | |
| 168 | |
| 169 /* voulme for single ended mic */ | |
| 170 ctrl_reg = (0x5 << B00_IN_VOL); | |
| 171 snd_soc_write(codec, WM8903_ANALOGUE_LEFT_INPUT_0, ctrl_reg); | |
| 172 snd_soc_write(codec, WM8903_ANALOGUE_RIGHT_INPUT_0, ctrl_reg); | |
| 173 | |
| 174 /* replicate mic setting on both channels */ | |
| 175 ctrl_reg = snd_soc_read(codec, WM8903_AUDIO_INTERFACE_0); | |
| 176 ctrl_reg = SET_REG_VAL(ctrl_reg, 0x1, B06_AIF_ADCR, 0x1); | |
| 177 ctrl_reg = SET_REG_VAL(ctrl_reg, 0x1, B06_AIF_ADCL, 0x1); | |
| 178 snd_soc_write(codec, WM8903_AUDIO_INTERFACE_0, ctrl_reg); | |
| 179 | |
| 180 /* Enable analog inputs */ | |
| 181 ctrl_reg = (0x1 << B01_INL_ENA) | (0x1 << B00_INR_ENA); | |
| 182 snd_soc_write(codec, WM8903_POWER_MANAGEMENT_0, ctrl_reg); | |
| 183 | |
| 184 /* ADC Settings */ | |
| 185 ctrl_reg = snd_soc_read(codec, WM8903_ADC_DIGITAL_0); | |
| 186 ctrl_reg |= (0x1<<B04_ADC_HPF_ENA); | |
| 187 snd_soc_write(codec, WM8903_ADC_DIGITAL_0, ctrl_reg); | |
| 188 | |
| 189 ctrl_reg = 0; | |
| 190 snd_soc_write(codec, R20_SIDETONE_CTRL, ctrl_reg); | |
| 191 | |
| 192 /* Enable ADC */ | |
| 193 ctrl_reg = snd_soc_read(codec, WM8903_POWER_MANAGEMENT_6); | |
| 194 ctrl_reg |= (0x1<<B00_ADCR_ENA)|(0x1<<B01_ADCL_ENA); | |
| 195 snd_soc_write(codec, WM8903_POWER_MANAGEMENT_6, ctrl_reg); | |
| 196 | |
| 197 /* Enable Sidetone */ | |
| 198 ctrl_reg = (0x1 << 2) | (0x2 << 0); | |
| 199 /* sidetone 0 db */ | |
| 200 ctrl_reg |= (12 << 8) | (12 << 4); | |
| 201 snd_soc_write(codec, R20_SIDETONE_CTRL, ctrl_reg); | |
| 202 | |
| 203 ctrl_reg = snd_soc_read(codec, R29_DRC_1); | |
| 204 ctrl_reg |= 0x3; /* mic volume 18 db */ | |
| 205 snd_soc_write(codec, R29_DRC_1, ctrl_reg); | |
| 206 | |
| 207 return 0; | |
| 208 } | |
| 209 | |
| 210 static struct snd_soc_ops tegra_hifi_ops = { | |
| 211 .hw_params = tegra_hifi_hw_params, | |
| 212 }; | |
| 213 | |
| 214 | |
| 215 static int tegra_get_jack(struct snd_kcontrol *kcontrol, | |
| 216 struct snd_ctl_elem_value *ucontrol) | |
| 217 { | |
| 218 ucontrol->value.integer.value[0] = tegra_jack_func; | |
| 219 return 0; | |
| 220 } | |
| 221 | |
| 222 static int tegra_set_jack(struct snd_kcontrol *kcontrol, | |
| 223 struct snd_ctl_elem_value *ucontrol) | |
| 224 { | |
| 225 struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); | |
| 226 | |
| 227 if (tegra_jack_func == ucontrol->value.integer.value[0]) | |
| 228 return 0; | |
| 229 | |
| 230 tegra_jack_func = ucontrol->value.integer.value[0]; | |
| 231 tegra_ext_control(codec); | |
| 232 return 1; | |
| 233 } | |
| 234 | |
| 235 static int tegra_get_spk(struct snd_kcontrol *kcontrol, | |
| 236 struct snd_ctl_elem_value *ucontrol) | |
| 237 { | |
| 238 ucontrol->value.integer.value[0] = tegra_spk_func; | |
| 239 return 0; | |
| 240 } | |
| 241 | |
| 242 static int tegra_set_spk(struct snd_kcontrol *kcontrol, | |
| 243 struct snd_ctl_elem_value *ucontrol) | |
| 244 { | |
| 245 struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); | |
| 246 | |
| 247 | |
| 248 if (tegra_spk_func == ucontrol->value.integer.value[0]) | |
| 249 return 0; | |
| 250 | |
| 251 tegra_spk_func = ucontrol->value.integer.value[0]; | |
| 252 tegra_ext_control(codec); | |
| 253 return 1; | |
| 254 } | |
| 255 | |
| 256 /*tegra machine dapm widgets */ | |
| 257 static const struct snd_soc_dapm_widget wm8903_dapm_widgets[] = { | |
| 258 SND_SOC_DAPM_HP("Headphone Jack", NULL), | |
| 259 SND_SOC_DAPM_MIC("Mic Jack", NULL), | |
| 260 SND_SOC_DAPM_SPK("Ext Spk", NULL), | |
| 261 SND_SOC_DAPM_LINE("Line Jack", NULL), | |
| 262 SND_SOC_DAPM_HP("Headset Jack", NULL), | |
| 263 }; | |
| 264 | |
| 265 /* Tegra machine audio map (connections to the codec pins) */ | |
| 266 static const struct snd_soc_dapm_route audio_map[] = { | |
| 267 | |
| 268 /* headset Jack - in = micin, out = LHPOUT*/ | |
| 269 {"Headset Jack", NULL, "HPOUTL"}, | |
| 270 | |
| 271 /* headphone connected to LHPOUT1, RHPOUT1 */ | |
| 272 {"Headphone Jack", NULL, "HPOUTR"}, {"Headphone Jack", NULL, "HPOUTL"}, | |
| 273 | |
| 274 /* speaker connected to LOUT, ROUT */ | |
| 275 {"Ext Spk", NULL, "LINEOUTR"}, {"Ext Spk", NULL, "LINEOUTL"}, | |
| 276 | |
| 277 /* mic is connected to MICIN (via right channel of headphone jack) */ | |
| 278 {"IN1R", NULL, "Mic Jack"}, | |
| 279 | |
| 280 /* Same as the above but no mic bias for line signals */ | |
| 281 {"IN2L", NULL, "Line Jack"}, | |
| 282 }; | |
| 283 | |
| 284 static const char *jack_function[] = {"Headphone", "Mic", "Line", "Headset", | |
| 285 "Off" | |
| 286 }; | |
| 287 static const char *spk_function[] = {"On", "Off"}; | |
| 288 static const struct soc_enum tegra_enum[] = { | |
| 289 SOC_ENUM_SINGLE_EXT(5, jack_function), | |
| 290 SOC_ENUM_SINGLE_EXT(2, spk_function), | |
| 291 }; | |
| 292 | |
| 293 static const struct snd_kcontrol_new wm8903_tegra_controls[] = { | |
| 294 SOC_ENUM_EXT("Jack Function", tegra_enum[0], tegra_get_jack, | |
| 295 tegra_set_jack), | |
| 296 SOC_ENUM_EXT("Speaker Function", tegra_enum[1], tegra_get_spk, | |
| 297 tegra_set_spk), | |
| 298 }; | |
| 299 | |
| 300 | |
| 301 static int tegra_codec_init(struct snd_soc_pcm_runtime *rtd) | |
| 302 { | |
| 303 struct snd_soc_codec *codec = rtd->codec; | |
| 304 int err; | |
| 305 | |
| 306 /* Add tegra specific controls */ | |
| 307 err = snd_soc_add_controls(codec, wm8903_tegra_controls, | |
| 308 ARRAY_SIZE(wm8903_tegra_controls)); | |
| 309 if (err < 0) | |
| 310 return err; | |
| 311 | |
| 312 /* Add tegra specific widgets */ | |
| 313 snd_soc_dapm_new_controls(codec, wm8903_dapm_widgets, | |
| 314 ARRAY_SIZE(wm8903_dapm_widgets)); | |
| 315 | |
| 316 /* Set up tegra specific audio path audio_map */ | |
| 317 snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); | |
| 318 | |
| 319 /* Default to HP output */ | |
| 320 tegra_jack_func = TEGRA_HP; | |
| 321 tegra_spk_func = TEGRA_SPK_ON; | |
| 322 tegra_ext_control(codec); | |
| 323 | |
| 324 snd_soc_dapm_sync(codec); | |
| 325 | |
| 326 return 0; | |
| 327 } | |
| 328 | |
| 329 static struct snd_soc_dai_link tegra_soc_dai = { | |
| 330 .name = "WM8903", | |
| 331 .stream_name = "WM8903 HiFi", | |
| 332 .cpu_dai_name = "tegra-i2s-dai.0", | |
| 333 .platform_name = "tegra-pcm-audio", | |
| 334 .codec_name = "wm8903-codec.0-001a", | |
| 335 .codec_dai_name = "wm8903-hifi", | |
| 336 .init = tegra_codec_init, | |
| 337 .ops = &tegra_hifi_ops, | |
| 338 }; | |
| 339 | |
| 340 static struct snd_soc_card tegra_snd_soc = { | |
| 341 .name = "tegra", | |
| 342 .dai_link = &tegra_soc_dai, | |
| 343 .num_links = 1, | |
| 344 }; | |
| 345 | |
| 346 static int __init tegra_init(void) | |
| 347 { | |
| 348 int ret; | |
| 349 | |
| 350 tegra_snd_device = platform_device_alloc("soc-audio", -1); | |
| 351 if (!tegra_snd_device) | |
| 352 return -ENOMEM; | |
| 353 | |
| 354 platform_set_drvdata(tegra_snd_device, &tegra_snd_soc); | |
| 355 | |
| 356 ret = platform_device_add(tegra_snd_device); | |
| 357 if (ret) { | |
| 358 printk(KERN_ERR "audio device could not be added\n"); | |
| 359 platform_device_put(tegra_snd_device); | |
| 360 return ret; | |
| 361 } | |
| 362 | |
| 363 return ret; | |
| 364 } | |
| 365 | |
| 366 static void __exit tegra_exit(void) | |
| 367 { | |
| 368 platform_device_unregister(tegra_snd_device); | |
| 369 tegra_snd_device = NULL; | |
| 370 } | |
| 371 | |
| 372 module_init(tegra_init); | |
| 373 module_exit(tegra_exit); | |
| 374 | |
| 375 /* Module information */ | |
| 376 MODULE_DESCRIPTION("Tegra ALSA SoC"); | |
| 377 MODULE_LICENSE("GPL"); | |
| OLD | NEW |