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> 25b6877a47SMark Brown #include <sound/wm8960.h> 26f2644a2cSMark Brown 27f2644a2cSMark Brown #include "wm8960.h" 28f2644a2cSMark Brown 29f2644a2cSMark Brown #define AUDIO_NAME "wm8960" 30f2644a2cSMark Brown 31f2644a2cSMark Brown struct snd_soc_codec_device soc_codec_dev_wm8960; 32f2644a2cSMark Brown 33f2644a2cSMark Brown /* R25 - Power 1 */ 34913d7b4cSMark Brown #define WM8960_VMID_MASK 0x180 35f2644a2cSMark Brown #define WM8960_VREF 0x40 36f2644a2cSMark Brown 37913d7b4cSMark Brown /* R26 - Power 2 */ 38913d7b4cSMark Brown #define WM8960_PWR2_LOUT1 0x40 39913d7b4cSMark Brown #define WM8960_PWR2_ROUT1 0x20 40913d7b4cSMark Brown #define WM8960_PWR2_OUT3 0x02 41913d7b4cSMark Brown 42f2644a2cSMark Brown /* R28 - Anti-pop 1 */ 43f2644a2cSMark Brown #define WM8960_POBCTRL 0x80 44f2644a2cSMark Brown #define WM8960_BUFDCOPEN 0x10 45f2644a2cSMark Brown #define WM8960_BUFIOEN 0x08 46f2644a2cSMark Brown #define WM8960_SOFT_ST 0x04 47f2644a2cSMark Brown #define WM8960_HPSTBY 0x01 48f2644a2cSMark Brown 49f2644a2cSMark Brown /* R29 - Anti-pop 2 */ 50f2644a2cSMark Brown #define WM8960_DISOP 0x40 51913d7b4cSMark Brown #define WM8960_DRES_MASK 0x30 52f2644a2cSMark Brown 53f2644a2cSMark Brown /* 54f2644a2cSMark Brown * wm8960 register cache 55f2644a2cSMark Brown * We can't read the WM8960 register space when we are 56f2644a2cSMark Brown * using 2 wire for device control, so we cache them instead. 57f2644a2cSMark Brown */ 58f2644a2cSMark Brown static const u16 wm8960_reg[WM8960_CACHEREGNUM] = { 59f2644a2cSMark Brown 0x0097, 0x0097, 0x0000, 0x0000, 60f2644a2cSMark Brown 0x0000, 0x0008, 0x0000, 0x000a, 61f2644a2cSMark Brown 0x01c0, 0x0000, 0x00ff, 0x00ff, 62f2644a2cSMark Brown 0x0000, 0x0000, 0x0000, 0x0000, 63f2644a2cSMark Brown 0x0000, 0x007b, 0x0100, 0x0032, 64f2644a2cSMark Brown 0x0000, 0x00c3, 0x00c3, 0x01c0, 65f2644a2cSMark Brown 0x0000, 0x0000, 0x0000, 0x0000, 66f2644a2cSMark Brown 0x0000, 0x0000, 0x0000, 0x0000, 67f2644a2cSMark Brown 0x0100, 0x0100, 0x0050, 0x0050, 68f2644a2cSMark Brown 0x0050, 0x0050, 0x0000, 0x0000, 69f2644a2cSMark Brown 0x0000, 0x0000, 0x0040, 0x0000, 70f2644a2cSMark Brown 0x0000, 0x0050, 0x0050, 0x0000, 71f2644a2cSMark Brown 0x0002, 0x0037, 0x004d, 0x0080, 72f2644a2cSMark Brown 0x0008, 0x0031, 0x0026, 0x00e9, 73f2644a2cSMark Brown }; 74f2644a2cSMark Brown 75f2644a2cSMark Brown struct wm8960_priv { 76f2644a2cSMark Brown u16 reg_cache[WM8960_CACHEREGNUM]; 77f2644a2cSMark Brown struct snd_soc_codec codec; 78913d7b4cSMark Brown struct snd_soc_dapm_widget *lout1; 79913d7b4cSMark Brown struct snd_soc_dapm_widget *rout1; 80913d7b4cSMark Brown struct snd_soc_dapm_widget *out3; 81f2644a2cSMark Brown }; 82f2644a2cSMark Brown 8317a52fd6SMark Brown #define wm8960_reset(c) snd_soc_write(c, WM8960_RESET, 0) 84f2644a2cSMark Brown 85f2644a2cSMark Brown /* enumerated controls */ 86f2644a2cSMark Brown static const char *wm8960_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; 87f2644a2cSMark Brown static const char *wm8960_polarity[] = {"No Inversion", "Left Inverted", 88f2644a2cSMark Brown "Right Inverted", "Stereo Inversion"}; 89f2644a2cSMark Brown static const char *wm8960_3d_upper_cutoff[] = {"High", "Low"}; 90f2644a2cSMark Brown static const char *wm8960_3d_lower_cutoff[] = {"Low", "High"}; 91f2644a2cSMark Brown static const char *wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"}; 92f2644a2cSMark Brown static const char *wm8960_alcmode[] = {"ALC", "Limiter"}; 93f2644a2cSMark Brown 94f2644a2cSMark Brown static const struct soc_enum wm8960_enum[] = { 95f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_DACCTL1, 1, 4, wm8960_deemph), 96f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity), 97f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity), 98f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff), 99f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_3D, 5, 2, wm8960_3d_lower_cutoff), 100f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_ALC1, 7, 4, wm8960_alcfunc), 101f2644a2cSMark Brown SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode), 102f2644a2cSMark Brown }; 103f2644a2cSMark Brown 104f2644a2cSMark Brown static const DECLARE_TLV_DB_SCALE(adc_tlv, -9700, 50, 0); 105f2644a2cSMark Brown static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 50, 1); 106f2644a2cSMark Brown static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0); 107f2644a2cSMark Brown static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); 108f2644a2cSMark Brown 109f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_snd_controls[] = { 110f2644a2cSMark Brown SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL, 111f2644a2cSMark Brown 0, 63, 0, adc_tlv), 112f2644a2cSMark Brown SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL, 113f2644a2cSMark Brown 6, 1, 0), 114f2644a2cSMark Brown SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL, 115f2644a2cSMark Brown 7, 1, 0), 116f2644a2cSMark Brown 117f2644a2cSMark Brown SOC_DOUBLE_R_TLV("Playback Volume", WM8960_LDAC, WM8960_RDAC, 118f2644a2cSMark Brown 0, 255, 0, dac_tlv), 119f2644a2cSMark Brown 120f2644a2cSMark Brown SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8960_LOUT1, WM8960_ROUT1, 121f2644a2cSMark Brown 0, 127, 0, out_tlv), 122f2644a2cSMark Brown SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8960_LOUT1, WM8960_ROUT1, 123f2644a2cSMark Brown 7, 1, 0), 124f2644a2cSMark Brown 125f2644a2cSMark Brown SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8960_LOUT2, WM8960_ROUT2, 126f2644a2cSMark Brown 0, 127, 0, out_tlv), 127f2644a2cSMark Brown SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8960_LOUT2, WM8960_ROUT2, 128f2644a2cSMark Brown 7, 1, 0), 129f2644a2cSMark Brown SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0), 130f2644a2cSMark Brown SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0), 131f2644a2cSMark Brown 132f2644a2cSMark Brown SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0), 133f2644a2cSMark Brown SOC_ENUM("ADC Polarity", wm8960_enum[1]), 134f2644a2cSMark Brown SOC_ENUM("Playback De-emphasis", wm8960_enum[0]), 135f2644a2cSMark Brown SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0), 136f2644a2cSMark Brown 137f2644a2cSMark Brown SOC_ENUM("DAC Polarity", wm8960_enum[2]), 138f2644a2cSMark Brown 139f2644a2cSMark Brown SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[3]), 140f2644a2cSMark Brown SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[4]), 141f2644a2cSMark Brown SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0), 142f2644a2cSMark Brown SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0), 143f2644a2cSMark Brown 144f2644a2cSMark Brown SOC_ENUM("ALC Function", wm8960_enum[5]), 145f2644a2cSMark Brown SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0), 146f2644a2cSMark Brown SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1), 147f2644a2cSMark Brown SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0), 148f2644a2cSMark Brown SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0), 149f2644a2cSMark Brown SOC_ENUM("ALC Mode", wm8960_enum[6]), 150f2644a2cSMark Brown SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0), 151f2644a2cSMark Brown SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0), 152f2644a2cSMark Brown 153f2644a2cSMark Brown SOC_SINGLE("Noise Gate Threshold", WM8960_NOISEG, 3, 31, 0), 154f2644a2cSMark Brown SOC_SINGLE("Noise Gate Switch", WM8960_NOISEG, 0, 1, 0), 155f2644a2cSMark Brown 156f2644a2cSMark Brown SOC_DOUBLE_R("ADC PCM Capture Volume", WM8960_LINPATH, WM8960_RINPATH, 157f2644a2cSMark Brown 0, 127, 0), 158f2644a2cSMark Brown 159f2644a2cSMark Brown SOC_SINGLE_TLV("Left Output Mixer Boost Bypass Volume", 160f2644a2cSMark Brown WM8960_BYPASS1, 4, 7, 1, bypass_tlv), 161f2644a2cSMark Brown SOC_SINGLE_TLV("Left Output Mixer LINPUT3 Volume", 162f2644a2cSMark Brown WM8960_LOUTMIX, 4, 7, 1, bypass_tlv), 163f2644a2cSMark Brown SOC_SINGLE_TLV("Right Output Mixer Boost Bypass Volume", 164f2644a2cSMark Brown WM8960_BYPASS2, 4, 7, 1, bypass_tlv), 165f2644a2cSMark Brown SOC_SINGLE_TLV("Right Output Mixer RINPUT3 Volume", 166f2644a2cSMark Brown WM8960_ROUTMIX, 4, 7, 1, bypass_tlv), 167f2644a2cSMark Brown }; 168f2644a2cSMark Brown 169f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_lin_boost[] = { 170f2644a2cSMark Brown SOC_DAPM_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0), 171f2644a2cSMark Brown SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LINPATH, 7, 1, 0), 172f2644a2cSMark Brown SOC_DAPM_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0), 173f2644a2cSMark Brown }; 174f2644a2cSMark Brown 175f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_lin[] = { 176f2644a2cSMark Brown SOC_DAPM_SINGLE("Boost Switch", WM8960_LINPATH, 3, 1, 0), 177f2644a2cSMark Brown }; 178f2644a2cSMark Brown 179f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_rin_boost[] = { 180f2644a2cSMark Brown SOC_DAPM_SINGLE("RINPUT2 Switch", WM8960_RINPATH, 6, 1, 0), 181f2644a2cSMark Brown SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_RINPATH, 7, 1, 0), 182f2644a2cSMark Brown SOC_DAPM_SINGLE("RINPUT1 Switch", WM8960_RINPATH, 8, 1, 0), 183f2644a2cSMark Brown }; 184f2644a2cSMark Brown 185f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_rin[] = { 186f2644a2cSMark Brown SOC_DAPM_SINGLE("Boost Switch", WM8960_RINPATH, 3, 1, 0), 187f2644a2cSMark Brown }; 188f2644a2cSMark Brown 189f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_loutput_mixer[] = { 190f2644a2cSMark Brown SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_LOUTMIX, 8, 1, 0), 191f2644a2cSMark Brown SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LOUTMIX, 7, 1, 0), 192f2644a2cSMark Brown SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS1, 7, 1, 0), 193f2644a2cSMark Brown }; 194f2644a2cSMark Brown 195f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_routput_mixer[] = { 196f2644a2cSMark Brown SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_ROUTMIX, 8, 1, 0), 197f2644a2cSMark Brown SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_ROUTMIX, 7, 1, 0), 198f2644a2cSMark Brown SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS2, 7, 1, 0), 199f2644a2cSMark Brown }; 200f2644a2cSMark Brown 201f2644a2cSMark Brown static const struct snd_kcontrol_new wm8960_mono_out[] = { 202f2644a2cSMark Brown SOC_DAPM_SINGLE("Left Switch", WM8960_MONOMIX1, 7, 1, 0), 203f2644a2cSMark Brown SOC_DAPM_SINGLE("Right Switch", WM8960_MONOMIX2, 7, 1, 0), 204f2644a2cSMark Brown }; 205f2644a2cSMark Brown 206f2644a2cSMark Brown static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = { 207f2644a2cSMark Brown SND_SOC_DAPM_INPUT("LINPUT1"), 208f2644a2cSMark Brown SND_SOC_DAPM_INPUT("RINPUT1"), 209f2644a2cSMark Brown SND_SOC_DAPM_INPUT("LINPUT2"), 210f2644a2cSMark Brown SND_SOC_DAPM_INPUT("RINPUT2"), 211f2644a2cSMark Brown SND_SOC_DAPM_INPUT("LINPUT3"), 212f2644a2cSMark Brown SND_SOC_DAPM_INPUT("RINPUT3"), 213f2644a2cSMark Brown 214f2644a2cSMark Brown SND_SOC_DAPM_MICBIAS("MICB", WM8960_POWER1, 1, 0), 215f2644a2cSMark Brown 216f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0, 217f2644a2cSMark Brown wm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)), 218f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8960_POWER1, 4, 0, 219f2644a2cSMark Brown wm8960_rin_boost, ARRAY_SIZE(wm8960_rin_boost)), 220f2644a2cSMark Brown 221f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0, 222f2644a2cSMark Brown wm8960_lin, ARRAY_SIZE(wm8960_lin)), 223f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Right Input Mixer", WM8960_POWER3, 4, 0, 224f2644a2cSMark Brown wm8960_rin, ARRAY_SIZE(wm8960_rin)), 225f2644a2cSMark Brown 226f2644a2cSMark Brown SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER2, 3, 0), 227f2644a2cSMark Brown SND_SOC_DAPM_ADC("Right ADC", "Capture", WM8960_POWER2, 2, 0), 228f2644a2cSMark Brown 229f2644a2cSMark Brown SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0), 230f2644a2cSMark Brown SND_SOC_DAPM_DAC("Right DAC", "Playback", WM8960_POWER2, 7, 0), 231f2644a2cSMark Brown 232f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0, 233f2644a2cSMark Brown &wm8960_loutput_mixer[0], 234f2644a2cSMark Brown ARRAY_SIZE(wm8960_loutput_mixer)), 235f2644a2cSMark Brown SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0, 236f2644a2cSMark Brown &wm8960_routput_mixer[0], 237f2644a2cSMark Brown ARRAY_SIZE(wm8960_routput_mixer)), 238f2644a2cSMark Brown 239f2644a2cSMark Brown SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0), 240f2644a2cSMark Brown SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0), 241f2644a2cSMark Brown 242f2644a2cSMark Brown SND_SOC_DAPM_PGA("Left Speaker PGA", WM8960_POWER2, 4, 0, NULL, 0), 243f2644a2cSMark Brown SND_SOC_DAPM_PGA("Right Speaker PGA", WM8960_POWER2, 3, 0, NULL, 0), 244f2644a2cSMark Brown 245f2644a2cSMark Brown SND_SOC_DAPM_PGA("Right Speaker Output", WM8960_CLASSD1, 7, 0, NULL, 0), 246f2644a2cSMark Brown SND_SOC_DAPM_PGA("Left Speaker Output", WM8960_CLASSD1, 6, 0, NULL, 0), 247f2644a2cSMark Brown 248f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("SPK_LP"), 249f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("SPK_LN"), 250f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("HP_L"), 251f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("HP_R"), 252f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("SPK_RP"), 253f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("SPK_RN"), 254f2644a2cSMark Brown SND_SOC_DAPM_OUTPUT("OUT3"), 255f2644a2cSMark Brown }; 256f2644a2cSMark Brown 257913d7b4cSMark Brown static const struct snd_soc_dapm_widget wm8960_dapm_widgets_out3[] = { 258913d7b4cSMark Brown SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0, 259913d7b4cSMark Brown &wm8960_mono_out[0], 260913d7b4cSMark Brown ARRAY_SIZE(wm8960_mono_out)), 261913d7b4cSMark Brown }; 262913d7b4cSMark Brown 263913d7b4cSMark Brown /* Represent OUT3 as a PGA so that it gets turned on with LOUT1/ROUT1 */ 264913d7b4cSMark Brown static const struct snd_soc_dapm_widget wm8960_dapm_widgets_capless[] = { 265913d7b4cSMark Brown SND_SOC_DAPM_PGA("OUT3 VMID", WM8960_POWER2, 1, 0, NULL, 0), 266913d7b4cSMark Brown }; 267913d7b4cSMark Brown 268f2644a2cSMark Brown static const struct snd_soc_dapm_route audio_paths[] = { 269f2644a2cSMark Brown { "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" }, 270f2644a2cSMark Brown { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" }, 271f2644a2cSMark Brown { "Left Boost Mixer", "LINPUT3 Switch", "LINPUT3" }, 272f2644a2cSMark Brown 273f2644a2cSMark Brown { "Left Input Mixer", "Boost Switch", "Left Boost Mixer", }, 274f2644a2cSMark Brown { "Left Input Mixer", NULL, "LINPUT1", }, /* Really Boost Switch */ 275f2644a2cSMark Brown { "Left Input Mixer", NULL, "LINPUT2" }, 276f2644a2cSMark Brown { "Left Input Mixer", NULL, "LINPUT3" }, 277f2644a2cSMark Brown 278f2644a2cSMark Brown { "Right Boost Mixer", "RINPUT1 Switch", "RINPUT1" }, 279f2644a2cSMark Brown { "Right Boost Mixer", "RINPUT2 Switch", "RINPUT2" }, 280f2644a2cSMark Brown { "Right Boost Mixer", "RINPUT3 Switch", "RINPUT3" }, 281f2644a2cSMark Brown 282f2644a2cSMark Brown { "Right Input Mixer", "Boost Switch", "Right Boost Mixer", }, 283f2644a2cSMark Brown { "Right Input Mixer", NULL, "RINPUT1", }, /* Really Boost Switch */ 284f2644a2cSMark Brown { "Right Input Mixer", NULL, "RINPUT2" }, 285f2644a2cSMark Brown { "Right Input Mixer", NULL, "LINPUT3" }, 286f2644a2cSMark Brown 287f2644a2cSMark Brown { "Left ADC", NULL, "Left Input Mixer" }, 288f2644a2cSMark Brown { "Right ADC", NULL, "Right Input Mixer" }, 289f2644a2cSMark Brown 290f2644a2cSMark Brown { "Left Output Mixer", "LINPUT3 Switch", "LINPUT3" }, 291f2644a2cSMark Brown { "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer"} , 292f2644a2cSMark Brown { "Left Output Mixer", "PCM Playback Switch", "Left DAC" }, 293f2644a2cSMark Brown 294f2644a2cSMark Brown { "Right Output Mixer", "RINPUT3 Switch", "RINPUT3" }, 295f2644a2cSMark Brown { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" } , 296f2644a2cSMark Brown { "Right Output Mixer", "PCM Playback Switch", "Right DAC" }, 297f2644a2cSMark Brown 298f2644a2cSMark Brown { "LOUT1 PGA", NULL, "Left Output Mixer" }, 299f2644a2cSMark Brown { "ROUT1 PGA", NULL, "Right Output Mixer" }, 300f2644a2cSMark Brown 301f2644a2cSMark Brown { "HP_L", NULL, "LOUT1 PGA" }, 302f2644a2cSMark Brown { "HP_R", NULL, "ROUT1 PGA" }, 303f2644a2cSMark Brown 304f2644a2cSMark Brown { "Left Speaker PGA", NULL, "Left Output Mixer" }, 305f2644a2cSMark Brown { "Right Speaker PGA", NULL, "Right Output Mixer" }, 306f2644a2cSMark Brown 307f2644a2cSMark Brown { "Left Speaker Output", NULL, "Left Speaker PGA" }, 308f2644a2cSMark Brown { "Right Speaker Output", NULL, "Right Speaker PGA" }, 309f2644a2cSMark Brown 310f2644a2cSMark Brown { "SPK_LN", NULL, "Left Speaker Output" }, 311f2644a2cSMark Brown { "SPK_LP", NULL, "Left Speaker Output" }, 312f2644a2cSMark Brown { "SPK_RN", NULL, "Right Speaker Output" }, 313f2644a2cSMark Brown { "SPK_RP", NULL, "Right Speaker Output" }, 314913d7b4cSMark Brown }; 315913d7b4cSMark Brown 316913d7b4cSMark Brown static const struct snd_soc_dapm_route audio_paths_out3[] = { 317913d7b4cSMark Brown { "Mono Output Mixer", "Left Switch", "Left Output Mixer" }, 318913d7b4cSMark Brown { "Mono Output Mixer", "Right Switch", "Right Output Mixer" }, 319f2644a2cSMark Brown 320f2644a2cSMark Brown { "OUT3", NULL, "Mono Output Mixer", } 321f2644a2cSMark Brown }; 322f2644a2cSMark Brown 323913d7b4cSMark Brown static const struct snd_soc_dapm_route audio_paths_capless[] = { 324913d7b4cSMark Brown { "HP_L", NULL, "OUT3 VMID" }, 325913d7b4cSMark Brown { "HP_R", NULL, "OUT3 VMID" }, 326913d7b4cSMark Brown 327913d7b4cSMark Brown { "OUT3 VMID", NULL, "Left Output Mixer" }, 328913d7b4cSMark Brown { "OUT3 VMID", NULL, "Right Output Mixer" }, 329913d7b4cSMark Brown }; 330913d7b4cSMark Brown 331f2644a2cSMark Brown static int wm8960_add_widgets(struct snd_soc_codec *codec) 332f2644a2cSMark Brown { 333913d7b4cSMark Brown struct wm8960_data *pdata = codec->dev->platform_data; 334913d7b4cSMark Brown struct wm8960_priv *wm8960 = codec->private_data; 335913d7b4cSMark Brown struct snd_soc_dapm_widget *w; 336913d7b4cSMark Brown 337f2644a2cSMark Brown snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets, 338f2644a2cSMark Brown ARRAY_SIZE(wm8960_dapm_widgets)); 339f2644a2cSMark Brown 340f2644a2cSMark Brown snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); 341f2644a2cSMark Brown 342913d7b4cSMark Brown /* In capless mode OUT3 is used to provide VMID for the 343913d7b4cSMark Brown * headphone outputs, otherwise it is used as a mono mixer. 344913d7b4cSMark Brown */ 345913d7b4cSMark Brown if (pdata && pdata->capless) { 346913d7b4cSMark Brown snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets_capless, 347913d7b4cSMark Brown ARRAY_SIZE(wm8960_dapm_widgets_capless)); 348913d7b4cSMark Brown 349913d7b4cSMark Brown snd_soc_dapm_add_routes(codec, audio_paths_capless, 350913d7b4cSMark Brown ARRAY_SIZE(audio_paths_capless)); 351913d7b4cSMark Brown } else { 352913d7b4cSMark Brown snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets_out3, 353913d7b4cSMark Brown ARRAY_SIZE(wm8960_dapm_widgets_out3)); 354913d7b4cSMark Brown 355913d7b4cSMark Brown snd_soc_dapm_add_routes(codec, audio_paths_out3, 356913d7b4cSMark Brown ARRAY_SIZE(audio_paths_out3)); 357913d7b4cSMark Brown } 358913d7b4cSMark Brown 359913d7b4cSMark Brown /* We need to power up the headphone output stage out of 360913d7b4cSMark Brown * sequence for capless mode. To save scanning the widget 361913d7b4cSMark Brown * list each time to find the desired power state do so now 362913d7b4cSMark Brown * and save the result. 363913d7b4cSMark Brown */ 364913d7b4cSMark Brown list_for_each_entry(w, &codec->dapm_widgets, list) { 365913d7b4cSMark Brown if (strcmp(w->name, "LOUT1 PGA") == 0) 366913d7b4cSMark Brown wm8960->lout1 = w; 367913d7b4cSMark Brown if (strcmp(w->name, "ROUT1 PGA") == 0) 368913d7b4cSMark Brown wm8960->rout1 = w; 369913d7b4cSMark Brown if (strcmp(w->name, "OUT3 VMID") == 0) 370913d7b4cSMark Brown wm8960->out3 = w; 371913d7b4cSMark Brown } 372913d7b4cSMark Brown 373f2644a2cSMark Brown return 0; 374f2644a2cSMark Brown } 375f2644a2cSMark Brown 376f2644a2cSMark Brown static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai, 377f2644a2cSMark Brown unsigned int fmt) 378f2644a2cSMark Brown { 379f2644a2cSMark Brown struct snd_soc_codec *codec = codec_dai->codec; 380f2644a2cSMark Brown u16 iface = 0; 381f2644a2cSMark Brown 382f2644a2cSMark Brown /* set master/slave audio interface */ 383f2644a2cSMark Brown switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { 384f2644a2cSMark Brown case SND_SOC_DAIFMT_CBM_CFM: 385f2644a2cSMark Brown iface |= 0x0040; 386f2644a2cSMark Brown break; 387f2644a2cSMark Brown case SND_SOC_DAIFMT_CBS_CFS: 388f2644a2cSMark Brown break; 389f2644a2cSMark Brown default: 390f2644a2cSMark Brown return -EINVAL; 391f2644a2cSMark Brown } 392f2644a2cSMark Brown 393f2644a2cSMark Brown /* interface format */ 394f2644a2cSMark Brown switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 395f2644a2cSMark Brown case SND_SOC_DAIFMT_I2S: 396f2644a2cSMark Brown iface |= 0x0002; 397f2644a2cSMark Brown break; 398f2644a2cSMark Brown case SND_SOC_DAIFMT_RIGHT_J: 399f2644a2cSMark Brown break; 400f2644a2cSMark Brown case SND_SOC_DAIFMT_LEFT_J: 401f2644a2cSMark Brown iface |= 0x0001; 402f2644a2cSMark Brown break; 403f2644a2cSMark Brown case SND_SOC_DAIFMT_DSP_A: 404f2644a2cSMark Brown iface |= 0x0003; 405f2644a2cSMark Brown break; 406f2644a2cSMark Brown case SND_SOC_DAIFMT_DSP_B: 407f2644a2cSMark Brown iface |= 0x0013; 408f2644a2cSMark Brown break; 409f2644a2cSMark Brown default: 410f2644a2cSMark Brown return -EINVAL; 411f2644a2cSMark Brown } 412f2644a2cSMark Brown 413f2644a2cSMark Brown /* clock inversion */ 414f2644a2cSMark Brown switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 415f2644a2cSMark Brown case SND_SOC_DAIFMT_NB_NF: 416f2644a2cSMark Brown break; 417f2644a2cSMark Brown case SND_SOC_DAIFMT_IB_IF: 418f2644a2cSMark Brown iface |= 0x0090; 419f2644a2cSMark Brown break; 420f2644a2cSMark Brown case SND_SOC_DAIFMT_IB_NF: 421f2644a2cSMark Brown iface |= 0x0080; 422f2644a2cSMark Brown break; 423f2644a2cSMark Brown case SND_SOC_DAIFMT_NB_IF: 424f2644a2cSMark Brown iface |= 0x0010; 425f2644a2cSMark Brown break; 426f2644a2cSMark Brown default: 427f2644a2cSMark Brown return -EINVAL; 428f2644a2cSMark Brown } 429f2644a2cSMark Brown 430f2644a2cSMark Brown /* set iface */ 43117a52fd6SMark Brown snd_soc_write(codec, WM8960_IFACE1, iface); 432f2644a2cSMark Brown return 0; 433f2644a2cSMark Brown } 434f2644a2cSMark Brown 435f2644a2cSMark Brown static int wm8960_hw_params(struct snd_pcm_substream *substream, 436f2644a2cSMark Brown struct snd_pcm_hw_params *params, 437f2644a2cSMark Brown struct snd_soc_dai *dai) 438f2644a2cSMark Brown { 439f2644a2cSMark Brown struct snd_soc_pcm_runtime *rtd = substream->private_data; 440f2644a2cSMark Brown struct snd_soc_device *socdev = rtd->socdev; 441f2644a2cSMark Brown struct snd_soc_codec *codec = socdev->card->codec; 44217a52fd6SMark Brown u16 iface = snd_soc_read(codec, WM8960_IFACE1) & 0xfff3; 443f2644a2cSMark Brown 444f2644a2cSMark Brown /* bit size */ 445f2644a2cSMark Brown switch (params_format(params)) { 446f2644a2cSMark Brown case SNDRV_PCM_FORMAT_S16_LE: 447f2644a2cSMark Brown break; 448f2644a2cSMark Brown case SNDRV_PCM_FORMAT_S20_3LE: 449f2644a2cSMark Brown iface |= 0x0004; 450f2644a2cSMark Brown break; 451f2644a2cSMark Brown case SNDRV_PCM_FORMAT_S24_LE: 452f2644a2cSMark Brown iface |= 0x0008; 453f2644a2cSMark Brown break; 454f2644a2cSMark Brown } 455f2644a2cSMark Brown 456f2644a2cSMark Brown /* set iface */ 45717a52fd6SMark Brown snd_soc_write(codec, WM8960_IFACE1, iface); 458f2644a2cSMark Brown return 0; 459f2644a2cSMark Brown } 460f2644a2cSMark Brown 461f2644a2cSMark Brown static int wm8960_mute(struct snd_soc_dai *dai, int mute) 462f2644a2cSMark Brown { 463f2644a2cSMark Brown struct snd_soc_codec *codec = dai->codec; 46417a52fd6SMark Brown u16 mute_reg = snd_soc_read(codec, WM8960_DACCTL1) & 0xfff7; 465f2644a2cSMark Brown 466f2644a2cSMark Brown if (mute) 46717a52fd6SMark Brown snd_soc_write(codec, WM8960_DACCTL1, mute_reg | 0x8); 468f2644a2cSMark Brown else 46917a52fd6SMark Brown snd_soc_write(codec, WM8960_DACCTL1, mute_reg); 470f2644a2cSMark Brown return 0; 471f2644a2cSMark Brown } 472f2644a2cSMark Brown 473913d7b4cSMark Brown static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec, 474f2644a2cSMark Brown enum snd_soc_bias_level level) 475f2644a2cSMark Brown { 476f2644a2cSMark Brown u16 reg; 477f2644a2cSMark Brown 478f2644a2cSMark Brown switch (level) { 479f2644a2cSMark Brown case SND_SOC_BIAS_ON: 480f2644a2cSMark Brown break; 481f2644a2cSMark Brown 482f2644a2cSMark Brown case SND_SOC_BIAS_PREPARE: 483f2644a2cSMark Brown /* Set VMID to 2x50k */ 48417a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_POWER1); 485f2644a2cSMark Brown reg &= ~0x180; 486f2644a2cSMark Brown reg |= 0x80; 48717a52fd6SMark Brown snd_soc_write(codec, WM8960_POWER1, reg); 488f2644a2cSMark Brown break; 489f2644a2cSMark Brown 490f2644a2cSMark Brown case SND_SOC_BIAS_STANDBY: 491f2644a2cSMark Brown if (codec->bias_level == SND_SOC_BIAS_OFF) { 492f2644a2cSMark Brown /* Enable anti-pop features */ 49317a52fd6SMark Brown snd_soc_write(codec, WM8960_APOP1, 494f2644a2cSMark Brown WM8960_POBCTRL | WM8960_SOFT_ST | 495f2644a2cSMark Brown WM8960_BUFDCOPEN | WM8960_BUFIOEN); 496f2644a2cSMark Brown 497f2644a2cSMark Brown /* Enable & ramp VMID at 2x50k */ 49817a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_POWER1); 499f2644a2cSMark Brown reg |= 0x80; 50017a52fd6SMark Brown snd_soc_write(codec, WM8960_POWER1, reg); 501f2644a2cSMark Brown msleep(100); 502f2644a2cSMark Brown 503f2644a2cSMark Brown /* Enable VREF */ 50417a52fd6SMark Brown snd_soc_write(codec, WM8960_POWER1, reg | WM8960_VREF); 505f2644a2cSMark Brown 506f2644a2cSMark Brown /* Disable anti-pop features */ 50717a52fd6SMark Brown snd_soc_write(codec, WM8960_APOP1, WM8960_BUFIOEN); 508f2644a2cSMark Brown } 509f2644a2cSMark Brown 510f2644a2cSMark Brown /* Set VMID to 2x250k */ 51117a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_POWER1); 512f2644a2cSMark Brown reg &= ~0x180; 513f2644a2cSMark Brown reg |= 0x100; 51417a52fd6SMark Brown snd_soc_write(codec, WM8960_POWER1, reg); 515f2644a2cSMark Brown break; 516f2644a2cSMark Brown 517f2644a2cSMark Brown case SND_SOC_BIAS_OFF: 518f2644a2cSMark Brown /* Enable anti-pop features */ 51917a52fd6SMark Brown snd_soc_write(codec, WM8960_APOP1, 520f2644a2cSMark Brown WM8960_POBCTRL | WM8960_SOFT_ST | 521f2644a2cSMark Brown WM8960_BUFDCOPEN | WM8960_BUFIOEN); 522f2644a2cSMark Brown 523f2644a2cSMark Brown /* Disable VMID and VREF, let them discharge */ 52417a52fd6SMark Brown snd_soc_write(codec, WM8960_POWER1, 0); 525f2644a2cSMark Brown msleep(600); 526913d7b4cSMark Brown break; 527913d7b4cSMark Brown } 528f2644a2cSMark Brown 529913d7b4cSMark Brown codec->bias_level = level; 530913d7b4cSMark Brown 531913d7b4cSMark Brown return 0; 532913d7b4cSMark Brown } 533913d7b4cSMark Brown 534913d7b4cSMark Brown static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec, 535913d7b4cSMark Brown enum snd_soc_bias_level level) 536913d7b4cSMark Brown { 537913d7b4cSMark Brown struct wm8960_priv *wm8960 = codec->private_data; 538913d7b4cSMark Brown int reg; 539913d7b4cSMark Brown 540913d7b4cSMark Brown switch (level) { 541913d7b4cSMark Brown case SND_SOC_BIAS_ON: 542913d7b4cSMark Brown break; 543913d7b4cSMark Brown 544913d7b4cSMark Brown case SND_SOC_BIAS_PREPARE: 545913d7b4cSMark Brown switch (codec->bias_level) { 546913d7b4cSMark Brown case SND_SOC_BIAS_STANDBY: 547913d7b4cSMark Brown /* Enable anti pop mode */ 548913d7b4cSMark Brown snd_soc_update_bits(codec, WM8960_APOP1, 549913d7b4cSMark Brown WM8960_POBCTRL | WM8960_SOFT_ST | 550913d7b4cSMark Brown WM8960_BUFDCOPEN, 551913d7b4cSMark Brown WM8960_POBCTRL | WM8960_SOFT_ST | 552913d7b4cSMark Brown WM8960_BUFDCOPEN); 553913d7b4cSMark Brown 554913d7b4cSMark Brown /* Enable LOUT1, ROUT1 and OUT3 if they're enabled */ 555913d7b4cSMark Brown reg = 0; 556913d7b4cSMark Brown if (wm8960->lout1 && wm8960->lout1->power) 557913d7b4cSMark Brown reg |= WM8960_PWR2_LOUT1; 558913d7b4cSMark Brown if (wm8960->rout1 && wm8960->rout1->power) 559913d7b4cSMark Brown reg |= WM8960_PWR2_ROUT1; 560913d7b4cSMark Brown if (wm8960->out3 && wm8960->out3->power) 561913d7b4cSMark Brown reg |= WM8960_PWR2_OUT3; 562913d7b4cSMark Brown snd_soc_update_bits(codec, WM8960_POWER2, 563913d7b4cSMark Brown WM8960_PWR2_LOUT1 | 564913d7b4cSMark Brown WM8960_PWR2_ROUT1 | 565913d7b4cSMark Brown WM8960_PWR2_OUT3, reg); 566913d7b4cSMark Brown 567913d7b4cSMark Brown /* Enable VMID at 2*50k */ 568913d7b4cSMark Brown snd_soc_update_bits(codec, WM8960_POWER1, 569913d7b4cSMark Brown WM8960_VMID_MASK, 0x80); 570913d7b4cSMark Brown 571913d7b4cSMark Brown /* Ramp */ 572913d7b4cSMark Brown msleep(100); 573913d7b4cSMark Brown 574913d7b4cSMark Brown /* Enable VREF */ 575913d7b4cSMark Brown snd_soc_update_bits(codec, WM8960_POWER1, 576913d7b4cSMark Brown WM8960_VREF, WM8960_VREF); 577913d7b4cSMark Brown 578913d7b4cSMark Brown msleep(100); 579913d7b4cSMark Brown break; 580913d7b4cSMark Brown 581913d7b4cSMark Brown case SND_SOC_BIAS_ON: 582913d7b4cSMark Brown /* Enable anti-pop mode */ 583913d7b4cSMark Brown snd_soc_update_bits(codec, WM8960_APOP1, 584913d7b4cSMark Brown WM8960_POBCTRL | WM8960_SOFT_ST | 585913d7b4cSMark Brown WM8960_BUFDCOPEN, 586913d7b4cSMark Brown WM8960_POBCTRL | WM8960_SOFT_ST | 587913d7b4cSMark Brown WM8960_BUFDCOPEN); 588913d7b4cSMark Brown 589913d7b4cSMark Brown /* Disable VMID and VREF */ 590913d7b4cSMark Brown snd_soc_update_bits(codec, WM8960_POWER1, 591913d7b4cSMark Brown WM8960_VREF | WM8960_VMID_MASK, 0); 592913d7b4cSMark Brown break; 593913d7b4cSMark Brown 594913d7b4cSMark Brown default: 595913d7b4cSMark Brown break; 596913d7b4cSMark Brown } 597913d7b4cSMark Brown break; 598913d7b4cSMark Brown 599913d7b4cSMark Brown case SND_SOC_BIAS_STANDBY: 600913d7b4cSMark Brown switch (codec->bias_level) { 601913d7b4cSMark Brown case SND_SOC_BIAS_PREPARE: 602913d7b4cSMark Brown /* Disable HP discharge */ 603913d7b4cSMark Brown snd_soc_update_bits(codec, WM8960_APOP2, 604913d7b4cSMark Brown WM8960_DISOP | WM8960_DRES_MASK, 605913d7b4cSMark Brown 0); 606913d7b4cSMark Brown 607913d7b4cSMark Brown /* Disable anti-pop features */ 608913d7b4cSMark Brown snd_soc_update_bits(codec, WM8960_APOP1, 609913d7b4cSMark Brown WM8960_POBCTRL | WM8960_SOFT_ST | 610913d7b4cSMark Brown WM8960_BUFDCOPEN, 611913d7b4cSMark Brown WM8960_POBCTRL | WM8960_SOFT_ST | 612913d7b4cSMark Brown WM8960_BUFDCOPEN); 613913d7b4cSMark Brown break; 614913d7b4cSMark Brown 615913d7b4cSMark Brown default: 616913d7b4cSMark Brown break; 617913d7b4cSMark Brown } 618913d7b4cSMark Brown break; 619913d7b4cSMark Brown 620913d7b4cSMark Brown case SND_SOC_BIAS_OFF: 621f2644a2cSMark Brown break; 622f2644a2cSMark Brown } 623f2644a2cSMark Brown 624f2644a2cSMark Brown codec->bias_level = level; 625f2644a2cSMark Brown 626f2644a2cSMark Brown return 0; 627f2644a2cSMark Brown } 628f2644a2cSMark Brown 629f2644a2cSMark Brown /* PLL divisors */ 630f2644a2cSMark Brown struct _pll_div { 631f2644a2cSMark Brown u32 pre_div:1; 632f2644a2cSMark Brown u32 n:4; 633f2644a2cSMark Brown u32 k:24; 634f2644a2cSMark Brown }; 635f2644a2cSMark Brown 636f2644a2cSMark Brown /* The size in bits of the pll divide multiplied by 10 637f2644a2cSMark Brown * to allow rounding later */ 638f2644a2cSMark Brown #define FIXED_PLL_SIZE ((1 << 24) * 10) 639f2644a2cSMark Brown 640f2644a2cSMark Brown static int pll_factors(unsigned int source, unsigned int target, 641f2644a2cSMark Brown struct _pll_div *pll_div) 642f2644a2cSMark Brown { 643f2644a2cSMark Brown unsigned long long Kpart; 644f2644a2cSMark Brown unsigned int K, Ndiv, Nmod; 645f2644a2cSMark Brown 646f2644a2cSMark Brown pr_debug("WM8960 PLL: setting %dHz->%dHz\n", source, target); 647f2644a2cSMark Brown 648f2644a2cSMark Brown /* Scale up target to PLL operating frequency */ 649f2644a2cSMark Brown target *= 4; 650f2644a2cSMark Brown 651f2644a2cSMark Brown Ndiv = target / source; 652f2644a2cSMark Brown if (Ndiv < 6) { 653f2644a2cSMark Brown source >>= 1; 654f2644a2cSMark Brown pll_div->pre_div = 1; 655f2644a2cSMark Brown Ndiv = target / source; 656f2644a2cSMark Brown } else 657f2644a2cSMark Brown pll_div->pre_div = 0; 658f2644a2cSMark Brown 659f2644a2cSMark Brown if ((Ndiv < 6) || (Ndiv > 12)) { 660f2644a2cSMark Brown pr_err("WM8960 PLL: Unsupported N=%d\n", Ndiv); 661f2644a2cSMark Brown return -EINVAL; 662f2644a2cSMark Brown } 663f2644a2cSMark Brown 664f2644a2cSMark Brown pll_div->n = Ndiv; 665f2644a2cSMark Brown Nmod = target % source; 666f2644a2cSMark Brown Kpart = FIXED_PLL_SIZE * (long long)Nmod; 667f2644a2cSMark Brown 668f2644a2cSMark Brown do_div(Kpart, source); 669f2644a2cSMark Brown 670f2644a2cSMark Brown K = Kpart & 0xFFFFFFFF; 671f2644a2cSMark Brown 672f2644a2cSMark Brown /* Check if we need to round */ 673f2644a2cSMark Brown if ((K % 10) >= 5) 674f2644a2cSMark Brown K += 5; 675f2644a2cSMark Brown 676f2644a2cSMark Brown /* Move down to proper range now rounding is done */ 677f2644a2cSMark Brown K /= 10; 678f2644a2cSMark Brown 679f2644a2cSMark Brown pll_div->k = K; 680f2644a2cSMark Brown 681f2644a2cSMark Brown pr_debug("WM8960 PLL: N=%x K=%x pre_div=%d\n", 682f2644a2cSMark Brown pll_div->n, pll_div->k, pll_div->pre_div); 683f2644a2cSMark Brown 684f2644a2cSMark Brown return 0; 685f2644a2cSMark Brown } 686f2644a2cSMark Brown 68785488037SMark Brown static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, 68885488037SMark Brown int source, unsigned int freq_in, unsigned int freq_out) 689f2644a2cSMark Brown { 690f2644a2cSMark Brown struct snd_soc_codec *codec = codec_dai->codec; 691f2644a2cSMark Brown u16 reg; 692f2644a2cSMark Brown static struct _pll_div pll_div; 693f2644a2cSMark Brown int ret; 694f2644a2cSMark Brown 695f2644a2cSMark Brown if (freq_in && freq_out) { 696f2644a2cSMark Brown ret = pll_factors(freq_in, freq_out, &pll_div); 697f2644a2cSMark Brown if (ret != 0) 698f2644a2cSMark Brown return ret; 699f2644a2cSMark Brown } 700f2644a2cSMark Brown 701f2644a2cSMark Brown /* Disable the PLL: even if we are changing the frequency the 702f2644a2cSMark Brown * PLL needs to be disabled while we do so. */ 70317a52fd6SMark Brown snd_soc_write(codec, WM8960_CLOCK1, 70417a52fd6SMark Brown snd_soc_read(codec, WM8960_CLOCK1) & ~1); 70517a52fd6SMark Brown snd_soc_write(codec, WM8960_POWER2, 70617a52fd6SMark Brown snd_soc_read(codec, WM8960_POWER2) & ~1); 707f2644a2cSMark Brown 708f2644a2cSMark Brown if (!freq_in || !freq_out) 709f2644a2cSMark Brown return 0; 710f2644a2cSMark Brown 71117a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_PLL1) & ~0x3f; 712f2644a2cSMark Brown reg |= pll_div.pre_div << 4; 713f2644a2cSMark Brown reg |= pll_div.n; 714f2644a2cSMark Brown 715f2644a2cSMark Brown if (pll_div.k) { 716f2644a2cSMark Brown reg |= 0x20; 717f2644a2cSMark Brown 71817a52fd6SMark Brown snd_soc_write(codec, WM8960_PLL2, (pll_div.k >> 18) & 0x3f); 71917a52fd6SMark Brown snd_soc_write(codec, WM8960_PLL3, (pll_div.k >> 9) & 0x1ff); 72017a52fd6SMark Brown snd_soc_write(codec, WM8960_PLL4, pll_div.k & 0x1ff); 721f2644a2cSMark Brown } 72217a52fd6SMark Brown snd_soc_write(codec, WM8960_PLL1, reg); 723f2644a2cSMark Brown 724f2644a2cSMark Brown /* Turn it on */ 72517a52fd6SMark Brown snd_soc_write(codec, WM8960_POWER2, 72617a52fd6SMark Brown snd_soc_read(codec, WM8960_POWER2) | 1); 727f2644a2cSMark Brown msleep(250); 72817a52fd6SMark Brown snd_soc_write(codec, WM8960_CLOCK1, 72917a52fd6SMark Brown snd_soc_read(codec, WM8960_CLOCK1) | 1); 730f2644a2cSMark Brown 731f2644a2cSMark Brown return 0; 732f2644a2cSMark Brown } 733f2644a2cSMark Brown 734f2644a2cSMark Brown static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai, 735f2644a2cSMark Brown int div_id, int div) 736f2644a2cSMark Brown { 737f2644a2cSMark Brown struct snd_soc_codec *codec = codec_dai->codec; 738f2644a2cSMark Brown u16 reg; 739f2644a2cSMark Brown 740f2644a2cSMark Brown switch (div_id) { 741f2644a2cSMark Brown case WM8960_SYSCLKSEL: 74217a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1fe; 74317a52fd6SMark Brown snd_soc_write(codec, WM8960_CLOCK1, reg | div); 744f2644a2cSMark Brown break; 745f2644a2cSMark Brown case WM8960_SYSCLKDIV: 74617a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1f9; 74717a52fd6SMark Brown snd_soc_write(codec, WM8960_CLOCK1, reg | div); 748f2644a2cSMark Brown break; 749f2644a2cSMark Brown case WM8960_DACDIV: 75017a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1c7; 75117a52fd6SMark Brown snd_soc_write(codec, WM8960_CLOCK1, reg | div); 752f2644a2cSMark Brown break; 753f2644a2cSMark Brown case WM8960_OPCLKDIV: 75417a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_PLL1) & 0x03f; 75517a52fd6SMark Brown snd_soc_write(codec, WM8960_PLL1, reg | div); 756f2644a2cSMark Brown break; 757f2644a2cSMark Brown case WM8960_DCLKDIV: 75817a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_CLOCK2) & 0x03f; 75917a52fd6SMark Brown snd_soc_write(codec, WM8960_CLOCK2, reg | div); 760f2644a2cSMark Brown break; 761f2644a2cSMark Brown case WM8960_TOCLKSEL: 76217a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_ADDCTL1) & 0x1fd; 76317a52fd6SMark Brown snd_soc_write(codec, WM8960_ADDCTL1, reg | div); 764f2644a2cSMark Brown break; 765f2644a2cSMark Brown default: 766f2644a2cSMark Brown return -EINVAL; 767f2644a2cSMark Brown } 768f2644a2cSMark Brown 769f2644a2cSMark Brown return 0; 770f2644a2cSMark Brown } 771f2644a2cSMark Brown 772f2644a2cSMark Brown #define WM8960_RATES SNDRV_PCM_RATE_8000_48000 773f2644a2cSMark Brown 774f2644a2cSMark Brown #define WM8960_FORMATS \ 775f2644a2cSMark Brown (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ 776f2644a2cSMark Brown SNDRV_PCM_FMTBIT_S24_LE) 777f2644a2cSMark Brown 778f2644a2cSMark Brown static struct snd_soc_dai_ops wm8960_dai_ops = { 779f2644a2cSMark Brown .hw_params = wm8960_hw_params, 780f2644a2cSMark Brown .digital_mute = wm8960_mute, 781f2644a2cSMark Brown .set_fmt = wm8960_set_dai_fmt, 782f2644a2cSMark Brown .set_clkdiv = wm8960_set_dai_clkdiv, 783f2644a2cSMark Brown .set_pll = wm8960_set_dai_pll, 784f2644a2cSMark Brown }; 785f2644a2cSMark Brown 786f2644a2cSMark Brown struct snd_soc_dai wm8960_dai = { 787f2644a2cSMark Brown .name = "WM8960", 788f2644a2cSMark Brown .playback = { 789f2644a2cSMark Brown .stream_name = "Playback", 790f2644a2cSMark Brown .channels_min = 1, 791f2644a2cSMark Brown .channels_max = 2, 792f2644a2cSMark Brown .rates = WM8960_RATES, 793f2644a2cSMark Brown .formats = WM8960_FORMATS,}, 794f2644a2cSMark Brown .capture = { 795f2644a2cSMark Brown .stream_name = "Capture", 796f2644a2cSMark Brown .channels_min = 1, 797f2644a2cSMark Brown .channels_max = 2, 798f2644a2cSMark Brown .rates = WM8960_RATES, 799f2644a2cSMark Brown .formats = WM8960_FORMATS,}, 800f2644a2cSMark Brown .ops = &wm8960_dai_ops, 801f2644a2cSMark Brown .symmetric_rates = 1, 802f2644a2cSMark Brown }; 803f2644a2cSMark Brown EXPORT_SYMBOL_GPL(wm8960_dai); 804f2644a2cSMark Brown 805f2644a2cSMark Brown static int wm8960_suspend(struct platform_device *pdev, pm_message_t state) 806f2644a2cSMark Brown { 807f2644a2cSMark Brown struct snd_soc_device *socdev = platform_get_drvdata(pdev); 808f2644a2cSMark Brown struct snd_soc_codec *codec = socdev->card->codec; 809f2644a2cSMark Brown 810913d7b4cSMark Brown codec->set_bias_level(codec, SND_SOC_BIAS_OFF); 811f2644a2cSMark Brown return 0; 812f2644a2cSMark Brown } 813f2644a2cSMark Brown 814f2644a2cSMark Brown static int wm8960_resume(struct platform_device *pdev) 815f2644a2cSMark Brown { 816f2644a2cSMark Brown struct snd_soc_device *socdev = platform_get_drvdata(pdev); 817f2644a2cSMark Brown struct snd_soc_codec *codec = socdev->card->codec; 818f2644a2cSMark Brown int i; 819f2644a2cSMark Brown u8 data[2]; 820f2644a2cSMark Brown u16 *cache = codec->reg_cache; 821f2644a2cSMark Brown 822f2644a2cSMark Brown /* Sync reg_cache with the hardware */ 823f2644a2cSMark Brown for (i = 0; i < ARRAY_SIZE(wm8960_reg); i++) { 824f2644a2cSMark Brown data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); 825f2644a2cSMark Brown data[1] = cache[i] & 0x00ff; 826f2644a2cSMark Brown codec->hw_write(codec->control_data, data, 2); 827f2644a2cSMark Brown } 828f2644a2cSMark Brown 829913d7b4cSMark Brown codec->set_bias_level(codec, SND_SOC_BIAS_STANDBY); 830913d7b4cSMark Brown codec->set_bias_level(codec, codec->suspend_bias_level); 831f2644a2cSMark Brown return 0; 832f2644a2cSMark Brown } 833f2644a2cSMark Brown 834f2644a2cSMark Brown static struct snd_soc_codec *wm8960_codec; 835f2644a2cSMark Brown 836f2644a2cSMark Brown static int wm8960_probe(struct platform_device *pdev) 837f2644a2cSMark Brown { 838f2644a2cSMark Brown struct snd_soc_device *socdev = platform_get_drvdata(pdev); 839f2644a2cSMark Brown struct snd_soc_codec *codec; 840f2644a2cSMark Brown int ret = 0; 841f2644a2cSMark Brown 842f2644a2cSMark Brown if (wm8960_codec == NULL) { 843f2644a2cSMark Brown dev_err(&pdev->dev, "Codec device not registered\n"); 844f2644a2cSMark Brown return -ENODEV; 845f2644a2cSMark Brown } 846f2644a2cSMark Brown 847f2644a2cSMark Brown socdev->card->codec = wm8960_codec; 848f2644a2cSMark Brown codec = wm8960_codec; 849f2644a2cSMark Brown 850f2644a2cSMark Brown /* register pcms */ 851f2644a2cSMark Brown ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); 852f2644a2cSMark Brown if (ret < 0) { 853f2644a2cSMark Brown dev_err(codec->dev, "failed to create pcms: %d\n", ret); 854f2644a2cSMark Brown goto pcm_err; 855f2644a2cSMark Brown } 856f2644a2cSMark Brown 857f2644a2cSMark Brown snd_soc_add_controls(codec, wm8960_snd_controls, 858f2644a2cSMark Brown ARRAY_SIZE(wm8960_snd_controls)); 859f2644a2cSMark Brown wm8960_add_widgets(codec); 860f2644a2cSMark Brown 861f2644a2cSMark Brown return ret; 862f2644a2cSMark Brown 863f2644a2cSMark Brown pcm_err: 864f2644a2cSMark Brown return ret; 865f2644a2cSMark Brown } 866f2644a2cSMark Brown 867f2644a2cSMark Brown /* power down chip */ 868f2644a2cSMark Brown static int wm8960_remove(struct platform_device *pdev) 869f2644a2cSMark Brown { 870f2644a2cSMark Brown struct snd_soc_device *socdev = platform_get_drvdata(pdev); 871f2644a2cSMark Brown 872f2644a2cSMark Brown snd_soc_free_pcms(socdev); 873f2644a2cSMark Brown snd_soc_dapm_free(socdev); 874f2644a2cSMark Brown 875f2644a2cSMark Brown return 0; 876f2644a2cSMark Brown } 877f2644a2cSMark Brown 878f2644a2cSMark Brown struct snd_soc_codec_device soc_codec_dev_wm8960 = { 879f2644a2cSMark Brown .probe = wm8960_probe, 880f2644a2cSMark Brown .remove = wm8960_remove, 881f2644a2cSMark Brown .suspend = wm8960_suspend, 882f2644a2cSMark Brown .resume = wm8960_resume, 883f2644a2cSMark Brown }; 884f2644a2cSMark Brown EXPORT_SYMBOL_GPL(soc_codec_dev_wm8960); 885f2644a2cSMark Brown 8867084a42bSMark Brown static int wm8960_register(struct wm8960_priv *wm8960, 8877084a42bSMark Brown enum snd_soc_control_type control) 888f2644a2cSMark Brown { 889f2644a2cSMark Brown struct wm8960_data *pdata = wm8960->codec.dev->platform_data; 890f2644a2cSMark Brown struct snd_soc_codec *codec = &wm8960->codec; 891f2644a2cSMark Brown int ret; 892f2644a2cSMark Brown u16 reg; 893f2644a2cSMark Brown 894f2644a2cSMark Brown if (wm8960_codec) { 895f2644a2cSMark Brown dev_err(codec->dev, "Another WM8960 is registered\n"); 8961a01417eSMark Brown ret = -EINVAL; 8971a01417eSMark Brown goto err; 898f2644a2cSMark Brown } 899f2644a2cSMark Brown 900913d7b4cSMark Brown codec->set_bias_level = wm8960_set_bias_level_out3; 901913d7b4cSMark Brown 902f2644a2cSMark Brown if (!pdata) { 903f2644a2cSMark Brown dev_warn(codec->dev, "No platform data supplied\n"); 904f2644a2cSMark Brown } else { 905f2644a2cSMark Brown if (pdata->dres > WM8960_DRES_MAX) { 906f2644a2cSMark Brown dev_err(codec->dev, "Invalid DRES: %d\n", pdata->dres); 907f2644a2cSMark Brown pdata->dres = 0; 908f2644a2cSMark Brown } 909913d7b4cSMark Brown 910913d7b4cSMark Brown if (pdata->capless) 911913d7b4cSMark Brown codec->set_bias_level = wm8960_set_bias_level_capless; 912f2644a2cSMark Brown } 913f2644a2cSMark Brown 914f2644a2cSMark Brown mutex_init(&codec->mutex); 915f2644a2cSMark Brown INIT_LIST_HEAD(&codec->dapm_widgets); 916f2644a2cSMark Brown INIT_LIST_HEAD(&codec->dapm_paths); 917f2644a2cSMark Brown 918f2644a2cSMark Brown codec->private_data = wm8960; 919f2644a2cSMark Brown codec->name = "WM8960"; 920f2644a2cSMark Brown codec->owner = THIS_MODULE; 921f2644a2cSMark Brown codec->bias_level = SND_SOC_BIAS_OFF; 922f2644a2cSMark Brown codec->dai = &wm8960_dai; 923f2644a2cSMark Brown codec->num_dai = 1; 924f2644a2cSMark Brown codec->reg_cache_size = WM8960_CACHEREGNUM; 925f2644a2cSMark Brown codec->reg_cache = &wm8960->reg_cache; 926f2644a2cSMark Brown 927f2644a2cSMark Brown memcpy(codec->reg_cache, wm8960_reg, sizeof(wm8960_reg)); 928f2644a2cSMark Brown 9297084a42bSMark Brown ret = snd_soc_codec_set_cache_io(codec, 7, 9, control); 93017a52fd6SMark Brown if (ret < 0) { 93117a52fd6SMark Brown dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); 93217a52fd6SMark Brown goto err; 93317a52fd6SMark Brown } 93417a52fd6SMark Brown 935f2644a2cSMark Brown ret = wm8960_reset(codec); 936f2644a2cSMark Brown if (ret < 0) { 937f2644a2cSMark Brown dev_err(codec->dev, "Failed to issue reset\n"); 9381a01417eSMark Brown goto err; 939f2644a2cSMark Brown } 940f2644a2cSMark Brown 941f2644a2cSMark Brown wm8960_dai.dev = codec->dev; 942f2644a2cSMark Brown 943913d7b4cSMark Brown codec->set_bias_level(codec, SND_SOC_BIAS_STANDBY); 944f2644a2cSMark Brown 945f2644a2cSMark Brown /* Latch the update bits */ 94617a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_LINVOL); 94717a52fd6SMark Brown snd_soc_write(codec, WM8960_LINVOL, reg | 0x100); 94817a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_RINVOL); 94917a52fd6SMark Brown snd_soc_write(codec, WM8960_RINVOL, reg | 0x100); 95017a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_LADC); 95117a52fd6SMark Brown snd_soc_write(codec, WM8960_LADC, reg | 0x100); 95217a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_RADC); 95317a52fd6SMark Brown snd_soc_write(codec, WM8960_RADC, reg | 0x100); 95417a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_LDAC); 95517a52fd6SMark Brown snd_soc_write(codec, WM8960_LDAC, reg | 0x100); 95617a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_RDAC); 95717a52fd6SMark Brown snd_soc_write(codec, WM8960_RDAC, reg | 0x100); 95817a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_LOUT1); 95917a52fd6SMark Brown snd_soc_write(codec, WM8960_LOUT1, reg | 0x100); 96017a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_ROUT1); 96117a52fd6SMark Brown snd_soc_write(codec, WM8960_ROUT1, reg | 0x100); 96217a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_LOUT2); 96317a52fd6SMark Brown snd_soc_write(codec, WM8960_LOUT2, reg | 0x100); 96417a52fd6SMark Brown reg = snd_soc_read(codec, WM8960_ROUT2); 96517a52fd6SMark Brown snd_soc_write(codec, WM8960_ROUT2, reg | 0x100); 966f2644a2cSMark Brown 967f2644a2cSMark Brown wm8960_codec = codec; 968f2644a2cSMark Brown 969f2644a2cSMark Brown ret = snd_soc_register_codec(codec); 970f2644a2cSMark Brown if (ret != 0) { 971f2644a2cSMark Brown dev_err(codec->dev, "Failed to register codec: %d\n", ret); 9721a01417eSMark Brown goto err; 973f2644a2cSMark Brown } 974f2644a2cSMark Brown 975f2644a2cSMark Brown ret = snd_soc_register_dai(&wm8960_dai); 976f2644a2cSMark Brown if (ret != 0) { 977f2644a2cSMark Brown dev_err(codec->dev, "Failed to register DAI: %d\n", ret); 9781a01417eSMark Brown goto err_codec; 979f2644a2cSMark Brown } 980f2644a2cSMark Brown 981f2644a2cSMark Brown return 0; 9821a01417eSMark Brown 9831a01417eSMark Brown err_codec: 9841a01417eSMark Brown snd_soc_unregister_codec(codec); 9851a01417eSMark Brown err: 9861a01417eSMark Brown kfree(wm8960); 9871a01417eSMark Brown return ret; 988f2644a2cSMark Brown } 989f2644a2cSMark Brown 990f2644a2cSMark Brown static void wm8960_unregister(struct wm8960_priv *wm8960) 991f2644a2cSMark Brown { 992913d7b4cSMark Brown wm8960->codec.set_bias_level(&wm8960->codec, SND_SOC_BIAS_OFF); 993f2644a2cSMark Brown snd_soc_unregister_dai(&wm8960_dai); 994f2644a2cSMark Brown snd_soc_unregister_codec(&wm8960->codec); 995f2644a2cSMark Brown kfree(wm8960); 996f2644a2cSMark Brown wm8960_codec = NULL; 997f2644a2cSMark Brown } 998f2644a2cSMark Brown 999f2644a2cSMark Brown static __devinit int wm8960_i2c_probe(struct i2c_client *i2c, 1000f2644a2cSMark Brown const struct i2c_device_id *id) 1001f2644a2cSMark Brown { 1002f2644a2cSMark Brown struct wm8960_priv *wm8960; 1003f2644a2cSMark Brown struct snd_soc_codec *codec; 1004f2644a2cSMark Brown 1005f2644a2cSMark Brown wm8960 = kzalloc(sizeof(struct wm8960_priv), GFP_KERNEL); 1006f2644a2cSMark Brown if (wm8960 == NULL) 1007f2644a2cSMark Brown return -ENOMEM; 1008f2644a2cSMark Brown 1009f2644a2cSMark Brown codec = &wm8960->codec; 1010f2644a2cSMark Brown 1011f2644a2cSMark Brown i2c_set_clientdata(i2c, wm8960); 1012f2644a2cSMark Brown codec->control_data = i2c; 1013f2644a2cSMark Brown 1014f2644a2cSMark Brown codec->dev = &i2c->dev; 1015f2644a2cSMark Brown 10167084a42bSMark Brown return wm8960_register(wm8960, SND_SOC_I2C); 1017f2644a2cSMark Brown } 1018f2644a2cSMark Brown 1019f2644a2cSMark Brown static __devexit int wm8960_i2c_remove(struct i2c_client *client) 1020f2644a2cSMark Brown { 1021f2644a2cSMark Brown struct wm8960_priv *wm8960 = i2c_get_clientdata(client); 1022f2644a2cSMark Brown wm8960_unregister(wm8960); 1023f2644a2cSMark Brown return 0; 1024f2644a2cSMark Brown } 1025f2644a2cSMark Brown 1026f2644a2cSMark Brown static const struct i2c_device_id wm8960_i2c_id[] = { 1027f2644a2cSMark Brown { "wm8960", 0 }, 1028f2644a2cSMark Brown { } 1029f2644a2cSMark Brown }; 1030f2644a2cSMark Brown MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id); 1031f2644a2cSMark Brown 1032f2644a2cSMark Brown static struct i2c_driver wm8960_i2c_driver = { 1033f2644a2cSMark Brown .driver = { 1034a24d62d2SMark Brown .name = "wm8960", 1035f2644a2cSMark Brown .owner = THIS_MODULE, 1036f2644a2cSMark Brown }, 1037f2644a2cSMark Brown .probe = wm8960_i2c_probe, 1038f2644a2cSMark Brown .remove = __devexit_p(wm8960_i2c_remove), 1039f2644a2cSMark Brown .id_table = wm8960_i2c_id, 1040f2644a2cSMark Brown }; 1041f2644a2cSMark Brown 1042f2644a2cSMark Brown static int __init wm8960_modinit(void) 1043f2644a2cSMark Brown { 1044f2644a2cSMark Brown int ret; 1045f2644a2cSMark Brown 1046f2644a2cSMark Brown ret = i2c_add_driver(&wm8960_i2c_driver); 1047f2644a2cSMark Brown if (ret != 0) { 1048f2644a2cSMark Brown printk(KERN_ERR "Failed to register WM8960 I2C driver: %d\n", 1049f2644a2cSMark Brown ret); 1050f2644a2cSMark Brown } 1051f2644a2cSMark Brown 1052f2644a2cSMark Brown return ret; 1053f2644a2cSMark Brown } 1054f2644a2cSMark Brown module_init(wm8960_modinit); 1055f2644a2cSMark Brown 1056f2644a2cSMark Brown static void __exit wm8960_exit(void) 1057f2644a2cSMark Brown { 1058f2644a2cSMark Brown i2c_del_driver(&wm8960_i2c_driver); 1059f2644a2cSMark Brown } 1060f2644a2cSMark Brown module_exit(wm8960_exit); 1061f2644a2cSMark Brown 1062f2644a2cSMark Brown 1063f2644a2cSMark Brown MODULE_DESCRIPTION("ASoC WM8960 driver"); 1064f2644a2cSMark Brown MODULE_AUTHOR("Liam Girdwood"); 1065f2644a2cSMark Brown MODULE_LICENSE("GPL"); 1066