]> bbs.cooldavid.org Git - net-next-2.6.git/blame - sound/soc/codecs/wm8974.c
ASoC: Add indirection for CODEC private data
[net-next-2.6.git] / sound / soc / codecs / wm8974.c
CommitLineData
0a1bf553
MB
1/*
2 * wm8974.c -- WM8974 ALSA Soc Audio driver
3 *
8b83a193 4 * Copyright 2006-2009 Wolfson Microelectronics PLC.
0a1bf553 5 *
4fcbbb67 6 * Author: Liam Girdwood <linux@wolfsonmicro.com>
0a1bf553
MB
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>
0a1bf553
MB
15#include <linux/kernel.h>
16#include <linux/init.h>
17#include <linux/delay.h>
18#include <linux/pm.h>
19#include <linux/i2c.h>
20#include <linux/platform_device.h>
21#include <sound/core.h>
22#include <sound/pcm.h>
23#include <sound/pcm_params.h>
24#include <sound/soc.h>
25#include <sound/soc-dapm.h>
26#include <sound/initval.h>
a5f8d2f1 27#include <sound/tlv.h>
0a1bf553
MB
28
29#include "wm8974.h"
30
0a1bf553 31static const u16 wm8974_reg[WM8974_CACHEREGNUM] = {
1a55b3f6
MB
32 0x0000, 0x0000, 0x0000, 0x0000,
33 0x0050, 0x0000, 0x0140, 0x0000,
34 0x0000, 0x0000, 0x0000, 0x00ff,
35 0x0000, 0x0000, 0x0100, 0x00ff,
36 0x0000, 0x0000, 0x012c, 0x002c,
37 0x002c, 0x002c, 0x002c, 0x0000,
38 0x0032, 0x0000, 0x0000, 0x0000,
39 0x0000, 0x0000, 0x0000, 0x0000,
40 0x0038, 0x000b, 0x0032, 0x0000,
41 0x0008, 0x000c, 0x0093, 0x00e9,
42 0x0000, 0x0000, 0x0000, 0x0000,
43 0x0003, 0x0010, 0x0000, 0x0000,
44 0x0000, 0x0002, 0x0000, 0x0000,
45 0x0000, 0x0000, 0x0039, 0x0000,
46 0x0000,
0a1bf553
MB
47};
48
df1ef7a3 49#define WM8974_POWER1_BIASEN 0x08
48c03ce7 50#define WM8974_POWER1_BUFIOEN 0x04
df1ef7a3 51
4fcbbb67
MB
52struct wm8974_priv {
53 struct snd_soc_codec codec;
54 u16 reg_cache[WM8974_CACHEREGNUM];
55};
56
57static struct snd_soc_codec *wm8974_codec;
58
1e97f50b 59#define wm8974_reset(c) snd_soc_write(c, WM8974_RESET, 0)
0a1bf553
MB
60
61static const char *wm8974_companding[] = {"Off", "NC", "u-law", "A-law" };
62static const char *wm8974_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz" };
63static const char *wm8974_eqmode[] = {"Capture", "Playback" };
64static const char *wm8974_bw[] = {"Narrow", "Wide" };
65static const char *wm8974_eq1[] = {"80Hz", "105Hz", "135Hz", "175Hz" };
66static const char *wm8974_eq2[] = {"230Hz", "300Hz", "385Hz", "500Hz" };
67static const char *wm8974_eq3[] = {"650Hz", "850Hz", "1.1kHz", "1.4kHz" };
68static const char *wm8974_eq4[] = {"1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz" };
69static const char *wm8974_eq5[] = {"5.3kHz", "6.9kHz", "9kHz", "11.7kHz" };
70static const char *wm8974_alc[] = {"ALC", "Limiter" };
71
72static const struct soc_enum wm8974_enum[] = {
73 SOC_ENUM_SINGLE(WM8974_COMP, 1, 4, wm8974_companding), /* adc */
74 SOC_ENUM_SINGLE(WM8974_COMP, 3, 4, wm8974_companding), /* dac */
75 SOC_ENUM_SINGLE(WM8974_DAC, 4, 4, wm8974_deemp),
76 SOC_ENUM_SINGLE(WM8974_EQ1, 8, 2, wm8974_eqmode),
77
78 SOC_ENUM_SINGLE(WM8974_EQ1, 5, 4, wm8974_eq1),
79 SOC_ENUM_SINGLE(WM8974_EQ2, 8, 2, wm8974_bw),
80 SOC_ENUM_SINGLE(WM8974_EQ2, 5, 4, wm8974_eq2),
81 SOC_ENUM_SINGLE(WM8974_EQ3, 8, 2, wm8974_bw),
82
83 SOC_ENUM_SINGLE(WM8974_EQ3, 5, 4, wm8974_eq3),
84 SOC_ENUM_SINGLE(WM8974_EQ4, 8, 2, wm8974_bw),
85 SOC_ENUM_SINGLE(WM8974_EQ4, 5, 4, wm8974_eq4),
86 SOC_ENUM_SINGLE(WM8974_EQ5, 8, 2, wm8974_bw),
87
88 SOC_ENUM_SINGLE(WM8974_EQ5, 5, 4, wm8974_eq5),
89 SOC_ENUM_SINGLE(WM8974_ALC3, 8, 2, wm8974_alc),
90};
91
8a123ee2
MB
92static const char *wm8974_auxmode_text[] = { "Buffer", "Mixer" };
93
94static const struct soc_enum wm8974_auxmode =
95 SOC_ENUM_SINGLE(WM8974_INPUT, 3, 2, wm8974_auxmode_text);
96
a5f8d2f1
MB
97static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1);
98static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
99static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1200, 75, 0);
100static const DECLARE_TLV_DB_SCALE(spk_tlv, -5700, 100, 0);
101
0a1bf553
MB
102static const struct snd_kcontrol_new wm8974_snd_controls[] = {
103
104SOC_SINGLE("Digital Loopback Switch", WM8974_COMP, 0, 1, 0),
105
106SOC_ENUM("DAC Companding", wm8974_enum[1]),
107SOC_ENUM("ADC Companding", wm8974_enum[0]),
108
109SOC_ENUM("Playback De-emphasis", wm8974_enum[2]),
110SOC_SINGLE("DAC Inversion Switch", WM8974_DAC, 0, 1, 0),
111
a5f8d2f1 112SOC_SINGLE_TLV("PCM Volume", WM8974_DACVOL, 0, 255, 0, digital_tlv),
0a1bf553
MB
113
114SOC_SINGLE("High Pass Filter Switch", WM8974_ADC, 8, 1, 0),
115SOC_SINGLE("High Pass Cut Off", WM8974_ADC, 4, 7, 0),
25cbf465 116SOC_SINGLE("ADC Inversion Switch", WM8974_ADC, 0, 1, 0),
0a1bf553 117
a5f8d2f1 118SOC_SINGLE_TLV("Capture Volume", WM8974_ADCVOL, 0, 255, 0, digital_tlv),
0a1bf553
MB
119
120SOC_ENUM("Equaliser Function", wm8974_enum[3]),
121SOC_ENUM("EQ1 Cut Off", wm8974_enum[4]),
a5f8d2f1 122SOC_SINGLE_TLV("EQ1 Volume", WM8974_EQ1, 0, 24, 1, eq_tlv),
0a1bf553
MB
123
124SOC_ENUM("Equaliser EQ2 Bandwith", wm8974_enum[5]),
125SOC_ENUM("EQ2 Cut Off", wm8974_enum[6]),
a5f8d2f1 126SOC_SINGLE_TLV("EQ2 Volume", WM8974_EQ2, 0, 24, 1, eq_tlv),
0a1bf553
MB
127
128SOC_ENUM("Equaliser EQ3 Bandwith", wm8974_enum[7]),
129SOC_ENUM("EQ3 Cut Off", wm8974_enum[8]),
a5f8d2f1 130SOC_SINGLE_TLV("EQ3 Volume", WM8974_EQ3, 0, 24, 1, eq_tlv),
0a1bf553
MB
131
132SOC_ENUM("Equaliser EQ4 Bandwith", wm8974_enum[9]),
133SOC_ENUM("EQ4 Cut Off", wm8974_enum[10]),
a5f8d2f1 134SOC_SINGLE_TLV("EQ4 Volume", WM8974_EQ4, 0, 24, 1, eq_tlv),
0a1bf553
MB
135
136SOC_ENUM("Equaliser EQ5 Bandwith", wm8974_enum[11]),
137SOC_ENUM("EQ5 Cut Off", wm8974_enum[12]),
a5f8d2f1 138SOC_SINGLE_TLV("EQ5 Volume", WM8974_EQ5, 0, 24, 1, eq_tlv),
0a1bf553
MB
139
140SOC_SINGLE("DAC Playback Limiter Switch", WM8974_DACLIM1, 8, 1, 0),
141SOC_SINGLE("DAC Playback Limiter Decay", WM8974_DACLIM1, 4, 15, 0),
142SOC_SINGLE("DAC Playback Limiter Attack", WM8974_DACLIM1, 0, 15, 0),
143
144SOC_SINGLE("DAC Playback Limiter Threshold", WM8974_DACLIM2, 4, 7, 0),
145SOC_SINGLE("DAC Playback Limiter Boost", WM8974_DACLIM2, 0, 15, 0),
146
147SOC_SINGLE("ALC Enable Switch", WM8974_ALC1, 8, 1, 0),
148SOC_SINGLE("ALC Capture Max Gain", WM8974_ALC1, 3, 7, 0),
149SOC_SINGLE("ALC Capture Min Gain", WM8974_ALC1, 0, 7, 0),
150
151SOC_SINGLE("ALC Capture ZC Switch", WM8974_ALC2, 8, 1, 0),
152SOC_SINGLE("ALC Capture Hold", WM8974_ALC2, 4, 7, 0),
153SOC_SINGLE("ALC Capture Target", WM8974_ALC2, 0, 15, 0),
154
155SOC_ENUM("ALC Capture Mode", wm8974_enum[13]),
156SOC_SINGLE("ALC Capture Decay", WM8974_ALC3, 4, 15, 0),
157SOC_SINGLE("ALC Capture Attack", WM8974_ALC3, 0, 15, 0),
158
159SOC_SINGLE("ALC Capture Noise Gate Switch", WM8974_NGATE, 3, 1, 0),
160SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8974_NGATE, 0, 7, 0),
161
162SOC_SINGLE("Capture PGA ZC Switch", WM8974_INPPGA, 7, 1, 0),
a5f8d2f1 163SOC_SINGLE_TLV("Capture PGA Volume", WM8974_INPPGA, 0, 63, 0, inpga_tlv),
0a1bf553
MB
164
165SOC_SINGLE("Speaker Playback ZC Switch", WM8974_SPKVOL, 7, 1, 0),
166SOC_SINGLE("Speaker Playback Switch", WM8974_SPKVOL, 6, 1, 1),
8a123ee2
MB
167SOC_SINGLE_TLV("Speaker Playback Volume", WM8974_SPKVOL, 0, 63, 0, spk_tlv),
168
169SOC_ENUM("Aux Mode", wm8974_auxmode),
0a1bf553
MB
170
171SOC_SINGLE("Capture Boost(+20dB)", WM8974_ADCBOOST, 8, 1, 0),
8a123ee2 172SOC_SINGLE("Mono Playback Switch", WM8974_MONOMIX, 6, 1, 1),
b2c3e923
GL
173
174/* DAC / ADC oversampling */
175SOC_SINGLE("DAC 128x Oversampling Switch", WM8974_DAC, 8, 1, 0),
176SOC_SINGLE("ADC 128x Oversampling Switch", WM8974_ADC, 8, 1, 0),
0a1bf553
MB
177};
178
0a1bf553
MB
179/* Speaker Output Mixer */
180static const struct snd_kcontrol_new wm8974_speaker_mixer_controls[] = {
181SOC_DAPM_SINGLE("Line Bypass Switch", WM8974_SPKMIX, 1, 1, 0),
182SOC_DAPM_SINGLE("Aux Playback Switch", WM8974_SPKMIX, 5, 1, 0),
183SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_SPKMIX, 0, 1, 1),
184};
185
186/* Mono Output Mixer */
187static const struct snd_kcontrol_new wm8974_mono_mixer_controls[] = {
188SOC_DAPM_SINGLE("Line Bypass Switch", WM8974_MONOMIX, 1, 1, 0),
189SOC_DAPM_SINGLE("Aux Playback Switch", WM8974_MONOMIX, 2, 1, 0),
8a123ee2
MB
190SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_MONOMIX, 0, 1, 0),
191};
192
193/* Boost mixer */
194static const struct snd_kcontrol_new wm8974_boost_mixer[] = {
195SOC_DAPM_SINGLE("Aux Switch", WM8974_INPPGA, 6, 1, 0),
196};
197
198/* Input PGA */
199static const struct snd_kcontrol_new wm8974_inpga[] = {
200SOC_DAPM_SINGLE("Aux Switch", WM8974_INPUT, 2, 1, 0),
201SOC_DAPM_SINGLE("MicN Switch", WM8974_INPUT, 1, 1, 0),
202SOC_DAPM_SINGLE("MicP Switch", WM8974_INPUT, 0, 1, 0),
0a1bf553
MB
203};
204
205/* AUX Input boost vol */
206static const struct snd_kcontrol_new wm8974_aux_boost_controls =
207SOC_DAPM_SINGLE("Aux Volume", WM8974_ADCBOOST, 0, 7, 0);
208
209/* Mic Input boost vol */
210static const struct snd_kcontrol_new wm8974_mic_boost_controls =
211SOC_DAPM_SINGLE("Mic Volume", WM8974_ADCBOOST, 4, 7, 0);
212
0a1bf553
MB
213static const struct snd_soc_dapm_widget wm8974_dapm_widgets[] = {
214SND_SOC_DAPM_MIXER("Speaker Mixer", WM8974_POWER3, 2, 0,
215 &wm8974_speaker_mixer_controls[0],
216 ARRAY_SIZE(wm8974_speaker_mixer_controls)),
217SND_SOC_DAPM_MIXER("Mono Mixer", WM8974_POWER3, 3, 0,
218 &wm8974_mono_mixer_controls[0],
219 ARRAY_SIZE(wm8974_mono_mixer_controls)),
220SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8974_POWER3, 0, 0),
8a123ee2 221SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8974_POWER2, 0, 0),
0a1bf553
MB
222SND_SOC_DAPM_PGA("Aux Input", WM8974_POWER1, 6, 0, NULL, 0),
223SND_SOC_DAPM_PGA("SpkN Out", WM8974_POWER3, 5, 0, NULL, 0),
224SND_SOC_DAPM_PGA("SpkP Out", WM8974_POWER3, 6, 0, NULL, 0),
225SND_SOC_DAPM_PGA("Mono Out", WM8974_POWER3, 7, 0, NULL, 0),
0a1bf553 226
8a123ee2
MB
227SND_SOC_DAPM_MIXER("Input PGA", WM8974_POWER2, 2, 0, wm8974_inpga,
228 ARRAY_SIZE(wm8974_inpga)),
229SND_SOC_DAPM_MIXER("Boost Mixer", WM8974_POWER2, 4, 0,
230 wm8974_boost_mixer, ARRAY_SIZE(wm8974_boost_mixer)),
0a1bf553
MB
231
232SND_SOC_DAPM_MICBIAS("Mic Bias", WM8974_POWER1, 4, 0),
233
234SND_SOC_DAPM_INPUT("MICN"),
235SND_SOC_DAPM_INPUT("MICP"),
236SND_SOC_DAPM_INPUT("AUX"),
237SND_SOC_DAPM_OUTPUT("MONOOUT"),
238SND_SOC_DAPM_OUTPUT("SPKOUTP"),
239SND_SOC_DAPM_OUTPUT("SPKOUTN"),
240};
241
242static const struct snd_soc_dapm_route audio_map[] = {
243 /* Mono output mixer */
244 {"Mono Mixer", "PCM Playback Switch", "DAC"},
245 {"Mono Mixer", "Aux Playback Switch", "Aux Input"},
246 {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"},
247
248 /* Speaker output mixer */
249 {"Speaker Mixer", "PCM Playback Switch", "DAC"},
250 {"Speaker Mixer", "Aux Playback Switch", "Aux Input"},
251 {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"},
252
253 /* Outputs */
254 {"Mono Out", NULL, "Mono Mixer"},
255 {"MONOOUT", NULL, "Mono Out"},
256 {"SpkN Out", NULL, "Speaker Mixer"},
257 {"SpkP Out", NULL, "Speaker Mixer"},
258 {"SPKOUTN", NULL, "SpkN Out"},
259 {"SPKOUTP", NULL, "SpkP Out"},
260
261 /* Boost Mixer */
8a123ee2
MB
262 {"ADC", NULL, "Boost Mixer"},
263 {"Boost Mixer", "Aux Switch", "Aux Input"},
264 {"Boost Mixer", NULL, "Input PGA"},
265 {"Boost Mixer", NULL, "MICP"},
266
267 /* Input PGA */
268 {"Input PGA", "Aux Switch", "Aux Input"},
269 {"Input PGA", "MicN Switch", "MICN"},
270 {"Input PGA", "MicP Switch", "MICP"},
0a1bf553
MB
271
272 /* Inputs */
8a123ee2 273 {"Aux Input", NULL, "AUX"},
0a1bf553
MB
274};
275
276static int wm8974_add_widgets(struct snd_soc_codec *codec)
277{
278 snd_soc_dapm_new_controls(codec, wm8974_dapm_widgets,
279 ARRAY_SIZE(wm8974_dapm_widgets));
280
281 snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
282
0a1bf553
MB
283 return 0;
284}
285
286struct pll_ {
c36b2fc7 287 unsigned int pre_div:1;
0a1bf553
MB
288 unsigned int n:4;
289 unsigned int k;
290};
291
91d0c3ec
MB
292/* The size in bits of the pll divide multiplied by 10
293 * to allow rounding later */
294#define FIXED_PLL_SIZE ((1 << 24) * 10)
295
c36b2fc7
MB
296static void pll_factors(struct pll_ *pll_div,
297 unsigned int target, unsigned int source)
91d0c3ec
MB
298{
299 unsigned long long Kpart;
300 unsigned int K, Ndiv, Nmod;
301
c36b2fc7
MB
302 /* There is a fixed divide by 4 in the output path */
303 target *= 4;
304
91d0c3ec
MB
305 Ndiv = target / source;
306 if (Ndiv < 6) {
c36b2fc7
MB
307 source /= 2;
308 pll_div->pre_div = 1;
91d0c3ec
MB
309 Ndiv = target / source;
310 } else
c36b2fc7 311 pll_div->pre_div = 0;
91d0c3ec
MB
312
313 if ((Ndiv < 6) || (Ndiv > 12))
314 printk(KERN_WARNING
8b83a193 315 "WM8974 N value %u outwith recommended range!\n",
91d0c3ec
MB
316 Ndiv);
317
c36b2fc7 318 pll_div->n = Ndiv;
91d0c3ec
MB
319 Nmod = target % source;
320 Kpart = FIXED_PLL_SIZE * (long long)Nmod;
321
322 do_div(Kpart, source);
323
324 K = Kpart & 0xFFFFFFFF;
325
326 /* Check if we need to round */
327 if ((K % 10) >= 5)
328 K += 5;
329
330 /* Move down to proper range now rounding is done */
331 K /= 10;
332
c36b2fc7 333 pll_div->k = K;
91d0c3ec 334}
0a1bf553 335
85488037
MB
336static int wm8974_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
337 int source, unsigned int freq_in, unsigned int freq_out)
0a1bf553
MB
338{
339 struct snd_soc_codec *codec = codec_dai->codec;
c36b2fc7 340 struct pll_ pll_div;
0a1bf553
MB
341 u16 reg;
342
1a55b3f6 343 if (freq_in == 0 || freq_out == 0) {
91d0c3ec 344 /* Clock CODEC directly from MCLK */
1e97f50b
MB
345 reg = snd_soc_read(codec, WM8974_CLOCK);
346 snd_soc_write(codec, WM8974_CLOCK, reg & 0x0ff);
91d0c3ec
MB
347
348 /* Turn off PLL */
1e97f50b
MB
349 reg = snd_soc_read(codec, WM8974_POWER1);
350 snd_soc_write(codec, WM8974_POWER1, reg & 0x1df);
0a1bf553
MB
351 return 0;
352 }
353
c36b2fc7 354 pll_factors(&pll_div, freq_out, freq_in);
91d0c3ec 355
1e97f50b
MB
356 snd_soc_write(codec, WM8974_PLLN, (pll_div.pre_div << 4) | pll_div.n);
357 snd_soc_write(codec, WM8974_PLLK1, pll_div.k >> 18);
358 snd_soc_write(codec, WM8974_PLLK2, (pll_div.k >> 9) & 0x1ff);
359 snd_soc_write(codec, WM8974_PLLK3, pll_div.k & 0x1ff);
360 reg = snd_soc_read(codec, WM8974_POWER1);
361 snd_soc_write(codec, WM8974_POWER1, reg | 0x020);
1a55b3f6 362
91d0c3ec 363 /* Run CODEC from PLL instead of MCLK */
1e97f50b
MB
364 reg = snd_soc_read(codec, WM8974_CLOCK);
365 snd_soc_write(codec, WM8974_CLOCK, reg | 0x100);
91d0c3ec
MB
366
367 return 0;
0a1bf553
MB
368}
369
370/*
371 * Configure WM8974 clock dividers.
372 */
373static int wm8974_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
374 int div_id, int div)
375{
376 struct snd_soc_codec *codec = codec_dai->codec;
377 u16 reg;
378
379 switch (div_id) {
380 case WM8974_OPCLKDIV:
1e97f50b
MB
381 reg = snd_soc_read(codec, WM8974_GPIO) & 0x1cf;
382 snd_soc_write(codec, WM8974_GPIO, reg | div);
0a1bf553
MB
383 break;
384 case WM8974_MCLKDIV:
1e97f50b
MB
385 reg = snd_soc_read(codec, WM8974_CLOCK) & 0x11f;
386 snd_soc_write(codec, WM8974_CLOCK, reg | div);
0a1bf553 387 break;
0a1bf553 388 case WM8974_BCLKDIV:
1e97f50b
MB
389 reg = snd_soc_read(codec, WM8974_CLOCK) & 0x1e3;
390 snd_soc_write(codec, WM8974_CLOCK, reg | div);
0a1bf553
MB
391 break;
392 default:
393 return -EINVAL;
394 }
395
396 return 0;
397}
398
399static int wm8974_set_dai_fmt(struct snd_soc_dai *codec_dai,
400 unsigned int fmt)
401{
402 struct snd_soc_codec *codec = codec_dai->codec;
403 u16 iface = 0;
1e97f50b 404 u16 clk = snd_soc_read(codec, WM8974_CLOCK) & 0x1fe;
0a1bf553
MB
405
406 /* set master/slave audio interface */
407 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
408 case SND_SOC_DAIFMT_CBM_CFM:
409 clk |= 0x0001;
410 break;
411 case SND_SOC_DAIFMT_CBS_CFS:
412 break;
413 default:
414 return -EINVAL;
415 }
416
417 /* interface format */
418 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
419 case SND_SOC_DAIFMT_I2S:
420 iface |= 0x0010;
421 break;
422 case SND_SOC_DAIFMT_RIGHT_J:
423 break;
424 case SND_SOC_DAIFMT_LEFT_J:
425 iface |= 0x0008;
426 break;
427 case SND_SOC_DAIFMT_DSP_A:
428 iface |= 0x00018;
429 break;
430 default:
431 return -EINVAL;
432 }
433
434 /* clock inversion */
435 switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
436 case SND_SOC_DAIFMT_NB_NF:
437 break;
438 case SND_SOC_DAIFMT_IB_IF:
439 iface |= 0x0180;
440 break;
441 case SND_SOC_DAIFMT_IB_NF:
442 iface |= 0x0100;
443 break;
444 case SND_SOC_DAIFMT_NB_IF:
445 iface |= 0x0080;
446 break;
447 default:
448 return -EINVAL;
449 }
450
1e97f50b
MB
451 snd_soc_write(codec, WM8974_IFACE, iface);
452 snd_soc_write(codec, WM8974_CLOCK, clk);
0a1bf553
MB
453 return 0;
454}
455
456static int wm8974_pcm_hw_params(struct snd_pcm_substream *substream,
457 struct snd_pcm_hw_params *params,
458 struct snd_soc_dai *dai)
459{
460 struct snd_soc_codec *codec = dai->codec;
1e97f50b
MB
461 u16 iface = snd_soc_read(codec, WM8974_IFACE) & 0x19f;
462 u16 adn = snd_soc_read(codec, WM8974_ADD) & 0x1f1;
0a1bf553
MB
463
464 /* bit size */
465 switch (params_format(params)) {
466 case SNDRV_PCM_FORMAT_S16_LE:
467 break;
468 case SNDRV_PCM_FORMAT_S20_3LE:
469 iface |= 0x0020;
470 break;
471 case SNDRV_PCM_FORMAT_S24_LE:
472 iface |= 0x0040;
473 break;
474 case SNDRV_PCM_FORMAT_S32_LE:
475 iface |= 0x0060;
476 break;
477 }
478
479 /* filter coefficient */
480 switch (params_rate(params)) {
b3172f22 481 case 8000:
0a1bf553
MB
482 adn |= 0x5 << 1;
483 break;
b3172f22 484 case 11025:
0a1bf553
MB
485 adn |= 0x4 << 1;
486 break;
b3172f22 487 case 16000:
0a1bf553
MB
488 adn |= 0x3 << 1;
489 break;
b3172f22 490 case 22050:
0a1bf553
MB
491 adn |= 0x2 << 1;
492 break;
b3172f22 493 case 32000:
0a1bf553
MB
494 adn |= 0x1 << 1;
495 break;
b3172f22
GL
496 case 44100:
497 case 48000:
0a1bf553
MB
498 break;
499 }
500
1e97f50b
MB
501 snd_soc_write(codec, WM8974_IFACE, iface);
502 snd_soc_write(codec, WM8974_ADD, adn);
0a1bf553
MB
503 return 0;
504}
505
506static int wm8974_mute(struct snd_soc_dai *dai, int mute)
507{
508 struct snd_soc_codec *codec = dai->codec;
1e97f50b 509 u16 mute_reg = snd_soc_read(codec, WM8974_DAC) & 0xffbf;
0a1bf553 510
1a55b3f6 511 if (mute)
1e97f50b 512 snd_soc_write(codec, WM8974_DAC, mute_reg | 0x40);
0a1bf553 513 else
1e97f50b 514 snd_soc_write(codec, WM8974_DAC, mute_reg);
0a1bf553
MB
515 return 0;
516}
517
518/* liam need to make this lower power with dapm */
519static int wm8974_set_bias_level(struct snd_soc_codec *codec,
520 enum snd_soc_bias_level level)
521{
1e97f50b 522 u16 power1 = snd_soc_read(codec, WM8974_POWER1) & ~0x3;
df1ef7a3 523
0a1bf553
MB
524 switch (level) {
525 case SND_SOC_BIAS_ON:
0a1bf553 526 case SND_SOC_BIAS_PREPARE:
df1ef7a3 527 power1 |= 0x1; /* VMID 50k */
1e97f50b 528 snd_soc_write(codec, WM8974_POWER1, power1);
0a1bf553 529 break;
df1ef7a3 530
0a1bf553 531 case SND_SOC_BIAS_STANDBY:
df1ef7a3
MB
532 power1 |= WM8974_POWER1_BIASEN | WM8974_POWER1_BUFIOEN;
533
534 if (codec->bias_level == SND_SOC_BIAS_OFF) {
535 /* Initial cap charge at VMID 5k */
1e97f50b 536 snd_soc_write(codec, WM8974_POWER1, power1 | 0x3);
df1ef7a3
MB
537 mdelay(100);
538 }
539
540 power1 |= 0x2; /* VMID 500k */
1e97f50b 541 snd_soc_write(codec, WM8974_POWER1, power1);
0a1bf553 542 break;
df1ef7a3 543
0a1bf553 544 case SND_SOC_BIAS_OFF:
1e97f50b
MB
545 snd_soc_write(codec, WM8974_POWER1, 0);
546 snd_soc_write(codec, WM8974_POWER2, 0);
547 snd_soc_write(codec, WM8974_POWER3, 0);
0a1bf553
MB
548 break;
549 }
df1ef7a3 550
0a1bf553
MB
551 codec->bias_level = level;
552 return 0;
553}
554
1a55b3f6 555#define WM8974_RATES (SNDRV_PCM_RATE_8000_48000)
0a1bf553
MB
556
557#define WM8974_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
558 SNDRV_PCM_FMTBIT_S24_LE)
559
560static struct snd_soc_dai_ops wm8974_ops = {
561 .hw_params = wm8974_pcm_hw_params,
562 .digital_mute = wm8974_mute,
563 .set_fmt = wm8974_set_dai_fmt,
564 .set_clkdiv = wm8974_set_dai_clkdiv,
565 .set_pll = wm8974_set_dai_pll,
566};
567
568struct snd_soc_dai wm8974_dai = {
569 .name = "WM8974 HiFi",
570 .playback = {
571 .stream_name = "Playback",
572 .channels_min = 1,
33d81af4 573 .channels_max = 2, /* Only 1 channel of data */
0a1bf553
MB
574 .rates = WM8974_RATES,
575 .formats = WM8974_FORMATS,},
576 .capture = {
577 .stream_name = "Capture",
578 .channels_min = 1,
33d81af4 579 .channels_max = 2, /* Only 1 channel of data */
0a1bf553
MB
580 .rates = WM8974_RATES,
581 .formats = WM8974_FORMATS,},
582 .ops = &wm8974_ops,
cb11d39e 583 .symmetric_rates = 1,
0a1bf553
MB
584};
585EXPORT_SYMBOL_GPL(wm8974_dai);
586
587static int wm8974_suspend(struct platform_device *pdev, pm_message_t state)
588{
589 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
590 struct snd_soc_codec *codec = socdev->card->codec;
591
592 wm8974_set_bias_level(codec, SND_SOC_BIAS_OFF);
593 return 0;
594}
595
596static int wm8974_resume(struct platform_device *pdev)
597{
598 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
599 struct snd_soc_codec *codec = socdev->card->codec;
600 int i;
601 u8 data[2];
602 u16 *cache = codec->reg_cache;
603
604 /* Sync reg_cache with the hardware */
605 for (i = 0; i < ARRAY_SIZE(wm8974_reg); i++) {
606 data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
607 data[1] = cache[i] & 0x00ff;
608 codec->hw_write(codec->control_data, data, 2);
609 }
610 wm8974_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
611 wm8974_set_bias_level(codec, codec->suspend_bias_level);
612 return 0;
613}
614
4fcbbb67 615static int wm8974_probe(struct platform_device *pdev)
0a1bf553 616{
4fcbbb67
MB
617 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
618 struct snd_soc_codec *codec;
0a1bf553
MB
619 int ret = 0;
620
4fcbbb67
MB
621 if (wm8974_codec == NULL) {
622 dev_err(&pdev->dev, "Codec device not registered\n");
623 return -ENODEV;
624 }
0a1bf553 625
4fcbbb67
MB
626 socdev->card->codec = wm8974_codec;
627 codec = wm8974_codec;
0a1bf553
MB
628
629 /* register pcms */
630 ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
1a55b3f6 631 if (ret < 0) {
4fcbbb67 632 dev_err(codec->dev, "failed to create pcms: %d\n", ret);
0a1bf553
MB
633 goto pcm_err;
634 }
635
4fcbbb67
MB
636 snd_soc_add_controls(codec, wm8974_snd_controls,
637 ARRAY_SIZE(wm8974_snd_controls));
0a1bf553 638 wm8974_add_widgets(codec);
4fcbbb67 639
0a1bf553
MB
640 return ret;
641
0a1bf553 642pcm_err:
0a1bf553
MB
643 return ret;
644}
645
4fcbbb67
MB
646/* power down chip */
647static int wm8974_remove(struct platform_device *pdev)
648{
649 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
0a1bf553 650
4fcbbb67
MB
651 snd_soc_free_pcms(socdev);
652 snd_soc_dapm_free(socdev);
0a1bf553 653
4fcbbb67
MB
654 return 0;
655}
0a1bf553 656
4fcbbb67
MB
657struct snd_soc_codec_device soc_codec_dev_wm8974 = {
658 .probe = wm8974_probe,
659 .remove = wm8974_remove,
660 .suspend = wm8974_suspend,
661 .resume = wm8974_resume,
662};
663EXPORT_SYMBOL_GPL(soc_codec_dev_wm8974);
0a1bf553 664
4fcbbb67 665static __devinit int wm8974_register(struct wm8974_priv *wm8974)
0a1bf553 666{
0a1bf553 667 int ret;
4fcbbb67 668 struct snd_soc_codec *codec = &wm8974->codec;
0a1bf553 669
4fcbbb67
MB
670 if (wm8974_codec) {
671 dev_err(codec->dev, "Another WM8974 is registered\n");
672 return -EINVAL;
673 }
0a1bf553 674
4fcbbb67
MB
675 mutex_init(&codec->mutex);
676 INIT_LIST_HEAD(&codec->dapm_widgets);
677 INIT_LIST_HEAD(&codec->dapm_paths);
0a1bf553 678
b2c812e2 679 snd_soc_codec_set_drvdata(codec, wm8974);
4fcbbb67
MB
680 codec->name = "WM8974";
681 codec->owner = THIS_MODULE;
4fcbbb67
MB
682 codec->bias_level = SND_SOC_BIAS_OFF;
683 codec->set_bias_level = wm8974_set_bias_level;
684 codec->dai = &wm8974_dai;
685 codec->num_dai = 1;
686 codec->reg_cache_size = WM8974_CACHEREGNUM;
687 codec->reg_cache = &wm8974->reg_cache;
0a1bf553 688
1e97f50b
MB
689 ret = snd_soc_codec_set_cache_io(codec, 7, 9, SND_SOC_I2C);
690 if (ret < 0) {
691 dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
692 goto err;
693 }
694
4fcbbb67
MB
695 memcpy(codec->reg_cache, wm8974_reg, sizeof(wm8974_reg));
696
697 ret = wm8974_reset(codec);
0a1bf553 698 if (ret < 0) {
4fcbbb67 699 dev_err(codec->dev, "Failed to issue reset\n");
1e97f50b 700 goto err;
0a1bf553
MB
701 }
702
4fcbbb67
MB
703 wm8974_dai.dev = codec->dev;
704
705 wm8974_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
706
707 wm8974_codec = codec;
708
709 ret = snd_soc_register_codec(codec);
710 if (ret != 0) {
711 dev_err(codec->dev, "Failed to register codec: %d\n", ret);
1e97f50b 712 goto err;
0a1bf553 713 }
0a1bf553 714
4fcbbb67
MB
715 ret = snd_soc_register_dai(&wm8974_dai);
716 if (ret != 0) {
717 dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
1e97f50b 718 goto err_codec;
4fcbbb67 719 }
0a1bf553 720
0a1bf553 721 return 0;
1e97f50b
MB
722
723err_codec:
724 snd_soc_unregister_codec(codec);
725err:
726 kfree(wm8974);
727 return ret;
0a1bf553
MB
728}
729
4fcbbb67 730static __devexit void wm8974_unregister(struct wm8974_priv *wm8974)
0a1bf553 731{
4fcbbb67
MB
732 wm8974_set_bias_level(&wm8974->codec, SND_SOC_BIAS_OFF);
733 snd_soc_unregister_dai(&wm8974_dai);
734 snd_soc_unregister_codec(&wm8974->codec);
735 kfree(wm8974);
736 wm8974_codec = NULL;
0a1bf553
MB
737}
738
4fcbbb67
MB
739static __devinit int wm8974_i2c_probe(struct i2c_client *i2c,
740 const struct i2c_device_id *id)
0a1bf553 741{
4fcbbb67 742 struct wm8974_priv *wm8974;
0a1bf553 743 struct snd_soc_codec *codec;
0a1bf553 744
4fcbbb67
MB
745 wm8974 = kzalloc(sizeof(struct wm8974_priv), GFP_KERNEL);
746 if (wm8974 == NULL)
0a1bf553
MB
747 return -ENOMEM;
748
4fcbbb67
MB
749 codec = &wm8974->codec;
750 codec->hw_write = (hw_write_t)i2c_master_send;
0a1bf553 751
4fcbbb67
MB
752 i2c_set_clientdata(i2c, wm8974);
753 codec->control_data = i2c;
0a1bf553 754
4fcbbb67 755 codec->dev = &i2c->dev;
0a1bf553 756
4fcbbb67
MB
757 return wm8974_register(wm8974);
758}
0a1bf553 759
4fcbbb67
MB
760static __devexit int wm8974_i2c_remove(struct i2c_client *client)
761{
762 struct wm8974_priv *wm8974 = i2c_get_clientdata(client);
763 wm8974_unregister(wm8974);
0a1bf553
MB
764 return 0;
765}
766
4fcbbb67
MB
767static const struct i2c_device_id wm8974_i2c_id[] = {
768 { "wm8974", 0 },
769 { }
770};
771MODULE_DEVICE_TABLE(i2c, wm8974_i2c_id);
772
773static struct i2c_driver wm8974_i2c_driver = {
774 .driver = {
8b83a193 775 .name = "WM8974",
4fcbbb67
MB
776 .owner = THIS_MODULE,
777 },
778 .probe = wm8974_i2c_probe,
779 .remove = __devexit_p(wm8974_i2c_remove),
780 .id_table = wm8974_i2c_id,
0a1bf553 781};
0a1bf553
MB
782
783static int __init wm8974_modinit(void)
784{
4fcbbb67 785 return i2c_add_driver(&wm8974_i2c_driver);
0a1bf553
MB
786}
787module_init(wm8974_modinit);
788
789static void __exit wm8974_exit(void)
790{
4fcbbb67 791 i2c_del_driver(&wm8974_i2c_driver);
0a1bf553
MB
792}
793module_exit(wm8974_exit);
794
795MODULE_DESCRIPTION("ASoC WM8974 driver");
796MODULE_AUTHOR("Liam Girdwood");
797MODULE_LICENSE("GPL");