diff options
Diffstat (limited to 'sound/soc/codecs/wm8770.c')
-rw-r--r-- | sound/soc/codecs/wm8770.c | 749 |
1 files changed, 749 insertions, 0 deletions
diff --git a/sound/soc/codecs/wm8770.c b/sound/soc/codecs/wm8770.c new file mode 100644 index 000000000000..19b92baa9e8c --- /dev/null +++ b/sound/soc/codecs/wm8770.c | |||
@@ -0,0 +1,749 @@ | |||
1 | /* | ||
2 | * wm8770.c -- WM8770 ALSA SoC Audio driver | ||
3 | * | ||
4 | * Copyright 2010 Wolfson Microelectronics plc | ||
5 | * | ||
6 | * Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License version 2 as | ||
10 | * published by the Free Software Foundation. | ||
11 | */ | ||
12 | |||
13 | #include <linux/module.h> | ||
14 | #include <linux/moduleparam.h> | ||
15 | #include <linux/init.h> | ||
16 | #include <linux/delay.h> | ||
17 | #include <linux/pm.h> | ||
18 | #include <linux/platform_device.h> | ||
19 | #include <linux/spi/spi.h> | ||
20 | #include <linux/regulator/consumer.h> | ||
21 | #include <linux/slab.h> | ||
22 | #include <sound/core.h> | ||
23 | #include <sound/pcm.h> | ||
24 | #include <sound/pcm_params.h> | ||
25 | #include <sound/soc.h> | ||
26 | #include <sound/initval.h> | ||
27 | #include <sound/tlv.h> | ||
28 | |||
29 | #include "wm8770.h" | ||
30 | |||
31 | #define WM8770_NUM_SUPPLIES 3 | ||
32 | static const char *wm8770_supply_names[WM8770_NUM_SUPPLIES] = { | ||
33 | "AVDD1", | ||
34 | "AVDD2", | ||
35 | "DVDD" | ||
36 | }; | ||
37 | |||
38 | static const u16 wm8770_reg_defs[WM8770_CACHEREGNUM] = { | ||
39 | 0x7f, 0x7f, 0x7f, 0x7f, | ||
40 | 0x7f, 0x7f, 0x7f, 0x7f, | ||
41 | 0x7f, 0xff, 0xff, 0xff, | ||
42 | 0xff, 0xff, 0xff, 0xff, | ||
43 | 0xff, 0xff, 0, 0x90, 0, | ||
44 | 0, 0x22, 0x22, 0x3e, | ||
45 | 0xc, 0xc, 0x100, 0x189, | ||
46 | 0x189, 0x8770 | ||
47 | }; | ||
48 | |||
49 | struct wm8770_priv { | ||
50 | enum snd_soc_control_type control_type; | ||
51 | struct regulator_bulk_data supplies[WM8770_NUM_SUPPLIES]; | ||
52 | struct notifier_block disable_nb[WM8770_NUM_SUPPLIES]; | ||
53 | struct snd_soc_codec *codec; | ||
54 | int sysclk; | ||
55 | }; | ||
56 | |||
57 | static int vout12supply_event(struct snd_soc_dapm_widget *w, | ||
58 | struct snd_kcontrol *kcontrol, int event); | ||
59 | static int vout34supply_event(struct snd_soc_dapm_widget *w, | ||
60 | struct snd_kcontrol *kcontrol, int event); | ||
61 | |||
62 | /* | ||
63 | * We can't use the same notifier block for more than one supply and | ||
64 | * there's no way I can see to get from a callback to the caller | ||
65 | * except container_of(). | ||
66 | */ | ||
67 | #define WM8770_REGULATOR_EVENT(n) \ | ||
68 | static int wm8770_regulator_event_##n(struct notifier_block *nb, \ | ||
69 | unsigned long event, void *data) \ | ||
70 | { \ | ||
71 | struct wm8770_priv *wm8770 = container_of(nb, struct wm8770_priv, \ | ||
72 | disable_nb[n]); \ | ||
73 | if (event & REGULATOR_EVENT_DISABLE) { \ | ||
74 | wm8770->codec->cache_sync = 1; \ | ||
75 | } \ | ||
76 | return 0; \ | ||
77 | } | ||
78 | |||
79 | WM8770_REGULATOR_EVENT(0) | ||
80 | WM8770_REGULATOR_EVENT(1) | ||
81 | WM8770_REGULATOR_EVENT(2) | ||
82 | |||
83 | static const DECLARE_TLV_DB_SCALE(adc_tlv, -1200, 100, 0); | ||
84 | static const DECLARE_TLV_DB_SCALE(dac_dig_tlv, -12750, 50, 1); | ||
85 | static const DECLARE_TLV_DB_SCALE(dac_alg_tlv, -12700, 100, 1); | ||
86 | |||
87 | static const char *dac_phase_text[][2] = { | ||
88 | { "DAC1 Normal", "DAC1 Inverted" }, | ||
89 | { "DAC2 Normal", "DAC2 Inverted" }, | ||
90 | { "DAC3 Normal", "DAC3 Inverted" }, | ||
91 | { "DAC4 Normal", "DAC4 Inverted" }, | ||
92 | }; | ||
93 | |||
94 | static const struct soc_enum dac_phase[] = { | ||
95 | SOC_ENUM_DOUBLE(WM8770_DACPHASE, 0, 1, 2, dac_phase_text[0]), | ||
96 | SOC_ENUM_DOUBLE(WM8770_DACPHASE, 2, 3, 2, dac_phase_text[1]), | ||
97 | SOC_ENUM_DOUBLE(WM8770_DACPHASE, 4, 5, 2, dac_phase_text[2]), | ||
98 | SOC_ENUM_DOUBLE(WM8770_DACPHASE, 6, 7, 2, dac_phase_text[3]), | ||
99 | }; | ||
100 | |||
101 | static const struct snd_kcontrol_new wm8770_snd_controls[] = { | ||
102 | /* global DAC playback controls */ | ||
103 | SOC_SINGLE_TLV("DAC Playback Volume", WM8770_MSDIGVOL, 0, 255, 0, | ||
104 | dac_dig_tlv), | ||
105 | SOC_SINGLE("DAC Playback Switch", WM8770_DACMUTE, 4, 1, 1), | ||
106 | SOC_SINGLE("DAC Playback ZC Switch", WM8770_DACCTRL1, 0, 1, 0), | ||
107 | |||
108 | /* global VOUT playback controls */ | ||
109 | SOC_SINGLE_TLV("VOUT Playback Volume", WM8770_MSALGVOL, 0, 127, 0, | ||
110 | dac_alg_tlv), | ||
111 | SOC_SINGLE("VOUT Playback ZC Switch", WM8770_MSALGVOL, 7, 1, 0), | ||
112 | |||
113 | /* VOUT1/2/3/4 specific controls */ | ||
114 | SOC_DOUBLE_R_TLV("VOUT1 Playback Volume", WM8770_VOUT1LVOL, | ||
115 | WM8770_VOUT1RVOL, 0, 127, 0, dac_alg_tlv), | ||
116 | SOC_DOUBLE_R("VOUT1 Playback ZC Switch", WM8770_VOUT1LVOL, | ||
117 | WM8770_VOUT1RVOL, 7, 1, 0), | ||
118 | SOC_DOUBLE_R_TLV("VOUT2 Playback Volume", WM8770_VOUT2LVOL, | ||
119 | WM8770_VOUT2RVOL, 0, 127, 0, dac_alg_tlv), | ||
120 | SOC_DOUBLE_R("VOUT2 Playback ZC Switch", WM8770_VOUT2LVOL, | ||
121 | WM8770_VOUT2RVOL, 7, 1, 0), | ||
122 | SOC_DOUBLE_R_TLV("VOUT3 Playback Volume", WM8770_VOUT3LVOL, | ||
123 | WM8770_VOUT3RVOL, 0, 127, 0, dac_alg_tlv), | ||
124 | SOC_DOUBLE_R("VOUT3 Playback ZC Switch", WM8770_VOUT3LVOL, | ||
125 | WM8770_VOUT3RVOL, 7, 1, 0), | ||
126 | SOC_DOUBLE_R_TLV("VOUT4 Playback Volume", WM8770_VOUT4LVOL, | ||
127 | WM8770_VOUT4RVOL, 0, 127, 0, dac_alg_tlv), | ||
128 | SOC_DOUBLE_R("VOUT4 Playback ZC Switch", WM8770_VOUT4LVOL, | ||
129 | WM8770_VOUT4RVOL, 7, 1, 0), | ||
130 | |||
131 | /* DAC1/2/3/4 specific controls */ | ||
132 | SOC_DOUBLE_R_TLV("DAC1 Playback Volume", WM8770_DAC1LVOL, | ||
133 | WM8770_DAC1RVOL, 0, 255, 0, dac_dig_tlv), | ||
134 | SOC_SINGLE("DAC1 Deemphasis Switch", WM8770_DACCTRL2, 0, 1, 0), | ||
135 | SOC_ENUM("DAC1 Phase", dac_phase[0]), | ||
136 | SOC_DOUBLE_R_TLV("DAC2 Playback Volume", WM8770_DAC2LVOL, | ||
137 | WM8770_DAC2RVOL, 0, 255, 0, dac_dig_tlv), | ||
138 | SOC_SINGLE("DAC2 Deemphasis Switch", WM8770_DACCTRL2, 1, 1, 0), | ||
139 | SOC_ENUM("DAC2 Phase", dac_phase[1]), | ||
140 | SOC_DOUBLE_R_TLV("DAC3 Playback Volume", WM8770_DAC3LVOL, | ||
141 | WM8770_DAC3RVOL, 0, 255, 0, dac_dig_tlv), | ||
142 | SOC_SINGLE("DAC3 Deemphasis Switch", WM8770_DACCTRL2, 2, 1, 0), | ||
143 | SOC_ENUM("DAC3 Phase", dac_phase[2]), | ||
144 | SOC_DOUBLE_R_TLV("DAC4 Playback Volume", WM8770_DAC4LVOL, | ||
145 | WM8770_DAC4RVOL, 0, 255, 0, dac_dig_tlv), | ||
146 | SOC_SINGLE("DAC4 Deemphasis Switch", WM8770_DACCTRL2, 3, 1, 0), | ||
147 | SOC_ENUM("DAC4 Phase", dac_phase[3]), | ||
148 | |||
149 | /* ADC specific controls */ | ||
150 | SOC_DOUBLE_R_TLV("Capture Volume", WM8770_ADCLCTRL, WM8770_ADCRCTRL, | ||
151 | 0, 31, 0, adc_tlv), | ||
152 | SOC_DOUBLE_R("Capture Switch", WM8770_ADCLCTRL, WM8770_ADCRCTRL, | ||
153 | 5, 1, 1), | ||
154 | |||
155 | /* other controls */ | ||
156 | SOC_SINGLE("ADC 128x Oversampling Switch", WM8770_MSTRCTRL, 3, 1, 0), | ||
157 | SOC_SINGLE("ADC Highpass Filter Switch", WM8770_IFACECTRL, 8, 1, 1) | ||
158 | }; | ||
159 | |||
160 | static const char *ain_text[] = { | ||
161 | "AIN1", "AIN2", "AIN3", "AIN4", | ||
162 | "AIN5", "AIN6", "AIN7", "AIN8" | ||
163 | }; | ||
164 | |||
165 | static const struct soc_enum ain_enum = | ||
166 | SOC_ENUM_DOUBLE(WM8770_ADCMUX, 0, 4, 8, ain_text); | ||
167 | |||
168 | static const struct snd_kcontrol_new ain_mux = | ||
169 | SOC_DAPM_ENUM("Capture Mux", ain_enum); | ||
170 | |||
171 | static const struct snd_kcontrol_new vout1_mix_controls[] = { | ||
172 | SOC_DAPM_SINGLE("DAC1 Switch", WM8770_OUTMUX1, 0, 1, 0), | ||
173 | SOC_DAPM_SINGLE("AUX1 Switch", WM8770_OUTMUX1, 1, 1, 0), | ||
174 | SOC_DAPM_SINGLE("Bypass Switch", WM8770_OUTMUX1, 2, 1, 0) | ||
175 | }; | ||
176 | |||
177 | static const struct snd_kcontrol_new vout2_mix_controls[] = { | ||
178 | SOC_DAPM_SINGLE("DAC2 Switch", WM8770_OUTMUX1, 3, 1, 0), | ||
179 | SOC_DAPM_SINGLE("AUX2 Switch", WM8770_OUTMUX1, 4, 1, 0), | ||
180 | SOC_DAPM_SINGLE("Bypass Switch", WM8770_OUTMUX1, 5, 1, 0) | ||
181 | }; | ||
182 | |||
183 | static const struct snd_kcontrol_new vout3_mix_controls[] = { | ||
184 | SOC_DAPM_SINGLE("DAC3 Switch", WM8770_OUTMUX2, 0, 1, 0), | ||
185 | SOC_DAPM_SINGLE("AUX3 Switch", WM8770_OUTMUX2, 1, 1, 0), | ||
186 | SOC_DAPM_SINGLE("Bypass Switch", WM8770_OUTMUX2, 2, 1, 0) | ||
187 | }; | ||
188 | |||
189 | static const struct snd_kcontrol_new vout4_mix_controls[] = { | ||
190 | SOC_DAPM_SINGLE("DAC4 Switch", WM8770_OUTMUX2, 3, 1, 0), | ||
191 | SOC_DAPM_SINGLE("Bypass Switch", WM8770_OUTMUX2, 4, 1, 0) | ||
192 | }; | ||
193 | |||
194 | static const struct snd_soc_dapm_widget wm8770_dapm_widgets[] = { | ||
195 | SND_SOC_DAPM_INPUT("AUX1"), | ||
196 | SND_SOC_DAPM_INPUT("AUX2"), | ||
197 | SND_SOC_DAPM_INPUT("AUX3"), | ||
198 | |||
199 | SND_SOC_DAPM_INPUT("AIN1"), | ||
200 | SND_SOC_DAPM_INPUT("AIN2"), | ||
201 | SND_SOC_DAPM_INPUT("AIN3"), | ||
202 | SND_SOC_DAPM_INPUT("AIN4"), | ||
203 | SND_SOC_DAPM_INPUT("AIN5"), | ||
204 | SND_SOC_DAPM_INPUT("AIN6"), | ||
205 | SND_SOC_DAPM_INPUT("AIN7"), | ||
206 | SND_SOC_DAPM_INPUT("AIN8"), | ||
207 | |||
208 | SND_SOC_DAPM_MUX("Capture Mux", WM8770_ADCMUX, 8, 1, &ain_mux), | ||
209 | |||
210 | SND_SOC_DAPM_ADC("ADC", "Capture", WM8770_PWDNCTRL, 1, 1), | ||
211 | |||
212 | SND_SOC_DAPM_DAC("DAC1", "Playback", WM8770_PWDNCTRL, 2, 1), | ||
213 | SND_SOC_DAPM_DAC("DAC2", "Playback", WM8770_PWDNCTRL, 3, 1), | ||
214 | SND_SOC_DAPM_DAC("DAC3", "Playback", WM8770_PWDNCTRL, 4, 1), | ||
215 | SND_SOC_DAPM_DAC("DAC4", "Playback", WM8770_PWDNCTRL, 5, 1), | ||
216 | |||
217 | SND_SOC_DAPM_SUPPLY("VOUT12 Supply", SND_SOC_NOPM, 0, 0, | ||
218 | vout12supply_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), | ||
219 | SND_SOC_DAPM_SUPPLY("VOUT34 Supply", SND_SOC_NOPM, 0, 0, | ||
220 | vout34supply_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), | ||
221 | |||
222 | SND_SOC_DAPM_MIXER("VOUT1 Mixer", SND_SOC_NOPM, 0, 0, | ||
223 | vout1_mix_controls, ARRAY_SIZE(vout1_mix_controls)), | ||
224 | SND_SOC_DAPM_MIXER("VOUT2 Mixer", SND_SOC_NOPM, 0, 0, | ||
225 | vout2_mix_controls, ARRAY_SIZE(vout2_mix_controls)), | ||
226 | SND_SOC_DAPM_MIXER("VOUT3 Mixer", SND_SOC_NOPM, 0, 0, | ||
227 | vout3_mix_controls, ARRAY_SIZE(vout3_mix_controls)), | ||
228 | SND_SOC_DAPM_MIXER("VOUT4 Mixer", SND_SOC_NOPM, 0, 0, | ||
229 | vout4_mix_controls, ARRAY_SIZE(vout4_mix_controls)), | ||
230 | |||
231 | SND_SOC_DAPM_OUTPUT("VOUT1"), | ||
232 | SND_SOC_DAPM_OUTPUT("VOUT2"), | ||
233 | SND_SOC_DAPM_OUTPUT("VOUT3"), | ||
234 | SND_SOC_DAPM_OUTPUT("VOUT4") | ||
235 | }; | ||
236 | |||
237 | static const struct snd_soc_dapm_route wm8770_intercon[] = { | ||
238 | { "Capture Mux", "AIN1", "AIN1" }, | ||
239 | { "Capture Mux", "AIN2", "AIN2" }, | ||
240 | { "Capture Mux", "AIN3", "AIN3" }, | ||
241 | { "Capture Mux", "AIN4", "AIN4" }, | ||
242 | { "Capture Mux", "AIN5", "AIN5" }, | ||
243 | { "Capture Mux", "AIN6", "AIN6" }, | ||
244 | { "Capture Mux", "AIN7", "AIN7" }, | ||
245 | { "Capture Mux", "AIN8", "AIN8" }, | ||
246 | |||
247 | { "ADC", NULL, "Capture Mux" }, | ||
248 | |||
249 | { "VOUT1 Mixer", NULL, "VOUT12 Supply" }, | ||
250 | { "VOUT1 Mixer", "DAC1 Switch", "DAC1" }, | ||
251 | { "VOUT1 Mixer", "AUX1 Switch", "AUX1" }, | ||
252 | { "VOUT1 Mixer", "Bypass Switch", "Capture Mux" }, | ||
253 | |||
254 | { "VOUT2 Mixer", NULL, "VOUT12 Supply" }, | ||
255 | { "VOUT2 Mixer", "DAC2 Switch", "DAC2" }, | ||
256 | { "VOUT2 Mixer", "AUX2 Switch", "AUX2" }, | ||
257 | { "VOUT2 Mixer", "Bypass Switch", "Capture Mux" }, | ||
258 | |||
259 | { "VOUT3 Mixer", NULL, "VOUT34 Supply" }, | ||
260 | { "VOUT3 Mixer", "DAC3 Switch", "DAC3" }, | ||
261 | { "VOUT3 Mixer", "AUX3 Switch", "AUX3" }, | ||
262 | { "VOUT3 Mixer", "Bypass Switch", "Capture Mux" }, | ||
263 | |||
264 | { "VOUT4 Mixer", NULL, "VOUT34 Supply" }, | ||
265 | { "VOUT4 Mixer", "DAC4 Switch", "DAC4" }, | ||
266 | { "VOUT4 Mixer", "Bypass Switch", "Capture Mux" }, | ||
267 | |||
268 | { "VOUT1", NULL, "VOUT1 Mixer" }, | ||
269 | { "VOUT2", NULL, "VOUT2 Mixer" }, | ||
270 | { "VOUT3", NULL, "VOUT3 Mixer" }, | ||
271 | { "VOUT4", NULL, "VOUT4 Mixer" } | ||
272 | }; | ||
273 | |||
274 | static int vout12supply_event(struct snd_soc_dapm_widget *w, | ||
275 | struct snd_kcontrol *kcontrol, int event) | ||
276 | { | ||
277 | struct snd_soc_codec *codec; | ||
278 | |||
279 | codec = w->codec; | ||
280 | |||
281 | switch (event) { | ||
282 | case SND_SOC_DAPM_PRE_PMU: | ||
283 | snd_soc_update_bits(codec, WM8770_OUTMUX1, 0x180, 0); | ||
284 | break; | ||
285 | case SND_SOC_DAPM_POST_PMD: | ||
286 | snd_soc_update_bits(codec, WM8770_OUTMUX1, 0x180, 0x180); | ||
287 | break; | ||
288 | } | ||
289 | |||
290 | return 0; | ||
291 | } | ||
292 | |||
293 | static int vout34supply_event(struct snd_soc_dapm_widget *w, | ||
294 | struct snd_kcontrol *kcontrol, int event) | ||
295 | { | ||
296 | struct snd_soc_codec *codec; | ||
297 | |||
298 | codec = w->codec; | ||
299 | |||
300 | switch (event) { | ||
301 | case SND_SOC_DAPM_PRE_PMU: | ||
302 | snd_soc_update_bits(codec, WM8770_OUTMUX2, 0x180, 0); | ||
303 | break; | ||
304 | case SND_SOC_DAPM_POST_PMD: | ||
305 | snd_soc_update_bits(codec, WM8770_OUTMUX2, 0x180, 0x180); | ||
306 | break; | ||
307 | } | ||
308 | |||
309 | return 0; | ||
310 | } | ||
311 | |||
312 | static int wm8770_reset(struct snd_soc_codec *codec) | ||
313 | { | ||
314 | return snd_soc_write(codec, WM8770_RESET, 0); | ||
315 | } | ||
316 | |||
317 | static int wm8770_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) | ||
318 | { | ||
319 | struct snd_soc_codec *codec; | ||
320 | int iface, master; | ||
321 | |||
322 | codec = dai->codec; | ||
323 | |||
324 | switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { | ||
325 | case SND_SOC_DAIFMT_CBM_CFM: | ||
326 | master = 0x100; | ||
327 | break; | ||
328 | case SND_SOC_DAIFMT_CBS_CFS: | ||
329 | master = 0; | ||
330 | break; | ||
331 | default: | ||
332 | return -EINVAL; | ||
333 | } | ||
334 | |||
335 | iface = 0; | ||
336 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | ||
337 | case SND_SOC_DAIFMT_I2S: | ||
338 | iface |= 0x2; | ||
339 | break; | ||
340 | case SND_SOC_DAIFMT_RIGHT_J: | ||
341 | break; | ||
342 | case SND_SOC_DAIFMT_LEFT_J: | ||
343 | iface |= 0x1; | ||
344 | break; | ||
345 | default: | ||
346 | return -EINVAL; | ||
347 | } | ||
348 | |||
349 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { | ||
350 | case SND_SOC_DAIFMT_NB_NF: | ||
351 | break; | ||
352 | case SND_SOC_DAIFMT_IB_IF: | ||
353 | iface |= 0xc; | ||
354 | break; | ||
355 | case SND_SOC_DAIFMT_IB_NF: | ||
356 | iface |= 0x8; | ||
357 | break; | ||
358 | case SND_SOC_DAIFMT_NB_IF: | ||
359 | iface |= 0x4; | ||
360 | break; | ||
361 | default: | ||
362 | return -EINVAL; | ||
363 | } | ||
364 | |||
365 | snd_soc_update_bits(codec, WM8770_IFACECTRL, 0xf, iface); | ||
366 | snd_soc_update_bits(codec, WM8770_MSTRCTRL, 0x100, master); | ||
367 | |||
368 | return 0; | ||
369 | } | ||
370 | |||
371 | static const int mclk_ratios[] = { | ||
372 | 128, | ||
373 | 192, | ||
374 | 256, | ||
375 | 384, | ||
376 | 512, | ||
377 | 768 | ||
378 | }; | ||
379 | |||
380 | static int wm8770_hw_params(struct snd_pcm_substream *substream, | ||
381 | struct snd_pcm_hw_params *params, | ||
382 | struct snd_soc_dai *dai) | ||
383 | { | ||
384 | struct snd_soc_codec *codec; | ||
385 | struct wm8770_priv *wm8770; | ||
386 | int i; | ||
387 | int iface; | ||
388 | int shift; | ||
389 | int ratio; | ||
390 | |||
391 | codec = dai->codec; | ||
392 | wm8770 = snd_soc_codec_get_drvdata(codec); | ||
393 | |||
394 | iface = 0; | ||
395 | switch (params_format(params)) { | ||
396 | case SNDRV_PCM_FORMAT_S16_LE: | ||
397 | break; | ||
398 | case SNDRV_PCM_FORMAT_S20_3LE: | ||
399 | iface |= 0x10; | ||
400 | break; | ||
401 | case SNDRV_PCM_FORMAT_S24_LE: | ||
402 | iface |= 0x20; | ||
403 | break; | ||
404 | case SNDRV_PCM_FORMAT_S32_LE: | ||
405 | iface |= 0x30; | ||
406 | break; | ||
407 | } | ||
408 | |||
409 | switch (substream->stream) { | ||
410 | case SNDRV_PCM_STREAM_PLAYBACK: | ||
411 | i = 0; | ||
412 | shift = 4; | ||
413 | break; | ||
414 | case SNDRV_PCM_STREAM_CAPTURE: | ||
415 | i = 2; | ||
416 | shift = 0; | ||
417 | break; | ||
418 | default: | ||
419 | return -EINVAL; | ||
420 | } | ||
421 | |||
422 | /* Only need to set MCLK/LRCLK ratio if we're master */ | ||
423 | if (snd_soc_read(codec, WM8770_MSTRCTRL) & 0x100) { | ||
424 | for (; i < ARRAY_SIZE(mclk_ratios); ++i) { | ||
425 | ratio = wm8770->sysclk / params_rate(params); | ||
426 | if (ratio == mclk_ratios[i]) | ||
427 | break; | ||
428 | } | ||
429 | |||
430 | if (i == ARRAY_SIZE(mclk_ratios)) { | ||
431 | dev_err(codec->dev, | ||
432 | "Unable to configure MCLK ratio %d/%d\n", | ||
433 | wm8770->sysclk, params_rate(params)); | ||
434 | return -EINVAL; | ||
435 | } | ||
436 | |||
437 | dev_dbg(codec->dev, "MCLK is %dfs\n", mclk_ratios[i]); | ||
438 | |||
439 | snd_soc_update_bits(codec, WM8770_MSTRCTRL, 0x7 << shift, | ||
440 | i << shift); | ||
441 | } | ||
442 | |||
443 | snd_soc_update_bits(codec, WM8770_IFACECTRL, 0x30, iface); | ||
444 | |||
445 | return 0; | ||
446 | } | ||
447 | |||
448 | static int wm8770_mute(struct snd_soc_dai *dai, int mute) | ||
449 | { | ||
450 | struct snd_soc_codec *codec; | ||
451 | |||
452 | codec = dai->codec; | ||
453 | return snd_soc_update_bits(codec, WM8770_DACMUTE, 0x10, | ||
454 | !!mute << 4); | ||
455 | } | ||
456 | |||
457 | static int wm8770_set_sysclk(struct snd_soc_dai *dai, | ||
458 | int clk_id, unsigned int freq, int dir) | ||
459 | { | ||
460 | struct snd_soc_codec *codec; | ||
461 | struct wm8770_priv *wm8770; | ||
462 | |||
463 | codec = dai->codec; | ||
464 | wm8770 = snd_soc_codec_get_drvdata(codec); | ||
465 | wm8770->sysclk = freq; | ||
466 | return 0; | ||
467 | } | ||
468 | |||
469 | static void wm8770_sync_cache(struct snd_soc_codec *codec) | ||
470 | { | ||
471 | int i; | ||
472 | u16 *cache; | ||
473 | |||
474 | if (!codec->cache_sync) | ||
475 | return; | ||
476 | |||
477 | codec->cache_only = 0; | ||
478 | cache = codec->reg_cache; | ||
479 | for (i = 0; i < codec->driver->reg_cache_size; i++) { | ||
480 | if (i == WM8770_RESET || cache[i] == wm8770_reg_defs[i]) | ||
481 | continue; | ||
482 | snd_soc_write(codec, i, cache[i]); | ||
483 | } | ||
484 | codec->cache_sync = 0; | ||
485 | } | ||
486 | |||
487 | static int wm8770_set_bias_level(struct snd_soc_codec *codec, | ||
488 | enum snd_soc_bias_level level) | ||
489 | { | ||
490 | int ret; | ||
491 | struct wm8770_priv *wm8770; | ||
492 | |||
493 | wm8770 = snd_soc_codec_get_drvdata(codec); | ||
494 | |||
495 | switch (level) { | ||
496 | case SND_SOC_BIAS_ON: | ||
497 | break; | ||
498 | case SND_SOC_BIAS_PREPARE: | ||
499 | break; | ||
500 | case SND_SOC_BIAS_STANDBY: | ||
501 | if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { | ||
502 | ret = regulator_bulk_enable(ARRAY_SIZE(wm8770->supplies), | ||
503 | wm8770->supplies); | ||
504 | if (ret) { | ||
505 | dev_err(codec->dev, | ||
506 | "Failed to enable supplies: %d\n", | ||
507 | ret); | ||
508 | return ret; | ||
509 | } | ||
510 | wm8770_sync_cache(codec); | ||
511 | /* global powerup */ | ||
512 | snd_soc_write(codec, WM8770_PWDNCTRL, 0); | ||
513 | } | ||
514 | break; | ||
515 | case SND_SOC_BIAS_OFF: | ||
516 | /* global powerdown */ | ||
517 | snd_soc_write(codec, WM8770_PWDNCTRL, 1); | ||
518 | regulator_bulk_disable(ARRAY_SIZE(wm8770->supplies), | ||
519 | wm8770->supplies); | ||
520 | break; | ||
521 | } | ||
522 | |||
523 | codec->dapm.bias_level = level; | ||
524 | return 0; | ||
525 | } | ||
526 | |||
527 | #define WM8770_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ | ||
528 | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) | ||
529 | |||
530 | static struct snd_soc_dai_ops wm8770_dai_ops = { | ||
531 | .digital_mute = wm8770_mute, | ||
532 | .hw_params = wm8770_hw_params, | ||
533 | .set_fmt = wm8770_set_fmt, | ||
534 | .set_sysclk = wm8770_set_sysclk, | ||
535 | }; | ||
536 | |||
537 | static struct snd_soc_dai_driver wm8770_dai = { | ||
538 | .name = "wm8770-hifi", | ||
539 | .playback = { | ||
540 | .stream_name = "Playback", | ||
541 | .channels_min = 2, | ||
542 | .channels_max = 2, | ||
543 | .rates = SNDRV_PCM_RATE_8000_192000, | ||
544 | .formats = WM8770_FORMATS | ||
545 | }, | ||
546 | .capture = { | ||
547 | .stream_name = "Capture", | ||
548 | .channels_min = 2, | ||
549 | .channels_max = 2, | ||
550 | .rates = SNDRV_PCM_RATE_8000_96000, | ||
551 | .formats = WM8770_FORMATS | ||
552 | }, | ||
553 | .ops = &wm8770_dai_ops, | ||
554 | .symmetric_rates = 1 | ||
555 | }; | ||
556 | |||
557 | #ifdef CONFIG_PM | ||
558 | static int wm8770_suspend(struct snd_soc_codec *codec, pm_message_t state) | ||
559 | { | ||
560 | wm8770_set_bias_level(codec, SND_SOC_BIAS_OFF); | ||
561 | return 0; | ||
562 | } | ||
563 | |||
564 | static int wm8770_resume(struct snd_soc_codec *codec) | ||
565 | { | ||
566 | wm8770_set_bias_level(codec, SND_SOC_BIAS_STANDBY); | ||
567 | return 0; | ||
568 | } | ||
569 | #else | ||
570 | #define wm8770_suspend NULL | ||
571 | #define wm8770_resume NULL | ||
572 | #endif | ||
573 | |||
574 | static int wm8770_probe(struct snd_soc_codec *codec) | ||
575 | { | ||
576 | struct wm8770_priv *wm8770; | ||
577 | int ret; | ||
578 | int i; | ||
579 | |||
580 | wm8770 = snd_soc_codec_get_drvdata(codec); | ||
581 | wm8770->codec = codec; | ||
582 | |||
583 | codec->dapm.idle_bias_off = 1; | ||
584 | |||
585 | ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8770->control_type); | ||
586 | if (ret < 0) { | ||
587 | dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); | ||
588 | return ret; | ||
589 | } | ||
590 | |||
591 | for (i = 0; i < ARRAY_SIZE(wm8770->supplies); i++) | ||
592 | wm8770->supplies[i].supply = wm8770_supply_names[i]; | ||
593 | |||
594 | ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8770->supplies), | ||
595 | wm8770->supplies); | ||
596 | if (ret) { | ||
597 | dev_err(codec->dev, "Failed to request supplies: %d\n", ret); | ||
598 | return ret; | ||
599 | } | ||
600 | |||
601 | wm8770->disable_nb[0].notifier_call = wm8770_regulator_event_0; | ||
602 | wm8770->disable_nb[1].notifier_call = wm8770_regulator_event_1; | ||
603 | wm8770->disable_nb[2].notifier_call = wm8770_regulator_event_2; | ||
604 | |||
605 | /* This should really be moved into the regulator core */ | ||
606 | for (i = 0; i < ARRAY_SIZE(wm8770->supplies); i++) { | ||
607 | ret = regulator_register_notifier(wm8770->supplies[i].consumer, | ||
608 | &wm8770->disable_nb[i]); | ||
609 | if (ret) { | ||
610 | dev_err(codec->dev, | ||
611 | "Failed to register regulator notifier: %d\n", | ||
612 | ret); | ||
613 | } | ||
614 | } | ||
615 | |||
616 | ret = regulator_bulk_enable(ARRAY_SIZE(wm8770->supplies), | ||
617 | wm8770->supplies); | ||
618 | if (ret) { | ||
619 | dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); | ||
620 | goto err_reg_get; | ||
621 | } | ||
622 | |||
623 | ret = wm8770_reset(codec); | ||
624 | if (ret < 0) { | ||
625 | dev_err(codec->dev, "Failed to issue reset: %d\n", ret); | ||
626 | goto err_reg_enable; | ||
627 | } | ||
628 | |||
629 | wm8770_set_bias_level(codec, SND_SOC_BIAS_STANDBY); | ||
630 | |||
631 | /* latch the volume update bits */ | ||
632 | snd_soc_update_bits(codec, WM8770_MSDIGVOL, 0x100, 0x100); | ||
633 | snd_soc_update_bits(codec, WM8770_MSALGVOL, 0x100, 0x100); | ||
634 | snd_soc_update_bits(codec, WM8770_VOUT1RVOL, 0x100, 0x100); | ||
635 | snd_soc_update_bits(codec, WM8770_VOUT2RVOL, 0x100, 0x100); | ||
636 | snd_soc_update_bits(codec, WM8770_VOUT3RVOL, 0x100, 0x100); | ||
637 | snd_soc_update_bits(codec, WM8770_VOUT4RVOL, 0x100, 0x100); | ||
638 | snd_soc_update_bits(codec, WM8770_DAC1RVOL, 0x100, 0x100); | ||
639 | snd_soc_update_bits(codec, WM8770_DAC2RVOL, 0x100, 0x100); | ||
640 | snd_soc_update_bits(codec, WM8770_DAC3RVOL, 0x100, 0x100); | ||
641 | snd_soc_update_bits(codec, WM8770_DAC4RVOL, 0x100, 0x100); | ||
642 | |||
643 | /* mute all DACs */ | ||
644 | snd_soc_update_bits(codec, WM8770_DACMUTE, 0x10, 0x10); | ||
645 | |||
646 | snd_soc_add_controls(codec, wm8770_snd_controls, | ||
647 | ARRAY_SIZE(wm8770_snd_controls)); | ||
648 | snd_soc_dapm_new_controls(&codec->dapm, wm8770_dapm_widgets, | ||
649 | ARRAY_SIZE(wm8770_dapm_widgets)); | ||
650 | snd_soc_dapm_add_routes(&codec->dapm, wm8770_intercon, | ||
651 | ARRAY_SIZE(wm8770_intercon)); | ||
652 | return 0; | ||
653 | |||
654 | err_reg_enable: | ||
655 | regulator_bulk_disable(ARRAY_SIZE(wm8770->supplies), wm8770->supplies); | ||
656 | err_reg_get: | ||
657 | regulator_bulk_free(ARRAY_SIZE(wm8770->supplies), wm8770->supplies); | ||
658 | return ret; | ||
659 | } | ||
660 | |||
661 | static int wm8770_remove(struct snd_soc_codec *codec) | ||
662 | { | ||
663 | struct wm8770_priv *wm8770; | ||
664 | int i; | ||
665 | |||
666 | wm8770 = snd_soc_codec_get_drvdata(codec); | ||
667 | wm8770_set_bias_level(codec, SND_SOC_BIAS_OFF); | ||
668 | |||
669 | for (i = 0; i < ARRAY_SIZE(wm8770->supplies); ++i) | ||
670 | regulator_unregister_notifier(wm8770->supplies[i].consumer, | ||
671 | &wm8770->disable_nb[i]); | ||
672 | regulator_bulk_free(ARRAY_SIZE(wm8770->supplies), wm8770->supplies); | ||
673 | return 0; | ||
674 | } | ||
675 | |||
676 | static struct snd_soc_codec_driver soc_codec_dev_wm8770 = { | ||
677 | .probe = wm8770_probe, | ||
678 | .remove = wm8770_remove, | ||
679 | .suspend = wm8770_suspend, | ||
680 | .resume = wm8770_resume, | ||
681 | .set_bias_level = wm8770_set_bias_level, | ||
682 | .reg_cache_size = ARRAY_SIZE(wm8770_reg_defs), | ||
683 | .reg_word_size = sizeof (u16), | ||
684 | .reg_cache_default = wm8770_reg_defs | ||
685 | }; | ||
686 | |||
687 | #if defined(CONFIG_SPI_MASTER) | ||
688 | static int __devinit wm8770_spi_probe(struct spi_device *spi) | ||
689 | { | ||
690 | struct wm8770_priv *wm8770; | ||
691 | int ret; | ||
692 | |||
693 | wm8770 = kzalloc(sizeof(struct wm8770_priv), GFP_KERNEL); | ||
694 | if (!wm8770) | ||
695 | return -ENOMEM; | ||
696 | |||
697 | wm8770->control_type = SND_SOC_SPI; | ||
698 | spi_set_drvdata(spi, wm8770); | ||
699 | |||
700 | ret = snd_soc_register_codec(&spi->dev, | ||
701 | &soc_codec_dev_wm8770, &wm8770_dai, 1); | ||
702 | if (ret < 0) | ||
703 | kfree(wm8770); | ||
704 | return ret; | ||
705 | } | ||
706 | |||
707 | static int __devexit wm8770_spi_remove(struct spi_device *spi) | ||
708 | { | ||
709 | snd_soc_unregister_codec(&spi->dev); | ||
710 | kfree(spi_get_drvdata(spi)); | ||
711 | return 0; | ||
712 | } | ||
713 | |||
714 | static struct spi_driver wm8770_spi_driver = { | ||
715 | .driver = { | ||
716 | .name = "wm8770", | ||
717 | .owner = THIS_MODULE, | ||
718 | }, | ||
719 | .probe = wm8770_spi_probe, | ||
720 | .remove = __devexit_p(wm8770_spi_remove) | ||
721 | }; | ||
722 | #endif | ||
723 | |||
724 | static int __init wm8770_modinit(void) | ||
725 | { | ||
726 | int ret = 0; | ||
727 | |||
728 | #if defined(CONFIG_SPI_MASTER) | ||
729 | ret = spi_register_driver(&wm8770_spi_driver); | ||
730 | if (ret) { | ||
731 | printk(KERN_ERR "Failed to register wm8770 SPI driver: %d\n", | ||
732 | ret); | ||
733 | } | ||
734 | #endif | ||
735 | return ret; | ||
736 | } | ||
737 | module_init(wm8770_modinit); | ||
738 | |||
739 | static void __exit wm8770_exit(void) | ||
740 | { | ||
741 | #if defined(CONFIG_SPI_MASTER) | ||
742 | spi_unregister_driver(&wm8770_spi_driver); | ||
743 | #endif | ||
744 | } | ||
745 | module_exit(wm8770_exit); | ||
746 | |||
747 | MODULE_DESCRIPTION("ASoC WM8770 driver"); | ||
748 | MODULE_AUTHOR("Dimitris Papastamos <dp@opensource.wolfsonmicro.com>"); | ||
749 | MODULE_LICENSE("GPL"); | ||