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