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