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 72f2644a2cSMark Brown /* 73f2644a2cSMark Brown * read wm8960 register cache 74f2644a2cSMark Brown */ 75f2644a2cSMark Brown static inline unsigned int wm8960_read_reg_cache(struct snd_soc_codec *codec, 76f2644a2cSMark Brown unsigned int reg) 77f2644a2cSMark Brown { 78f2644a2cSMark Brown u16 *cache = codec->reg_cache; 79f2644a2cSMark Brown if (reg == WM8960_RESET) 80f2644a2cSMark Brown return 0; 81f2644a2cSMark Brown if (reg >= WM8960_CACHEREGNUM) 82f2644a2cSMark Brown return -1; 83f2644a2cSMark Brown return cache[reg]; 84f2644a2cSMark Brown } 85f2644a2cSMark Brown 86f2644a2cSMark Brown /* 87f2644a2cSMark Brown * write wm8960 register cache 88f2644a2cSMark Brown */ 89f2644a2cSMark Brown static inline void wm8960_write_reg_cache(struct snd_soc_codec *codec, 90f2644a2cSMark Brown u16 reg, unsigned int value) 91f2644a2cSMark Brown { 92f2644a2cSMark Brown u16 *cache = codec->reg_cache; 93f2644a2cSMark Brown if (reg >= WM8960_CACHEREGNUM) 94f2644a2cSMark Brown return; 95f2644a2cSMark Brown cache[reg] = value; 96f2644a2cSMark Brown } 97f2644a2cSMark Brown 98f2644a2cSMark Brown static inline unsigned int wm8960_read(struct snd_soc_codec *codec, 99f2644a2cSMark Brown unsigned int reg) 100f2644a2cSMark Brown { 101f2644a2cSMark Brown return wm8960_read_reg_cache(codec, reg); 102f2644a2cSMark Brown } 103f2644a2cSMark Brown 104f2644a2cSMark Brown /* 105f2644a2cSMark Brown * write to the WM8960 register space 106f2644a2cSMark Brown */ 107f2644a2cSMark Brown static int wm8960_write(struct snd_soc_codec *codec, unsigned int reg, 108f2644a2cSMark Brown unsigned int value) 109f2644a2cSMark Brown { 110f2644a2cSMark Brown u8 data[2]; 111f2644a2cSMark Brown 112f2644a2cSMark Brown /* data is 113f2644a2cSMark Brown * D15..D9 WM8960 register offset 114f2644a2cSMark Brown * D8...D0 register data 115f2644a2cSMark Brown */ 116f2644a2cSMark Brown data[0] = (reg << 1) | ((value >> 8) & 0x0001); 117f2644a2cSMark Brown data[1] = value & 0x00ff; 118f2644a2cSMark Brown 119f2644a2cSMark Brown wm8960_write_reg_cache(codec, reg, value); 120f2644a2cSMark Brown if (codec->hw_write(codec->control_data, data, 2) == 2) 121f2644a2cSMark Brown return 0; 122f2644a2cSMark Brown else 123f2644a2cSMark Brown return -EIO; 124f2644a2cSMark Brown } 125f2644a2cSMark Brown 126f2644a2cSMark Brown #define wm8960_reset(c) wm8960_write(c, WM8960_RESET, 0) 127f2644a2cSMark Brown 128f2644a2cSMark Brown /* enumerated controls */ 129f2644a2cSMark Brown static const char *wm8960_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; 130f2644a2cSMark Brown static const char *wm8960_polarity[] = {"No Inversion", "Left Inverted", 131f2644a2cSMark Brown "Right Inverted", "Stereo Inversion"}; 132f2644a2cSMark Brown static const char *wm8960_3d_upper_cutoff[] = {"High", "Low"}; 133f2644a2cSMark Brown static const char *wm8960_3d_lower_cutoff[] = {"Low", "High"}; 134f2644a2cSMark Brown static const char *wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"}; 135f2644a2cSMark Brown static const char *wm8960_alcmode[] = {"ALC", "Limiter"}; 136f2644a2cSMark Brown 137f2644a2cSMark Brown static const struct soc_enum wm8960_enum[] = { 138f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_DACCTL1, 1, 4, wm8960_deemph), 139f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity), 140f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity), 141f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff), 142f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_3D, 5, 2, wm8960_3d_lower_cutoff), 143f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_ALC1, 7, 4, wm8960_alcfunc), 144f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode), 145f2644a2cSMark Brown }; 146f2644a2cSMark Brown 147f2644a2cSMark Brown static const DECLARE_TLV_DB_SCALE(adc_tlv, -9700, 50, 0); 148f2644a2cSMark Brown static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 50, 1); 149f2644a2cSMark Brown static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0); 150f2644a2cSMark Brown static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); 151f2644a2cSMark Brown 152f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_snd_controls[] = { 153f2644a2cSMark Brown SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL, 154f2644a2cSMark Brown 0, 63, 0, adc_tlv), 155f2644a2cSMark Brown SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL, 156f2644a2cSMark Brown 6, 1, 0), 157f2644a2cSMark Brown SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL, 158f2644a2cSMark Brown 7, 1, 0), 159f2644a2cSMark Brown 160f2644a2cSMark Brown SOC_DOUBLE_R_TLV("Playback Volume", WM8960_LDAC, WM8960_RDAC, 161f2644a2cSMark Brown 0, 255, 0, dac_tlv), 162f2644a2cSMark Brown 163f2644a2cSMark Brown SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8960_LOUT1, WM8960_ROUT1, 164f2644a2cSMark Brown 0, 127, 0, out_tlv), 165f2644a2cSMark Brown SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8960_LOUT1, WM8960_ROUT1, 166f2644a2cSMark Brown 7, 1, 0), 167f2644a2cSMark Brown 168f2644a2cSMark Brown SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8960_LOUT2, WM8960_ROUT2, 169f2644a2cSMark Brown 0, 127, 0, out_tlv), 170f2644a2cSMark Brown SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8960_LOUT2, WM8960_ROUT2, 171f2644a2cSMark Brown 7, 1, 0), 172f2644a2cSMark Brown SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0), 173f2644a2cSMark Brown SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0), 174f2644a2cSMark Brown 175f2644a2cSMark Brown SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0), 176f2644a2cSMark Brown SOC_ENUM("ADC Polarity", wm8960_enum[1]), 177f2644a2cSMark Brown SOC_ENUM("Playback De-emphasis", wm8960_enum[0]), 178f2644a2cSMark Brown SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0), 179f2644a2cSMark Brown 180f2644a2cSMark Brown SOC_ENUM("DAC Polarity", wm8960_enum[2]), 181f2644a2cSMark Brown 182f2644a2cSMark Brown SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[3]), 183f2644a2cSMark Brown SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[4]), 184f2644a2cSMark Brown SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0), 185f2644a2cSMark Brown SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0), 186f2644a2cSMark Brown 187f2644a2cSMark Brown SOC_ENUM("ALC Function", wm8960_enum[5]), 188f2644a2cSMark Brown SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0), 189f2644a2cSMark Brown SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1), 190f2644a2cSMark Brown SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0), 191f2644a2cSMark Brown SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0), 192f2644a2cSMark Brown SOC_ENUM("ALC Mode", wm8960_enum[6]), 193f2644a2cSMark Brown SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0), 194f2644a2cSMark Brown SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0), 195f2644a2cSMark Brown 196f2644a2cSMark Brown SOC_SINGLE("Noise Gate Threshold", WM8960_NOISEG, 3, 31, 0), 197f2644a2cSMark Brown SOC_SINGLE("Noise Gate Switch", WM8960_NOISEG, 0, 1, 0), 198f2644a2cSMark Brown 199f2644a2cSMark Brown SOC_DOUBLE_R("ADC PCM Capture Volume", WM8960_LINPATH, WM8960_RINPATH, 200f2644a2cSMark Brown 0, 127, 0), 201f2644a2cSMark Brown 202f2644a2cSMark Brown SOC_SINGLE_TLV("Left Output Mixer Boost Bypass Volume", 203f2644a2cSMark Brown WM8960_BYPASS1, 4, 7, 1, bypass_tlv), 204f2644a2cSMark Brown SOC_SINGLE_TLV("Left Output Mixer LINPUT3 Volume", 205f2644a2cSMark Brown WM8960_LOUTMIX, 4, 7, 1, bypass_tlv), 206f2644a2cSMark Brown SOC_SINGLE_TLV("Right Output Mixer Boost Bypass Volume", 207f2644a2cSMark Brown WM8960_BYPASS2, 4, 7, 1, bypass_tlv), 208f2644a2cSMark Brown SOC_SINGLE_TLV("Right Output Mixer RINPUT3 Volume", 209f2644a2cSMark Brown WM8960_ROUTMIX, 4, 7, 1, bypass_tlv), 210f2644a2cSMark Brown }; 211f2644a2cSMark Brown 212f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_lin_boost[] = { 213f2644a2cSMark Brown SOC_DAPM_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0), 214f2644a2cSMark Brown SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LINPATH, 7, 1, 0), 215f2644a2cSMark Brown SOC_DAPM_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0), 216f2644a2cSMark Brown }; 217f2644a2cSMark Brown 218f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_lin[] = { 219f2644a2cSMark Brown SOC_DAPM_SINGLE("Boost Switch", WM8960_LINPATH, 3, 1, 0), 220f2644a2cSMark Brown }; 221f2644a2cSMark Brown 222f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_rin_boost[] = { 223f2644a2cSMark Brown SOC_DAPM_SINGLE("RINPUT2 Switch", WM8960_RINPATH, 6, 1, 0), 224f2644a2cSMark Brown SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_RINPATH, 7, 1, 0), 225f2644a2cSMark Brown SOC_DAPM_SINGLE("RINPUT1 Switch", WM8960_RINPATH, 8, 1, 0), 226f2644a2cSMark Brown }; 227f2644a2cSMark Brown 228f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_rin[] = { 229f2644a2cSMark Brown SOC_DAPM_SINGLE("Boost Switch", WM8960_RINPATH, 3, 1, 0), 230f2644a2cSMark Brown }; 231f2644a2cSMark Brown 232f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_loutput_mixer[] = { 233f2644a2cSMark Brown SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_LOUTMIX, 8, 1, 0), 234f2644a2cSMark Brown SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LOUTMIX, 7, 1, 0), 235f2644a2cSMark Brown SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS1, 7, 1, 0), 236f2644a2cSMark Brown }; 237f2644a2cSMark Brown 238f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_routput_mixer[] = { 239f2644a2cSMark Brown SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_ROUTMIX, 8, 1, 0), 240f2644a2cSMark Brown SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_ROUTMIX, 7, 1, 0), 241f2644a2cSMark Brown SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS2, 7, 1, 0), 242f2644a2cSMark Brown }; 243f2644a2cSMark Brown 244f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_mono_out[] = { 245f2644a2cSMark Brown SOC_DAPM_SINGLE("Left Switch", WM8960_MONOMIX1, 7, 1, 0), 246f2644a2cSMark Brown SOC_DAPM_SINGLE("Right Switch", WM8960_MONOMIX2, 7, 1, 0), 247f2644a2cSMark Brown }; 248f2644a2cSMark Brown 249f2644a2cSMark Brown static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = { 250f2644a2cSMark Brown SND_SOC_DAPM_INPUT("LINPUT1"), 251f2644a2cSMark Brown SND_SOC_DAPM_INPUT("RINPUT1"), 252f2644a2cSMark Brown SND_SOC_DAPM_INPUT("LINPUT2"), 253f2644a2cSMark Brown SND_SOC_DAPM_INPUT("RINPUT2"), 254f2644a2cSMark Brown SND_SOC_DAPM_INPUT("LINPUT3"), 255f2644a2cSMark Brown SND_SOC_DAPM_INPUT("RINPUT3"), 256f2644a2cSMark Brown 257f2644a2cSMark Brown SND_SOC_DAPM_MICBIAS("MICB", WM8960_POWER1, 1, 0), 258f2644a2cSMark Brown 259f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0, 260f2644a2cSMark Brown wm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)), 261f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8960_POWER1, 4, 0, 262f2644a2cSMark Brown wm8960_rin_boost, ARRAY_SIZE(wm8960_rin_boost)), 263f2644a2cSMark Brown 264f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0, 265f2644a2cSMark Brown wm8960_lin, ARRAY_SIZE(wm8960_lin)), 266f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Right Input Mixer", WM8960_POWER3, 4, 0, 267f2644a2cSMark Brown wm8960_rin, ARRAY_SIZE(wm8960_rin)), 268f2644a2cSMark Brown 269f2644a2cSMark Brown SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER2, 3, 0), 270f2644a2cSMark Brown SND_SOC_DAPM_ADC("Right ADC", "Capture", WM8960_POWER2, 2, 0), 271f2644a2cSMark Brown 272f2644a2cSMark Brown SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0), 273f2644a2cSMark Brown SND_SOC_DAPM_DAC("Right DAC", "Playback", WM8960_POWER2, 7, 0), 274f2644a2cSMark Brown 275f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0, 276f2644a2cSMark Brown &wm8960_loutput_mixer[0], 277f2644a2cSMark Brown ARRAY_SIZE(wm8960_loutput_mixer)), 278f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0, 279f2644a2cSMark Brown &wm8960_routput_mixer[0], 280f2644a2cSMark Brown ARRAY_SIZE(wm8960_routput_mixer)), 281f2644a2cSMark Brown 282f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0, 283f2644a2cSMark Brown &wm8960_mono_out[0], 284f2644a2cSMark Brown ARRAY_SIZE(wm8960_mono_out)), 285f2644a2cSMark Brown 286f2644a2cSMark Brown SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0), 287f2644a2cSMark Brown SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0), 288f2644a2cSMark Brown 289f2644a2cSMark Brown SND_SOC_DAPM_PGA("Left Speaker PGA", WM8960_POWER2, 4, 0, NULL, 0), 290f2644a2cSMark Brown SND_SOC_DAPM_PGA("Right Speaker PGA", WM8960_POWER2, 3, 0, NULL, 0), 291f2644a2cSMark Brown 292f2644a2cSMark Brown SND_SOC_DAPM_PGA("Right Speaker Output", WM8960_CLASSD1, 7, 0, NULL, 0), 293f2644a2cSMark Brown SND_SOC_DAPM_PGA("Left Speaker Output", WM8960_CLASSD1, 6, 0, NULL, 0), 294f2644a2cSMark Brown 295f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("SPK_LP"), 296f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("SPK_LN"), 297f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("HP_L"), 298f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("HP_R"), 299f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("SPK_RP"), 300f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("SPK_RN"), 301f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("OUT3"), 302f2644a2cSMark Brown }; 303f2644a2cSMark Brown 304f2644a2cSMark Brown static const struct snd_soc_dapm_route audio_paths[] = { 305f2644a2cSMark Brown { "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" }, 306f2644a2cSMark Brown { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" }, 307f2644a2cSMark Brown { "Left Boost Mixer", "LINPUT3 Switch", "LINPUT3" }, 308f2644a2cSMark Brown 309f2644a2cSMark Brown { "Left Input Mixer", "Boost Switch", "Left Boost Mixer", }, 310f2644a2cSMark Brown { "Left Input Mixer", NULL, "LINPUT1", }, /* Really Boost Switch */ 311f2644a2cSMark Brown { "Left Input Mixer", NULL, "LINPUT2" }, 312f2644a2cSMark Brown { "Left Input Mixer", NULL, "LINPUT3" }, 313f2644a2cSMark Brown 314f2644a2cSMark Brown { "Right Boost Mixer", "RINPUT1 Switch", "RINPUT1" }, 315f2644a2cSMark Brown { "Right Boost Mixer", "RINPUT2 Switch", "RINPUT2" }, 316f2644a2cSMark Brown { "Right Boost Mixer", "RINPUT3 Switch", "RINPUT3" }, 317f2644a2cSMark Brown 318f2644a2cSMark Brown { "Right Input Mixer", "Boost Switch", "Right Boost Mixer", }, 319f2644a2cSMark Brown { "Right Input Mixer", NULL, "RINPUT1", }, /* Really Boost Switch */ 320f2644a2cSMark Brown { "Right Input Mixer", NULL, "RINPUT2" }, 321f2644a2cSMark Brown { "Right Input Mixer", NULL, "LINPUT3" }, 322f2644a2cSMark Brown 323f2644a2cSMark Brown { "Left ADC", NULL, "Left Input Mixer" }, 324f2644a2cSMark Brown { "Right ADC", NULL, "Right Input Mixer" }, 325f2644a2cSMark Brown 326f2644a2cSMark Brown { "Left Output Mixer", "LINPUT3 Switch", "LINPUT3" }, 327f2644a2cSMark Brown { "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer"} , 328f2644a2cSMark Brown { "Left Output Mixer", "PCM Playback Switch", "Left DAC" }, 329f2644a2cSMark Brown 330f2644a2cSMark Brown { "Right Output Mixer", "RINPUT3 Switch", "RINPUT3" }, 331f2644a2cSMark Brown { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" } , 332f2644a2cSMark Brown { "Right Output Mixer", "PCM Playback Switch", "Right DAC" }, 333f2644a2cSMark Brown 334f2644a2cSMark Brown { "Mono Output Mixer", "Left Switch", "Left Output Mixer" }, 335f2644a2cSMark Brown { "Mono Output Mixer", "Right Switch", "Right Output Mixer" }, 336f2644a2cSMark Brown 337f2644a2cSMark Brown { "LOUT1 PGA", NULL, "Left Output Mixer" }, 338f2644a2cSMark Brown { "ROUT1 PGA", NULL, "Right Output Mixer" }, 339f2644a2cSMark Brown 340f2644a2cSMark Brown { "HP_L", NULL, "LOUT1 PGA" }, 341f2644a2cSMark Brown { "HP_R", NULL, "ROUT1 PGA" }, 342f2644a2cSMark Brown 343f2644a2cSMark Brown { "Left Speaker PGA", NULL, "Left Output Mixer" }, 344f2644a2cSMark Brown { "Right Speaker PGA", NULL, "Right Output Mixer" }, 345f2644a2cSMark Brown 346f2644a2cSMark Brown { "Left Speaker Output", NULL, "Left Speaker PGA" }, 347f2644a2cSMark Brown { "Right Speaker Output", NULL, "Right Speaker PGA" }, 348f2644a2cSMark Brown 349f2644a2cSMark Brown { "SPK_LN", NULL, "Left Speaker Output" }, 350f2644a2cSMark Brown { "SPK_LP", NULL, "Left Speaker Output" }, 351f2644a2cSMark Brown { "SPK_RN", NULL, "Right Speaker Output" }, 352f2644a2cSMark Brown { "SPK_RP", NULL, "Right Speaker Output" }, 353f2644a2cSMark Brown 354f2644a2cSMark Brown { "OUT3", NULL, "Mono Output Mixer", } 355f2644a2cSMark Brown }; 356f2644a2cSMark Brown 357f2644a2cSMark Brown static int wm8960_add_widgets(struct snd_soc_codec *codec) 358f2644a2cSMark Brown { 359f2644a2cSMark Brown snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets, 360f2644a2cSMark Brown ARRAY_SIZE(wm8960_dapm_widgets)); 361f2644a2cSMark Brown 362f2644a2cSMark Brown snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); 363f2644a2cSMark Brown 364f2644a2cSMark Brown snd_soc_dapm_new_widgets(codec); 365f2644a2cSMark Brown return 0; 366f2644a2cSMark Brown } 367f2644a2cSMark Brown 368f2644a2cSMark Brown static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai, 369f2644a2cSMark Brown unsigned int fmt) 370f2644a2cSMark Brown { 371f2644a2cSMark Brown struct snd_soc_codec *codec = codec_dai->codec; 372f2644a2cSMark Brown u16 iface = 0; 373f2644a2cSMark Brown 374f2644a2cSMark Brown /* set master/slave audio interface */ 375f2644a2cSMark Brown switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { 376f2644a2cSMark Brown case SND_SOC_DAIFMT_CBM_CFM: 377f2644a2cSMark Brown iface |= 0x0040; 378f2644a2cSMark Brown break; 379f2644a2cSMark Brown case SND_SOC_DAIFMT_CBS_CFS: 380f2644a2cSMark Brown break; 381f2644a2cSMark Brown default: 382f2644a2cSMark Brown return -EINVAL; 383f2644a2cSMark Brown } 384f2644a2cSMark Brown 385f2644a2cSMark Brown /* interface format */ 386f2644a2cSMark Brown switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 387f2644a2cSMark Brown case SND_SOC_DAIFMT_I2S: 388f2644a2cSMark Brown iface |= 0x0002; 389f2644a2cSMark Brown break; 390f2644a2cSMark Brown case SND_SOC_DAIFMT_RIGHT_J: 391f2644a2cSMark Brown break; 392f2644a2cSMark Brown case SND_SOC_DAIFMT_LEFT_J: 393f2644a2cSMark Brown iface |= 0x0001; 394f2644a2cSMark Brown break; 395f2644a2cSMark Brown case SND_SOC_DAIFMT_DSP_A: 396f2644a2cSMark Brown iface |= 0x0003; 397f2644a2cSMark Brown break; 398f2644a2cSMark Brown case SND_SOC_DAIFMT_DSP_B: 399f2644a2cSMark Brown iface |= 0x0013; 400f2644a2cSMark Brown break; 401f2644a2cSMark Brown default: 402f2644a2cSMark Brown return -EINVAL; 403f2644a2cSMark Brown } 404f2644a2cSMark Brown 405f2644a2cSMark Brown /* clock inversion */ 406f2644a2cSMark Brown switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 407f2644a2cSMark Brown case SND_SOC_DAIFMT_NB_NF: 408f2644a2cSMark Brown break; 409f2644a2cSMark Brown case SND_SOC_DAIFMT_IB_IF: 410f2644a2cSMark Brown iface |= 0x0090; 411f2644a2cSMark Brown break; 412f2644a2cSMark Brown case SND_SOC_DAIFMT_IB_NF: 413f2644a2cSMark Brown iface |= 0x0080; 414f2644a2cSMark Brown break; 415f2644a2cSMark Brown case SND_SOC_DAIFMT_NB_IF: 416f2644a2cSMark Brown iface |= 0x0010; 417f2644a2cSMark Brown break; 418f2644a2cSMark Brown default: 419f2644a2cSMark Brown return -EINVAL; 420f2644a2cSMark Brown } 421f2644a2cSMark Brown 422f2644a2cSMark Brown /* set iface */ 423f2644a2cSMark Brown wm8960_write(codec, WM8960_IFACE1, iface); 424f2644a2cSMark Brown return 0; 425f2644a2cSMark Brown } 426f2644a2cSMark Brown 427f2644a2cSMark Brown static int wm8960_hw_params(struct snd_pcm_substream *substream, 428f2644a2cSMark Brown struct snd_pcm_hw_params *params, 429f2644a2cSMark Brown struct snd_soc_dai *dai) 430f2644a2cSMark Brown { 431f2644a2cSMark Brown struct snd_soc_pcm_runtime *rtd = substream->private_data; 432f2644a2cSMark Brown struct snd_soc_device *socdev = rtd->socdev; 433f2644a2cSMark Brown struct snd_soc_codec *codec = socdev->card->codec; 434f2644a2cSMark Brown u16 iface = wm8960_read(codec, WM8960_IFACE1) & 0xfff3; 435f2644a2cSMark Brown 436f2644a2cSMark Brown /* bit size */ 437f2644a2cSMark Brown switch (params_format(params)) { 438f2644a2cSMark Brown case SNDRV_PCM_FORMAT_S16_LE: 439f2644a2cSMark Brown break; 440f2644a2cSMark Brown case SNDRV_PCM_FORMAT_S20_3LE: 441f2644a2cSMark Brown iface |= 0x0004; 442f2644a2cSMark Brown break; 443f2644a2cSMark Brown case SNDRV_PCM_FORMAT_S24_LE: 444f2644a2cSMark Brown iface |= 0x0008; 445f2644a2cSMark Brown break; 446f2644a2cSMark Brown } 447f2644a2cSMark Brown 448f2644a2cSMark Brown /* set iface */ 449f2644a2cSMark Brown wm8960_write(codec, WM8960_IFACE1, iface); 450f2644a2cSMark Brown return 0; 451f2644a2cSMark Brown } 452f2644a2cSMark Brown 453f2644a2cSMark Brown static int wm8960_mute(struct snd_soc_dai *dai, int mute) 454f2644a2cSMark Brown { 455f2644a2cSMark Brown struct snd_soc_codec *codec = dai->codec; 456f2644a2cSMark Brown u16 mute_reg = wm8960_read(codec, WM8960_DACCTL1) & 0xfff7; 457f2644a2cSMark Brown 458f2644a2cSMark Brown if (mute) 459f2644a2cSMark Brown wm8960_write(codec, WM8960_DACCTL1, mute_reg | 0x8); 460f2644a2cSMark Brown else 461f2644a2cSMark Brown wm8960_write(codec, WM8960_DACCTL1, mute_reg); 462f2644a2cSMark Brown return 0; 463f2644a2cSMark Brown } 464f2644a2cSMark Brown 465f2644a2cSMark Brown static int wm8960_set_bias_level(struct snd_soc_codec *codec, 466f2644a2cSMark Brown enum snd_soc_bias_level level) 467f2644a2cSMark Brown { 468f2644a2cSMark Brown struct wm8960_data *pdata = codec->dev->platform_data; 469f2644a2cSMark Brown u16 reg; 470f2644a2cSMark Brown 471f2644a2cSMark Brown switch (level) { 472f2644a2cSMark Brown case SND_SOC_BIAS_ON: 473f2644a2cSMark Brown break; 474f2644a2cSMark Brown 475f2644a2cSMark Brown case SND_SOC_BIAS_PREPARE: 476f2644a2cSMark Brown /* Set VMID to 2x50k */ 477f2644a2cSMark Brown reg = wm8960_read(codec, WM8960_POWER1); 478f2644a2cSMark Brown reg &= ~0x180; 479f2644a2cSMark Brown reg |= 0x80; 480f2644a2cSMark Brown wm8960_write(codec, WM8960_POWER1, reg); 481f2644a2cSMark Brown break; 482f2644a2cSMark Brown 483f2644a2cSMark Brown case SND_SOC_BIAS_STANDBY: 484f2644a2cSMark Brown if (codec->bias_level == SND_SOC_BIAS_OFF) { 485f2644a2cSMark Brown /* Enable anti-pop features */ 486f2644a2cSMark Brown wm8960_write(codec, WM8960_APOP1, 487f2644a2cSMark Brown WM8960_POBCTRL | WM8960_SOFT_ST | 488f2644a2cSMark Brown WM8960_BUFDCOPEN | WM8960_BUFIOEN); 489f2644a2cSMark Brown 490f2644a2cSMark Brown /* Discharge HP output */ 491f2644a2cSMark Brown reg = WM8960_DISOP; 492f2644a2cSMark Brown if (pdata) 493f2644a2cSMark Brown reg |= pdata->dres << 4; 494f2644a2cSMark Brown wm8960_write(codec, WM8960_APOP2, reg); 495f2644a2cSMark Brown 496f2644a2cSMark Brown msleep(400); 497f2644a2cSMark Brown 498f2644a2cSMark Brown wm8960_write(codec, WM8960_APOP2, 0); 499f2644a2cSMark Brown 500f2644a2cSMark Brown /* Enable & ramp VMID at 2x50k */ 501f2644a2cSMark Brown reg = wm8960_read(codec, WM8960_POWER1); 502f2644a2cSMark Brown reg |= 0x80; 503f2644a2cSMark Brown wm8960_write(codec, WM8960_POWER1, reg); 504f2644a2cSMark Brown msleep(100); 505f2644a2cSMark Brown 506f2644a2cSMark Brown /* Enable VREF */ 507f2644a2cSMark Brown wm8960_write(codec, WM8960_POWER1, reg | WM8960_VREF); 508f2644a2cSMark Brown 509f2644a2cSMark Brown /* Disable anti-pop features */ 510f2644a2cSMark Brown wm8960_write(codec, WM8960_APOP1, WM8960_BUFIOEN); 511f2644a2cSMark Brown } 512f2644a2cSMark Brown 513f2644a2cSMark Brown /* Set VMID to 2x250k */ 514f2644a2cSMark Brown reg = wm8960_read(codec, WM8960_POWER1); 515f2644a2cSMark Brown reg &= ~0x180; 516f2644a2cSMark Brown reg |= 0x100; 517f2644a2cSMark Brown wm8960_write(codec, WM8960_POWER1, reg); 518f2644a2cSMark Brown break; 519f2644a2cSMark Brown 520f2644a2cSMark Brown case SND_SOC_BIAS_OFF: 521f2644a2cSMark Brown /* Enable anti-pop features */ 522f2644a2cSMark Brown wm8960_write(codec, WM8960_APOP1, 523f2644a2cSMark Brown WM8960_POBCTRL | WM8960_SOFT_ST | 524f2644a2cSMark Brown WM8960_BUFDCOPEN | WM8960_BUFIOEN); 525f2644a2cSMark Brown 526f2644a2cSMark Brown /* Disable VMID and VREF, let them discharge */ 527f2644a2cSMark Brown wm8960_write(codec, WM8960_POWER1, 0); 528f2644a2cSMark Brown msleep(600); 529f2644a2cSMark Brown 530f2644a2cSMark Brown wm8960_write(codec, WM8960_APOP1, 0); 531f2644a2cSMark Brown break; 532f2644a2cSMark Brown } 533f2644a2cSMark Brown 534f2644a2cSMark Brown codec->bias_level = level; 535f2644a2cSMark Brown 536f2644a2cSMark Brown return 0; 537f2644a2cSMark Brown } 538f2644a2cSMark Brown 539f2644a2cSMark Brown /* PLL divisors */ 540f2644a2cSMark Brown struct _pll_div { 541f2644a2cSMark Brown u32 pre_div:1; 542f2644a2cSMark Brown u32 n:4; 543f2644a2cSMark Brown u32 k:24; 544f2644a2cSMark Brown }; 545f2644a2cSMark Brown 546f2644a2cSMark Brown /* The size in bits of the pll divide multiplied by 10 547f2644a2cSMark Brown * to allow rounding later */ 548f2644a2cSMark Brown #define FIXED_PLL_SIZE ((1 << 24) * 10) 549f2644a2cSMark Brown 550f2644a2cSMark Brown static int pll_factors(unsigned int source, unsigned int target, 551f2644a2cSMark Brown struct _pll_div *pll_div) 552f2644a2cSMark Brown { 553f2644a2cSMark Brown unsigned long long Kpart; 554f2644a2cSMark Brown unsigned int K, Ndiv, Nmod; 555f2644a2cSMark Brown 556f2644a2cSMark Brown pr_debug("WM8960 PLL: setting %dHz->%dHz\n", source, target); 557f2644a2cSMark Brown 558f2644a2cSMark Brown /* Scale up target to PLL operating frequency */ 559f2644a2cSMark Brown target *= 4; 560f2644a2cSMark Brown 561f2644a2cSMark Brown Ndiv = target / source; 562f2644a2cSMark Brown if (Ndiv < 6) { 563f2644a2cSMark Brown source >>= 1; 564f2644a2cSMark Brown pll_div->pre_div = 1; 565f2644a2cSMark Brown Ndiv = target / source; 566f2644a2cSMark Brown } else 567f2644a2cSMark Brown pll_div->pre_div = 0; 568f2644a2cSMark Brown 569f2644a2cSMark Brown if ((Ndiv < 6) || (Ndiv > 12)) { 570f2644a2cSMark Brown pr_err("WM8960 PLL: Unsupported N=%d\n", Ndiv); 571f2644a2cSMark Brown return -EINVAL; 572f2644a2cSMark Brown } 573f2644a2cSMark Brown 574f2644a2cSMark Brown pll_div->n = Ndiv; 575f2644a2cSMark Brown Nmod = target % source; 576f2644a2cSMark Brown Kpart = FIXED_PLL_SIZE * (long long)Nmod; 577f2644a2cSMark Brown 578f2644a2cSMark Brown do_div(Kpart, source); 579f2644a2cSMark Brown 580f2644a2cSMark Brown K = Kpart & 0xFFFFFFFF; 581f2644a2cSMark Brown 582f2644a2cSMark Brown /* Check if we need to round */ 583f2644a2cSMark Brown if ((K % 10) >= 5) 584f2644a2cSMark Brown K += 5; 585f2644a2cSMark Brown 586f2644a2cSMark Brown /* Move down to proper range now rounding is done */ 587f2644a2cSMark Brown K /= 10; 588f2644a2cSMark Brown 589f2644a2cSMark Brown pll_div->k = K; 590f2644a2cSMark Brown 591f2644a2cSMark Brown pr_debug("WM8960 PLL: N=%x K=%x pre_div=%d\n", 592f2644a2cSMark Brown pll_div->n, pll_div->k, pll_div->pre_div); 593f2644a2cSMark Brown 594f2644a2cSMark Brown return 0; 595f2644a2cSMark Brown } 596f2644a2cSMark Brown 597f2644a2cSMark Brown static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, 598f2644a2cSMark Brown int pll_id, unsigned int freq_in, unsigned int freq_out) 599f2644a2cSMark Brown { 600f2644a2cSMark Brown struct snd_soc_codec *codec = codec_dai->codec; 601f2644a2cSMark Brown u16 reg; 602f2644a2cSMark Brown static struct _pll_div pll_div; 603f2644a2cSMark Brown int ret; 604f2644a2cSMark Brown 605f2644a2cSMark Brown if (freq_in && freq_out) { 606f2644a2cSMark Brown ret = pll_factors(freq_in, freq_out, &pll_div); 607f2644a2cSMark Brown if (ret != 0) 608f2644a2cSMark Brown return ret; 609f2644a2cSMark Brown } 610f2644a2cSMark Brown 611f2644a2cSMark Brown /* Disable the PLL: even if we are changing the frequency the 612f2644a2cSMark Brown * PLL needs to be disabled while we do so. */ 613f2644a2cSMark Brown wm8960_write(codec, WM8960_CLOCK1, 614f2644a2cSMark Brown wm8960_read(codec, WM8960_CLOCK1) & ~1); 615f2644a2cSMark Brown wm8960_write(codec, WM8960_POWER2, 616f2644a2cSMark Brown wm8960_read(codec, WM8960_POWER2) & ~1); 617f2644a2cSMark Brown 618f2644a2cSMark Brown if (!freq_in || !freq_out) 619f2644a2cSMark Brown return 0; 620f2644a2cSMark Brown 621f2644a2cSMark Brown reg = wm8960_read(codec, WM8960_PLL1) & ~0x3f; 622f2644a2cSMark Brown reg |= pll_div.pre_div << 4; 623f2644a2cSMark Brown reg |= pll_div.n; 624f2644a2cSMark Brown 625f2644a2cSMark Brown if (pll_div.k) { 626f2644a2cSMark Brown reg |= 0x20; 627f2644a2cSMark Brown 628f2644a2cSMark Brown wm8960_write(codec, WM8960_PLL2, (pll_div.k >> 18) & 0x3f); 629f2644a2cSMark Brown wm8960_write(codec, WM8960_PLL3, (pll_div.k >> 9) & 0x1ff); 630f2644a2cSMark Brown wm8960_write(codec, WM8960_PLL4, pll_div.k & 0x1ff); 631f2644a2cSMark Brown } 632f2644a2cSMark Brown wm8960_write(codec, WM8960_PLL1, reg); 633f2644a2cSMark Brown 634f2644a2cSMark Brown /* Turn it on */ 635f2644a2cSMark Brown wm8960_write(codec, WM8960_POWER2, 636f2644a2cSMark Brown wm8960_read(codec, WM8960_POWER2) | 1); 637f2644a2cSMark Brown msleep(250); 638f2644a2cSMark Brown wm8960_write(codec, WM8960_CLOCK1, 639f2644a2cSMark Brown wm8960_read(codec, WM8960_CLOCK1) | 1); 640f2644a2cSMark Brown 641f2644a2cSMark Brown return 0; 642f2644a2cSMark Brown } 643f2644a2cSMark Brown 644f2644a2cSMark Brown static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai, 645f2644a2cSMark Brown int div_id, int div) 646f2644a2cSMark Brown { 647f2644a2cSMark Brown struct snd_soc_codec *codec = codec_dai->codec; 648f2644a2cSMark Brown u16 reg; 649f2644a2cSMark Brown 650f2644a2cSMark Brown switch (div_id) { 651f2644a2cSMark Brown case WM8960_SYSCLKSEL: 652f2644a2cSMark Brown reg = wm8960_read(codec, WM8960_CLOCK1) & 0x1fe; 653f2644a2cSMark Brown wm8960_write(codec, WM8960_CLOCK1, reg | div); 654f2644a2cSMark Brown break; 655f2644a2cSMark Brown case WM8960_SYSCLKDIV: 656f2644a2cSMark Brown reg = wm8960_read(codec, WM8960_CLOCK1) & 0x1f9; 657f2644a2cSMark Brown wm8960_write(codec, WM8960_CLOCK1, reg | div); 658f2644a2cSMark Brown break; 659f2644a2cSMark Brown case WM8960_DACDIV: 660f2644a2cSMark Brown reg = wm8960_read(codec, WM8960_CLOCK1) & 0x1c7; 661f2644a2cSMark Brown wm8960_write(codec, WM8960_CLOCK1, reg | div); 662f2644a2cSMark Brown break; 663f2644a2cSMark Brown case WM8960_OPCLKDIV: 664f2644a2cSMark Brown reg = wm8960_read(codec, WM8960_PLL1) & 0x03f; 665f2644a2cSMark Brown wm8960_write(codec, WM8960_PLL1, reg | div); 666f2644a2cSMark Brown break; 667f2644a2cSMark Brown case WM8960_DCLKDIV: 668f2644a2cSMark Brown reg = wm8960_read(codec, WM8960_CLOCK2) & 0x03f; 669f2644a2cSMark Brown wm8960_write(codec, WM8960_CLOCK2, reg | div); 670f2644a2cSMark Brown break; 671f2644a2cSMark Brown case WM8960_TOCLKSEL: 672f2644a2cSMark Brown reg = wm8960_read(codec, WM8960_ADDCTL1) & 0x1fd; 673f2644a2cSMark Brown wm8960_write(codec, WM8960_ADDCTL1, reg | div); 674f2644a2cSMark Brown break; 675f2644a2cSMark Brown default: 676f2644a2cSMark Brown return -EINVAL; 677f2644a2cSMark Brown } 678f2644a2cSMark Brown 679f2644a2cSMark Brown return 0; 680f2644a2cSMark Brown } 681f2644a2cSMark Brown 682f2644a2cSMark Brown #define WM8960_RATES SNDRV_PCM_RATE_8000_48000 683f2644a2cSMark Brown 684f2644a2cSMark Brown #define WM8960_FORMATS \ 685f2644a2cSMark Brown (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ 686f2644a2cSMark Brown SNDRV_PCM_FMTBIT_S24_LE) 687f2644a2cSMark Brown 688f2644a2cSMark Brown static struct snd_soc_dai_ops wm8960_dai_ops = { 689f2644a2cSMark Brown .hw_params = wm8960_hw_params, 690f2644a2cSMark Brown .digital_mute = wm8960_mute, 691f2644a2cSMark Brown .set_fmt = wm8960_set_dai_fmt, 692f2644a2cSMark Brown .set_clkdiv = wm8960_set_dai_clkdiv, 693f2644a2cSMark Brown .set_pll = wm8960_set_dai_pll, 694f2644a2cSMark Brown }; 695f2644a2cSMark Brown 696f2644a2cSMark Brown struct snd_soc_dai wm8960_dai = { 697f2644a2cSMark Brown .name = "WM8960", 698f2644a2cSMark Brown .playback = { 699f2644a2cSMark Brown .stream_name = "Playback", 700f2644a2cSMark Brown .channels_min = 1, 701f2644a2cSMark Brown .channels_max = 2, 702f2644a2cSMark Brown .rates = WM8960_RATES, 703f2644a2cSMark Brown .formats = WM8960_FORMATS,}, 704f2644a2cSMark Brown .capture = { 705f2644a2cSMark Brown .stream_name = "Capture", 706f2644a2cSMark Brown .channels_min = 1, 707f2644a2cSMark Brown .channels_max = 2, 708f2644a2cSMark Brown .rates = WM8960_RATES, 709f2644a2cSMark Brown .formats = WM8960_FORMATS,}, 710f2644a2cSMark Brown .ops = &wm8960_dai_ops, 711f2644a2cSMark Brown .symmetric_rates = 1, 712f2644a2cSMark Brown }; 713f2644a2cSMark Brown EXPORT_SYMBOL_GPL(wm8960_dai); 714f2644a2cSMark Brown 715f2644a2cSMark Brown static int wm8960_suspend(struct platform_device *pdev, pm_message_t state) 716f2644a2cSMark Brown { 717f2644a2cSMark Brown struct snd_soc_device *socdev = platform_get_drvdata(pdev); 718f2644a2cSMark Brown struct snd_soc_codec *codec = socdev->card->codec; 719f2644a2cSMark Brown 720f2644a2cSMark Brown wm8960_set_bias_level(codec, SND_SOC_BIAS_OFF); 721f2644a2cSMark Brown return 0; 722f2644a2cSMark Brown } 723f2644a2cSMark Brown 724f2644a2cSMark Brown static int wm8960_resume(struct platform_device *pdev) 725f2644a2cSMark Brown { 726f2644a2cSMark Brown struct snd_soc_device *socdev = platform_get_drvdata(pdev); 727f2644a2cSMark Brown struct snd_soc_codec *codec = socdev->card->codec; 728f2644a2cSMark Brown int i; 729f2644a2cSMark Brown u8 data[2]; 730f2644a2cSMark Brown u16 *cache = codec->reg_cache; 731f2644a2cSMark Brown 732f2644a2cSMark Brown /* Sync reg_cache with the hardware */ 733f2644a2cSMark Brown for (i = 0; i < ARRAY_SIZE(wm8960_reg); i++) { 734f2644a2cSMark Brown data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); 735f2644a2cSMark Brown data[1] = cache[i] & 0x00ff; 736f2644a2cSMark Brown codec->hw_write(codec->control_data, data, 2); 737f2644a2cSMark Brown } 738f2644a2cSMark Brown 739f2644a2cSMark Brown wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 740f2644a2cSMark Brown wm8960_set_bias_level(codec, codec->suspend_bias_level); 741f2644a2cSMark Brown return 0; 742f2644a2cSMark Brown } 743f2644a2cSMark Brown 744f2644a2cSMark Brown static struct snd_soc_codec *wm8960_codec; 745f2644a2cSMark Brown 746f2644a2cSMark Brown static int wm8960_probe(struct platform_device *pdev) 747f2644a2cSMark Brown { 748f2644a2cSMark Brown struct snd_soc_device *socdev = platform_get_drvdata(pdev); 749f2644a2cSMark Brown struct snd_soc_codec *codec; 750f2644a2cSMark Brown int ret = 0; 751f2644a2cSMark Brown 752f2644a2cSMark Brown if (wm8960_codec == NULL) { 753f2644a2cSMark Brown dev_err(&pdev->dev, "Codec device not registered\n"); 754f2644a2cSMark Brown return -ENODEV; 755f2644a2cSMark Brown } 756f2644a2cSMark Brown 757f2644a2cSMark Brown socdev->card->codec = wm8960_codec; 758f2644a2cSMark Brown codec = wm8960_codec; 759f2644a2cSMark Brown 760f2644a2cSMark Brown /* register pcms */ 761f2644a2cSMark Brown ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); 762f2644a2cSMark Brown if (ret < 0) { 763f2644a2cSMark Brown dev_err(codec->dev, "failed to create pcms: %d\n", ret); 764f2644a2cSMark Brown goto pcm_err; 765f2644a2cSMark Brown } 766f2644a2cSMark Brown 767f2644a2cSMark Brown snd_soc_add_controls(codec, wm8960_snd_controls, 768f2644a2cSMark Brown ARRAY_SIZE(wm8960_snd_controls)); 769f2644a2cSMark Brown wm8960_add_widgets(codec); 770f2644a2cSMark Brown ret = snd_soc_init_card(socdev); 771f2644a2cSMark Brown if (ret < 0) { 772f2644a2cSMark Brown dev_err(codec->dev, "failed to register card: %d\n", ret); 773f2644a2cSMark Brown goto card_err; 774f2644a2cSMark Brown } 775f2644a2cSMark Brown 776f2644a2cSMark Brown return ret; 777f2644a2cSMark Brown 778f2644a2cSMark Brown card_err: 779f2644a2cSMark Brown snd_soc_free_pcms(socdev); 780f2644a2cSMark Brown snd_soc_dapm_free(socdev); 781f2644a2cSMark Brown pcm_err: 782f2644a2cSMark Brown return ret; 783f2644a2cSMark Brown } 784f2644a2cSMark Brown 785f2644a2cSMark Brown /* power down chip */ 786f2644a2cSMark Brown static int wm8960_remove(struct platform_device *pdev) 787f2644a2cSMark Brown { 788f2644a2cSMark Brown struct snd_soc_device *socdev = platform_get_drvdata(pdev); 789f2644a2cSMark Brown 790f2644a2cSMark Brown snd_soc_free_pcms(socdev); 791f2644a2cSMark Brown snd_soc_dapm_free(socdev); 792f2644a2cSMark Brown 793f2644a2cSMark Brown return 0; 794f2644a2cSMark Brown } 795f2644a2cSMark Brown 796f2644a2cSMark Brown struct snd_soc_codec_device soc_codec_dev_wm8960 = { 797f2644a2cSMark Brown .probe = wm8960_probe, 798f2644a2cSMark Brown .remove = wm8960_remove, 799f2644a2cSMark Brown .suspend = wm8960_suspend, 800f2644a2cSMark Brown .resume = wm8960_resume, 801f2644a2cSMark Brown }; 802f2644a2cSMark Brown EXPORT_SYMBOL_GPL(soc_codec_dev_wm8960); 803f2644a2cSMark Brown 804f2644a2cSMark Brown static int wm8960_register(struct wm8960_priv *wm8960) 805f2644a2cSMark Brown { 806f2644a2cSMark Brown struct wm8960_data *pdata = wm8960->codec.dev->platform_data; 807f2644a2cSMark Brown struct snd_soc_codec *codec = &wm8960->codec; 808f2644a2cSMark Brown int ret; 809f2644a2cSMark Brown u16 reg; 810f2644a2cSMark Brown 811f2644a2cSMark Brown if (wm8960_codec) { 812f2644a2cSMark Brown dev_err(codec->dev, "Another WM8960 is registered\n"); 813f2644a2cSMark Brown return -EINVAL; 814f2644a2cSMark Brown } 815f2644a2cSMark Brown 816f2644a2cSMark Brown if (!pdata) { 817f2644a2cSMark Brown dev_warn(codec->dev, "No platform data supplied\n"); 818f2644a2cSMark Brown } else { 819f2644a2cSMark Brown if (pdata->dres > WM8960_DRES_MAX) { 820f2644a2cSMark Brown dev_err(codec->dev, "Invalid DRES: %d\n", pdata->dres); 821f2644a2cSMark Brown pdata->dres = 0; 822f2644a2cSMark Brown } 823f2644a2cSMark Brown } 824f2644a2cSMark Brown 825f2644a2cSMark Brown mutex_init(&codec->mutex); 826f2644a2cSMark Brown INIT_LIST_HEAD(&codec->dapm_widgets); 827f2644a2cSMark Brown INIT_LIST_HEAD(&codec->dapm_paths); 828f2644a2cSMark Brown 829f2644a2cSMark Brown codec->private_data = wm8960; 830f2644a2cSMark Brown codec->name = "WM8960"; 831f2644a2cSMark Brown codec->owner = THIS_MODULE; 832f2644a2cSMark Brown codec->read = wm8960_read_reg_cache; 833f2644a2cSMark Brown codec->write = wm8960_write; 834f2644a2cSMark Brown codec->bias_level = SND_SOC_BIAS_OFF; 835f2644a2cSMark Brown codec->set_bias_level = wm8960_set_bias_level; 836f2644a2cSMark Brown codec->dai = &wm8960_dai; 837f2644a2cSMark Brown codec->num_dai = 1; 838f2644a2cSMark Brown codec->reg_cache_size = WM8960_CACHEREGNUM; 839f2644a2cSMark Brown codec->reg_cache = &wm8960->reg_cache; 840f2644a2cSMark Brown 841f2644a2cSMark Brown memcpy(codec->reg_cache, wm8960_reg, sizeof(wm8960_reg)); 842f2644a2cSMark Brown 843f2644a2cSMark Brown ret = wm8960_reset(codec); 844f2644a2cSMark Brown if (ret < 0) { 845f2644a2cSMark Brown dev_err(codec->dev, "Failed to issue reset\n"); 846f2644a2cSMark Brown return ret; 847f2644a2cSMark Brown } 848f2644a2cSMark Brown 849f2644a2cSMark Brown wm8960_dai.dev = codec->dev; 850f2644a2cSMark Brown 851f2644a2cSMark Brown wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 852f2644a2cSMark Brown 853f2644a2cSMark Brown /* Latch the update bits */ 854f2644a2cSMark Brown reg = wm8960_read(codec, WM8960_LINVOL); 855f2644a2cSMark Brown wm8960_write(codec, WM8960_LINVOL, reg | 0x100); 856f2644a2cSMark Brown reg = wm8960_read(codec, WM8960_RINVOL); 857f2644a2cSMark Brown wm8960_write(codec, WM8960_RINVOL, reg | 0x100); 858f2644a2cSMark Brown reg = wm8960_read(codec, WM8960_LADC); 859f2644a2cSMark Brown wm8960_write(codec, WM8960_LADC, reg | 0x100); 860f2644a2cSMark Brown reg = wm8960_read(codec, WM8960_RADC); 861f2644a2cSMark Brown wm8960_write(codec, WM8960_RADC, reg | 0x100); 862f2644a2cSMark Brown reg = wm8960_read(codec, WM8960_LDAC); 863f2644a2cSMark Brown wm8960_write(codec, WM8960_LDAC, reg | 0x100); 864f2644a2cSMark Brown reg = wm8960_read(codec, WM8960_RDAC); 865f2644a2cSMark Brown wm8960_write(codec, WM8960_RDAC, reg | 0x100); 866f2644a2cSMark Brown reg = wm8960_read(codec, WM8960_LOUT1); 867f2644a2cSMark Brown wm8960_write(codec, WM8960_LOUT1, reg | 0x100); 868f2644a2cSMark Brown reg = wm8960_read(codec, WM8960_ROUT1); 869f2644a2cSMark Brown wm8960_write(codec, WM8960_ROUT1, reg | 0x100); 870f2644a2cSMark Brown reg = wm8960_read(codec, WM8960_LOUT2); 871f2644a2cSMark Brown wm8960_write(codec, WM8960_LOUT2, reg | 0x100); 872f2644a2cSMark Brown reg = wm8960_read(codec, WM8960_ROUT2); 873f2644a2cSMark Brown wm8960_write(codec, WM8960_ROUT2, reg | 0x100); 874f2644a2cSMark Brown 875f2644a2cSMark Brown wm8960_codec = codec; 876f2644a2cSMark Brown 877f2644a2cSMark Brown ret = snd_soc_register_codec(codec); 878f2644a2cSMark Brown if (ret != 0) { 879f2644a2cSMark Brown dev_err(codec->dev, "Failed to register codec: %d\n", ret); 880f2644a2cSMark Brown return ret; 881f2644a2cSMark Brown } 882f2644a2cSMark Brown 883f2644a2cSMark Brown ret = snd_soc_register_dai(&wm8960_dai); 884f2644a2cSMark Brown if (ret != 0) { 885f2644a2cSMark Brown dev_err(codec->dev, "Failed to register DAI: %d\n", ret); 886f2644a2cSMark Brown snd_soc_unregister_codec(codec); 887f2644a2cSMark Brown return ret; 888f2644a2cSMark Brown } 889f2644a2cSMark Brown 890f2644a2cSMark Brown return 0; 891f2644a2cSMark Brown } 892f2644a2cSMark Brown 893f2644a2cSMark Brown static void wm8960_unregister(struct wm8960_priv *wm8960) 894f2644a2cSMark Brown { 895f2644a2cSMark Brown wm8960_set_bias_level(&wm8960->codec, SND_SOC_BIAS_OFF); 896f2644a2cSMark Brown snd_soc_unregister_dai(&wm8960_dai); 897f2644a2cSMark Brown snd_soc_unregister_codec(&wm8960->codec); 898f2644a2cSMark Brown kfree(wm8960); 899f2644a2cSMark Brown wm8960_codec = NULL; 900f2644a2cSMark Brown } 901f2644a2cSMark Brown 902f2644a2cSMark Brown static __devinit int wm8960_i2c_probe(struct i2c_client *i2c, 903f2644a2cSMark Brown const struct i2c_device_id *id) 904f2644a2cSMark Brown { 905f2644a2cSMark Brown struct wm8960_priv *wm8960; 906f2644a2cSMark Brown struct snd_soc_codec *codec; 907f2644a2cSMark Brown 908f2644a2cSMark Brown wm8960 = kzalloc(sizeof(struct wm8960_priv), GFP_KERNEL); 909f2644a2cSMark Brown if (wm8960 == NULL) 910f2644a2cSMark Brown return -ENOMEM; 911f2644a2cSMark Brown 912f2644a2cSMark Brown codec = &wm8960->codec; 913f2644a2cSMark Brown codec->hw_write = (hw_write_t)i2c_master_send; 914f2644a2cSMark Brown 915f2644a2cSMark Brown i2c_set_clientdata(i2c, wm8960); 916f2644a2cSMark Brown codec->control_data = i2c; 917f2644a2cSMark Brown 918f2644a2cSMark Brown codec->dev = &i2c->dev; 919f2644a2cSMark Brown 920f2644a2cSMark Brown return wm8960_register(wm8960); 921f2644a2cSMark Brown } 922f2644a2cSMark Brown 923f2644a2cSMark Brown static __devexit int wm8960_i2c_remove(struct i2c_client *client) 924f2644a2cSMark Brown { 925f2644a2cSMark Brown struct wm8960_priv *wm8960 = i2c_get_clientdata(client); 926f2644a2cSMark Brown wm8960_unregister(wm8960); 927f2644a2cSMark Brown return 0; 928f2644a2cSMark Brown } 929f2644a2cSMark Brown 930f2644a2cSMark Brown static const struct i2c_device_id wm8960_i2c_id[] = { 931f2644a2cSMark Brown { "wm8960", 0 }, 932f2644a2cSMark Brown { } 933f2644a2cSMark Brown }; 934f2644a2cSMark Brown MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id); 935f2644a2cSMark Brown 936f2644a2cSMark Brown static struct i2c_driver wm8960_i2c_driver = { 937f2644a2cSMark Brown .driver = { 938f2644a2cSMark Brown .name = "WM8960 I2C Codec", 939f2644a2cSMark Brown .owner = THIS_MODULE, 940f2644a2cSMark Brown }, 941f2644a2cSMark Brown .probe = wm8960_i2c_probe, 942f2644a2cSMark Brown .remove = __devexit_p(wm8960_i2c_remove), 943f2644a2cSMark Brown .id_table = wm8960_i2c_id, 944f2644a2cSMark Brown }; 945f2644a2cSMark Brown 946f2644a2cSMark Brown static int __init wm8960_modinit(void) 947f2644a2cSMark Brown { 948f2644a2cSMark Brown int ret; 949f2644a2cSMark Brown 950f2644a2cSMark Brown ret = i2c_add_driver(&wm8960_i2c_driver); 951f2644a2cSMark Brown if (ret != 0) { 952f2644a2cSMark Brown printk(KERN_ERR "Failed to register WM8960 I2C driver: %d\n", 953f2644a2cSMark Brown ret); 954f2644a2cSMark Brown } 955f2644a2cSMark Brown 956f2644a2cSMark Brown return ret; 957f2644a2cSMark Brown } 958f2644a2cSMark Brown module_init(wm8960_modinit); 959f2644a2cSMark Brown 960f2644a2cSMark Brown static void __exit wm8960_exit(void) 961f2644a2cSMark Brown { 962f2644a2cSMark Brown i2c_del_driver(&wm8960_i2c_driver); 963f2644a2cSMark Brown } 964f2644a2cSMark Brown module_exit(wm8960_exit); 965f2644a2cSMark Brown 966f2644a2cSMark Brown 967f2644a2cSMark Brown MODULE_DESCRIPTION("ASoC WM8960 driver"); 968f2644a2cSMark Brown MODULE_AUTHOR("Liam Girdwood"); 969f2644a2cSMark Brown MODULE_LICENSE("GPL"); 970