aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/codecs/wm8974.c
diff options
context:
space:
mode:
authorMark Brown <broonie@opensource.wolfsonmicro.com>2009-05-23 06:18:41 -0400
committerMark Brown <broonie@opensource.wolfsonmicro.com>2009-05-23 06:28:03 -0400
commit0a1bf553359013621c8c5cf745354212c6ef51d3 (patch)
treeb496557733e102a9663d72ec7929257cf63e4feb /sound/soc/codecs/wm8974.c
parent0154724d487586241c1ad57cfd348ed2ff2274e2 (diff)
ASoC: Add WM8974 CODEC driver
The WM8974 is a low power, high quality mono CODEC designed for portable applications such as digital still cameras or digital voice recorders. This driver was originally written by Graeme Gregory and Liam Girdwood and has since been maintained by myself with some updates contributed by Brett Saunders and Javier Martin. Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound/soc/codecs/wm8974.c')
-rw-r--r--sound/soc/codecs/wm8974.c844
1 files changed, 844 insertions, 0 deletions
diff --git a/sound/soc/codecs/wm8974.c b/sound/soc/codecs/wm8974.c
new file mode 100644
index 000000000000..2b0c99c3e65a
--- /dev/null
+++ b/sound/soc/codecs/wm8974.c
@@ -0,0 +1,844 @@
1/*
2 * wm8974.c -- WM8974 ALSA Soc Audio driver
3 *
4 * Copyright 2006 Wolfson Microelectronics PLC.
5 *
6 * Author: Liam Girdwood <liam.girdwood@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/version.h>
16#include <linux/kernel.h>
17#include <linux/init.h>
18#include <linux/delay.h>
19#include <linux/pm.h>
20#include <linux/i2c.h>
21#include <linux/platform_device.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
29#include "wm8974.h"
30
31#define AUDIO_NAME "wm8974"
32#define WM8974_VERSION "0.6"
33
34struct snd_soc_codec_device soc_codec_dev_wm8974;
35
36/*
37 * wm8974 register cache
38 * We can't read the WM8974 register space when we are
39 * using 2 wire for device control, so we cache them instead.
40 */
41static const u16 wm8974_reg[WM8974_CACHEREGNUM] = {
42 0x0000, 0x0000, 0x0000, 0x0000,
43 0x0050, 0x0000, 0x0140, 0x0000,
44 0x0000, 0x0000, 0x0000, 0x00ff,
45 0x0000, 0x0000, 0x0100, 0x00ff,
46 0x0000, 0x0000, 0x012c, 0x002c,
47 0x002c, 0x002c, 0x002c, 0x0000,
48 0x0032, 0x0000, 0x0000, 0x0000,
49 0x0000, 0x0000, 0x0000, 0x0000,
50 0x0038, 0x000b, 0x0032, 0x0000,
51 0x0008, 0x000c, 0x0093, 0x00e9,
52 0x0000, 0x0000, 0x0000, 0x0000,
53 0x0003, 0x0010, 0x0000, 0x0000,
54 0x0000, 0x0002, 0x0000, 0x0000,
55 0x0000, 0x0000, 0x0039, 0x0000,
56 0x0000,
57};
58
59/*
60 * read wm8974 register cache
61 */
62static inline unsigned int wm8974_read_reg_cache(struct snd_soc_codec * codec,
63 unsigned int reg)
64{
65 u16 *cache = codec->reg_cache;
66 if (reg == WM8974_RESET)
67 return 0;
68 if (reg >= WM8974_CACHEREGNUM)
69 return -1;
70 return cache[reg];
71}
72
73/*
74 * write wm8974 register cache
75 */
76static inline void wm8974_write_reg_cache(struct snd_soc_codec *codec,
77 u16 reg, unsigned int value)
78{
79 u16 *cache = codec->reg_cache;
80 if (reg >= WM8974_CACHEREGNUM)
81 return;
82 cache[reg] = value;
83}
84
85/*
86 * write to the WM8974 register space
87 */
88static int wm8974_write(struct snd_soc_codec *codec, unsigned int reg,
89 unsigned int value)
90{
91 u8 data[2];
92
93 /* data is
94 * D15..D9 WM8974 register offset
95 * D8...D0 register data
96 */
97 data[0] = (reg << 1) | ((value >> 8) & 0x0001);
98 data[1] = value & 0x00ff;
99
100 wm8974_write_reg_cache (codec, reg, value);
101 if (codec->hw_write(codec->control_data, data, 2) == 2)
102 return 0;
103 else
104 return -EIO;
105}
106
107#define wm8974_reset(c) wm8974_write(c, WM8974_RESET, 0)
108
109static const char *wm8974_companding[] = {"Off", "NC", "u-law", "A-law" };
110static const char *wm8974_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz" };
111static const char *wm8974_eqmode[] = {"Capture", "Playback" };
112static const char *wm8974_bw[] = {"Narrow", "Wide" };
113static const char *wm8974_eq1[] = {"80Hz", "105Hz", "135Hz", "175Hz" };
114static const char *wm8974_eq2[] = {"230Hz", "300Hz", "385Hz", "500Hz" };
115static const char *wm8974_eq3[] = {"650Hz", "850Hz", "1.1kHz", "1.4kHz" };
116static const char *wm8974_eq4[] = {"1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz" };
117static const char *wm8974_eq5[] = {"5.3kHz", "6.9kHz", "9kHz", "11.7kHz" };
118static const char *wm8974_alc[] = {"ALC", "Limiter" };
119
120static const struct soc_enum wm8974_enum[] = {
121 SOC_ENUM_SINGLE(WM8974_COMP, 1, 4, wm8974_companding), /* adc */
122 SOC_ENUM_SINGLE(WM8974_COMP, 3, 4, wm8974_companding), /* dac */
123 SOC_ENUM_SINGLE(WM8974_DAC, 4, 4, wm8974_deemp),
124 SOC_ENUM_SINGLE(WM8974_EQ1, 8, 2, wm8974_eqmode),
125
126 SOC_ENUM_SINGLE(WM8974_EQ1, 5, 4, wm8974_eq1),
127 SOC_ENUM_SINGLE(WM8974_EQ2, 8, 2, wm8974_bw),
128 SOC_ENUM_SINGLE(WM8974_EQ2, 5, 4, wm8974_eq2),
129 SOC_ENUM_SINGLE(WM8974_EQ3, 8, 2, wm8974_bw),
130
131 SOC_ENUM_SINGLE(WM8974_EQ3, 5, 4, wm8974_eq3),
132 SOC_ENUM_SINGLE(WM8974_EQ4, 8, 2, wm8974_bw),
133 SOC_ENUM_SINGLE(WM8974_EQ4, 5, 4, wm8974_eq4),
134 SOC_ENUM_SINGLE(WM8974_EQ5, 8, 2, wm8974_bw),
135
136 SOC_ENUM_SINGLE(WM8974_EQ5, 5, 4, wm8974_eq5),
137 SOC_ENUM_SINGLE(WM8974_ALC3, 8, 2, wm8974_alc),
138};
139
140static const struct snd_kcontrol_new wm8974_snd_controls[] = {
141
142SOC_SINGLE("Digital Loopback Switch", WM8974_COMP, 0, 1, 0),
143
144SOC_ENUM("DAC Companding", wm8974_enum[1]),
145SOC_ENUM("ADC Companding", wm8974_enum[0]),
146
147SOC_ENUM("Playback De-emphasis", wm8974_enum[2]),
148SOC_SINGLE("DAC Inversion Switch", WM8974_DAC, 0, 1, 0),
149
150SOC_SINGLE("PCM Volume", WM8974_DACVOL, 0, 127, 0),
151
152SOC_SINGLE("High Pass Filter Switch", WM8974_ADC, 8, 1, 0),
153SOC_SINGLE("High Pass Cut Off", WM8974_ADC, 4, 7, 0),
154SOC_SINGLE("ADC Inversion Switch", WM8974_COMP, 0, 1, 0),
155
156SOC_SINGLE("Capture Volume", WM8974_ADCVOL, 0, 127, 0),
157
158SOC_ENUM("Equaliser Function", wm8974_enum[3]),
159SOC_ENUM("EQ1 Cut Off", wm8974_enum[4]),
160SOC_SINGLE("EQ1 Volume", WM8974_EQ1, 0, 31, 1),
161
162SOC_ENUM("Equaliser EQ2 Bandwith", wm8974_enum[5]),
163SOC_ENUM("EQ2 Cut Off", wm8974_enum[6]),
164SOC_SINGLE("EQ2 Volume", WM8974_EQ2, 0, 31, 1),
165
166SOC_ENUM("Equaliser EQ3 Bandwith", wm8974_enum[7]),
167SOC_ENUM("EQ3 Cut Off", wm8974_enum[8]),
168SOC_SINGLE("EQ3 Volume", WM8974_EQ3, 0, 31, 1),
169
170SOC_ENUM("Equaliser EQ4 Bandwith", wm8974_enum[9]),
171SOC_ENUM("EQ4 Cut Off", wm8974_enum[10]),
172SOC_SINGLE("EQ4 Volume", WM8974_EQ4, 0, 31, 1),
173
174SOC_ENUM("Equaliser EQ5 Bandwith", wm8974_enum[11]),
175SOC_ENUM("EQ5 Cut Off", wm8974_enum[12]),
176SOC_SINGLE("EQ5 Volume", WM8974_EQ5, 0, 31, 1),
177
178SOC_SINGLE("DAC Playback Limiter Switch", WM8974_DACLIM1, 8, 1, 0),
179SOC_SINGLE("DAC Playback Limiter Decay", WM8974_DACLIM1, 4, 15, 0),
180SOC_SINGLE("DAC Playback Limiter Attack", WM8974_DACLIM1, 0, 15, 0),
181
182SOC_SINGLE("DAC Playback Limiter Threshold", WM8974_DACLIM2, 4, 7, 0),
183SOC_SINGLE("DAC Playback Limiter Boost", WM8974_DACLIM2, 0, 15, 0),
184
185SOC_SINGLE("ALC Enable Switch", WM8974_ALC1, 8, 1, 0),
186SOC_SINGLE("ALC Capture Max Gain", WM8974_ALC1, 3, 7, 0),
187SOC_SINGLE("ALC Capture Min Gain", WM8974_ALC1, 0, 7, 0),
188
189SOC_SINGLE("ALC Capture ZC Switch", WM8974_ALC2, 8, 1, 0),
190SOC_SINGLE("ALC Capture Hold", WM8974_ALC2, 4, 7, 0),
191SOC_SINGLE("ALC Capture Target", WM8974_ALC2, 0, 15, 0),
192
193SOC_ENUM("ALC Capture Mode", wm8974_enum[13]),
194SOC_SINGLE("ALC Capture Decay", WM8974_ALC3, 4, 15, 0),
195SOC_SINGLE("ALC Capture Attack", WM8974_ALC3, 0, 15, 0),
196
197SOC_SINGLE("ALC Capture Noise Gate Switch", WM8974_NGATE, 3, 1, 0),
198SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8974_NGATE, 0, 7, 0),
199
200SOC_SINGLE("Capture PGA ZC Switch", WM8974_INPPGA, 7, 1, 0),
201SOC_SINGLE("Capture PGA Volume", WM8974_INPPGA, 0, 63, 0),
202
203SOC_SINGLE("Speaker Playback ZC Switch", WM8974_SPKVOL, 7, 1, 0),
204SOC_SINGLE("Speaker Playback Switch", WM8974_SPKVOL, 6, 1, 1),
205SOC_SINGLE("Speaker Playback Volume", WM8974_SPKVOL, 0, 63, 0),
206
207SOC_SINGLE("Capture Boost(+20dB)", WM8974_ADCBOOST, 8, 1, 0),
208SOC_SINGLE("Mono Playback Switch", WM8974_MONOMIX, 6, 1, 0),
209};
210
211/* add non dapm controls */
212static int wm8974_add_controls(struct snd_soc_codec *codec)
213{
214 int err, i;
215
216 for (i = 0; i < ARRAY_SIZE(wm8974_snd_controls); i++) {
217 err = snd_ctl_add(codec->card,
218 snd_soc_cnew(&wm8974_snd_controls[i],codec, NULL));
219 if (err < 0)
220 return err;
221 }
222
223 return 0;
224}
225
226/* Speaker Output Mixer */
227static const struct snd_kcontrol_new wm8974_speaker_mixer_controls[] = {
228SOC_DAPM_SINGLE("Line Bypass Switch", WM8974_SPKMIX, 1, 1, 0),
229SOC_DAPM_SINGLE("Aux Playback Switch", WM8974_SPKMIX, 5, 1, 0),
230SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_SPKMIX, 0, 1, 1),
231};
232
233/* Mono Output Mixer */
234static const struct snd_kcontrol_new wm8974_mono_mixer_controls[] = {
235SOC_DAPM_SINGLE("Line Bypass Switch", WM8974_MONOMIX, 1, 1, 0),
236SOC_DAPM_SINGLE("Aux Playback Switch", WM8974_MONOMIX, 2, 1, 0),
237SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_MONOMIX, 0, 1, 1),
238};
239
240/* AUX Input boost vol */
241static const struct snd_kcontrol_new wm8974_aux_boost_controls =
242SOC_DAPM_SINGLE("Aux Volume", WM8974_ADCBOOST, 0, 7, 0);
243
244/* Mic Input boost vol */
245static const struct snd_kcontrol_new wm8974_mic_boost_controls =
246SOC_DAPM_SINGLE("Mic Volume", WM8974_ADCBOOST, 4, 7, 0);
247
248/* Capture boost switch */
249static const struct snd_kcontrol_new wm8974_capture_boost_controls =
250SOC_DAPM_SINGLE("Capture Boost Switch", WM8974_INPPGA, 6, 1, 0);
251
252/* Aux In to PGA */
253static const struct snd_kcontrol_new wm8974_aux_capture_boost_controls =
254SOC_DAPM_SINGLE("Aux Capture Boost Switch", WM8974_INPPGA, 2, 1, 0);
255
256/* Mic P In to PGA */
257static const struct snd_kcontrol_new wm8974_micp_capture_boost_controls =
258SOC_DAPM_SINGLE("Mic P Capture Boost Switch", WM8974_INPPGA, 0, 1, 0);
259
260/* Mic N In to PGA */
261static const struct snd_kcontrol_new wm8974_micn_capture_boost_controls =
262SOC_DAPM_SINGLE("Mic N Capture Boost Switch", WM8974_INPPGA, 1, 1, 0);
263
264static const struct snd_soc_dapm_widget wm8974_dapm_widgets[] = {
265SND_SOC_DAPM_MIXER("Speaker Mixer", WM8974_POWER3, 2, 0,
266 &wm8974_speaker_mixer_controls[0],
267 ARRAY_SIZE(wm8974_speaker_mixer_controls)),
268SND_SOC_DAPM_MIXER("Mono Mixer", WM8974_POWER3, 3, 0,
269 &wm8974_mono_mixer_controls[0],
270 ARRAY_SIZE(wm8974_mono_mixer_controls)),
271SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8974_POWER3, 0, 0),
272SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8974_POWER3, 0, 0),
273SND_SOC_DAPM_PGA("Aux Input", WM8974_POWER1, 6, 0, NULL, 0),
274SND_SOC_DAPM_PGA("SpkN Out", WM8974_POWER3, 5, 0, NULL, 0),
275SND_SOC_DAPM_PGA("SpkP Out", WM8974_POWER3, 6, 0, NULL, 0),
276SND_SOC_DAPM_PGA("Mono Out", WM8974_POWER3, 7, 0, NULL, 0),
277SND_SOC_DAPM_PGA("Mic PGA", WM8974_POWER2, 2, 0, NULL, 0),
278
279SND_SOC_DAPM_PGA("Aux Boost", SND_SOC_NOPM, 0, 0,
280 &wm8974_aux_boost_controls, 1),
281SND_SOC_DAPM_PGA("Mic Boost", SND_SOC_NOPM, 0, 0,
282 &wm8974_mic_boost_controls, 1),
283SND_SOC_DAPM_SWITCH("Capture Boost", SND_SOC_NOPM, 0, 0,
284 &wm8974_capture_boost_controls),
285
286SND_SOC_DAPM_MIXER("Boost Mixer", WM8974_POWER2, 4, 0, NULL, 0),
287
288SND_SOC_DAPM_MICBIAS("Mic Bias", WM8974_POWER1, 4, 0),
289
290SND_SOC_DAPM_INPUT("MICN"),
291SND_SOC_DAPM_INPUT("MICP"),
292SND_SOC_DAPM_INPUT("AUX"),
293SND_SOC_DAPM_OUTPUT("MONOOUT"),
294SND_SOC_DAPM_OUTPUT("SPKOUTP"),
295SND_SOC_DAPM_OUTPUT("SPKOUTN"),
296};
297
298static const struct snd_soc_dapm_route audio_map[] = {
299 /* Mono output mixer */
300 {"Mono Mixer", "PCM Playback Switch", "DAC"},
301 {"Mono Mixer", "Aux Playback Switch", "Aux Input"},
302 {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"},
303
304 /* Speaker output mixer */
305 {"Speaker Mixer", "PCM Playback Switch", "DAC"},
306 {"Speaker Mixer", "Aux Playback Switch", "Aux Input"},
307 {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"},
308
309 /* Outputs */
310 {"Mono Out", NULL, "Mono Mixer"},
311 {"MONOOUT", NULL, "Mono Out"},
312 {"SpkN Out", NULL, "Speaker Mixer"},
313 {"SpkP Out", NULL, "Speaker Mixer"},
314 {"SPKOUTN", NULL, "SpkN Out"},
315 {"SPKOUTP", NULL, "SpkP Out"},
316
317 /* Boost Mixer */
318 {"Boost Mixer", NULL, "ADC"},
319 {"Capture Boost Switch", "Aux Capture Boost Switch", "AUX"},
320 {"Aux Boost", "Aux Volume", "Boost Mixer"},
321 {"Capture Boost", "Capture Switch", "Boost Mixer"},
322 {"Mic Boost", "Mic Volume", "Boost Mixer"},
323
324 /* Inputs */
325 {"MICP", NULL, "Mic Boost"},
326 {"MICN", NULL, "Mic PGA"},
327 {"Mic PGA", NULL, "Capture Boost"},
328 {"AUX", NULL, "Aux Input"},
329};
330
331static int wm8974_add_widgets(struct snd_soc_codec *codec)
332{
333 snd_soc_dapm_new_controls(codec, wm8974_dapm_widgets,
334 ARRAY_SIZE(wm8974_dapm_widgets));
335
336 snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
337
338 snd_soc_dapm_new_widgets(codec);
339 return 0;
340}
341
342struct pll_ {
343 unsigned int in_hz, out_hz;
344 unsigned int pre:4; /* prescale - 1 */
345 unsigned int n:4;
346 unsigned int k;
347};
348
349static struct pll_ pll[] = {
350 {12000000, 11289600, 0, 7, 0x86c220},
351 {12000000, 12288000, 0, 8, 0x3126e8},
352 {13000000, 11289600, 0, 6, 0xf28bd4},
353 {13000000, 12288000, 0, 7, 0x8fd525},
354 {12288000, 11289600, 0, 7, 0x59999a},
355 {11289600, 12288000, 0, 8, 0x80dee9},
356 /* liam - add more entries */
357};
358
359static int wm8974_set_dai_pll(struct snd_soc_dai *codec_dai,
360 int pll_id, unsigned int freq_in, unsigned int freq_out)
361{
362 struct snd_soc_codec *codec = codec_dai->codec;
363 int i;
364 u16 reg;
365
366 if(freq_in == 0 || freq_out == 0) {
367 reg = wm8974_read_reg_cache(codec, WM8974_POWER1);
368 wm8974_write(codec, WM8974_POWER1, reg & 0x1df);
369 return 0;
370 }
371
372 for(i = 0; i < ARRAY_SIZE(pll); i++) {
373 if (freq_in == pll[i].in_hz && freq_out == pll[i].out_hz) {
374 wm8974_write(codec, WM8974_PLLN, (pll[i].pre << 4) | pll[i].n);
375 wm8974_write(codec, WM8974_PLLK1, pll[i].k >> 18);
376 wm8974_write(codec, WM8974_PLLK2, (pll[i].k >> 9) & 0x1ff);
377 wm8974_write(codec, WM8974_PLLK3, pll[i].k & 0x1ff);
378 reg = wm8974_read_reg_cache(codec, WM8974_POWER1);
379 wm8974_write(codec, WM8974_POWER1, reg | 0x020);
380 return 0;
381 }
382 }
383 return -EINVAL;
384}
385
386/*
387 * Configure WM8974 clock dividers.
388 */
389static int wm8974_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
390 int div_id, int div)
391{
392 struct snd_soc_codec *codec = codec_dai->codec;
393 u16 reg;
394
395 switch (div_id) {
396 case WM8974_OPCLKDIV:
397 reg = wm8974_read_reg_cache(codec, WM8974_GPIO) & 0x1cf;
398 wm8974_write(codec, WM8974_GPIO, reg | div);
399 break;
400 case WM8974_MCLKDIV:
401 reg = wm8974_read_reg_cache(codec, WM8974_CLOCK) & 0x11f;
402 wm8974_write(codec, WM8974_CLOCK, reg | div);
403 break;
404 case WM8974_ADCCLK:
405 reg = wm8974_read_reg_cache(codec, WM8974_ADC) & 0x1f7;
406 wm8974_write(codec, WM8974_ADC, reg | div);
407 break;
408 case WM8974_DACCLK:
409 reg = wm8974_read_reg_cache(codec, WM8974_DAC) & 0x1f7;
410 wm8974_write(codec, WM8974_DAC, reg | div);
411 break;
412 case WM8974_BCLKDIV:
413 reg = wm8974_read_reg_cache(codec, WM8974_CLOCK) & 0x1e3;
414 wm8974_write(codec, WM8974_CLOCK, reg | div);
415 break;
416 default:
417 return -EINVAL;
418 }
419
420 return 0;
421}
422
423static int wm8974_set_dai_fmt(struct snd_soc_dai *codec_dai,
424 unsigned int fmt)
425{
426 struct snd_soc_codec *codec = codec_dai->codec;
427 u16 iface = 0;
428 u16 clk = wm8974_read_reg_cache(codec, WM8974_CLOCK) & 0x1fe;
429
430 /* set master/slave audio interface */
431 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
432 case SND_SOC_DAIFMT_CBM_CFM:
433 clk |= 0x0001;
434 break;
435 case SND_SOC_DAIFMT_CBS_CFS:
436 break;
437 default:
438 return -EINVAL;
439 }
440
441 /* interface format */
442 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
443 case SND_SOC_DAIFMT_I2S:
444 iface |= 0x0010;
445 break;
446 case SND_SOC_DAIFMT_RIGHT_J:
447 break;
448 case SND_SOC_DAIFMT_LEFT_J:
449 iface |= 0x0008;
450 break;
451 case SND_SOC_DAIFMT_DSP_A:
452 iface |= 0x00018;
453 break;
454 default:
455 return -EINVAL;
456 }
457
458 /* clock inversion */
459 switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
460 case SND_SOC_DAIFMT_NB_NF:
461 break;
462 case SND_SOC_DAIFMT_IB_IF:
463 iface |= 0x0180;
464 break;
465 case SND_SOC_DAIFMT_IB_NF:
466 iface |= 0x0100;
467 break;
468 case SND_SOC_DAIFMT_NB_IF:
469 iface |= 0x0080;
470 break;
471 default:
472 return -EINVAL;
473 }
474
475 wm8974_write(codec, WM8974_IFACE, iface);
476 wm8974_write(codec, WM8974_CLOCK, clk);
477 return 0;
478}
479
480static int wm8974_pcm_hw_params(struct snd_pcm_substream *substream,
481 struct snd_pcm_hw_params *params,
482 struct snd_soc_dai *dai)
483{
484 struct snd_soc_codec *codec = dai->codec;
485 u16 iface = wm8974_read_reg_cache(codec, WM8974_IFACE) & 0x19f;
486 u16 adn = wm8974_read_reg_cache(codec, WM8974_ADD) & 0x1f1;
487
488 /* bit size */
489 switch (params_format(params)) {
490 case SNDRV_PCM_FORMAT_S16_LE:
491 break;
492 case SNDRV_PCM_FORMAT_S20_3LE:
493 iface |= 0x0020;
494 break;
495 case SNDRV_PCM_FORMAT_S24_LE:
496 iface |= 0x0040;
497 break;
498 case SNDRV_PCM_FORMAT_S32_LE:
499 iface |= 0x0060;
500 break;
501 }
502
503 /* filter coefficient */
504 switch (params_rate(params)) {
505 case SNDRV_PCM_RATE_8000:
506 adn |= 0x5 << 1;
507 break;
508 case SNDRV_PCM_RATE_11025:
509 adn |= 0x4 << 1;
510 break;
511 case SNDRV_PCM_RATE_16000:
512 adn |= 0x3 << 1;
513 break;
514 case SNDRV_PCM_RATE_22050:
515 adn |= 0x2 << 1;
516 break;
517 case SNDRV_PCM_RATE_32000:
518 adn |= 0x1 << 1;
519 break;
520 case SNDRV_PCM_RATE_44100:
521 break;
522 }
523
524 wm8974_write(codec, WM8974_IFACE, iface);
525 wm8974_write(codec, WM8974_ADD, adn);
526 return 0;
527}
528
529static int wm8974_mute(struct snd_soc_dai *dai, int mute)
530{
531 struct snd_soc_codec *codec = dai->codec;
532 u16 mute_reg = wm8974_read_reg_cache(codec, WM8974_DAC) & 0xffbf;
533
534 if(mute)
535 wm8974_write(codec, WM8974_DAC, mute_reg | 0x40);
536 else
537 wm8974_write(codec, WM8974_DAC, mute_reg);
538 return 0;
539}
540
541/* liam need to make this lower power with dapm */
542static int wm8974_set_bias_level(struct snd_soc_codec *codec,
543 enum snd_soc_bias_level level)
544{
545 switch (level) {
546 case SND_SOC_BIAS_ON:
547 wm8974_write(codec, WM8974_POWER1, 0x1ff);
548 wm8974_write(codec, WM8974_POWER2, 0x1ff);
549 wm8974_write(codec, WM8974_POWER3, 0x1ff);
550 break;
551 case SND_SOC_BIAS_PREPARE:
552 break;
553 case SND_SOC_BIAS_STANDBY:
554 break;
555 case SND_SOC_BIAS_OFF:
556 wm8974_write(codec, WM8974_POWER1, 0x0);
557 wm8974_write(codec, WM8974_POWER2, 0x0);
558 wm8974_write(codec, WM8974_POWER3, 0x0);
559 break;
560 }
561 codec->bias_level = level;
562 return 0;
563}
564
565#define WM8974_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
566 SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
567 SNDRV_PCM_RATE_48000)
568
569#define WM8974_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
570 SNDRV_PCM_FMTBIT_S24_LE)
571
572static struct snd_soc_dai_ops wm8974_ops = {
573 .hw_params = wm8974_pcm_hw_params,
574 .digital_mute = wm8974_mute,
575 .set_fmt = wm8974_set_dai_fmt,
576 .set_clkdiv = wm8974_set_dai_clkdiv,
577 .set_pll = wm8974_set_dai_pll,
578};
579
580struct snd_soc_dai wm8974_dai = {
581 .name = "WM8974 HiFi",
582 .playback = {
583 .stream_name = "Playback",
584 .channels_min = 1,
585 .channels_max = 1,
586 .rates = WM8974_RATES,
587 .formats = WM8974_FORMATS,},
588 .capture = {
589 .stream_name = "Capture",
590 .channels_min = 1,
591 .channels_max = 1,
592 .rates = WM8974_RATES,
593 .formats = WM8974_FORMATS,},
594 .ops = &wm8974_ops,
595};
596EXPORT_SYMBOL_GPL(wm8974_dai);
597
598static int wm8974_suspend(struct platform_device *pdev, pm_message_t state)
599{
600 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
601 struct snd_soc_codec *codec = socdev->card->codec;
602
603 wm8974_set_bias_level(codec, SND_SOC_BIAS_OFF);
604 return 0;
605}
606
607static int wm8974_resume(struct platform_device *pdev)
608{
609 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
610 struct snd_soc_codec *codec = socdev->card->codec;
611 int i;
612 u8 data[2];
613 u16 *cache = codec->reg_cache;
614
615 /* Sync reg_cache with the hardware */
616 for (i = 0; i < ARRAY_SIZE(wm8974_reg); i++) {
617 data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
618 data[1] = cache[i] & 0x00ff;
619 codec->hw_write(codec->control_data, data, 2);
620 }
621 wm8974_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
622 wm8974_set_bias_level(codec, codec->suspend_bias_level);
623 return 0;
624}
625
626/*
627 * initialise the WM8974 driver
628 * register the mixer and dsp interfaces with the kernel
629 */
630static int wm8974_init(struct snd_soc_device *socdev)
631{
632 struct snd_soc_codec *codec = socdev->card->codec;
633 int ret = 0;
634
635 codec->name = "WM8974";
636 codec->owner = THIS_MODULE;
637 codec->read = wm8974_read_reg_cache;
638 codec->write = wm8974_write;
639 codec->set_bias_level = wm8974_set_bias_level;
640 codec->dai = &wm8974_dai;
641 codec->num_dai = 1;
642 codec->reg_cache_size = ARRAY_SIZE(wm8974_reg);
643 codec->reg_cache = kmemdup(wm8974_reg, sizeof(wm8974_reg), GFP_KERNEL);
644
645 if (codec->reg_cache == NULL)
646 return -ENOMEM;
647
648 wm8974_reset(codec);
649
650 /* register pcms */
651 ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
652 if(ret < 0) {
653 printk(KERN_ERR "wm8974: failed to create pcms\n");
654 goto pcm_err;
655 }
656
657 /* power on device */
658 wm8974_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
659 wm8974_add_controls(codec);
660 wm8974_add_widgets(codec);
661 ret = snd_soc_init_card(socdev);
662 if (ret < 0) {
663 printk(KERN_ERR "wm8974: failed to register card\n");
664 goto card_err;
665 }
666 return ret;
667
668card_err:
669 snd_soc_free_pcms(socdev);
670 snd_soc_dapm_free(socdev);
671pcm_err:
672 kfree(codec->reg_cache);
673 return ret;
674}
675
676static struct snd_soc_device *wm8974_socdev;
677
678#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
679
680/*
681 * WM8974 2 wire address is 0x1a
682 */
683#define I2C_DRIVERID_WM8974 0xfefe /* liam - need a proper id */
684
685static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END };
686
687/* Magic definition of all other variables and things */
688I2C_CLIENT_INSMOD;
689
690static struct i2c_driver wm8974_i2c_driver;
691static struct i2c_client client_template;
692
693/* If the i2c layer weren't so broken, we could pass this kind of data
694 around */
695
696static int wm8974_codec_probe(struct i2c_adapter *adap, int addr, int kind)
697{
698 struct snd_soc_device *socdev = wm8974_socdev;
699 struct wm8974_setup_data *setup = socdev->codec_data;
700 struct snd_soc_codec *codec = socdev->card->codec;
701 struct i2c_client *i2c;
702 int ret;
703
704 if (addr != setup->i2c_address)
705 return -ENODEV;
706
707 client_template.adapter = adap;
708 client_template.addr = addr;
709
710 i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL);
711 if (i2c == NULL) {
712 kfree(codec);
713 return -ENOMEM;
714 }
715 i2c_set_clientdata(i2c, codec);
716 codec->control_data = i2c;
717
718 ret = i2c_attach_client(i2c);
719 if (ret < 0) {
720 pr_err("failed to attach codec at addr %x\n", addr);
721 goto err;
722 }
723
724 ret = wm8974_init(socdev);
725 if (ret < 0) {
726 pr_err("failed to initialise WM8974\n");
727 goto err;
728 }
729 return ret;
730
731err:
732 kfree(codec);
733 kfree(i2c);
734 return ret;
735}
736
737static int wm8974_i2c_detach(struct i2c_client *client)
738{
739 struct snd_soc_codec *codec = i2c_get_clientdata(client);
740 i2c_detach_client(client);
741 kfree(codec->reg_cache);
742 kfree(client);
743 return 0;
744}
745
746static int wm8974_i2c_attach(struct i2c_adapter *adap)
747{
748 return i2c_probe(adap, &addr_data, wm8974_codec_probe);
749}
750
751/* corgi i2c codec control layer */
752static struct i2c_driver wm8974_i2c_driver = {
753 .driver = {
754 .name = "WM8974 I2C Codec",
755 .owner = THIS_MODULE,
756 },
757 .id = I2C_DRIVERID_WM8974,
758 .attach_adapter = wm8974_i2c_attach,
759 .detach_client = wm8974_i2c_detach,
760 .command = NULL,
761};
762
763static struct i2c_client client_template = {
764 .name = "WM8974",
765 .driver = &wm8974_i2c_driver,
766};
767#endif
768
769static int wm8974_probe(struct platform_device *pdev)
770{
771 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
772 struct wm8974_setup_data *setup;
773 struct snd_soc_codec *codec;
774 int ret = 0;
775
776 pr_info("WM8974 Audio Codec %s", WM8974_VERSION);
777
778 setup = socdev->codec_data;
779 codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
780 if (codec == NULL)
781 return -ENOMEM;
782
783 socdev->card->codec = codec;
784 mutex_init(&codec->mutex);
785 INIT_LIST_HEAD(&codec->dapm_widgets);
786 INIT_LIST_HEAD(&codec->dapm_paths);
787
788 wm8974_socdev = socdev;
789#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
790 if (setup->i2c_address) {
791 normal_i2c[0] = setup->i2c_address;
792 codec->hw_write = (hw_write_t)i2c_master_send;
793 ret = i2c_add_driver(&wm8974_i2c_driver);
794 if (ret != 0)
795 printk(KERN_ERR "can't add i2c driver");
796 }
797#else
798 /* Add other interfaces here */
799#endif
800 return ret;
801}
802
803/* power down chip */
804static int wm8974_remove(struct platform_device *pdev)
805{
806 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
807 struct snd_soc_codec *codec = socdev->card->codec;
808
809 if (codec->control_data)
810 wm8974_set_bias_level(codec, SND_SOC_BIAS_OFF);
811
812 snd_soc_free_pcms(socdev);
813 snd_soc_dapm_free(socdev);
814#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
815 i2c_del_driver(&wm8974_i2c_driver);
816#endif
817 kfree(codec);
818
819 return 0;
820}
821
822struct snd_soc_codec_device soc_codec_dev_wm8974 = {
823 .probe = wm8974_probe,
824 .remove = wm8974_remove,
825 .suspend = wm8974_suspend,
826 .resume = wm8974_resume,
827};
828EXPORT_SYMBOL_GPL(soc_codec_dev_wm8974);
829
830static int __init wm8974_modinit(void)
831{
832 return snd_soc_register_dai(&wm8974_dai);
833}
834module_init(wm8974_modinit);
835
836static void __exit wm8974_exit(void)
837{
838 snd_soc_unregister_dai(&wm8974_dai);
839}
840module_exit(wm8974_exit);
841
842MODULE_DESCRIPTION("ASoC WM8974 driver");
843MODULE_AUTHOR("Liam Girdwood");
844MODULE_LICENSE("GPL");