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> 185a0e3ad6STejun Heo #include <linux/slab.h> 19f2644a2cSMark Brown #include <sound/core.h> 20f2644a2cSMark Brown #include <sound/pcm.h> 21f2644a2cSMark Brown #include <sound/pcm_params.h> 22f2644a2cSMark Brown #include <sound/soc.h> 23f2644a2cSMark Brown #include <sound/soc-dapm.h> 24f2644a2cSMark Brown #include <sound/initval.h> 25f2644a2cSMark Brown #include <sound/tlv.h> 26b6877a47SMark Brown #include <sound/wm8960.h> 27f2644a2cSMark Brown 28f2644a2cSMark Brown #include "wm8960.h" 29f2644a2cSMark Brown 30f2644a2cSMark Brown #define AUDIO_NAME "wm8960" 31f2644a2cSMark Brown 32f2644a2cSMark Brown /* R25 - Power 1 */ 33913d7b4cSMark Brown #define WM8960_VMID_MASK 0x180 34f2644a2cSMark Brown #define WM8960_VREF 0x40 35f2644a2cSMark Brown 36913d7b4cSMark Brown /* R26 - Power 2 */ 37913d7b4cSMark Brown #define WM8960_PWR2_LOUT1 0x40 38913d7b4cSMark Brown #define WM8960_PWR2_ROUT1 0x20 39913d7b4cSMark Brown #define WM8960_PWR2_OUT3 0x02 40913d7b4cSMark Brown 41f2644a2cSMark Brown /* R28 - Anti-pop 1 */ 42f2644a2cSMark Brown #define WM8960_POBCTRL 0x80 43f2644a2cSMark Brown #define WM8960_BUFDCOPEN 0x10 44f2644a2cSMark Brown #define WM8960_BUFIOEN 0x08 45f2644a2cSMark Brown #define WM8960_SOFT_ST 0x04 46f2644a2cSMark Brown #define WM8960_HPSTBY 0x01 47f2644a2cSMark Brown 48f2644a2cSMark Brown /* R29 - Anti-pop 2 */ 49f2644a2cSMark Brown #define WM8960_DISOP 0x40 50913d7b4cSMark Brown #define WM8960_DRES_MASK 0x30 51f2644a2cSMark Brown 52f2644a2cSMark Brown /* 53f2644a2cSMark Brown * wm8960 register cache 54f2644a2cSMark Brown * We can't read the WM8960 register space when we are 55f2644a2cSMark Brown * using 2 wire for device control, so we cache them instead. 56f2644a2cSMark Brown */ 57f2644a2cSMark Brown static const u16 wm8960_reg[WM8960_CACHEREGNUM] = { 58f2644a2cSMark Brown 0x0097, 0x0097, 0x0000, 0x0000, 59f2644a2cSMark Brown 0x0000, 0x0008, 0x0000, 0x000a, 60f2644a2cSMark Brown 0x01c0, 0x0000, 0x00ff, 0x00ff, 61f2644a2cSMark Brown 0x0000, 0x0000, 0x0000, 0x0000, 62f2644a2cSMark Brown 0x0000, 0x007b, 0x0100, 0x0032, 63f2644a2cSMark Brown 0x0000, 0x00c3, 0x00c3, 0x01c0, 64f2644a2cSMark Brown 0x0000, 0x0000, 0x0000, 0x0000, 65f2644a2cSMark Brown 0x0000, 0x0000, 0x0000, 0x0000, 66f2644a2cSMark Brown 0x0100, 0x0100, 0x0050, 0x0050, 67f2644a2cSMark Brown 0x0050, 0x0050, 0x0000, 0x0000, 68f2644a2cSMark Brown 0x0000, 0x0000, 0x0040, 0x0000, 69f2644a2cSMark Brown 0x0000, 0x0050, 0x0050, 0x0000, 70f2644a2cSMark Brown 0x0002, 0x0037, 0x004d, 0x0080, 71f2644a2cSMark Brown 0x0008, 0x0031, 0x0026, 0x00e9, 72f2644a2cSMark Brown }; 73f2644a2cSMark Brown 74f2644a2cSMark Brown struct wm8960_priv { 75f2644a2cSMark Brown u16 reg_cache[WM8960_CACHEREGNUM]; 76f0fba2adSLiam Girdwood enum snd_soc_control_type control_type; 77f0fba2adSLiam Girdwood void *control_data; 78f0fba2adSLiam Girdwood int (*set_bias_level)(struct snd_soc_codec *, 79f0fba2adSLiam Girdwood enum snd_soc_bias_level level); 80913d7b4cSMark Brown struct snd_soc_dapm_widget *lout1; 81913d7b4cSMark Brown struct snd_soc_dapm_widget *rout1; 82913d7b4cSMark Brown struct snd_soc_dapm_widget *out3; 83afd6d36aSMark Brown bool deemph; 84afd6d36aSMark Brown int playback_fs; 85f2644a2cSMark Brown }; 86f2644a2cSMark Brown 8717a52fd6SMark Brown #define wm8960_reset(c) snd_soc_write(c, WM8960_RESET, 0) 88f2644a2cSMark Brown 89f2644a2cSMark Brown /* enumerated controls */ 90f2644a2cSMark Brown static const char *wm8960_polarity[] = {"No Inversion", "Left Inverted", 91f2644a2cSMark Brown "Right Inverted", "Stereo Inversion"}; 92f2644a2cSMark Brown static const char *wm8960_3d_upper_cutoff[] = {"High", "Low"}; 93f2644a2cSMark Brown static const char *wm8960_3d_lower_cutoff[] = {"Low", "High"}; 94f2644a2cSMark Brown static const char *wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"}; 95f2644a2cSMark Brown static const char *wm8960_alcmode[] = {"ALC", "Limiter"}; 96f2644a2cSMark Brown 97f2644a2cSMark Brown static const struct soc_enum wm8960_enum[] = { 98f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity), 99f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity), 100f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff), 101f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_3D, 5, 2, wm8960_3d_lower_cutoff), 102f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_ALC1, 7, 4, wm8960_alcfunc), 103f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode), 104f2644a2cSMark Brown }; 105f2644a2cSMark Brown 106afd6d36aSMark Brown static const int deemph_settings[] = { 0, 32000, 44100, 48000 }; 107afd6d36aSMark Brown 108afd6d36aSMark Brown static int wm8960_set_deemph(struct snd_soc_codec *codec) 109afd6d36aSMark Brown { 110afd6d36aSMark Brown struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); 111afd6d36aSMark Brown int val, i, best; 112afd6d36aSMark Brown 113afd6d36aSMark Brown /* If we're using deemphasis select the nearest available sample 114afd6d36aSMark Brown * rate. 115afd6d36aSMark Brown */ 116afd6d36aSMark Brown if (wm8960->deemph) { 117afd6d36aSMark Brown best = 1; 118afd6d36aSMark Brown for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) { 119afd6d36aSMark Brown if (abs(deemph_settings[i] - wm8960->playback_fs) < 120afd6d36aSMark Brown abs(deemph_settings[best] - wm8960->playback_fs)) 121afd6d36aSMark Brown best = i; 122afd6d36aSMark Brown } 123afd6d36aSMark Brown 124afd6d36aSMark Brown val = best << 1; 125afd6d36aSMark Brown } else { 126afd6d36aSMark Brown val = 0; 127afd6d36aSMark Brown } 128afd6d36aSMark Brown 129afd6d36aSMark Brown dev_dbg(codec->dev, "Set deemphasis %d\n", val); 130afd6d36aSMark Brown 131afd6d36aSMark Brown return snd_soc_update_bits(codec, WM8960_DACCTL1, 132afd6d36aSMark Brown 0x6, val); 133afd6d36aSMark Brown } 134afd6d36aSMark Brown 135afd6d36aSMark Brown static int wm8960_get_deemph(struct snd_kcontrol *kcontrol, 136afd6d36aSMark Brown struct snd_ctl_elem_value *ucontrol) 137afd6d36aSMark Brown { 138afd6d36aSMark Brown struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); 139afd6d36aSMark Brown struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); 140afd6d36aSMark Brown 141afd6d36aSMark Brown return wm8960->deemph; 142afd6d36aSMark Brown } 143afd6d36aSMark Brown 144afd6d36aSMark Brown static int wm8960_put_deemph(struct snd_kcontrol *kcontrol, 145afd6d36aSMark Brown struct snd_ctl_elem_value *ucontrol) 146afd6d36aSMark Brown { 147afd6d36aSMark Brown struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); 148afd6d36aSMark Brown struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); 149afd6d36aSMark Brown int deemph = ucontrol->value.enumerated.item[0]; 150afd6d36aSMark Brown 151afd6d36aSMark Brown if (deemph > 1) 152afd6d36aSMark Brown return -EINVAL; 153afd6d36aSMark Brown 154afd6d36aSMark Brown wm8960->deemph = deemph; 155afd6d36aSMark Brown 156afd6d36aSMark Brown return wm8960_set_deemph(codec); 157afd6d36aSMark Brown } 158afd6d36aSMark Brown 159f2644a2cSMark Brown static const DECLARE_TLV_DB_SCALE(adc_tlv, -9700, 50, 0); 160f2644a2cSMark Brown static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 50, 1); 161f2644a2cSMark Brown static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0); 162f2644a2cSMark Brown static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); 163f2644a2cSMark Brown 164f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_snd_controls[] = { 165f2644a2cSMark Brown SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL, 166f2644a2cSMark Brown 0, 63, 0, adc_tlv), 167f2644a2cSMark Brown SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL, 168f2644a2cSMark Brown 6, 1, 0), 169f2644a2cSMark Brown SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL, 170f2644a2cSMark Brown 7, 1, 0), 171f2644a2cSMark Brown 172f2644a2cSMark Brown SOC_DOUBLE_R_TLV("Playback Volume", WM8960_LDAC, WM8960_RDAC, 173f2644a2cSMark Brown 0, 255, 0, dac_tlv), 174f2644a2cSMark Brown 175f2644a2cSMark Brown SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8960_LOUT1, WM8960_ROUT1, 176f2644a2cSMark Brown 0, 127, 0, out_tlv), 177f2644a2cSMark Brown SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8960_LOUT1, WM8960_ROUT1, 178f2644a2cSMark Brown 7, 1, 0), 179f2644a2cSMark Brown 180f2644a2cSMark Brown SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8960_LOUT2, WM8960_ROUT2, 181f2644a2cSMark Brown 0, 127, 0, out_tlv), 182f2644a2cSMark Brown SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8960_LOUT2, WM8960_ROUT2, 183f2644a2cSMark Brown 7, 1, 0), 184f2644a2cSMark Brown SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0), 185f2644a2cSMark Brown SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0), 186f2644a2cSMark Brown 187f2644a2cSMark Brown SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0), 1884faaa8d9SMark Brown SOC_ENUM("ADC Polarity", wm8960_enum[0]), 189f2644a2cSMark Brown SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0), 190f2644a2cSMark Brown 191f2644a2cSMark Brown SOC_ENUM("DAC Polarity", wm8960_enum[2]), 192afd6d36aSMark Brown SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0, 193afd6d36aSMark Brown wm8960_get_deemph, wm8960_put_deemph), 194f2644a2cSMark Brown 1954faaa8d9SMark Brown SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[2]), 1964faaa8d9SMark Brown SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[3]), 197f2644a2cSMark Brown SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0), 198f2644a2cSMark Brown SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0), 199f2644a2cSMark Brown 2004faaa8d9SMark Brown SOC_ENUM("ALC Function", wm8960_enum[4]), 201f2644a2cSMark Brown SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0), 202f2644a2cSMark Brown SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1), 203f2644a2cSMark Brown SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0), 204f2644a2cSMark Brown SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0), 2054faaa8d9SMark Brown SOC_ENUM("ALC Mode", wm8960_enum[5]), 206f2644a2cSMark Brown SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0), 207f2644a2cSMark Brown SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0), 208f2644a2cSMark Brown 209f2644a2cSMark Brown SOC_SINGLE("Noise Gate Threshold", WM8960_NOISEG, 3, 31, 0), 210f2644a2cSMark Brown SOC_SINGLE("Noise Gate Switch", WM8960_NOISEG, 0, 1, 0), 211f2644a2cSMark Brown 212f2644a2cSMark Brown SOC_DOUBLE_R("ADC PCM Capture Volume", WM8960_LINPATH, WM8960_RINPATH, 213f2644a2cSMark Brown 0, 127, 0), 214f2644a2cSMark Brown 215f2644a2cSMark Brown SOC_SINGLE_TLV("Left Output Mixer Boost Bypass Volume", 216f2644a2cSMark Brown WM8960_BYPASS1, 4, 7, 1, bypass_tlv), 217f2644a2cSMark Brown SOC_SINGLE_TLV("Left Output Mixer LINPUT3 Volume", 218f2644a2cSMark Brown WM8960_LOUTMIX, 4, 7, 1, bypass_tlv), 219f2644a2cSMark Brown SOC_SINGLE_TLV("Right Output Mixer Boost Bypass Volume", 220f2644a2cSMark Brown WM8960_BYPASS2, 4, 7, 1, bypass_tlv), 221f2644a2cSMark Brown SOC_SINGLE_TLV("Right Output Mixer RINPUT3 Volume", 222f2644a2cSMark Brown WM8960_ROUTMIX, 4, 7, 1, bypass_tlv), 223f2644a2cSMark Brown }; 224f2644a2cSMark Brown 225f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_lin_boost[] = { 226f2644a2cSMark Brown SOC_DAPM_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0), 227f2644a2cSMark Brown SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LINPATH, 7, 1, 0), 228f2644a2cSMark Brown SOC_DAPM_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0), 229f2644a2cSMark Brown }; 230f2644a2cSMark Brown 231f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_lin[] = { 232f2644a2cSMark Brown SOC_DAPM_SINGLE("Boost Switch", WM8960_LINPATH, 3, 1, 0), 233f2644a2cSMark Brown }; 234f2644a2cSMark Brown 235f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_rin_boost[] = { 236f2644a2cSMark Brown SOC_DAPM_SINGLE("RINPUT2 Switch", WM8960_RINPATH, 6, 1, 0), 237f2644a2cSMark Brown SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_RINPATH, 7, 1, 0), 238f2644a2cSMark Brown SOC_DAPM_SINGLE("RINPUT1 Switch", WM8960_RINPATH, 8, 1, 0), 239f2644a2cSMark Brown }; 240f2644a2cSMark Brown 241f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_rin[] = { 242f2644a2cSMark Brown SOC_DAPM_SINGLE("Boost Switch", WM8960_RINPATH, 3, 1, 0), 243f2644a2cSMark Brown }; 244f2644a2cSMark Brown 245f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_loutput_mixer[] = { 246f2644a2cSMark Brown SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_LOUTMIX, 8, 1, 0), 247f2644a2cSMark Brown SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LOUTMIX, 7, 1, 0), 248f2644a2cSMark Brown SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS1, 7, 1, 0), 249f2644a2cSMark Brown }; 250f2644a2cSMark Brown 251f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_routput_mixer[] = { 252f2644a2cSMark Brown SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_ROUTMIX, 8, 1, 0), 253f2644a2cSMark Brown SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_ROUTMIX, 7, 1, 0), 254f2644a2cSMark Brown SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS2, 7, 1, 0), 255f2644a2cSMark Brown }; 256f2644a2cSMark Brown 257f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_mono_out[] = { 258f2644a2cSMark Brown SOC_DAPM_SINGLE("Left Switch", WM8960_MONOMIX1, 7, 1, 0), 259f2644a2cSMark Brown SOC_DAPM_SINGLE("Right Switch", WM8960_MONOMIX2, 7, 1, 0), 260f2644a2cSMark Brown }; 261f2644a2cSMark Brown 262f2644a2cSMark Brown static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = { 263f2644a2cSMark Brown SND_SOC_DAPM_INPUT("LINPUT1"), 264f2644a2cSMark Brown SND_SOC_DAPM_INPUT("RINPUT1"), 265f2644a2cSMark Brown SND_SOC_DAPM_INPUT("LINPUT2"), 266f2644a2cSMark Brown SND_SOC_DAPM_INPUT("RINPUT2"), 267f2644a2cSMark Brown SND_SOC_DAPM_INPUT("LINPUT3"), 268f2644a2cSMark Brown SND_SOC_DAPM_INPUT("RINPUT3"), 269f2644a2cSMark Brown 270f2644a2cSMark Brown SND_SOC_DAPM_MICBIAS("MICB", WM8960_POWER1, 1, 0), 271f2644a2cSMark Brown 272f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0, 273f2644a2cSMark Brown wm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)), 274f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8960_POWER1, 4, 0, 275f2644a2cSMark Brown wm8960_rin_boost, ARRAY_SIZE(wm8960_rin_boost)), 276f2644a2cSMark Brown 277f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0, 278f2644a2cSMark Brown wm8960_lin, ARRAY_SIZE(wm8960_lin)), 279f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Right Input Mixer", WM8960_POWER3, 4, 0, 280f2644a2cSMark Brown wm8960_rin, ARRAY_SIZE(wm8960_rin)), 281f2644a2cSMark Brown 282f2644a2cSMark Brown SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER2, 3, 0), 283f2644a2cSMark Brown SND_SOC_DAPM_ADC("Right ADC", "Capture", WM8960_POWER2, 2, 0), 284f2644a2cSMark Brown 285f2644a2cSMark Brown SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0), 286f2644a2cSMark Brown SND_SOC_DAPM_DAC("Right DAC", "Playback", WM8960_POWER2, 7, 0), 287f2644a2cSMark Brown 288f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0, 289f2644a2cSMark Brown &wm8960_loutput_mixer[0], 290f2644a2cSMark Brown ARRAY_SIZE(wm8960_loutput_mixer)), 291f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0, 292f2644a2cSMark Brown &wm8960_routput_mixer[0], 293f2644a2cSMark Brown ARRAY_SIZE(wm8960_routput_mixer)), 294f2644a2cSMark Brown 295f2644a2cSMark Brown SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0), 296f2644a2cSMark Brown SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0), 297f2644a2cSMark Brown 298f2644a2cSMark Brown SND_SOC_DAPM_PGA("Left Speaker PGA", WM8960_POWER2, 4, 0, NULL, 0), 299f2644a2cSMark Brown SND_SOC_DAPM_PGA("Right Speaker PGA", WM8960_POWER2, 3, 0, NULL, 0), 300f2644a2cSMark Brown 301f2644a2cSMark Brown SND_SOC_DAPM_PGA("Right Speaker Output", WM8960_CLASSD1, 7, 0, NULL, 0), 302f2644a2cSMark Brown SND_SOC_DAPM_PGA("Left Speaker Output", WM8960_CLASSD1, 6, 0, NULL, 0), 303f2644a2cSMark Brown 304f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("SPK_LP"), 305f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("SPK_LN"), 306f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("HP_L"), 307f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("HP_R"), 308f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("SPK_RP"), 309f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("SPK_RN"), 310f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("OUT3"), 311f2644a2cSMark Brown }; 312f2644a2cSMark Brown 313913d7b4cSMark Brown static const struct snd_soc_dapm_widget wm8960_dapm_widgets_out3[] = { 314913d7b4cSMark Brown SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0, 315913d7b4cSMark Brown &wm8960_mono_out[0], 316913d7b4cSMark Brown ARRAY_SIZE(wm8960_mono_out)), 317913d7b4cSMark Brown }; 318913d7b4cSMark Brown 319913d7b4cSMark Brown /* Represent OUT3 as a PGA so that it gets turned on with LOUT1/ROUT1 */ 320913d7b4cSMark Brown static const struct snd_soc_dapm_widget wm8960_dapm_widgets_capless[] = { 321913d7b4cSMark Brown SND_SOC_DAPM_PGA("OUT3 VMID", WM8960_POWER2, 1, 0, NULL, 0), 322913d7b4cSMark Brown }; 323913d7b4cSMark Brown 324f2644a2cSMark Brown static const struct snd_soc_dapm_route audio_paths[] = { 325f2644a2cSMark Brown { "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" }, 326f2644a2cSMark Brown { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" }, 327f2644a2cSMark Brown { "Left Boost Mixer", "LINPUT3 Switch", "LINPUT3" }, 328f2644a2cSMark Brown 329f2644a2cSMark Brown { "Left Input Mixer", "Boost Switch", "Left Boost Mixer", }, 330f2644a2cSMark Brown { "Left Input Mixer", NULL, "LINPUT1", }, /* Really Boost Switch */ 331f2644a2cSMark Brown { "Left Input Mixer", NULL, "LINPUT2" }, 332f2644a2cSMark Brown { "Left Input Mixer", NULL, "LINPUT3" }, 333f2644a2cSMark Brown 334f2644a2cSMark Brown { "Right Boost Mixer", "RINPUT1 Switch", "RINPUT1" }, 335f2644a2cSMark Brown { "Right Boost Mixer", "RINPUT2 Switch", "RINPUT2" }, 336f2644a2cSMark Brown { "Right Boost Mixer", "RINPUT3 Switch", "RINPUT3" }, 337f2644a2cSMark Brown 338f2644a2cSMark Brown { "Right Input Mixer", "Boost Switch", "Right Boost Mixer", }, 339f2644a2cSMark Brown { "Right Input Mixer", NULL, "RINPUT1", }, /* Really Boost Switch */ 340f2644a2cSMark Brown { "Right Input Mixer", NULL, "RINPUT2" }, 341f2644a2cSMark Brown { "Right Input Mixer", NULL, "LINPUT3" }, 342f2644a2cSMark Brown 343f2644a2cSMark Brown { "Left ADC", NULL, "Left Input Mixer" }, 344f2644a2cSMark Brown { "Right ADC", NULL, "Right Input Mixer" }, 345f2644a2cSMark Brown 346f2644a2cSMark Brown { "Left Output Mixer", "LINPUT3 Switch", "LINPUT3" }, 347f2644a2cSMark Brown { "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer"} , 348f2644a2cSMark Brown { "Left Output Mixer", "PCM Playback Switch", "Left DAC" }, 349f2644a2cSMark Brown 350f2644a2cSMark Brown { "Right Output Mixer", "RINPUT3 Switch", "RINPUT3" }, 351f2644a2cSMark Brown { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" } , 352f2644a2cSMark Brown { "Right Output Mixer", "PCM Playback Switch", "Right DAC" }, 353f2644a2cSMark Brown 354f2644a2cSMark Brown { "LOUT1 PGA", NULL, "Left Output Mixer" }, 355f2644a2cSMark Brown { "ROUT1 PGA", NULL, "Right Output Mixer" }, 356f2644a2cSMark Brown 357f2644a2cSMark Brown { "HP_L", NULL, "LOUT1 PGA" }, 358f2644a2cSMark Brown { "HP_R", NULL, "ROUT1 PGA" }, 359f2644a2cSMark Brown 360f2644a2cSMark Brown { "Left Speaker PGA", NULL, "Left Output Mixer" }, 361f2644a2cSMark Brown { "Right Speaker PGA", NULL, "Right Output Mixer" }, 362f2644a2cSMark Brown 363f2644a2cSMark Brown { "Left Speaker Output", NULL, "Left Speaker PGA" }, 364f2644a2cSMark Brown { "Right Speaker Output", NULL, "Right Speaker PGA" }, 365f2644a2cSMark Brown 366f2644a2cSMark Brown { "SPK_LN", NULL, "Left Speaker Output" }, 367f2644a2cSMark Brown { "SPK_LP", NULL, "Left Speaker Output" }, 368f2644a2cSMark Brown { "SPK_RN", NULL, "Right Speaker Output" }, 369f2644a2cSMark Brown { "SPK_RP", NULL, "Right Speaker Output" }, 370913d7b4cSMark Brown }; 371913d7b4cSMark Brown 372913d7b4cSMark Brown static const struct snd_soc_dapm_route audio_paths_out3[] = { 373913d7b4cSMark Brown { "Mono Output Mixer", "Left Switch", "Left Output Mixer" }, 374913d7b4cSMark Brown { "Mono Output Mixer", "Right Switch", "Right Output Mixer" }, 375f2644a2cSMark Brown 376f2644a2cSMark Brown { "OUT3", NULL, "Mono Output Mixer", } 377f2644a2cSMark Brown }; 378f2644a2cSMark Brown 379913d7b4cSMark Brown static const struct snd_soc_dapm_route audio_paths_capless[] = { 380913d7b4cSMark Brown { "HP_L", NULL, "OUT3 VMID" }, 381913d7b4cSMark Brown { "HP_R", NULL, "OUT3 VMID" }, 382913d7b4cSMark Brown 383913d7b4cSMark Brown { "OUT3 VMID", NULL, "Left Output Mixer" }, 384913d7b4cSMark Brown { "OUT3 VMID", NULL, "Right Output Mixer" }, 385913d7b4cSMark Brown }; 386913d7b4cSMark Brown 387f2644a2cSMark Brown static int wm8960_add_widgets(struct snd_soc_codec *codec) 388f2644a2cSMark Brown { 389913d7b4cSMark Brown struct wm8960_data *pdata = codec->dev->platform_data; 390b2c812e2SMark Brown struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); 391ce6120ccSLiam Girdwood struct snd_soc_dapm_context *dapm = &codec->dapm; 392913d7b4cSMark Brown struct snd_soc_dapm_widget *w; 393913d7b4cSMark Brown 394ce6120ccSLiam Girdwood snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets, 395f2644a2cSMark Brown ARRAY_SIZE(wm8960_dapm_widgets)); 396f2644a2cSMark Brown 397ce6120ccSLiam Girdwood snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths)); 398f2644a2cSMark Brown 399913d7b4cSMark Brown /* In capless mode OUT3 is used to provide VMID for the 400913d7b4cSMark Brown * headphone outputs, otherwise it is used as a mono mixer. 401913d7b4cSMark Brown */ 402913d7b4cSMark Brown if (pdata && pdata->capless) { 403ce6120ccSLiam Girdwood snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_capless, 404913d7b4cSMark Brown ARRAY_SIZE(wm8960_dapm_widgets_capless)); 405913d7b4cSMark Brown 406ce6120ccSLiam Girdwood snd_soc_dapm_add_routes(dapm, audio_paths_capless, 407913d7b4cSMark Brown ARRAY_SIZE(audio_paths_capless)); 408913d7b4cSMark Brown } else { 409ce6120ccSLiam Girdwood snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_out3, 410913d7b4cSMark Brown ARRAY_SIZE(wm8960_dapm_widgets_out3)); 411913d7b4cSMark Brown 412ce6120ccSLiam Girdwood snd_soc_dapm_add_routes(dapm, audio_paths_out3, 413913d7b4cSMark Brown ARRAY_SIZE(audio_paths_out3)); 414913d7b4cSMark Brown } 415913d7b4cSMark Brown 416913d7b4cSMark Brown /* We need to power up the headphone output stage out of 417913d7b4cSMark Brown * sequence for capless mode. To save scanning the widget 418913d7b4cSMark Brown * list each time to find the desired power state do so now 419913d7b4cSMark Brown * and save the result. 420913d7b4cSMark Brown */ 421ce6120ccSLiam Girdwood list_for_each_entry(w, &codec->dapm.widgets, list) { 422913d7b4cSMark Brown if (strcmp(w->name, "LOUT1 PGA") == 0) 423913d7b4cSMark Brown wm8960->lout1 = w; 424913d7b4cSMark Brown if (strcmp(w->name, "ROUT1 PGA") == 0) 425913d7b4cSMark Brown wm8960->rout1 = w; 426913d7b4cSMark Brown if (strcmp(w->name, "OUT3 VMID") == 0) 427913d7b4cSMark Brown wm8960->out3 = w; 428913d7b4cSMark Brown } 429913d7b4cSMark Brown 430f2644a2cSMark Brown return 0; 431f2644a2cSMark Brown } 432f2644a2cSMark Brown 433f2644a2cSMark Brown static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai, 434f2644a2cSMark Brown unsigned int fmt) 435f2644a2cSMark Brown { 436f2644a2cSMark Brown struct snd_soc_codec *codec = codec_dai->codec; 437f2644a2cSMark Brown u16 iface = 0; 438f2644a2cSMark Brown 439f2644a2cSMark Brown /* set master/slave audio interface */ 440f2644a2cSMark Brown switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { 441f2644a2cSMark Brown case SND_SOC_DAIFMT_CBM_CFM: 442f2644a2cSMark Brown iface |= 0x0040; 443f2644a2cSMark Brown break; 444f2644a2cSMark Brown case SND_SOC_DAIFMT_CBS_CFS: 445f2644a2cSMark Brown break; 446f2644a2cSMark Brown default: 447f2644a2cSMark Brown return -EINVAL; 448f2644a2cSMark Brown } 449f2644a2cSMark Brown 450f2644a2cSMark Brown /* interface format */ 451f2644a2cSMark Brown switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 452f2644a2cSMark Brown case SND_SOC_DAIFMT_I2S: 453f2644a2cSMark Brown iface |= 0x0002; 454f2644a2cSMark Brown break; 455f2644a2cSMark Brown case SND_SOC_DAIFMT_RIGHT_J: 456f2644a2cSMark Brown break; 457f2644a2cSMark Brown case SND_SOC_DAIFMT_LEFT_J: 458f2644a2cSMark Brown iface |= 0x0001; 459f2644a2cSMark Brown break; 460f2644a2cSMark Brown case SND_SOC_DAIFMT_DSP_A: 461f2644a2cSMark Brown iface |= 0x0003; 462f2644a2cSMark Brown break; 463f2644a2cSMark Brown case SND_SOC_DAIFMT_DSP_B: 464f2644a2cSMark Brown iface |= 0x0013; 465f2644a2cSMark Brown break; 466f2644a2cSMark Brown default: 467f2644a2cSMark Brown return -EINVAL; 468f2644a2cSMark Brown } 469f2644a2cSMark Brown 470f2644a2cSMark Brown /* clock inversion */ 471f2644a2cSMark Brown switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 472f2644a2cSMark Brown case SND_SOC_DAIFMT_NB_NF: 473f2644a2cSMark Brown break; 474f2644a2cSMark Brown case SND_SOC_DAIFMT_IB_IF: 475f2644a2cSMark Brown iface |= 0x0090; 476f2644a2cSMark Brown break; 477f2644a2cSMark Brown case SND_SOC_DAIFMT_IB_NF: 478f2644a2cSMark Brown iface |= 0x0080; 479f2644a2cSMark Brown break; 480f2644a2cSMark Brown case SND_SOC_DAIFMT_NB_IF: 481f2644a2cSMark Brown iface |= 0x0010; 482f2644a2cSMark Brown break; 483f2644a2cSMark Brown default: 484f2644a2cSMark Brown return -EINVAL; 485f2644a2cSMark Brown } 486f2644a2cSMark Brown 487f2644a2cSMark Brown /* set iface */ 48817a52fd6SMark Brown snd_soc_write(codec, WM8960_IFACE1, iface); 489f2644a2cSMark Brown return 0; 490f2644a2cSMark Brown } 491f2644a2cSMark Brown 492db059c0fSMark Brown static struct { 493db059c0fSMark Brown int rate; 494db059c0fSMark Brown unsigned int val; 495db059c0fSMark Brown } alc_rates[] = { 496db059c0fSMark Brown { 48000, 0 }, 497db059c0fSMark Brown { 44100, 0 }, 498db059c0fSMark Brown { 32000, 1 }, 499db059c0fSMark Brown { 22050, 2 }, 500db059c0fSMark Brown { 24000, 2 }, 501db059c0fSMark Brown { 16000, 3 }, 502db059c0fSMark Brown { 11250, 4 }, 503db059c0fSMark Brown { 12000, 4 }, 504db059c0fSMark Brown { 8000, 5 }, 505db059c0fSMark Brown }; 506db059c0fSMark Brown 507f2644a2cSMark Brown static int wm8960_hw_params(struct snd_pcm_substream *substream, 508f2644a2cSMark Brown struct snd_pcm_hw_params *params, 509f2644a2cSMark Brown struct snd_soc_dai *dai) 510f2644a2cSMark Brown { 511f2644a2cSMark Brown struct snd_soc_pcm_runtime *rtd = substream->private_data; 512f0fba2adSLiam Girdwood struct snd_soc_codec *codec = rtd->codec; 513afd6d36aSMark Brown struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); 51417a52fd6SMark Brown u16 iface = snd_soc_read(codec, WM8960_IFACE1) & 0xfff3; 515db059c0fSMark Brown int i; 516f2644a2cSMark Brown 517f2644a2cSMark Brown /* bit size */ 518f2644a2cSMark Brown switch (params_format(params)) { 519f2644a2cSMark Brown case SNDRV_PCM_FORMAT_S16_LE: 520f2644a2cSMark Brown break; 521f2644a2cSMark Brown case SNDRV_PCM_FORMAT_S20_3LE: 522f2644a2cSMark Brown iface |= 0x0004; 523f2644a2cSMark Brown break; 524f2644a2cSMark Brown case SNDRV_PCM_FORMAT_S24_LE: 525f2644a2cSMark Brown iface |= 0x0008; 526f2644a2cSMark Brown break; 527f2644a2cSMark Brown } 528f2644a2cSMark Brown 529afd6d36aSMark Brown /* Update filters for the new rate */ 530afd6d36aSMark Brown if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 531afd6d36aSMark Brown wm8960->playback_fs = params_rate(params); 532afd6d36aSMark Brown wm8960_set_deemph(codec); 533db059c0fSMark Brown } else { 534db059c0fSMark Brown for (i = 0; i < ARRAY_SIZE(alc_rates); i++) 535db059c0fSMark Brown if (alc_rates[i].rate == params_rate(params)) 536db059c0fSMark Brown snd_soc_update_bits(codec, 537db059c0fSMark Brown WM8960_ADDCTL3, 0x7, 538db059c0fSMark Brown alc_rates[i].val); 539afd6d36aSMark Brown } 540afd6d36aSMark Brown 541f2644a2cSMark Brown /* set iface */ 54217a52fd6SMark Brown snd_soc_write(codec, WM8960_IFACE1, iface); 543f2644a2cSMark Brown return 0; 544f2644a2cSMark Brown } 545f2644a2cSMark Brown 546f2644a2cSMark Brown static int wm8960_mute(struct snd_soc_dai *dai, int mute) 547f2644a2cSMark Brown { 548f2644a2cSMark Brown struct snd_soc_codec *codec = dai->codec; 54917a52fd6SMark Brown u16 mute_reg = snd_soc_read(codec, WM8960_DACCTL1) & 0xfff7; 550f2644a2cSMark Brown 551f2644a2cSMark Brown if (mute) 55217a52fd6SMark Brown snd_soc_write(codec, WM8960_DACCTL1, mute_reg | 0x8); 553f2644a2cSMark Brown else 55417a52fd6SMark Brown snd_soc_write(codec, WM8960_DACCTL1, mute_reg); 555f2644a2cSMark Brown return 0; 556f2644a2cSMark Brown } 557f2644a2cSMark Brown 558913d7b4cSMark Brown static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec, 559f2644a2cSMark Brown enum snd_soc_bias_level level) 560f2644a2cSMark Brown { 561f2644a2cSMark Brown u16 reg; 562f2644a2cSMark Brown 563f2644a2cSMark Brown switch (level) { 564f2644a2cSMark Brown case SND_SOC_BIAS_ON: 565f2644a2cSMark Brown break; 566f2644a2cSMark Brown 567f2644a2cSMark Brown case SND_SOC_BIAS_PREPARE: 568f2644a2cSMark Brown /* Set VMID to 2x50k */ 56917a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_POWER1); 570f2644a2cSMark Brown reg &= ~0x180; 571f2644a2cSMark Brown reg |= 0x80; 57217a52fd6SMark Brown snd_soc_write(codec, WM8960_POWER1, reg); 573f2644a2cSMark Brown break; 574f2644a2cSMark Brown 575f2644a2cSMark Brown case SND_SOC_BIAS_STANDBY: 576ce6120ccSLiam Girdwood if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { 577f2644a2cSMark Brown /* Enable anti-pop features */ 57817a52fd6SMark Brown snd_soc_write(codec, WM8960_APOP1, 579f2644a2cSMark Brown WM8960_POBCTRL | WM8960_SOFT_ST | 580f2644a2cSMark Brown WM8960_BUFDCOPEN | WM8960_BUFIOEN); 581f2644a2cSMark Brown 582f2644a2cSMark Brown /* Enable & ramp VMID at 2x50k */ 58317a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_POWER1); 584f2644a2cSMark Brown reg |= 0x80; 58517a52fd6SMark Brown snd_soc_write(codec, WM8960_POWER1, reg); 586f2644a2cSMark Brown msleep(100); 587f2644a2cSMark Brown 588f2644a2cSMark Brown /* Enable VREF */ 58917a52fd6SMark Brown snd_soc_write(codec, WM8960_POWER1, reg | WM8960_VREF); 590f2644a2cSMark Brown 591f2644a2cSMark Brown /* Disable anti-pop features */ 59217a52fd6SMark Brown snd_soc_write(codec, WM8960_APOP1, WM8960_BUFIOEN); 593f2644a2cSMark Brown } 594f2644a2cSMark Brown 595f2644a2cSMark Brown /* Set VMID to 2x250k */ 59617a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_POWER1); 597f2644a2cSMark Brown reg &= ~0x180; 598f2644a2cSMark Brown reg |= 0x100; 59917a52fd6SMark Brown snd_soc_write(codec, WM8960_POWER1, reg); 600f2644a2cSMark Brown break; 601f2644a2cSMark Brown 602f2644a2cSMark Brown case SND_SOC_BIAS_OFF: 603f2644a2cSMark Brown /* Enable anti-pop features */ 60417a52fd6SMark Brown snd_soc_write(codec, WM8960_APOP1, 605f2644a2cSMark Brown WM8960_POBCTRL | WM8960_SOFT_ST | 606f2644a2cSMark Brown WM8960_BUFDCOPEN | WM8960_BUFIOEN); 607f2644a2cSMark Brown 608f2644a2cSMark Brown /* Disable VMID and VREF, let them discharge */ 60917a52fd6SMark Brown snd_soc_write(codec, WM8960_POWER1, 0); 610f2644a2cSMark Brown msleep(600); 611913d7b4cSMark Brown break; 612913d7b4cSMark Brown } 613f2644a2cSMark Brown 614ce6120ccSLiam Girdwood codec->dapm.bias_level = level; 615913d7b4cSMark Brown 616913d7b4cSMark Brown return 0; 617913d7b4cSMark Brown } 618913d7b4cSMark Brown 619913d7b4cSMark Brown static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec, 620913d7b4cSMark Brown enum snd_soc_bias_level level) 621913d7b4cSMark Brown { 622b2c812e2SMark Brown struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); 623913d7b4cSMark Brown int reg; 624913d7b4cSMark Brown 625913d7b4cSMark Brown switch (level) { 626913d7b4cSMark Brown case SND_SOC_BIAS_ON: 627913d7b4cSMark Brown break; 628913d7b4cSMark Brown 629913d7b4cSMark Brown case SND_SOC_BIAS_PREPARE: 630ce6120ccSLiam Girdwood switch (codec->dapm.bias_level) { 631913d7b4cSMark Brown case SND_SOC_BIAS_STANDBY: 632913d7b4cSMark Brown /* Enable anti pop mode */ 633913d7b4cSMark Brown snd_soc_update_bits(codec, WM8960_APOP1, 634913d7b4cSMark Brown WM8960_POBCTRL | WM8960_SOFT_ST | 635913d7b4cSMark Brown WM8960_BUFDCOPEN, 636913d7b4cSMark Brown WM8960_POBCTRL | WM8960_SOFT_ST | 637913d7b4cSMark Brown WM8960_BUFDCOPEN); 638913d7b4cSMark Brown 639913d7b4cSMark Brown /* Enable LOUT1, ROUT1 and OUT3 if they're enabled */ 640913d7b4cSMark Brown reg = 0; 641913d7b4cSMark Brown if (wm8960->lout1 && wm8960->lout1->power) 642913d7b4cSMark Brown reg |= WM8960_PWR2_LOUT1; 643913d7b4cSMark Brown if (wm8960->rout1 && wm8960->rout1->power) 644913d7b4cSMark Brown reg |= WM8960_PWR2_ROUT1; 645913d7b4cSMark Brown if (wm8960->out3 && wm8960->out3->power) 646913d7b4cSMark Brown reg |= WM8960_PWR2_OUT3; 647913d7b4cSMark Brown snd_soc_update_bits(codec, WM8960_POWER2, 648913d7b4cSMark Brown WM8960_PWR2_LOUT1 | 649913d7b4cSMark Brown WM8960_PWR2_ROUT1 | 650913d7b4cSMark Brown WM8960_PWR2_OUT3, reg); 651913d7b4cSMark Brown 652913d7b4cSMark Brown /* Enable VMID at 2*50k */ 653913d7b4cSMark Brown snd_soc_update_bits(codec, WM8960_POWER1, 654913d7b4cSMark Brown WM8960_VMID_MASK, 0x80); 655913d7b4cSMark Brown 656913d7b4cSMark Brown /* Ramp */ 657913d7b4cSMark Brown msleep(100); 658913d7b4cSMark Brown 659913d7b4cSMark Brown /* Enable VREF */ 660913d7b4cSMark Brown snd_soc_update_bits(codec, WM8960_POWER1, 661913d7b4cSMark Brown WM8960_VREF, WM8960_VREF); 662913d7b4cSMark Brown 663913d7b4cSMark Brown msleep(100); 664913d7b4cSMark Brown break; 665913d7b4cSMark Brown 666913d7b4cSMark Brown case SND_SOC_BIAS_ON: 667913d7b4cSMark Brown /* Enable anti-pop mode */ 668913d7b4cSMark Brown snd_soc_update_bits(codec, WM8960_APOP1, 669913d7b4cSMark Brown WM8960_POBCTRL | WM8960_SOFT_ST | 670913d7b4cSMark Brown WM8960_BUFDCOPEN, 671913d7b4cSMark Brown WM8960_POBCTRL | WM8960_SOFT_ST | 672913d7b4cSMark Brown WM8960_BUFDCOPEN); 673913d7b4cSMark Brown 674913d7b4cSMark Brown /* Disable VMID and VREF */ 675913d7b4cSMark Brown snd_soc_update_bits(codec, WM8960_POWER1, 676913d7b4cSMark Brown WM8960_VREF | WM8960_VMID_MASK, 0); 677913d7b4cSMark Brown break; 678913d7b4cSMark Brown 679913d7b4cSMark Brown default: 680913d7b4cSMark Brown break; 681913d7b4cSMark Brown } 682913d7b4cSMark Brown break; 683913d7b4cSMark Brown 684913d7b4cSMark Brown case SND_SOC_BIAS_STANDBY: 685ce6120ccSLiam Girdwood switch (codec->dapm.bias_level) { 686913d7b4cSMark Brown case SND_SOC_BIAS_PREPARE: 687913d7b4cSMark Brown /* Disable HP discharge */ 688913d7b4cSMark Brown snd_soc_update_bits(codec, WM8960_APOP2, 689913d7b4cSMark Brown WM8960_DISOP | WM8960_DRES_MASK, 690913d7b4cSMark Brown 0); 691913d7b4cSMark Brown 692913d7b4cSMark Brown /* Disable anti-pop features */ 693913d7b4cSMark Brown snd_soc_update_bits(codec, WM8960_APOP1, 694913d7b4cSMark Brown WM8960_POBCTRL | WM8960_SOFT_ST | 695913d7b4cSMark Brown WM8960_BUFDCOPEN, 696913d7b4cSMark Brown WM8960_POBCTRL | WM8960_SOFT_ST | 697913d7b4cSMark Brown WM8960_BUFDCOPEN); 698913d7b4cSMark Brown break; 699913d7b4cSMark Brown 700913d7b4cSMark Brown default: 701913d7b4cSMark Brown break; 702913d7b4cSMark Brown } 703913d7b4cSMark Brown break; 704913d7b4cSMark Brown 705913d7b4cSMark Brown case SND_SOC_BIAS_OFF: 706f2644a2cSMark Brown break; 707f2644a2cSMark Brown } 708f2644a2cSMark Brown 709ce6120ccSLiam Girdwood codec->dapm.bias_level = level; 710f2644a2cSMark Brown 711f2644a2cSMark Brown return 0; 712f2644a2cSMark Brown } 713f2644a2cSMark Brown 714f2644a2cSMark Brown /* PLL divisors */ 715f2644a2cSMark Brown struct _pll_div { 716f2644a2cSMark Brown u32 pre_div:1; 717f2644a2cSMark Brown u32 n:4; 718f2644a2cSMark Brown u32 k:24; 719f2644a2cSMark Brown }; 720f2644a2cSMark Brown 721f2644a2cSMark Brown /* The size in bits of the pll divide multiplied by 10 722f2644a2cSMark Brown * to allow rounding later */ 723f2644a2cSMark Brown #define FIXED_PLL_SIZE ((1 << 24) * 10) 724f2644a2cSMark Brown 725f2644a2cSMark Brown static int pll_factors(unsigned int source, unsigned int target, 726f2644a2cSMark Brown struct _pll_div *pll_div) 727f2644a2cSMark Brown { 728f2644a2cSMark Brown unsigned long long Kpart; 729f2644a2cSMark Brown unsigned int K, Ndiv, Nmod; 730f2644a2cSMark Brown 731f2644a2cSMark Brown pr_debug("WM8960 PLL: setting %dHz->%dHz\n", source, target); 732f2644a2cSMark Brown 733f2644a2cSMark Brown /* Scale up target to PLL operating frequency */ 734f2644a2cSMark Brown target *= 4; 735f2644a2cSMark Brown 736f2644a2cSMark Brown Ndiv = target / source; 737f2644a2cSMark Brown if (Ndiv < 6) { 738f2644a2cSMark Brown source >>= 1; 739f2644a2cSMark Brown pll_div->pre_div = 1; 740f2644a2cSMark Brown Ndiv = target / source; 741f2644a2cSMark Brown } else 742f2644a2cSMark Brown pll_div->pre_div = 0; 743f2644a2cSMark Brown 744f2644a2cSMark Brown if ((Ndiv < 6) || (Ndiv > 12)) { 745f2644a2cSMark Brown pr_err("WM8960 PLL: Unsupported N=%d\n", Ndiv); 746f2644a2cSMark Brown return -EINVAL; 747f2644a2cSMark Brown } 748f2644a2cSMark Brown 749f2644a2cSMark Brown pll_div->n = Ndiv; 750f2644a2cSMark Brown Nmod = target % source; 751f2644a2cSMark Brown Kpart = FIXED_PLL_SIZE * (long long)Nmod; 752f2644a2cSMark Brown 753f2644a2cSMark Brown do_div(Kpart, source); 754f2644a2cSMark Brown 755f2644a2cSMark Brown K = Kpart & 0xFFFFFFFF; 756f2644a2cSMark Brown 757f2644a2cSMark Brown /* Check if we need to round */ 758f2644a2cSMark Brown if ((K % 10) >= 5) 759f2644a2cSMark Brown K += 5; 760f2644a2cSMark Brown 761f2644a2cSMark Brown /* Move down to proper range now rounding is done */ 762f2644a2cSMark Brown K /= 10; 763f2644a2cSMark Brown 764f2644a2cSMark Brown pll_div->k = K; 765f2644a2cSMark Brown 766f2644a2cSMark Brown pr_debug("WM8960 PLL: N=%x K=%x pre_div=%d\n", 767f2644a2cSMark Brown pll_div->n, pll_div->k, pll_div->pre_div); 768f2644a2cSMark Brown 769f2644a2cSMark Brown return 0; 770f2644a2cSMark Brown } 771f2644a2cSMark Brown 77285488037SMark Brown static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, 77385488037SMark Brown int source, unsigned int freq_in, unsigned int freq_out) 774f2644a2cSMark Brown { 775f2644a2cSMark Brown struct snd_soc_codec *codec = codec_dai->codec; 776f2644a2cSMark Brown u16 reg; 777f2644a2cSMark Brown static struct _pll_div pll_div; 778f2644a2cSMark Brown int ret; 779f2644a2cSMark Brown 780f2644a2cSMark Brown if (freq_in && freq_out) { 781f2644a2cSMark Brown ret = pll_factors(freq_in, freq_out, &pll_div); 782f2644a2cSMark Brown if (ret != 0) 783f2644a2cSMark Brown return ret; 784f2644a2cSMark Brown } 785f2644a2cSMark Brown 786f2644a2cSMark Brown /* Disable the PLL: even if we are changing the frequency the 787f2644a2cSMark Brown * PLL needs to be disabled while we do so. */ 78817a52fd6SMark Brown snd_soc_write(codec, WM8960_CLOCK1, 78917a52fd6SMark Brown snd_soc_read(codec, WM8960_CLOCK1) & ~1); 79017a52fd6SMark Brown snd_soc_write(codec, WM8960_POWER2, 79117a52fd6SMark Brown snd_soc_read(codec, WM8960_POWER2) & ~1); 792f2644a2cSMark Brown 793f2644a2cSMark Brown if (!freq_in || !freq_out) 794f2644a2cSMark Brown return 0; 795f2644a2cSMark Brown 79617a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_PLL1) & ~0x3f; 797f2644a2cSMark Brown reg |= pll_div.pre_div << 4; 798f2644a2cSMark Brown reg |= pll_div.n; 799f2644a2cSMark Brown 800f2644a2cSMark Brown if (pll_div.k) { 801f2644a2cSMark Brown reg |= 0x20; 802f2644a2cSMark Brown 80317a52fd6SMark Brown snd_soc_write(codec, WM8960_PLL2, (pll_div.k >> 18) & 0x3f); 80417a52fd6SMark Brown snd_soc_write(codec, WM8960_PLL3, (pll_div.k >> 9) & 0x1ff); 80517a52fd6SMark Brown snd_soc_write(codec, WM8960_PLL4, pll_div.k & 0x1ff); 806f2644a2cSMark Brown } 80717a52fd6SMark Brown snd_soc_write(codec, WM8960_PLL1, reg); 808f2644a2cSMark Brown 809f2644a2cSMark Brown /* Turn it on */ 81017a52fd6SMark Brown snd_soc_write(codec, WM8960_POWER2, 81117a52fd6SMark Brown snd_soc_read(codec, WM8960_POWER2) | 1); 812f2644a2cSMark Brown msleep(250); 81317a52fd6SMark Brown snd_soc_write(codec, WM8960_CLOCK1, 81417a52fd6SMark Brown snd_soc_read(codec, WM8960_CLOCK1) | 1); 815f2644a2cSMark Brown 816f2644a2cSMark Brown return 0; 817f2644a2cSMark Brown } 818f2644a2cSMark Brown 819f2644a2cSMark Brown static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai, 820f2644a2cSMark Brown int div_id, int div) 821f2644a2cSMark Brown { 822f2644a2cSMark Brown struct snd_soc_codec *codec = codec_dai->codec; 823f2644a2cSMark Brown u16 reg; 824f2644a2cSMark Brown 825f2644a2cSMark Brown switch (div_id) { 826f2644a2cSMark Brown case WM8960_SYSCLKDIV: 82717a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1f9; 82817a52fd6SMark Brown snd_soc_write(codec, WM8960_CLOCK1, reg | div); 829f2644a2cSMark Brown break; 830f2644a2cSMark Brown case WM8960_DACDIV: 83117a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1c7; 83217a52fd6SMark Brown snd_soc_write(codec, WM8960_CLOCK1, reg | div); 833f2644a2cSMark Brown break; 834f2644a2cSMark Brown case WM8960_OPCLKDIV: 83517a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_PLL1) & 0x03f; 83617a52fd6SMark Brown snd_soc_write(codec, WM8960_PLL1, reg | div); 837f2644a2cSMark Brown break; 838f2644a2cSMark Brown case WM8960_DCLKDIV: 83917a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_CLOCK2) & 0x03f; 84017a52fd6SMark Brown snd_soc_write(codec, WM8960_CLOCK2, reg | div); 841f2644a2cSMark Brown break; 842f2644a2cSMark Brown case WM8960_TOCLKSEL: 84317a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_ADDCTL1) & 0x1fd; 84417a52fd6SMark Brown snd_soc_write(codec, WM8960_ADDCTL1, reg | div); 845f2644a2cSMark Brown break; 846f2644a2cSMark Brown default: 847f2644a2cSMark Brown return -EINVAL; 848f2644a2cSMark Brown } 849f2644a2cSMark Brown 850f2644a2cSMark Brown return 0; 851f2644a2cSMark Brown } 852f2644a2cSMark Brown 853f0fba2adSLiam Girdwood static int wm8960_set_bias_level(struct snd_soc_codec *codec, 854f0fba2adSLiam Girdwood enum snd_soc_bias_level level) 855f0fba2adSLiam Girdwood { 856f0fba2adSLiam Girdwood struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); 857f0fba2adSLiam Girdwood 858f0fba2adSLiam Girdwood return wm8960->set_bias_level(codec, level); 859f0fba2adSLiam Girdwood } 860f0fba2adSLiam Girdwood 861f2644a2cSMark Brown #define WM8960_RATES SNDRV_PCM_RATE_8000_48000 862f2644a2cSMark Brown 863f2644a2cSMark Brown #define WM8960_FORMATS \ 864f2644a2cSMark Brown (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ 865f2644a2cSMark Brown SNDRV_PCM_FMTBIT_S24_LE) 866f2644a2cSMark Brown 867f2644a2cSMark Brown static struct snd_soc_dai_ops wm8960_dai_ops = { 868f2644a2cSMark Brown .hw_params = wm8960_hw_params, 869f2644a2cSMark Brown .digital_mute = wm8960_mute, 870f2644a2cSMark Brown .set_fmt = wm8960_set_dai_fmt, 871f2644a2cSMark Brown .set_clkdiv = wm8960_set_dai_clkdiv, 872f2644a2cSMark Brown .set_pll = wm8960_set_dai_pll, 873f2644a2cSMark Brown }; 874f2644a2cSMark Brown 875f0fba2adSLiam Girdwood static struct snd_soc_dai_driver wm8960_dai = { 876f0fba2adSLiam Girdwood .name = "wm8960-hifi", 877f2644a2cSMark Brown .playback = { 878f2644a2cSMark Brown .stream_name = "Playback", 879f2644a2cSMark Brown .channels_min = 1, 880f2644a2cSMark Brown .channels_max = 2, 881f2644a2cSMark Brown .rates = WM8960_RATES, 882f2644a2cSMark Brown .formats = WM8960_FORMATS,}, 883f2644a2cSMark Brown .capture = { 884f2644a2cSMark Brown .stream_name = "Capture", 885f2644a2cSMark Brown .channels_min = 1, 886f2644a2cSMark Brown .channels_max = 2, 887f2644a2cSMark Brown .rates = WM8960_RATES, 888f2644a2cSMark Brown .formats = WM8960_FORMATS,}, 889f2644a2cSMark Brown .ops = &wm8960_dai_ops, 890f2644a2cSMark Brown .symmetric_rates = 1, 891f2644a2cSMark Brown }; 892f2644a2cSMark Brown 893f0fba2adSLiam Girdwood static int wm8960_suspend(struct snd_soc_codec *codec, pm_message_t state) 894f2644a2cSMark Brown { 895f0fba2adSLiam Girdwood struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); 896f2644a2cSMark Brown 897f0fba2adSLiam Girdwood wm8960->set_bias_level(codec, SND_SOC_BIAS_OFF); 898f2644a2cSMark Brown return 0; 899f2644a2cSMark Brown } 900f2644a2cSMark Brown 901f0fba2adSLiam Girdwood static int wm8960_resume(struct snd_soc_codec *codec) 902f2644a2cSMark Brown { 903f0fba2adSLiam Girdwood struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); 904f2644a2cSMark Brown int i; 905f2644a2cSMark Brown u8 data[2]; 906f2644a2cSMark Brown u16 *cache = codec->reg_cache; 907f2644a2cSMark Brown 908f2644a2cSMark Brown /* Sync reg_cache with the hardware */ 909f2644a2cSMark Brown for (i = 0; i < ARRAY_SIZE(wm8960_reg); i++) { 910f2644a2cSMark Brown data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); 911f2644a2cSMark Brown data[1] = cache[i] & 0x00ff; 912f2644a2cSMark Brown codec->hw_write(codec->control_data, data, 2); 913f2644a2cSMark Brown } 914f2644a2cSMark Brown 915f0fba2adSLiam Girdwood wm8960->set_bias_level(codec, SND_SOC_BIAS_STANDBY); 916f2644a2cSMark Brown return 0; 917f2644a2cSMark Brown } 918f2644a2cSMark Brown 919f0fba2adSLiam Girdwood static int wm8960_probe(struct snd_soc_codec *codec) 920f2644a2cSMark Brown { 921f0fba2adSLiam Girdwood struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); 922f0fba2adSLiam Girdwood struct wm8960_data *pdata = dev_get_platdata(codec->dev); 923f2644a2cSMark Brown int ret; 924f2644a2cSMark Brown u16 reg; 925f2644a2cSMark Brown 926f0fba2adSLiam Girdwood wm8960->set_bias_level = wm8960_set_bias_level_out3; 927f0fba2adSLiam Girdwood codec->control_data = wm8960->control_data; 928913d7b4cSMark Brown 929f2644a2cSMark Brown if (!pdata) { 930f2644a2cSMark Brown dev_warn(codec->dev, "No platform data supplied\n"); 931f2644a2cSMark Brown } else { 932f2644a2cSMark Brown if (pdata->dres > WM8960_DRES_MAX) { 933f2644a2cSMark Brown dev_err(codec->dev, "Invalid DRES: %d\n", pdata->dres); 934f2644a2cSMark Brown pdata->dres = 0; 935f2644a2cSMark Brown } 936913d7b4cSMark Brown 937913d7b4cSMark Brown if (pdata->capless) 938f0fba2adSLiam Girdwood wm8960->set_bias_level = wm8960_set_bias_level_capless; 939f2644a2cSMark Brown } 940f2644a2cSMark Brown 941f0fba2adSLiam Girdwood ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8960->control_type); 94217a52fd6SMark Brown if (ret < 0) { 94317a52fd6SMark Brown dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); 944f0fba2adSLiam Girdwood return ret; 94517a52fd6SMark Brown } 94617a52fd6SMark Brown 947f2644a2cSMark Brown ret = wm8960_reset(codec); 948f2644a2cSMark Brown if (ret < 0) { 949f2644a2cSMark Brown dev_err(codec->dev, "Failed to issue reset\n"); 950f0fba2adSLiam Girdwood return ret; 951f2644a2cSMark Brown } 952f2644a2cSMark Brown 953f0fba2adSLiam Girdwood wm8960->set_bias_level(codec, SND_SOC_BIAS_STANDBY); 954f2644a2cSMark Brown 955f2644a2cSMark Brown /* Latch the update bits */ 95617a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_LINVOL); 95717a52fd6SMark Brown snd_soc_write(codec, WM8960_LINVOL, reg | 0x100); 95817a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_RINVOL); 95917a52fd6SMark Brown snd_soc_write(codec, WM8960_RINVOL, reg | 0x100); 96017a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_LADC); 96117a52fd6SMark Brown snd_soc_write(codec, WM8960_LADC, reg | 0x100); 96217a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_RADC); 96317a52fd6SMark Brown snd_soc_write(codec, WM8960_RADC, reg | 0x100); 96417a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_LDAC); 96517a52fd6SMark Brown snd_soc_write(codec, WM8960_LDAC, reg | 0x100); 96617a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_RDAC); 96717a52fd6SMark Brown snd_soc_write(codec, WM8960_RDAC, reg | 0x100); 96817a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_LOUT1); 96917a52fd6SMark Brown snd_soc_write(codec, WM8960_LOUT1, reg | 0x100); 97017a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_ROUT1); 97117a52fd6SMark Brown snd_soc_write(codec, WM8960_ROUT1, reg | 0x100); 97217a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_LOUT2); 97317a52fd6SMark Brown snd_soc_write(codec, WM8960_LOUT2, reg | 0x100); 97417a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_ROUT2); 97517a52fd6SMark Brown snd_soc_write(codec, WM8960_ROUT2, reg | 0x100); 976f2644a2cSMark Brown 977f0fba2adSLiam Girdwood snd_soc_add_controls(codec, wm8960_snd_controls, 978f0fba2adSLiam Girdwood ARRAY_SIZE(wm8960_snd_controls)); 979f0fba2adSLiam Girdwood wm8960_add_widgets(codec); 980f2644a2cSMark Brown 981f2644a2cSMark Brown return 0; 982f2644a2cSMark Brown } 983f2644a2cSMark Brown 984f0fba2adSLiam Girdwood /* power down chip */ 985f0fba2adSLiam Girdwood static int wm8960_remove(struct snd_soc_codec *codec) 986f2644a2cSMark Brown { 987f0fba2adSLiam Girdwood struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); 988f0fba2adSLiam Girdwood 989f0fba2adSLiam Girdwood wm8960->set_bias_level(codec, SND_SOC_BIAS_OFF); 990f0fba2adSLiam Girdwood return 0; 991f2644a2cSMark Brown } 992f2644a2cSMark Brown 993f0fba2adSLiam Girdwood static struct snd_soc_codec_driver soc_codec_dev_wm8960 = { 994f0fba2adSLiam Girdwood .probe = wm8960_probe, 995f0fba2adSLiam Girdwood .remove = wm8960_remove, 996f0fba2adSLiam Girdwood .suspend = wm8960_suspend, 997f0fba2adSLiam Girdwood .resume = wm8960_resume, 998f0fba2adSLiam Girdwood .set_bias_level = wm8960_set_bias_level, 999f0fba2adSLiam Girdwood .reg_cache_size = ARRAY_SIZE(wm8960_reg), 1000f0fba2adSLiam Girdwood .reg_word_size = sizeof(u16), 1001f0fba2adSLiam Girdwood .reg_cache_default = wm8960_reg, 1002f0fba2adSLiam Girdwood }; 1003f0fba2adSLiam Girdwood 1004f0fba2adSLiam Girdwood #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) 1005f2644a2cSMark Brown static __devinit int wm8960_i2c_probe(struct i2c_client *i2c, 1006f2644a2cSMark Brown const struct i2c_device_id *id) 1007f2644a2cSMark Brown { 1008f2644a2cSMark Brown struct wm8960_priv *wm8960; 1009f0fba2adSLiam Girdwood int ret; 1010f2644a2cSMark Brown 1011f2644a2cSMark Brown wm8960 = kzalloc(sizeof(struct wm8960_priv), GFP_KERNEL); 1012f2644a2cSMark Brown if (wm8960 == NULL) 1013f2644a2cSMark Brown return -ENOMEM; 1014f2644a2cSMark Brown 1015f2644a2cSMark Brown i2c_set_clientdata(i2c, wm8960); 1016f0fba2adSLiam Girdwood wm8960->control_data = i2c; 1017f2644a2cSMark Brown 1018f0fba2adSLiam Girdwood ret = snd_soc_register_codec(&i2c->dev, 1019f0fba2adSLiam Girdwood &soc_codec_dev_wm8960, &wm8960_dai, 1); 1020f0fba2adSLiam Girdwood if (ret < 0) 1021f0fba2adSLiam Girdwood kfree(wm8960); 1022f0fba2adSLiam Girdwood return ret; 1023f2644a2cSMark Brown } 1024f2644a2cSMark Brown 1025f2644a2cSMark Brown static __devexit int wm8960_i2c_remove(struct i2c_client *client) 1026f2644a2cSMark Brown { 1027f0fba2adSLiam Girdwood snd_soc_unregister_codec(&client->dev); 1028f0fba2adSLiam Girdwood kfree(i2c_get_clientdata(client)); 1029f2644a2cSMark Brown return 0; 1030f2644a2cSMark Brown } 1031f2644a2cSMark Brown 1032f2644a2cSMark Brown static const struct i2c_device_id wm8960_i2c_id[] = { 1033f2644a2cSMark Brown { "wm8960", 0 }, 1034f2644a2cSMark Brown { } 1035f2644a2cSMark Brown }; 1036f2644a2cSMark Brown MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id); 1037f2644a2cSMark Brown 1038f2644a2cSMark Brown static struct i2c_driver wm8960_i2c_driver = { 1039f2644a2cSMark Brown .driver = { 1040f0fba2adSLiam Girdwood .name = "wm8960-codec", 1041f2644a2cSMark Brown .owner = THIS_MODULE, 1042f2644a2cSMark Brown }, 1043f2644a2cSMark Brown .probe = wm8960_i2c_probe, 1044f2644a2cSMark Brown .remove = __devexit_p(wm8960_i2c_remove), 1045f2644a2cSMark Brown .id_table = wm8960_i2c_id, 1046f2644a2cSMark Brown }; 1047f0fba2adSLiam Girdwood #endif 1048f2644a2cSMark Brown 1049f2644a2cSMark Brown static int __init wm8960_modinit(void) 1050f2644a2cSMark Brown { 1051f0fba2adSLiam Girdwood int ret = 0; 1052f0fba2adSLiam Girdwood #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) 1053f2644a2cSMark Brown ret = i2c_add_driver(&wm8960_i2c_driver); 1054f2644a2cSMark Brown if (ret != 0) { 1055f2644a2cSMark Brown printk(KERN_ERR "Failed to register WM8960 I2C driver: %d\n", 1056f2644a2cSMark Brown ret); 1057f2644a2cSMark Brown } 1058f0fba2adSLiam Girdwood #endif 1059f2644a2cSMark Brown return ret; 1060f2644a2cSMark Brown } 1061f2644a2cSMark Brown module_init(wm8960_modinit); 1062f2644a2cSMark Brown 1063f2644a2cSMark Brown static void __exit wm8960_exit(void) 1064f2644a2cSMark Brown { 1065f0fba2adSLiam Girdwood #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) 1066f2644a2cSMark Brown i2c_del_driver(&wm8960_i2c_driver); 1067f0fba2adSLiam Girdwood #endif 1068f2644a2cSMark Brown } 1069f2644a2cSMark Brown module_exit(wm8960_exit); 1070f2644a2cSMark Brown 1071f2644a2cSMark Brown MODULE_DESCRIPTION("ASoC WM8960 driver"); 1072f2644a2cSMark Brown MODULE_AUTHOR("Liam Girdwood"); 1073f2644a2cSMark Brown MODULE_LICENSE("GPL"); 1074