1f2644a2cSMark Brown /* 2f2644a2cSMark Brown * wm8960.c -- WM8960 ALSA SoC Audio driver 3f2644a2cSMark Brown * 4f2644a2cSMark Brown * Author: Liam Girdwood 5f2644a2cSMark Brown * 6f2644a2cSMark Brown * This program is free software; you can redistribute it and/or modify 7f2644a2cSMark Brown * it under the terms of the GNU General Public License version 2 as 8f2644a2cSMark Brown * published by the Free Software Foundation. 9f2644a2cSMark Brown */ 10f2644a2cSMark Brown 11f2644a2cSMark Brown #include <linux/module.h> 12f2644a2cSMark Brown #include <linux/moduleparam.h> 13f2644a2cSMark Brown #include <linux/init.h> 14f2644a2cSMark Brown #include <linux/delay.h> 15f2644a2cSMark Brown #include <linux/pm.h> 16f2644a2cSMark Brown #include <linux/i2c.h> 17f2644a2cSMark Brown #include <linux/platform_device.h> 18f2644a2cSMark Brown #include <sound/core.h> 19f2644a2cSMark Brown #include <sound/pcm.h> 20f2644a2cSMark Brown #include <sound/pcm_params.h> 21f2644a2cSMark Brown #include <sound/soc.h> 22f2644a2cSMark Brown #include <sound/soc-dapm.h> 23f2644a2cSMark Brown #include <sound/initval.h> 24f2644a2cSMark Brown #include <sound/tlv.h> 25f2644a2cSMark Brown 26f2644a2cSMark Brown #include "wm8960.h" 27f2644a2cSMark Brown 28f2644a2cSMark Brown #define AUDIO_NAME "wm8960" 29f2644a2cSMark Brown 30f2644a2cSMark Brown struct snd_soc_codec_device soc_codec_dev_wm8960; 31f2644a2cSMark Brown 32f2644a2cSMark Brown /* R25 - Power 1 */ 33f2644a2cSMark Brown #define WM8960_VREF 0x40 34f2644a2cSMark Brown 35f2644a2cSMark Brown /* R28 - Anti-pop 1 */ 36f2644a2cSMark Brown #define WM8960_POBCTRL 0x80 37f2644a2cSMark Brown #define WM8960_BUFDCOPEN 0x10 38f2644a2cSMark Brown #define WM8960_BUFIOEN 0x08 39f2644a2cSMark Brown #define WM8960_SOFT_ST 0x04 40f2644a2cSMark Brown #define WM8960_HPSTBY 0x01 41f2644a2cSMark Brown 42f2644a2cSMark Brown /* R29 - Anti-pop 2 */ 43f2644a2cSMark Brown #define WM8960_DISOP 0x40 44f2644a2cSMark Brown 45f2644a2cSMark Brown /* 46f2644a2cSMark Brown * wm8960 register cache 47f2644a2cSMark Brown * We can't read the WM8960 register space when we are 48f2644a2cSMark Brown * using 2 wire for device control, so we cache them instead. 49f2644a2cSMark Brown */ 50f2644a2cSMark Brown static const u16 wm8960_reg[WM8960_CACHEREGNUM] = { 51f2644a2cSMark Brown 0x0097, 0x0097, 0x0000, 0x0000, 52f2644a2cSMark Brown 0x0000, 0x0008, 0x0000, 0x000a, 53f2644a2cSMark Brown 0x01c0, 0x0000, 0x00ff, 0x00ff, 54f2644a2cSMark Brown 0x0000, 0x0000, 0x0000, 0x0000, 55f2644a2cSMark Brown 0x0000, 0x007b, 0x0100, 0x0032, 56f2644a2cSMark Brown 0x0000, 0x00c3, 0x00c3, 0x01c0, 57f2644a2cSMark Brown 0x0000, 0x0000, 0x0000, 0x0000, 58f2644a2cSMark Brown 0x0000, 0x0000, 0x0000, 0x0000, 59f2644a2cSMark Brown 0x0100, 0x0100, 0x0050, 0x0050, 60f2644a2cSMark Brown 0x0050, 0x0050, 0x0000, 0x0000, 61f2644a2cSMark Brown 0x0000, 0x0000, 0x0040, 0x0000, 62f2644a2cSMark Brown 0x0000, 0x0050, 0x0050, 0x0000, 63f2644a2cSMark Brown 0x0002, 0x0037, 0x004d, 0x0080, 64f2644a2cSMark Brown 0x0008, 0x0031, 0x0026, 0x00e9, 65f2644a2cSMark Brown }; 66f2644a2cSMark Brown 67f2644a2cSMark Brown struct wm8960_priv { 68f2644a2cSMark Brown u16 reg_cache[WM8960_CACHEREGNUM]; 69f2644a2cSMark Brown struct snd_soc_codec codec; 70f2644a2cSMark Brown }; 71f2644a2cSMark Brown 7217a52fd6SMark Brown #define wm8960_reset(c) snd_soc_write(c, WM8960_RESET, 0) 73f2644a2cSMark Brown 74f2644a2cSMark Brown /* enumerated controls */ 75f2644a2cSMark Brown static const char *wm8960_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; 76f2644a2cSMark Brown static const char *wm8960_polarity[] = {"No Inversion", "Left Inverted", 77f2644a2cSMark Brown "Right Inverted", "Stereo Inversion"}; 78f2644a2cSMark Brown static const char *wm8960_3d_upper_cutoff[] = {"High", "Low"}; 79f2644a2cSMark Brown static const char *wm8960_3d_lower_cutoff[] = {"Low", "High"}; 80f2644a2cSMark Brown static const char *wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"}; 81f2644a2cSMark Brown static const char *wm8960_alcmode[] = {"ALC", "Limiter"}; 82f2644a2cSMark Brown 83f2644a2cSMark Brown static const struct soc_enum wm8960_enum[] = { 84f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_DACCTL1, 1, 4, wm8960_deemph), 85f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity), 86f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity), 87f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff), 88f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_3D, 5, 2, wm8960_3d_lower_cutoff), 89f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_ALC1, 7, 4, wm8960_alcfunc), 90f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode), 91f2644a2cSMark Brown }; 92f2644a2cSMark Brown 93f2644a2cSMark Brown static const DECLARE_TLV_DB_SCALE(adc_tlv, -9700, 50, 0); 94f2644a2cSMark Brown static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 50, 1); 95f2644a2cSMark Brown static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0); 96f2644a2cSMark Brown static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); 97f2644a2cSMark Brown 98f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_snd_controls[] = { 99f2644a2cSMark Brown SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL, 100f2644a2cSMark Brown 0, 63, 0, adc_tlv), 101f2644a2cSMark Brown SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL, 102f2644a2cSMark Brown 6, 1, 0), 103f2644a2cSMark Brown SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL, 104f2644a2cSMark Brown 7, 1, 0), 105f2644a2cSMark Brown 106f2644a2cSMark Brown SOC_DOUBLE_R_TLV("Playback Volume", WM8960_LDAC, WM8960_RDAC, 107f2644a2cSMark Brown 0, 255, 0, dac_tlv), 108f2644a2cSMark Brown 109f2644a2cSMark Brown SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8960_LOUT1, WM8960_ROUT1, 110f2644a2cSMark Brown 0, 127, 0, out_tlv), 111f2644a2cSMark Brown SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8960_LOUT1, WM8960_ROUT1, 112f2644a2cSMark Brown 7, 1, 0), 113f2644a2cSMark Brown 114f2644a2cSMark Brown SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8960_LOUT2, WM8960_ROUT2, 115f2644a2cSMark Brown 0, 127, 0, out_tlv), 116f2644a2cSMark Brown SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8960_LOUT2, WM8960_ROUT2, 117f2644a2cSMark Brown 7, 1, 0), 118f2644a2cSMark Brown SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0), 119f2644a2cSMark Brown SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0), 120f2644a2cSMark Brown 121f2644a2cSMark Brown SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0), 122f2644a2cSMark Brown SOC_ENUM("ADC Polarity", wm8960_enum[1]), 123f2644a2cSMark Brown SOC_ENUM("Playback De-emphasis", wm8960_enum[0]), 124f2644a2cSMark Brown SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0), 125f2644a2cSMark Brown 126f2644a2cSMark Brown SOC_ENUM("DAC Polarity", wm8960_enum[2]), 127f2644a2cSMark Brown 128f2644a2cSMark Brown SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[3]), 129f2644a2cSMark Brown SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[4]), 130f2644a2cSMark Brown SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0), 131f2644a2cSMark Brown SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0), 132f2644a2cSMark Brown 133f2644a2cSMark Brown SOC_ENUM("ALC Function", wm8960_enum[5]), 134f2644a2cSMark Brown SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0), 135f2644a2cSMark Brown SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1), 136f2644a2cSMark Brown SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0), 137f2644a2cSMark Brown SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0), 138f2644a2cSMark Brown SOC_ENUM("ALC Mode", wm8960_enum[6]), 139f2644a2cSMark Brown SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0), 140f2644a2cSMark Brown SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0), 141f2644a2cSMark Brown 142f2644a2cSMark Brown SOC_SINGLE("Noise Gate Threshold", WM8960_NOISEG, 3, 31, 0), 143f2644a2cSMark Brown SOC_SINGLE("Noise Gate Switch", WM8960_NOISEG, 0, 1, 0), 144f2644a2cSMark Brown 145f2644a2cSMark Brown SOC_DOUBLE_R("ADC PCM Capture Volume", WM8960_LINPATH, WM8960_RINPATH, 146f2644a2cSMark Brown 0, 127, 0), 147f2644a2cSMark Brown 148f2644a2cSMark Brown SOC_SINGLE_TLV("Left Output Mixer Boost Bypass Volume", 149f2644a2cSMark Brown WM8960_BYPASS1, 4, 7, 1, bypass_tlv), 150f2644a2cSMark Brown SOC_SINGLE_TLV("Left Output Mixer LINPUT3 Volume", 151f2644a2cSMark Brown WM8960_LOUTMIX, 4, 7, 1, bypass_tlv), 152f2644a2cSMark Brown SOC_SINGLE_TLV("Right Output Mixer Boost Bypass Volume", 153f2644a2cSMark Brown WM8960_BYPASS2, 4, 7, 1, bypass_tlv), 154f2644a2cSMark Brown SOC_SINGLE_TLV("Right Output Mixer RINPUT3 Volume", 155f2644a2cSMark Brown WM8960_ROUTMIX, 4, 7, 1, bypass_tlv), 156f2644a2cSMark Brown }; 157f2644a2cSMark Brown 158f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_lin_boost[] = { 159f2644a2cSMark Brown SOC_DAPM_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0), 160f2644a2cSMark Brown SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LINPATH, 7, 1, 0), 161f2644a2cSMark Brown SOC_DAPM_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0), 162f2644a2cSMark Brown }; 163f2644a2cSMark Brown 164f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_lin[] = { 165f2644a2cSMark Brown SOC_DAPM_SINGLE("Boost Switch", WM8960_LINPATH, 3, 1, 0), 166f2644a2cSMark Brown }; 167f2644a2cSMark Brown 168f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_rin_boost[] = { 169f2644a2cSMark Brown SOC_DAPM_SINGLE("RINPUT2 Switch", WM8960_RINPATH, 6, 1, 0), 170f2644a2cSMark Brown SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_RINPATH, 7, 1, 0), 171f2644a2cSMark Brown SOC_DAPM_SINGLE("RINPUT1 Switch", WM8960_RINPATH, 8, 1, 0), 172f2644a2cSMark Brown }; 173f2644a2cSMark Brown 174f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_rin[] = { 175f2644a2cSMark Brown SOC_DAPM_SINGLE("Boost Switch", WM8960_RINPATH, 3, 1, 0), 176f2644a2cSMark Brown }; 177f2644a2cSMark Brown 178f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_loutput_mixer[] = { 179f2644a2cSMark Brown SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_LOUTMIX, 8, 1, 0), 180f2644a2cSMark Brown SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LOUTMIX, 7, 1, 0), 181f2644a2cSMark Brown SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS1, 7, 1, 0), 182f2644a2cSMark Brown }; 183f2644a2cSMark Brown 184f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_routput_mixer[] = { 185f2644a2cSMark Brown SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_ROUTMIX, 8, 1, 0), 186f2644a2cSMark Brown SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_ROUTMIX, 7, 1, 0), 187f2644a2cSMark Brown SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS2, 7, 1, 0), 188f2644a2cSMark Brown }; 189f2644a2cSMark Brown 190f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_mono_out[] = { 191f2644a2cSMark Brown SOC_DAPM_SINGLE("Left Switch", WM8960_MONOMIX1, 7, 1, 0), 192f2644a2cSMark Brown SOC_DAPM_SINGLE("Right Switch", WM8960_MONOMIX2, 7, 1, 0), 193f2644a2cSMark Brown }; 194f2644a2cSMark Brown 195f2644a2cSMark Brown static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = { 196f2644a2cSMark Brown SND_SOC_DAPM_INPUT("LINPUT1"), 197f2644a2cSMark Brown SND_SOC_DAPM_INPUT("RINPUT1"), 198f2644a2cSMark Brown SND_SOC_DAPM_INPUT("LINPUT2"), 199f2644a2cSMark Brown SND_SOC_DAPM_INPUT("RINPUT2"), 200f2644a2cSMark Brown SND_SOC_DAPM_INPUT("LINPUT3"), 201f2644a2cSMark Brown SND_SOC_DAPM_INPUT("RINPUT3"), 202f2644a2cSMark Brown 203f2644a2cSMark Brown SND_SOC_DAPM_MICBIAS("MICB", WM8960_POWER1, 1, 0), 204f2644a2cSMark Brown 205f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0, 206f2644a2cSMark Brown wm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)), 207f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8960_POWER1, 4, 0, 208f2644a2cSMark Brown wm8960_rin_boost, ARRAY_SIZE(wm8960_rin_boost)), 209f2644a2cSMark Brown 210f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0, 211f2644a2cSMark Brown wm8960_lin, ARRAY_SIZE(wm8960_lin)), 212f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Right Input Mixer", WM8960_POWER3, 4, 0, 213f2644a2cSMark Brown wm8960_rin, ARRAY_SIZE(wm8960_rin)), 214f2644a2cSMark Brown 215f2644a2cSMark Brown SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER2, 3, 0), 216f2644a2cSMark Brown SND_SOC_DAPM_ADC("Right ADC", "Capture", WM8960_POWER2, 2, 0), 217f2644a2cSMark Brown 218f2644a2cSMark Brown SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0), 219f2644a2cSMark Brown SND_SOC_DAPM_DAC("Right DAC", "Playback", WM8960_POWER2, 7, 0), 220f2644a2cSMark Brown 221f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0, 222f2644a2cSMark Brown &wm8960_loutput_mixer[0], 223f2644a2cSMark Brown ARRAY_SIZE(wm8960_loutput_mixer)), 224f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0, 225f2644a2cSMark Brown &wm8960_routput_mixer[0], 226f2644a2cSMark Brown ARRAY_SIZE(wm8960_routput_mixer)), 227f2644a2cSMark Brown 228f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0, 229f2644a2cSMark Brown &wm8960_mono_out[0], 230f2644a2cSMark Brown ARRAY_SIZE(wm8960_mono_out)), 231f2644a2cSMark Brown 232f2644a2cSMark Brown SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0), 233f2644a2cSMark Brown SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0), 234f2644a2cSMark Brown 235f2644a2cSMark Brown SND_SOC_DAPM_PGA("Left Speaker PGA", WM8960_POWER2, 4, 0, NULL, 0), 236f2644a2cSMark Brown SND_SOC_DAPM_PGA("Right Speaker PGA", WM8960_POWER2, 3, 0, NULL, 0), 237f2644a2cSMark Brown 238f2644a2cSMark Brown SND_SOC_DAPM_PGA("Right Speaker Output", WM8960_CLASSD1, 7, 0, NULL, 0), 239f2644a2cSMark Brown SND_SOC_DAPM_PGA("Left Speaker Output", WM8960_CLASSD1, 6, 0, NULL, 0), 240f2644a2cSMark Brown 241f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("SPK_LP"), 242f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("SPK_LN"), 243f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("HP_L"), 244f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("HP_R"), 245f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("SPK_RP"), 246f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("SPK_RN"), 247f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("OUT3"), 248f2644a2cSMark Brown }; 249f2644a2cSMark Brown 250f2644a2cSMark Brown static const struct snd_soc_dapm_route audio_paths[] = { 251f2644a2cSMark Brown { "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" }, 252f2644a2cSMark Brown { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" }, 253f2644a2cSMark Brown { "Left Boost Mixer", "LINPUT3 Switch", "LINPUT3" }, 254f2644a2cSMark Brown 255f2644a2cSMark Brown { "Left Input Mixer", "Boost Switch", "Left Boost Mixer", }, 256f2644a2cSMark Brown { "Left Input Mixer", NULL, "LINPUT1", }, /* Really Boost Switch */ 257f2644a2cSMark Brown { "Left Input Mixer", NULL, "LINPUT2" }, 258f2644a2cSMark Brown { "Left Input Mixer", NULL, "LINPUT3" }, 259f2644a2cSMark Brown 260f2644a2cSMark Brown { "Right Boost Mixer", "RINPUT1 Switch", "RINPUT1" }, 261f2644a2cSMark Brown { "Right Boost Mixer", "RINPUT2 Switch", "RINPUT2" }, 262f2644a2cSMark Brown { "Right Boost Mixer", "RINPUT3 Switch", "RINPUT3" }, 263f2644a2cSMark Brown 264f2644a2cSMark Brown { "Right Input Mixer", "Boost Switch", "Right Boost Mixer", }, 265f2644a2cSMark Brown { "Right Input Mixer", NULL, "RINPUT1", }, /* Really Boost Switch */ 266f2644a2cSMark Brown { "Right Input Mixer", NULL, "RINPUT2" }, 267f2644a2cSMark Brown { "Right Input Mixer", NULL, "LINPUT3" }, 268f2644a2cSMark Brown 269f2644a2cSMark Brown { "Left ADC", NULL, "Left Input Mixer" }, 270f2644a2cSMark Brown { "Right ADC", NULL, "Right Input Mixer" }, 271f2644a2cSMark Brown 272f2644a2cSMark Brown { "Left Output Mixer", "LINPUT3 Switch", "LINPUT3" }, 273f2644a2cSMark Brown { "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer"} , 274f2644a2cSMark Brown { "Left Output Mixer", "PCM Playback Switch", "Left DAC" }, 275f2644a2cSMark Brown 276f2644a2cSMark Brown { "Right Output Mixer", "RINPUT3 Switch", "RINPUT3" }, 277f2644a2cSMark Brown { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" } , 278f2644a2cSMark Brown { "Right Output Mixer", "PCM Playback Switch", "Right DAC" }, 279f2644a2cSMark Brown 280f2644a2cSMark Brown { "Mono Output Mixer", "Left Switch", "Left Output Mixer" }, 281f2644a2cSMark Brown { "Mono Output Mixer", "Right Switch", "Right Output Mixer" }, 282f2644a2cSMark Brown 283f2644a2cSMark Brown { "LOUT1 PGA", NULL, "Left Output Mixer" }, 284f2644a2cSMark Brown { "ROUT1 PGA", NULL, "Right Output Mixer" }, 285f2644a2cSMark Brown 286f2644a2cSMark Brown { "HP_L", NULL, "LOUT1 PGA" }, 287f2644a2cSMark Brown { "HP_R", NULL, "ROUT1 PGA" }, 288f2644a2cSMark Brown 289f2644a2cSMark Brown { "Left Speaker PGA", NULL, "Left Output Mixer" }, 290f2644a2cSMark Brown { "Right Speaker PGA", NULL, "Right Output Mixer" }, 291f2644a2cSMark Brown 292f2644a2cSMark Brown { "Left Speaker Output", NULL, "Left Speaker PGA" }, 293f2644a2cSMark Brown { "Right Speaker Output", NULL, "Right Speaker PGA" }, 294f2644a2cSMark Brown 295f2644a2cSMark Brown { "SPK_LN", NULL, "Left Speaker Output" }, 296f2644a2cSMark Brown { "SPK_LP", NULL, "Left Speaker Output" }, 297f2644a2cSMark Brown { "SPK_RN", NULL, "Right Speaker Output" }, 298f2644a2cSMark Brown { "SPK_RP", NULL, "Right Speaker Output" }, 299f2644a2cSMark Brown 300f2644a2cSMark Brown { "OUT3", NULL, "Mono Output Mixer", } 301f2644a2cSMark Brown }; 302f2644a2cSMark Brown 303f2644a2cSMark Brown static int wm8960_add_widgets(struct snd_soc_codec *codec) 304f2644a2cSMark Brown { 305f2644a2cSMark Brown snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets, 306f2644a2cSMark Brown ARRAY_SIZE(wm8960_dapm_widgets)); 307f2644a2cSMark Brown 308f2644a2cSMark Brown snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); 309f2644a2cSMark Brown 310f2644a2cSMark Brown snd_soc_dapm_new_widgets(codec); 311f2644a2cSMark Brown return 0; 312f2644a2cSMark Brown } 313f2644a2cSMark Brown 314f2644a2cSMark Brown static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai, 315f2644a2cSMark Brown unsigned int fmt) 316f2644a2cSMark Brown { 317f2644a2cSMark Brown struct snd_soc_codec *codec = codec_dai->codec; 318f2644a2cSMark Brown u16 iface = 0; 319f2644a2cSMark Brown 320f2644a2cSMark Brown /* set master/slave audio interface */ 321f2644a2cSMark Brown switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { 322f2644a2cSMark Brown case SND_SOC_DAIFMT_CBM_CFM: 323f2644a2cSMark Brown iface |= 0x0040; 324f2644a2cSMark Brown break; 325f2644a2cSMark Brown case SND_SOC_DAIFMT_CBS_CFS: 326f2644a2cSMark Brown break; 327f2644a2cSMark Brown default: 328f2644a2cSMark Brown return -EINVAL; 329f2644a2cSMark Brown } 330f2644a2cSMark Brown 331f2644a2cSMark Brown /* interface format */ 332f2644a2cSMark Brown switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 333f2644a2cSMark Brown case SND_SOC_DAIFMT_I2S: 334f2644a2cSMark Brown iface |= 0x0002; 335f2644a2cSMark Brown break; 336f2644a2cSMark Brown case SND_SOC_DAIFMT_RIGHT_J: 337f2644a2cSMark Brown break; 338f2644a2cSMark Brown case SND_SOC_DAIFMT_LEFT_J: 339f2644a2cSMark Brown iface |= 0x0001; 340f2644a2cSMark Brown break; 341f2644a2cSMark Brown case SND_SOC_DAIFMT_DSP_A: 342f2644a2cSMark Brown iface |= 0x0003; 343f2644a2cSMark Brown break; 344f2644a2cSMark Brown case SND_SOC_DAIFMT_DSP_B: 345f2644a2cSMark Brown iface |= 0x0013; 346f2644a2cSMark Brown break; 347f2644a2cSMark Brown default: 348f2644a2cSMark Brown return -EINVAL; 349f2644a2cSMark Brown } 350f2644a2cSMark Brown 351f2644a2cSMark Brown /* clock inversion */ 352f2644a2cSMark Brown switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 353f2644a2cSMark Brown case SND_SOC_DAIFMT_NB_NF: 354f2644a2cSMark Brown break; 355f2644a2cSMark Brown case SND_SOC_DAIFMT_IB_IF: 356f2644a2cSMark Brown iface |= 0x0090; 357f2644a2cSMark Brown break; 358f2644a2cSMark Brown case SND_SOC_DAIFMT_IB_NF: 359f2644a2cSMark Brown iface |= 0x0080; 360f2644a2cSMark Brown break; 361f2644a2cSMark Brown case SND_SOC_DAIFMT_NB_IF: 362f2644a2cSMark Brown iface |= 0x0010; 363f2644a2cSMark Brown break; 364f2644a2cSMark Brown default: 365f2644a2cSMark Brown return -EINVAL; 366f2644a2cSMark Brown } 367f2644a2cSMark Brown 368f2644a2cSMark Brown /* set iface */ 36917a52fd6SMark Brown snd_soc_write(codec, WM8960_IFACE1, iface); 370f2644a2cSMark Brown return 0; 371f2644a2cSMark Brown } 372f2644a2cSMark Brown 373f2644a2cSMark Brown static int wm8960_hw_params(struct snd_pcm_substream *substream, 374f2644a2cSMark Brown struct snd_pcm_hw_params *params, 375f2644a2cSMark Brown struct snd_soc_dai *dai) 376f2644a2cSMark Brown { 377f2644a2cSMark Brown struct snd_soc_pcm_runtime *rtd = substream->private_data; 378f2644a2cSMark Brown struct snd_soc_device *socdev = rtd->socdev; 379f2644a2cSMark Brown struct snd_soc_codec *codec = socdev->card->codec; 38017a52fd6SMark Brown u16 iface = snd_soc_read(codec, WM8960_IFACE1) & 0xfff3; 381f2644a2cSMark Brown 382f2644a2cSMark Brown /* bit size */ 383f2644a2cSMark Brown switch (params_format(params)) { 384f2644a2cSMark Brown case SNDRV_PCM_FORMAT_S16_LE: 385f2644a2cSMark Brown break; 386f2644a2cSMark Brown case SNDRV_PCM_FORMAT_S20_3LE: 387f2644a2cSMark Brown iface |= 0x0004; 388f2644a2cSMark Brown break; 389f2644a2cSMark Brown case SNDRV_PCM_FORMAT_S24_LE: 390f2644a2cSMark Brown iface |= 0x0008; 391f2644a2cSMark Brown break; 392f2644a2cSMark Brown } 393f2644a2cSMark Brown 394f2644a2cSMark Brown /* set iface */ 39517a52fd6SMark Brown snd_soc_write(codec, WM8960_IFACE1, iface); 396f2644a2cSMark Brown return 0; 397f2644a2cSMark Brown } 398f2644a2cSMark Brown 399f2644a2cSMark Brown static int wm8960_mute(struct snd_soc_dai *dai, int mute) 400f2644a2cSMark Brown { 401f2644a2cSMark Brown struct snd_soc_codec *codec = dai->codec; 40217a52fd6SMark Brown u16 mute_reg = snd_soc_read(codec, WM8960_DACCTL1) & 0xfff7; 403f2644a2cSMark Brown 404f2644a2cSMark Brown if (mute) 40517a52fd6SMark Brown snd_soc_write(codec, WM8960_DACCTL1, mute_reg | 0x8); 406f2644a2cSMark Brown else 40717a52fd6SMark Brown snd_soc_write(codec, WM8960_DACCTL1, mute_reg); 408f2644a2cSMark Brown return 0; 409f2644a2cSMark Brown } 410f2644a2cSMark Brown 411f2644a2cSMark Brown static int wm8960_set_bias_level(struct snd_soc_codec *codec, 412f2644a2cSMark Brown enum snd_soc_bias_level level) 413f2644a2cSMark Brown { 414f2644a2cSMark Brown struct wm8960_data *pdata = codec->dev->platform_data; 415f2644a2cSMark Brown u16 reg; 416f2644a2cSMark Brown 417f2644a2cSMark Brown switch (level) { 418f2644a2cSMark Brown case SND_SOC_BIAS_ON: 419f2644a2cSMark Brown break; 420f2644a2cSMark Brown 421f2644a2cSMark Brown case SND_SOC_BIAS_PREPARE: 422f2644a2cSMark Brown /* Set VMID to 2x50k */ 42317a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_POWER1); 424f2644a2cSMark Brown reg &= ~0x180; 425f2644a2cSMark Brown reg |= 0x80; 42617a52fd6SMark Brown snd_soc_write(codec, WM8960_POWER1, reg); 427f2644a2cSMark Brown break; 428f2644a2cSMark Brown 429f2644a2cSMark Brown case SND_SOC_BIAS_STANDBY: 430f2644a2cSMark Brown if (codec->bias_level == SND_SOC_BIAS_OFF) { 431f2644a2cSMark Brown /* Enable anti-pop features */ 43217a52fd6SMark Brown snd_soc_write(codec, WM8960_APOP1, 433f2644a2cSMark Brown WM8960_POBCTRL | WM8960_SOFT_ST | 434f2644a2cSMark Brown WM8960_BUFDCOPEN | WM8960_BUFIOEN); 435f2644a2cSMark Brown 436f2644a2cSMark Brown /* Discharge HP output */ 437f2644a2cSMark Brown reg = WM8960_DISOP; 438f2644a2cSMark Brown if (pdata) 439f2644a2cSMark Brown reg |= pdata->dres << 4; 44017a52fd6SMark Brown snd_soc_write(codec, WM8960_APOP2, reg); 441f2644a2cSMark Brown 442f2644a2cSMark Brown msleep(400); 443f2644a2cSMark Brown 44417a52fd6SMark Brown snd_soc_write(codec, WM8960_APOP2, 0); 445f2644a2cSMark Brown 446f2644a2cSMark Brown /* Enable & ramp VMID at 2x50k */ 44717a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_POWER1); 448f2644a2cSMark Brown reg |= 0x80; 44917a52fd6SMark Brown snd_soc_write(codec, WM8960_POWER1, reg); 450f2644a2cSMark Brown msleep(100); 451f2644a2cSMark Brown 452f2644a2cSMark Brown /* Enable VREF */ 45317a52fd6SMark Brown snd_soc_write(codec, WM8960_POWER1, reg | WM8960_VREF); 454f2644a2cSMark Brown 455f2644a2cSMark Brown /* Disable anti-pop features */ 45617a52fd6SMark Brown snd_soc_write(codec, WM8960_APOP1, WM8960_BUFIOEN); 457f2644a2cSMark Brown } 458f2644a2cSMark Brown 459f2644a2cSMark Brown /* Set VMID to 2x250k */ 46017a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_POWER1); 461f2644a2cSMark Brown reg &= ~0x180; 462f2644a2cSMark Brown reg |= 0x100; 46317a52fd6SMark Brown snd_soc_write(codec, WM8960_POWER1, reg); 464f2644a2cSMark Brown break; 465f2644a2cSMark Brown 466f2644a2cSMark Brown case SND_SOC_BIAS_OFF: 467f2644a2cSMark Brown /* Enable anti-pop features */ 46817a52fd6SMark Brown snd_soc_write(codec, WM8960_APOP1, 469f2644a2cSMark Brown WM8960_POBCTRL | WM8960_SOFT_ST | 470f2644a2cSMark Brown WM8960_BUFDCOPEN | WM8960_BUFIOEN); 471f2644a2cSMark Brown 472f2644a2cSMark Brown /* Disable VMID and VREF, let them discharge */ 47317a52fd6SMark Brown snd_soc_write(codec, WM8960_POWER1, 0); 474f2644a2cSMark Brown msleep(600); 475f2644a2cSMark Brown 47617a52fd6SMark Brown snd_soc_write(codec, WM8960_APOP1, 0); 477f2644a2cSMark Brown break; 478f2644a2cSMark Brown } 479f2644a2cSMark Brown 480f2644a2cSMark Brown codec->bias_level = level; 481f2644a2cSMark Brown 482f2644a2cSMark Brown return 0; 483f2644a2cSMark Brown } 484f2644a2cSMark Brown 485f2644a2cSMark Brown /* PLL divisors */ 486f2644a2cSMark Brown struct _pll_div { 487f2644a2cSMark Brown u32 pre_div:1; 488f2644a2cSMark Brown u32 n:4; 489f2644a2cSMark Brown u32 k:24; 490f2644a2cSMark Brown }; 491f2644a2cSMark Brown 492f2644a2cSMark Brown /* The size in bits of the pll divide multiplied by 10 493f2644a2cSMark Brown * to allow rounding later */ 494f2644a2cSMark Brown #define FIXED_PLL_SIZE ((1 << 24) * 10) 495f2644a2cSMark Brown 496f2644a2cSMark Brown static int pll_factors(unsigned int source, unsigned int target, 497f2644a2cSMark Brown struct _pll_div *pll_div) 498f2644a2cSMark Brown { 499f2644a2cSMark Brown unsigned long long Kpart; 500f2644a2cSMark Brown unsigned int K, Ndiv, Nmod; 501f2644a2cSMark Brown 502f2644a2cSMark Brown pr_debug("WM8960 PLL: setting %dHz->%dHz\n", source, target); 503f2644a2cSMark Brown 504f2644a2cSMark Brown /* Scale up target to PLL operating frequency */ 505f2644a2cSMark Brown target *= 4; 506f2644a2cSMark Brown 507f2644a2cSMark Brown Ndiv = target / source; 508f2644a2cSMark Brown if (Ndiv < 6) { 509f2644a2cSMark Brown source >>= 1; 510f2644a2cSMark Brown pll_div->pre_div = 1; 511f2644a2cSMark Brown Ndiv = target / source; 512f2644a2cSMark Brown } else 513f2644a2cSMark Brown pll_div->pre_div = 0; 514f2644a2cSMark Brown 515f2644a2cSMark Brown if ((Ndiv < 6) || (Ndiv > 12)) { 516f2644a2cSMark Brown pr_err("WM8960 PLL: Unsupported N=%d\n", Ndiv); 517f2644a2cSMark Brown return -EINVAL; 518f2644a2cSMark Brown } 519f2644a2cSMark Brown 520f2644a2cSMark Brown pll_div->n = Ndiv; 521f2644a2cSMark Brown Nmod = target % source; 522f2644a2cSMark Brown Kpart = FIXED_PLL_SIZE * (long long)Nmod; 523f2644a2cSMark Brown 524f2644a2cSMark Brown do_div(Kpart, source); 525f2644a2cSMark Brown 526f2644a2cSMark Brown K = Kpart & 0xFFFFFFFF; 527f2644a2cSMark Brown 528f2644a2cSMark Brown /* Check if we need to round */ 529f2644a2cSMark Brown if ((K % 10) >= 5) 530f2644a2cSMark Brown K += 5; 531f2644a2cSMark Brown 532f2644a2cSMark Brown /* Move down to proper range now rounding is done */ 533f2644a2cSMark Brown K /= 10; 534f2644a2cSMark Brown 535f2644a2cSMark Brown pll_div->k = K; 536f2644a2cSMark Brown 537f2644a2cSMark Brown pr_debug("WM8960 PLL: N=%x K=%x pre_div=%d\n", 538f2644a2cSMark Brown pll_div->n, pll_div->k, pll_div->pre_div); 539f2644a2cSMark Brown 540f2644a2cSMark Brown return 0; 541f2644a2cSMark Brown } 542f2644a2cSMark Brown 543f2644a2cSMark Brown static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, 544f2644a2cSMark Brown int pll_id, unsigned int freq_in, unsigned int freq_out) 545f2644a2cSMark Brown { 546f2644a2cSMark Brown struct snd_soc_codec *codec = codec_dai->codec; 547f2644a2cSMark Brown u16 reg; 548f2644a2cSMark Brown static struct _pll_div pll_div; 549f2644a2cSMark Brown int ret; 550f2644a2cSMark Brown 551f2644a2cSMark Brown if (freq_in && freq_out) { 552f2644a2cSMark Brown ret = pll_factors(freq_in, freq_out, &pll_div); 553f2644a2cSMark Brown if (ret != 0) 554f2644a2cSMark Brown return ret; 555f2644a2cSMark Brown } 556f2644a2cSMark Brown 557f2644a2cSMark Brown /* Disable the PLL: even if we are changing the frequency the 558f2644a2cSMark Brown * PLL needs to be disabled while we do so. */ 55917a52fd6SMark Brown snd_soc_write(codec, WM8960_CLOCK1, 56017a52fd6SMark Brown snd_soc_read(codec, WM8960_CLOCK1) & ~1); 56117a52fd6SMark Brown snd_soc_write(codec, WM8960_POWER2, 56217a52fd6SMark Brown snd_soc_read(codec, WM8960_POWER2) & ~1); 563f2644a2cSMark Brown 564f2644a2cSMark Brown if (!freq_in || !freq_out) 565f2644a2cSMark Brown return 0; 566f2644a2cSMark Brown 56717a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_PLL1) & ~0x3f; 568f2644a2cSMark Brown reg |= pll_div.pre_div << 4; 569f2644a2cSMark Brown reg |= pll_div.n; 570f2644a2cSMark Brown 571f2644a2cSMark Brown if (pll_div.k) { 572f2644a2cSMark Brown reg |= 0x20; 573f2644a2cSMark Brown 57417a52fd6SMark Brown snd_soc_write(codec, WM8960_PLL2, (pll_div.k >> 18) & 0x3f); 57517a52fd6SMark Brown snd_soc_write(codec, WM8960_PLL3, (pll_div.k >> 9) & 0x1ff); 57617a52fd6SMark Brown snd_soc_write(codec, WM8960_PLL4, pll_div.k & 0x1ff); 577f2644a2cSMark Brown } 57817a52fd6SMark Brown snd_soc_write(codec, WM8960_PLL1, reg); 579f2644a2cSMark Brown 580f2644a2cSMark Brown /* Turn it on */ 58117a52fd6SMark Brown snd_soc_write(codec, WM8960_POWER2, 58217a52fd6SMark Brown snd_soc_read(codec, WM8960_POWER2) | 1); 583f2644a2cSMark Brown msleep(250); 58417a52fd6SMark Brown snd_soc_write(codec, WM8960_CLOCK1, 58517a52fd6SMark Brown snd_soc_read(codec, WM8960_CLOCK1) | 1); 586f2644a2cSMark Brown 587f2644a2cSMark Brown return 0; 588f2644a2cSMark Brown } 589f2644a2cSMark Brown 590f2644a2cSMark Brown static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai, 591f2644a2cSMark Brown int div_id, int div) 592f2644a2cSMark Brown { 593f2644a2cSMark Brown struct snd_soc_codec *codec = codec_dai->codec; 594f2644a2cSMark Brown u16 reg; 595f2644a2cSMark Brown 596f2644a2cSMark Brown switch (div_id) { 597f2644a2cSMark Brown case WM8960_SYSCLKSEL: 59817a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1fe; 59917a52fd6SMark Brown snd_soc_write(codec, WM8960_CLOCK1, reg | div); 600f2644a2cSMark Brown break; 601f2644a2cSMark Brown case WM8960_SYSCLKDIV: 60217a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1f9; 60317a52fd6SMark Brown snd_soc_write(codec, WM8960_CLOCK1, reg | div); 604f2644a2cSMark Brown break; 605f2644a2cSMark Brown case WM8960_DACDIV: 60617a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1c7; 60717a52fd6SMark Brown snd_soc_write(codec, WM8960_CLOCK1, reg | div); 608f2644a2cSMark Brown break; 609f2644a2cSMark Brown case WM8960_OPCLKDIV: 61017a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_PLL1) & 0x03f; 61117a52fd6SMark Brown snd_soc_write(codec, WM8960_PLL1, reg | div); 612f2644a2cSMark Brown break; 613f2644a2cSMark Brown case WM8960_DCLKDIV: 61417a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_CLOCK2) & 0x03f; 61517a52fd6SMark Brown snd_soc_write(codec, WM8960_CLOCK2, reg | div); 616f2644a2cSMark Brown break; 617f2644a2cSMark Brown case WM8960_TOCLKSEL: 61817a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_ADDCTL1) & 0x1fd; 61917a52fd6SMark Brown snd_soc_write(codec, WM8960_ADDCTL1, reg | div); 620f2644a2cSMark Brown break; 621f2644a2cSMark Brown default: 622f2644a2cSMark Brown return -EINVAL; 623f2644a2cSMark Brown } 624f2644a2cSMark Brown 625f2644a2cSMark Brown return 0; 626f2644a2cSMark Brown } 627f2644a2cSMark Brown 628f2644a2cSMark Brown #define WM8960_RATES SNDRV_PCM_RATE_8000_48000 629f2644a2cSMark Brown 630f2644a2cSMark Brown #define WM8960_FORMATS \ 631f2644a2cSMark Brown (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ 632f2644a2cSMark Brown SNDRV_PCM_FMTBIT_S24_LE) 633f2644a2cSMark Brown 634f2644a2cSMark Brown static struct snd_soc_dai_ops wm8960_dai_ops = { 635f2644a2cSMark Brown .hw_params = wm8960_hw_params, 636f2644a2cSMark Brown .digital_mute = wm8960_mute, 637f2644a2cSMark Brown .set_fmt = wm8960_set_dai_fmt, 638f2644a2cSMark Brown .set_clkdiv = wm8960_set_dai_clkdiv, 639f2644a2cSMark Brown .set_pll = wm8960_set_dai_pll, 640f2644a2cSMark Brown }; 641f2644a2cSMark Brown 642f2644a2cSMark Brown struct snd_soc_dai wm8960_dai = { 643f2644a2cSMark Brown .name = "WM8960", 644f2644a2cSMark Brown .playback = { 645f2644a2cSMark Brown .stream_name = "Playback", 646f2644a2cSMark Brown .channels_min = 1, 647f2644a2cSMark Brown .channels_max = 2, 648f2644a2cSMark Brown .rates = WM8960_RATES, 649f2644a2cSMark Brown .formats = WM8960_FORMATS,}, 650f2644a2cSMark Brown .capture = { 651f2644a2cSMark Brown .stream_name = "Capture", 652f2644a2cSMark Brown .channels_min = 1, 653f2644a2cSMark Brown .channels_max = 2, 654f2644a2cSMark Brown .rates = WM8960_RATES, 655f2644a2cSMark Brown .formats = WM8960_FORMATS,}, 656f2644a2cSMark Brown .ops = &wm8960_dai_ops, 657f2644a2cSMark Brown .symmetric_rates = 1, 658f2644a2cSMark Brown }; 659f2644a2cSMark Brown EXPORT_SYMBOL_GPL(wm8960_dai); 660f2644a2cSMark Brown 661f2644a2cSMark Brown static int wm8960_suspend(struct platform_device *pdev, pm_message_t state) 662f2644a2cSMark Brown { 663f2644a2cSMark Brown struct snd_soc_device *socdev = platform_get_drvdata(pdev); 664f2644a2cSMark Brown struct snd_soc_codec *codec = socdev->card->codec; 665f2644a2cSMark Brown 666f2644a2cSMark Brown wm8960_set_bias_level(codec, SND_SOC_BIAS_OFF); 667f2644a2cSMark Brown return 0; 668f2644a2cSMark Brown } 669f2644a2cSMark Brown 670f2644a2cSMark Brown static int wm8960_resume(struct platform_device *pdev) 671f2644a2cSMark Brown { 672f2644a2cSMark Brown struct snd_soc_device *socdev = platform_get_drvdata(pdev); 673f2644a2cSMark Brown struct snd_soc_codec *codec = socdev->card->codec; 674f2644a2cSMark Brown int i; 675f2644a2cSMark Brown u8 data[2]; 676f2644a2cSMark Brown u16 *cache = codec->reg_cache; 677f2644a2cSMark Brown 678f2644a2cSMark Brown /* Sync reg_cache with the hardware */ 679f2644a2cSMark Brown for (i = 0; i < ARRAY_SIZE(wm8960_reg); i++) { 680f2644a2cSMark Brown data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); 681f2644a2cSMark Brown data[1] = cache[i] & 0x00ff; 682f2644a2cSMark Brown codec->hw_write(codec->control_data, data, 2); 683f2644a2cSMark Brown } 684f2644a2cSMark Brown 685f2644a2cSMark Brown wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 686f2644a2cSMark Brown wm8960_set_bias_level(codec, codec->suspend_bias_level); 687f2644a2cSMark Brown return 0; 688f2644a2cSMark Brown } 689f2644a2cSMark Brown 690f2644a2cSMark Brown static struct snd_soc_codec *wm8960_codec; 691f2644a2cSMark Brown 692f2644a2cSMark Brown static int wm8960_probe(struct platform_device *pdev) 693f2644a2cSMark Brown { 694f2644a2cSMark Brown struct snd_soc_device *socdev = platform_get_drvdata(pdev); 695f2644a2cSMark Brown struct snd_soc_codec *codec; 696f2644a2cSMark Brown int ret = 0; 697f2644a2cSMark Brown 698f2644a2cSMark Brown if (wm8960_codec == NULL) { 699f2644a2cSMark Brown dev_err(&pdev->dev, "Codec device not registered\n"); 700f2644a2cSMark Brown return -ENODEV; 701f2644a2cSMark Brown } 702f2644a2cSMark Brown 703f2644a2cSMark Brown socdev->card->codec = wm8960_codec; 704f2644a2cSMark Brown codec = wm8960_codec; 705f2644a2cSMark Brown 706f2644a2cSMark Brown /* register pcms */ 707f2644a2cSMark Brown ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); 708f2644a2cSMark Brown if (ret < 0) { 709f2644a2cSMark Brown dev_err(codec->dev, "failed to create pcms: %d\n", ret); 710f2644a2cSMark Brown goto pcm_err; 711f2644a2cSMark Brown } 712f2644a2cSMark Brown 713f2644a2cSMark Brown snd_soc_add_controls(codec, wm8960_snd_controls, 714f2644a2cSMark Brown ARRAY_SIZE(wm8960_snd_controls)); 715f2644a2cSMark Brown wm8960_add_widgets(codec); 716f2644a2cSMark Brown ret = snd_soc_init_card(socdev); 717f2644a2cSMark Brown if (ret < 0) { 718f2644a2cSMark Brown dev_err(codec->dev, "failed to register card: %d\n", ret); 719f2644a2cSMark Brown goto card_err; 720f2644a2cSMark Brown } 721f2644a2cSMark Brown 722f2644a2cSMark Brown return ret; 723f2644a2cSMark Brown 724f2644a2cSMark Brown card_err: 725f2644a2cSMark Brown snd_soc_free_pcms(socdev); 726f2644a2cSMark Brown snd_soc_dapm_free(socdev); 727f2644a2cSMark Brown pcm_err: 728f2644a2cSMark Brown return ret; 729f2644a2cSMark Brown } 730f2644a2cSMark Brown 731f2644a2cSMark Brown /* power down chip */ 732f2644a2cSMark Brown static int wm8960_remove(struct platform_device *pdev) 733f2644a2cSMark Brown { 734f2644a2cSMark Brown struct snd_soc_device *socdev = platform_get_drvdata(pdev); 735f2644a2cSMark Brown 736f2644a2cSMark Brown snd_soc_free_pcms(socdev); 737f2644a2cSMark Brown snd_soc_dapm_free(socdev); 738f2644a2cSMark Brown 739f2644a2cSMark Brown return 0; 740f2644a2cSMark Brown } 741f2644a2cSMark Brown 742f2644a2cSMark Brown struct snd_soc_codec_device soc_codec_dev_wm8960 = { 743f2644a2cSMark Brown .probe = wm8960_probe, 744f2644a2cSMark Brown .remove = wm8960_remove, 745f2644a2cSMark Brown .suspend = wm8960_suspend, 746f2644a2cSMark Brown .resume = wm8960_resume, 747f2644a2cSMark Brown }; 748f2644a2cSMark Brown EXPORT_SYMBOL_GPL(soc_codec_dev_wm8960); 749f2644a2cSMark Brown 7507084a42bSMark Brown static int wm8960_register(struct wm8960_priv *wm8960, 7517084a42bSMark Brown enum snd_soc_control_type control) 752f2644a2cSMark Brown { 753f2644a2cSMark Brown struct wm8960_data *pdata = wm8960->codec.dev->platform_data; 754f2644a2cSMark Brown struct snd_soc_codec *codec = &wm8960->codec; 755f2644a2cSMark Brown int ret; 756f2644a2cSMark Brown u16 reg; 757f2644a2cSMark Brown 758f2644a2cSMark Brown if (wm8960_codec) { 759f2644a2cSMark Brown dev_err(codec->dev, "Another WM8960 is registered\n"); 7601a01417eSMark Brown ret = -EINVAL; 7611a01417eSMark Brown goto err; 762f2644a2cSMark Brown } 763f2644a2cSMark Brown 764f2644a2cSMark Brown if (!pdata) { 765f2644a2cSMark Brown dev_warn(codec->dev, "No platform data supplied\n"); 766f2644a2cSMark Brown } else { 767f2644a2cSMark Brown if (pdata->dres > WM8960_DRES_MAX) { 768f2644a2cSMark Brown dev_err(codec->dev, "Invalid DRES: %d\n", pdata->dres); 769f2644a2cSMark Brown pdata->dres = 0; 770f2644a2cSMark Brown } 771f2644a2cSMark Brown } 772f2644a2cSMark Brown 773f2644a2cSMark Brown mutex_init(&codec->mutex); 774f2644a2cSMark Brown INIT_LIST_HEAD(&codec->dapm_widgets); 775f2644a2cSMark Brown INIT_LIST_HEAD(&codec->dapm_paths); 776f2644a2cSMark Brown 777f2644a2cSMark Brown codec->private_data = wm8960; 778f2644a2cSMark Brown codec->name = "WM8960"; 779f2644a2cSMark Brown codec->owner = THIS_MODULE; 780f2644a2cSMark Brown codec->bias_level = SND_SOC_BIAS_OFF; 781f2644a2cSMark Brown codec->set_bias_level = wm8960_set_bias_level; 782f2644a2cSMark Brown codec->dai = &wm8960_dai; 783f2644a2cSMark Brown codec->num_dai = 1; 784f2644a2cSMark Brown codec->reg_cache_size = WM8960_CACHEREGNUM; 785f2644a2cSMark Brown codec->reg_cache = &wm8960->reg_cache; 786f2644a2cSMark Brown 787f2644a2cSMark Brown memcpy(codec->reg_cache, wm8960_reg, sizeof(wm8960_reg)); 788f2644a2cSMark Brown 7897084a42bSMark Brown ret = snd_soc_codec_set_cache_io(codec, 7, 9, control); 79017a52fd6SMark Brown if (ret < 0) { 79117a52fd6SMark Brown dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); 79217a52fd6SMark Brown goto err; 79317a52fd6SMark Brown } 79417a52fd6SMark Brown 795f2644a2cSMark Brown ret = wm8960_reset(codec); 796f2644a2cSMark Brown if (ret < 0) { 797f2644a2cSMark Brown dev_err(codec->dev, "Failed to issue reset\n"); 7981a01417eSMark Brown goto err; 799f2644a2cSMark Brown } 800f2644a2cSMark Brown 801f2644a2cSMark Brown wm8960_dai.dev = codec->dev; 802f2644a2cSMark Brown 803f2644a2cSMark Brown wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 804f2644a2cSMark Brown 805f2644a2cSMark Brown /* Latch the update bits */ 80617a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_LINVOL); 80717a52fd6SMark Brown snd_soc_write(codec, WM8960_LINVOL, reg | 0x100); 80817a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_RINVOL); 80917a52fd6SMark Brown snd_soc_write(codec, WM8960_RINVOL, reg | 0x100); 81017a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_LADC); 81117a52fd6SMark Brown snd_soc_write(codec, WM8960_LADC, reg | 0x100); 81217a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_RADC); 81317a52fd6SMark Brown snd_soc_write(codec, WM8960_RADC, reg | 0x100); 81417a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_LDAC); 81517a52fd6SMark Brown snd_soc_write(codec, WM8960_LDAC, reg | 0x100); 81617a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_RDAC); 81717a52fd6SMark Brown snd_soc_write(codec, WM8960_RDAC, reg | 0x100); 81817a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_LOUT1); 81917a52fd6SMark Brown snd_soc_write(codec, WM8960_LOUT1, reg | 0x100); 82017a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_ROUT1); 82117a52fd6SMark Brown snd_soc_write(codec, WM8960_ROUT1, reg | 0x100); 82217a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_LOUT2); 82317a52fd6SMark Brown snd_soc_write(codec, WM8960_LOUT2, reg | 0x100); 82417a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_ROUT2); 82517a52fd6SMark Brown snd_soc_write(codec, WM8960_ROUT2, reg | 0x100); 826f2644a2cSMark Brown 827f2644a2cSMark Brown wm8960_codec = codec; 828f2644a2cSMark Brown 829f2644a2cSMark Brown ret = snd_soc_register_codec(codec); 830f2644a2cSMark Brown if (ret != 0) { 831f2644a2cSMark Brown dev_err(codec->dev, "Failed to register codec: %d\n", ret); 8321a01417eSMark Brown goto err; 833f2644a2cSMark Brown } 834f2644a2cSMark Brown 835f2644a2cSMark Brown ret = snd_soc_register_dai(&wm8960_dai); 836f2644a2cSMark Brown if (ret != 0) { 837f2644a2cSMark Brown dev_err(codec->dev, "Failed to register DAI: %d\n", ret); 8381a01417eSMark Brown goto err_codec; 839f2644a2cSMark Brown } 840f2644a2cSMark Brown 841f2644a2cSMark Brown return 0; 8421a01417eSMark Brown 8431a01417eSMark Brown err_codec: 8441a01417eSMark Brown snd_soc_unregister_codec(codec); 8451a01417eSMark Brown err: 8461a01417eSMark Brown kfree(wm8960); 8471a01417eSMark Brown return ret; 848f2644a2cSMark Brown } 849f2644a2cSMark Brown 850f2644a2cSMark Brown static void wm8960_unregister(struct wm8960_priv *wm8960) 851f2644a2cSMark Brown { 852f2644a2cSMark Brown wm8960_set_bias_level(&wm8960->codec, SND_SOC_BIAS_OFF); 853f2644a2cSMark Brown snd_soc_unregister_dai(&wm8960_dai); 854f2644a2cSMark Brown snd_soc_unregister_codec(&wm8960->codec); 855f2644a2cSMark Brown kfree(wm8960); 856f2644a2cSMark Brown wm8960_codec = NULL; 857f2644a2cSMark Brown } 858f2644a2cSMark Brown 859f2644a2cSMark Brown static __devinit int wm8960_i2c_probe(struct i2c_client *i2c, 860f2644a2cSMark Brown const struct i2c_device_id *id) 861f2644a2cSMark Brown { 862f2644a2cSMark Brown struct wm8960_priv *wm8960; 863f2644a2cSMark Brown struct snd_soc_codec *codec; 864f2644a2cSMark Brown 865f2644a2cSMark Brown wm8960 = kzalloc(sizeof(struct wm8960_priv), GFP_KERNEL); 866f2644a2cSMark Brown if (wm8960 == NULL) 867f2644a2cSMark Brown return -ENOMEM; 868f2644a2cSMark Brown 869f2644a2cSMark Brown codec = &wm8960->codec; 870f2644a2cSMark Brown 871f2644a2cSMark Brown i2c_set_clientdata(i2c, wm8960); 872f2644a2cSMark Brown codec->control_data = i2c; 873f2644a2cSMark Brown 874f2644a2cSMark Brown codec->dev = &i2c->dev; 875f2644a2cSMark Brown 8767084a42bSMark Brown return wm8960_register(wm8960, SND_SOC_I2C); 877f2644a2cSMark Brown } 878f2644a2cSMark Brown 879f2644a2cSMark Brown static __devexit int wm8960_i2c_remove(struct i2c_client *client) 880f2644a2cSMark Brown { 881f2644a2cSMark Brown struct wm8960_priv *wm8960 = i2c_get_clientdata(client); 882f2644a2cSMark Brown wm8960_unregister(wm8960); 883f2644a2cSMark Brown return 0; 884f2644a2cSMark Brown } 885f2644a2cSMark Brown 886b3b50b3fSMark Brown #ifdef CONFIG_PM 887b3b50b3fSMark Brown static int wm8960_i2c_suspend(struct i2c_client *client, pm_message_t msg) 888b3b50b3fSMark Brown { 889b3b50b3fSMark Brown return snd_soc_suspend_device(&client->dev); 890b3b50b3fSMark Brown } 891b3b50b3fSMark Brown 892b3b50b3fSMark Brown static int wm8960_i2c_resume(struct i2c_client *client) 893b3b50b3fSMark Brown { 894b3b50b3fSMark Brown return snd_soc_resume_device(&client->dev); 895b3b50b3fSMark Brown } 896b3b50b3fSMark Brown #else 897b3b50b3fSMark Brown #define wm8960_i2c_suspend NULL 898b3b50b3fSMark Brown #define wm8960_i2c_resume NULL 899b3b50b3fSMark Brown #endif 900b3b50b3fSMark Brown 901f2644a2cSMark Brown static const struct i2c_device_id wm8960_i2c_id[] = { 902f2644a2cSMark Brown { "wm8960", 0 }, 903f2644a2cSMark Brown { } 904f2644a2cSMark Brown }; 905f2644a2cSMark Brown MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id); 906f2644a2cSMark Brown 907f2644a2cSMark Brown static struct i2c_driver wm8960_i2c_driver = { 908f2644a2cSMark Brown .driver = { 909f2644a2cSMark Brown .name = "WM8960 I2C Codec", 910f2644a2cSMark Brown .owner = THIS_MODULE, 911f2644a2cSMark Brown }, 912f2644a2cSMark Brown .probe = wm8960_i2c_probe, 913f2644a2cSMark Brown .remove = __devexit_p(wm8960_i2c_remove), 914b3b50b3fSMark Brown .suspend = wm8960_i2c_suspend, 915b3b50b3fSMark Brown .resume = wm8960_i2c_resume, 916f2644a2cSMark Brown .id_table = wm8960_i2c_id, 917f2644a2cSMark Brown }; 918f2644a2cSMark Brown 919f2644a2cSMark Brown static int __init wm8960_modinit(void) 920f2644a2cSMark Brown { 921f2644a2cSMark Brown int ret; 922f2644a2cSMark Brown 923f2644a2cSMark Brown ret = i2c_add_driver(&wm8960_i2c_driver); 924f2644a2cSMark Brown if (ret != 0) { 925f2644a2cSMark Brown printk(KERN_ERR "Failed to register WM8960 I2C driver: %d\n", 926f2644a2cSMark Brown ret); 927f2644a2cSMark Brown } 928f2644a2cSMark Brown 929f2644a2cSMark Brown return ret; 930f2644a2cSMark Brown } 931f2644a2cSMark Brown module_init(wm8960_modinit); 932f2644a2cSMark Brown 933f2644a2cSMark Brown static void __exit wm8960_exit(void) 934f2644a2cSMark Brown { 935f2644a2cSMark Brown i2c_del_driver(&wm8960_i2c_driver); 936f2644a2cSMark Brown } 937f2644a2cSMark Brown module_exit(wm8960_exit); 938f2644a2cSMark Brown 939f2644a2cSMark Brown 940f2644a2cSMark Brown MODULE_DESCRIPTION("ASoC WM8960 driver"); 941f2644a2cSMark Brown MODULE_AUTHOR("Liam Girdwood"); 942f2644a2cSMark Brown MODULE_LICENSE("GPL"); 943