blob: 397432a736ef057a95ad5422af5a1ab4c4741e89 [file] [log] [blame]
Mark Brown0a1bf552009-05-23 11:18:41 +01001/*
2 * wm8974.c -- WM8974 ALSA Soc Audio driver
3 *
4 * Copyright 2006 Wolfson Microelectronics PLC.
5 *
Mark Brown4fcbbb62009-05-23 12:27:03 +01006 * Author: Liam Girdwood <linux@wolfsonmicro.com>
Mark Brown0a1bf552009-05-23 11:18:41 +01007 *
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
Mark Brown0a1bf552009-05-23 11:18:41 +010031static const u16 wm8974_reg[WM8974_CACHEREGNUM] = {
Mark Brown1a55b3f2009-05-23 11:31:40 +010032 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,
Mark Brown0a1bf552009-05-23 11:18:41 +010047};
48
Mark Brown4fcbbb62009-05-23 12:27:03 +010049struct wm8974_priv {
50 struct snd_soc_codec codec;
51 u16 reg_cache[WM8974_CACHEREGNUM];
52};
53
54static struct snd_soc_codec *wm8974_codec;
55
Mark Brown0a1bf552009-05-23 11:18:41 +010056/*
57 * read wm8974 register cache
58 */
Mark Brown1a55b3f2009-05-23 11:31:40 +010059static inline unsigned int wm8974_read_reg_cache(struct snd_soc_codec *codec,
Mark Brown0a1bf552009-05-23 11:18:41 +010060 unsigned int reg)
61{
62 u16 *cache = codec->reg_cache;
63 if (reg == WM8974_RESET)
64 return 0;
65 if (reg >= WM8974_CACHEREGNUM)
66 return -1;
67 return cache[reg];
68}
69
70/*
71 * write wm8974 register cache
72 */
73static inline void wm8974_write_reg_cache(struct snd_soc_codec *codec,
74 u16 reg, unsigned int value)
75{
76 u16 *cache = codec->reg_cache;
77 if (reg >= WM8974_CACHEREGNUM)
78 return;
79 cache[reg] = value;
80}
81
82/*
83 * write to the WM8974 register space
84 */
85static int wm8974_write(struct snd_soc_codec *codec, unsigned int reg,
86 unsigned int value)
87{
88 u8 data[2];
89
90 /* data is
91 * D15..D9 WM8974 register offset
92 * D8...D0 register data
93 */
94 data[0] = (reg << 1) | ((value >> 8) & 0x0001);
95 data[1] = value & 0x00ff;
96
Mark Brown1a55b3f2009-05-23 11:31:40 +010097 wm8974_write_reg_cache(codec, reg, value);
Mark Brown0a1bf552009-05-23 11:18:41 +010098 if (codec->hw_write(codec->control_data, data, 2) == 2)
99 return 0;
100 else
101 return -EIO;
102}
103
104#define wm8974_reset(c) wm8974_write(c, WM8974_RESET, 0)
105
106static const char *wm8974_companding[] = {"Off", "NC", "u-law", "A-law" };
107static const char *wm8974_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz" };
108static const char *wm8974_eqmode[] = {"Capture", "Playback" };
109static const char *wm8974_bw[] = {"Narrow", "Wide" };
110static const char *wm8974_eq1[] = {"80Hz", "105Hz", "135Hz", "175Hz" };
111static const char *wm8974_eq2[] = {"230Hz", "300Hz", "385Hz", "500Hz" };
112static const char *wm8974_eq3[] = {"650Hz", "850Hz", "1.1kHz", "1.4kHz" };
113static const char *wm8974_eq4[] = {"1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz" };
114static const char *wm8974_eq5[] = {"5.3kHz", "6.9kHz", "9kHz", "11.7kHz" };
115static const char *wm8974_alc[] = {"ALC", "Limiter" };
116
117static const struct soc_enum wm8974_enum[] = {
118 SOC_ENUM_SINGLE(WM8974_COMP, 1, 4, wm8974_companding), /* adc */
119 SOC_ENUM_SINGLE(WM8974_COMP, 3, 4, wm8974_companding), /* dac */
120 SOC_ENUM_SINGLE(WM8974_DAC, 4, 4, wm8974_deemp),
121 SOC_ENUM_SINGLE(WM8974_EQ1, 8, 2, wm8974_eqmode),
122
123 SOC_ENUM_SINGLE(WM8974_EQ1, 5, 4, wm8974_eq1),
124 SOC_ENUM_SINGLE(WM8974_EQ2, 8, 2, wm8974_bw),
125 SOC_ENUM_SINGLE(WM8974_EQ2, 5, 4, wm8974_eq2),
126 SOC_ENUM_SINGLE(WM8974_EQ3, 8, 2, wm8974_bw),
127
128 SOC_ENUM_SINGLE(WM8974_EQ3, 5, 4, wm8974_eq3),
129 SOC_ENUM_SINGLE(WM8974_EQ4, 8, 2, wm8974_bw),
130 SOC_ENUM_SINGLE(WM8974_EQ4, 5, 4, wm8974_eq4),
131 SOC_ENUM_SINGLE(WM8974_EQ5, 8, 2, wm8974_bw),
132
133 SOC_ENUM_SINGLE(WM8974_EQ5, 5, 4, wm8974_eq5),
134 SOC_ENUM_SINGLE(WM8974_ALC3, 8, 2, wm8974_alc),
135};
136
137static const struct snd_kcontrol_new wm8974_snd_controls[] = {
138
139SOC_SINGLE("Digital Loopback Switch", WM8974_COMP, 0, 1, 0),
140
141SOC_ENUM("DAC Companding", wm8974_enum[1]),
142SOC_ENUM("ADC Companding", wm8974_enum[0]),
143
144SOC_ENUM("Playback De-emphasis", wm8974_enum[2]),
145SOC_SINGLE("DAC Inversion Switch", WM8974_DAC, 0, 1, 0),
146
147SOC_SINGLE("PCM Volume", WM8974_DACVOL, 0, 127, 0),
148
149SOC_SINGLE("High Pass Filter Switch", WM8974_ADC, 8, 1, 0),
150SOC_SINGLE("High Pass Cut Off", WM8974_ADC, 4, 7, 0),
151SOC_SINGLE("ADC Inversion Switch", WM8974_COMP, 0, 1, 0),
152
153SOC_SINGLE("Capture Volume", WM8974_ADCVOL, 0, 127, 0),
154
155SOC_ENUM("Equaliser Function", wm8974_enum[3]),
156SOC_ENUM("EQ1 Cut Off", wm8974_enum[4]),
157SOC_SINGLE("EQ1 Volume", WM8974_EQ1, 0, 31, 1),
158
159SOC_ENUM("Equaliser EQ2 Bandwith", wm8974_enum[5]),
160SOC_ENUM("EQ2 Cut Off", wm8974_enum[6]),
161SOC_SINGLE("EQ2 Volume", WM8974_EQ2, 0, 31, 1),
162
163SOC_ENUM("Equaliser EQ3 Bandwith", wm8974_enum[7]),
164SOC_ENUM("EQ3 Cut Off", wm8974_enum[8]),
165SOC_SINGLE("EQ3 Volume", WM8974_EQ3, 0, 31, 1),
166
167SOC_ENUM("Equaliser EQ4 Bandwith", wm8974_enum[9]),
168SOC_ENUM("EQ4 Cut Off", wm8974_enum[10]),
169SOC_SINGLE("EQ4 Volume", WM8974_EQ4, 0, 31, 1),
170
171SOC_ENUM("Equaliser EQ5 Bandwith", wm8974_enum[11]),
172SOC_ENUM("EQ5 Cut Off", wm8974_enum[12]),
173SOC_SINGLE("EQ5 Volume", WM8974_EQ5, 0, 31, 1),
174
175SOC_SINGLE("DAC Playback Limiter Switch", WM8974_DACLIM1, 8, 1, 0),
176SOC_SINGLE("DAC Playback Limiter Decay", WM8974_DACLIM1, 4, 15, 0),
177SOC_SINGLE("DAC Playback Limiter Attack", WM8974_DACLIM1, 0, 15, 0),
178
179SOC_SINGLE("DAC Playback Limiter Threshold", WM8974_DACLIM2, 4, 7, 0),
180SOC_SINGLE("DAC Playback Limiter Boost", WM8974_DACLIM2, 0, 15, 0),
181
182SOC_SINGLE("ALC Enable Switch", WM8974_ALC1, 8, 1, 0),
183SOC_SINGLE("ALC Capture Max Gain", WM8974_ALC1, 3, 7, 0),
184SOC_SINGLE("ALC Capture Min Gain", WM8974_ALC1, 0, 7, 0),
185
186SOC_SINGLE("ALC Capture ZC Switch", WM8974_ALC2, 8, 1, 0),
187SOC_SINGLE("ALC Capture Hold", WM8974_ALC2, 4, 7, 0),
188SOC_SINGLE("ALC Capture Target", WM8974_ALC2, 0, 15, 0),
189
190SOC_ENUM("ALC Capture Mode", wm8974_enum[13]),
191SOC_SINGLE("ALC Capture Decay", WM8974_ALC3, 4, 15, 0),
192SOC_SINGLE("ALC Capture Attack", WM8974_ALC3, 0, 15, 0),
193
194SOC_SINGLE("ALC Capture Noise Gate Switch", WM8974_NGATE, 3, 1, 0),
195SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8974_NGATE, 0, 7, 0),
196
197SOC_SINGLE("Capture PGA ZC Switch", WM8974_INPPGA, 7, 1, 0),
198SOC_SINGLE("Capture PGA Volume", WM8974_INPPGA, 0, 63, 0),
199
200SOC_SINGLE("Speaker Playback ZC Switch", WM8974_SPKVOL, 7, 1, 0),
201SOC_SINGLE("Speaker Playback Switch", WM8974_SPKVOL, 6, 1, 1),
202SOC_SINGLE("Speaker Playback Volume", WM8974_SPKVOL, 0, 63, 0),
203
204SOC_SINGLE("Capture Boost(+20dB)", WM8974_ADCBOOST, 8, 1, 0),
205SOC_SINGLE("Mono Playback Switch", WM8974_MONOMIX, 6, 1, 0),
206};
207
208/* add non dapm controls */
209static int wm8974_add_controls(struct snd_soc_codec *codec)
210{
211 int err, i;
212
213 for (i = 0; i < ARRAY_SIZE(wm8974_snd_controls); i++) {
214 err = snd_ctl_add(codec->card,
Mark Brown1a55b3f2009-05-23 11:31:40 +0100215 snd_soc_cnew(&wm8974_snd_controls[i],
216 codec, NULL));
Mark Brown0a1bf552009-05-23 11:18:41 +0100217 if (err < 0)
218 return err;
219 }
220
221 return 0;
222}
223
224/* Speaker Output Mixer */
225static const struct snd_kcontrol_new wm8974_speaker_mixer_controls[] = {
226SOC_DAPM_SINGLE("Line Bypass Switch", WM8974_SPKMIX, 1, 1, 0),
227SOC_DAPM_SINGLE("Aux Playback Switch", WM8974_SPKMIX, 5, 1, 0),
228SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_SPKMIX, 0, 1, 1),
229};
230
231/* Mono Output Mixer */
232static const struct snd_kcontrol_new wm8974_mono_mixer_controls[] = {
233SOC_DAPM_SINGLE("Line Bypass Switch", WM8974_MONOMIX, 1, 1, 0),
234SOC_DAPM_SINGLE("Aux Playback Switch", WM8974_MONOMIX, 2, 1, 0),
235SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_MONOMIX, 0, 1, 1),
236};
237
238/* AUX Input boost vol */
239static const struct snd_kcontrol_new wm8974_aux_boost_controls =
240SOC_DAPM_SINGLE("Aux Volume", WM8974_ADCBOOST, 0, 7, 0);
241
242/* Mic Input boost vol */
243static const struct snd_kcontrol_new wm8974_mic_boost_controls =
244SOC_DAPM_SINGLE("Mic Volume", WM8974_ADCBOOST, 4, 7, 0);
245
246/* Capture boost switch */
247static const struct snd_kcontrol_new wm8974_capture_boost_controls =
248SOC_DAPM_SINGLE("Capture Boost Switch", WM8974_INPPGA, 6, 1, 0);
249
250/* Aux In to PGA */
251static const struct snd_kcontrol_new wm8974_aux_capture_boost_controls =
252SOC_DAPM_SINGLE("Aux Capture Boost Switch", WM8974_INPPGA, 2, 1, 0);
253
254/* Mic P In to PGA */
255static const struct snd_kcontrol_new wm8974_micp_capture_boost_controls =
256SOC_DAPM_SINGLE("Mic P Capture Boost Switch", WM8974_INPPGA, 0, 1, 0);
257
258/* Mic N In to PGA */
259static const struct snd_kcontrol_new wm8974_micn_capture_boost_controls =
260SOC_DAPM_SINGLE("Mic N Capture Boost Switch", WM8974_INPPGA, 1, 1, 0);
261
262static const struct snd_soc_dapm_widget wm8974_dapm_widgets[] = {
263SND_SOC_DAPM_MIXER("Speaker Mixer", WM8974_POWER3, 2, 0,
264 &wm8974_speaker_mixer_controls[0],
265 ARRAY_SIZE(wm8974_speaker_mixer_controls)),
266SND_SOC_DAPM_MIXER("Mono Mixer", WM8974_POWER3, 3, 0,
267 &wm8974_mono_mixer_controls[0],
268 ARRAY_SIZE(wm8974_mono_mixer_controls)),
269SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8974_POWER3, 0, 0),
270SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8974_POWER3, 0, 0),
271SND_SOC_DAPM_PGA("Aux Input", WM8974_POWER1, 6, 0, NULL, 0),
272SND_SOC_DAPM_PGA("SpkN Out", WM8974_POWER3, 5, 0, NULL, 0),
273SND_SOC_DAPM_PGA("SpkP Out", WM8974_POWER3, 6, 0, NULL, 0),
274SND_SOC_DAPM_PGA("Mono Out", WM8974_POWER3, 7, 0, NULL, 0),
275SND_SOC_DAPM_PGA("Mic PGA", WM8974_POWER2, 2, 0, NULL, 0),
276
277SND_SOC_DAPM_PGA("Aux Boost", SND_SOC_NOPM, 0, 0,
278 &wm8974_aux_boost_controls, 1),
279SND_SOC_DAPM_PGA("Mic Boost", SND_SOC_NOPM, 0, 0,
280 &wm8974_mic_boost_controls, 1),
281SND_SOC_DAPM_SWITCH("Capture Boost", SND_SOC_NOPM, 0, 0,
282 &wm8974_capture_boost_controls),
283
284SND_SOC_DAPM_MIXER("Boost Mixer", WM8974_POWER2, 4, 0, NULL, 0),
285
286SND_SOC_DAPM_MICBIAS("Mic Bias", WM8974_POWER1, 4, 0),
287
288SND_SOC_DAPM_INPUT("MICN"),
289SND_SOC_DAPM_INPUT("MICP"),
290SND_SOC_DAPM_INPUT("AUX"),
291SND_SOC_DAPM_OUTPUT("MONOOUT"),
292SND_SOC_DAPM_OUTPUT("SPKOUTP"),
293SND_SOC_DAPM_OUTPUT("SPKOUTN"),
294};
295
296static const struct snd_soc_dapm_route audio_map[] = {
297 /* Mono output mixer */
298 {"Mono Mixer", "PCM Playback Switch", "DAC"},
299 {"Mono Mixer", "Aux Playback Switch", "Aux Input"},
300 {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"},
301
302 /* Speaker output mixer */
303 {"Speaker Mixer", "PCM Playback Switch", "DAC"},
304 {"Speaker Mixer", "Aux Playback Switch", "Aux Input"},
305 {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"},
306
307 /* Outputs */
308 {"Mono Out", NULL, "Mono Mixer"},
309 {"MONOOUT", NULL, "Mono Out"},
310 {"SpkN Out", NULL, "Speaker Mixer"},
311 {"SpkP Out", NULL, "Speaker Mixer"},
312 {"SPKOUTN", NULL, "SpkN Out"},
313 {"SPKOUTP", NULL, "SpkP Out"},
314
315 /* Boost Mixer */
316 {"Boost Mixer", NULL, "ADC"},
317 {"Capture Boost Switch", "Aux Capture Boost Switch", "AUX"},
318 {"Aux Boost", "Aux Volume", "Boost Mixer"},
319 {"Capture Boost", "Capture Switch", "Boost Mixer"},
320 {"Mic Boost", "Mic Volume", "Boost Mixer"},
321
322 /* Inputs */
323 {"MICP", NULL, "Mic Boost"},
324 {"MICN", NULL, "Mic PGA"},
325 {"Mic PGA", NULL, "Capture Boost"},
326 {"AUX", NULL, "Aux Input"},
327};
328
329static int wm8974_add_widgets(struct snd_soc_codec *codec)
330{
331 snd_soc_dapm_new_controls(codec, wm8974_dapm_widgets,
332 ARRAY_SIZE(wm8974_dapm_widgets));
333
334 snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
335
336 snd_soc_dapm_new_widgets(codec);
337 return 0;
338}
339
340struct pll_ {
341 unsigned int in_hz, out_hz;
342 unsigned int pre:4; /* prescale - 1 */
343 unsigned int n:4;
344 unsigned int k;
345};
346
347static struct pll_ pll[] = {
Mark Brown1a55b3f2009-05-23 11:31:40 +0100348 { 12000000, 11289600, 0, 7, 0x86c220 },
349 { 12000000, 12288000, 0, 8, 0x3126e8 },
350 { 13000000, 11289600, 0, 6, 0xf28bd4 },
351 { 13000000, 12288000, 0, 7, 0x8fd525 },
352 { 12288000, 11289600, 0, 7, 0x59999a },
353 { 11289600, 12288000, 0, 8, 0x80dee9 },
354 { 25000000, 11289600, 1, 7, 0x39B024 },
355 { 25000000, 24576000, 1, 7, 0xdd4413 }
Mark Brown0a1bf552009-05-23 11:18:41 +0100356};
357
358static int wm8974_set_dai_pll(struct snd_soc_dai *codec_dai,
359 int pll_id, unsigned int freq_in, unsigned int freq_out)
360{
361 struct snd_soc_codec *codec = codec_dai->codec;
362 int i;
363 u16 reg;
364
Mark Brown1a55b3f2009-05-23 11:31:40 +0100365 if (freq_in == 0 || freq_out == 0) {
Mark Brown0a1bf552009-05-23 11:18:41 +0100366 reg = wm8974_read_reg_cache(codec, WM8974_POWER1);
367 wm8974_write(codec, WM8974_POWER1, reg & 0x1df);
368 return 0;
369 }
370
Mark Brown1a55b3f2009-05-23 11:31:40 +0100371 for (i = 0; i < ARRAY_SIZE(pll); i++) {
Mark Brown0a1bf552009-05-23 11:18:41 +0100372 if (freq_in == pll[i].in_hz && freq_out == pll[i].out_hz) {
Mark Brown1a55b3f2009-05-23 11:31:40 +0100373 wm8974_write(codec, WM8974_PLLN,
374 (pll[i].pre << 4) | pll[i].n);
Mark Brown0a1bf552009-05-23 11:18:41 +0100375 wm8974_write(codec, WM8974_PLLK1, pll[i].k >> 18);
Mark Brown1a55b3f2009-05-23 11:31:40 +0100376 wm8974_write(codec, WM8974_PLLK2,
377 (pll[i].k >> 9) & 0x1ff);
Mark Brown0a1bf552009-05-23 11:18:41 +0100378 wm8974_write(codec, WM8974_PLLK3, pll[i].k & 0x1ff);
379 reg = wm8974_read_reg_cache(codec, WM8974_POWER1);
380 wm8974_write(codec, WM8974_POWER1, reg | 0x020);
381 return 0;
382 }
383 }
Mark Brown1a55b3f2009-05-23 11:31:40 +0100384
Mark Brown0a1bf552009-05-23 11:18:41 +0100385 return -EINVAL;
386}
387
388/*
389 * Configure WM8974 clock dividers.
390 */
391static int wm8974_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
392 int div_id, int div)
393{
394 struct snd_soc_codec *codec = codec_dai->codec;
395 u16 reg;
396
397 switch (div_id) {
398 case WM8974_OPCLKDIV:
Mark Brown1a55b3f2009-05-23 11:31:40 +0100399 reg = wm8974_read_reg_cache(codec, WM8974_GPIO) & 0x1cf;
Mark Brown0a1bf552009-05-23 11:18:41 +0100400 wm8974_write(codec, WM8974_GPIO, reg | div);
401 break;
402 case WM8974_MCLKDIV:
403 reg = wm8974_read_reg_cache(codec, WM8974_CLOCK) & 0x11f;
404 wm8974_write(codec, WM8974_CLOCK, reg | div);
405 break;
406 case WM8974_ADCCLK:
407 reg = wm8974_read_reg_cache(codec, WM8974_ADC) & 0x1f7;
408 wm8974_write(codec, WM8974_ADC, reg | div);
409 break;
410 case WM8974_DACCLK:
411 reg = wm8974_read_reg_cache(codec, WM8974_DAC) & 0x1f7;
412 wm8974_write(codec, WM8974_DAC, reg | div);
413 break;
414 case WM8974_BCLKDIV:
415 reg = wm8974_read_reg_cache(codec, WM8974_CLOCK) & 0x1e3;
416 wm8974_write(codec, WM8974_CLOCK, reg | div);
417 break;
418 default:
419 return -EINVAL;
420 }
421
422 return 0;
423}
424
425static int wm8974_set_dai_fmt(struct snd_soc_dai *codec_dai,
426 unsigned int fmt)
427{
428 struct snd_soc_codec *codec = codec_dai->codec;
429 u16 iface = 0;
430 u16 clk = wm8974_read_reg_cache(codec, WM8974_CLOCK) & 0x1fe;
431
432 /* set master/slave audio interface */
433 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
434 case SND_SOC_DAIFMT_CBM_CFM:
435 clk |= 0x0001;
436 break;
437 case SND_SOC_DAIFMT_CBS_CFS:
438 break;
439 default:
440 return -EINVAL;
441 }
442
443 /* interface format */
444 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
445 case SND_SOC_DAIFMT_I2S:
446 iface |= 0x0010;
447 break;
448 case SND_SOC_DAIFMT_RIGHT_J:
449 break;
450 case SND_SOC_DAIFMT_LEFT_J:
451 iface |= 0x0008;
452 break;
453 case SND_SOC_DAIFMT_DSP_A:
454 iface |= 0x00018;
455 break;
456 default:
457 return -EINVAL;
458 }
459
460 /* clock inversion */
461 switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
462 case SND_SOC_DAIFMT_NB_NF:
463 break;
464 case SND_SOC_DAIFMT_IB_IF:
465 iface |= 0x0180;
466 break;
467 case SND_SOC_DAIFMT_IB_NF:
468 iface |= 0x0100;
469 break;
470 case SND_SOC_DAIFMT_NB_IF:
471 iface |= 0x0080;
472 break;
473 default:
474 return -EINVAL;
475 }
476
477 wm8974_write(codec, WM8974_IFACE, iface);
478 wm8974_write(codec, WM8974_CLOCK, clk);
479 return 0;
480}
481
482static int wm8974_pcm_hw_params(struct snd_pcm_substream *substream,
483 struct snd_pcm_hw_params *params,
484 struct snd_soc_dai *dai)
485{
486 struct snd_soc_codec *codec = dai->codec;
487 u16 iface = wm8974_read_reg_cache(codec, WM8974_IFACE) & 0x19f;
488 u16 adn = wm8974_read_reg_cache(codec, WM8974_ADD) & 0x1f1;
489
490 /* bit size */
491 switch (params_format(params)) {
492 case SNDRV_PCM_FORMAT_S16_LE:
493 break;
494 case SNDRV_PCM_FORMAT_S20_3LE:
495 iface |= 0x0020;
496 break;
497 case SNDRV_PCM_FORMAT_S24_LE:
498 iface |= 0x0040;
499 break;
500 case SNDRV_PCM_FORMAT_S32_LE:
501 iface |= 0x0060;
502 break;
503 }
504
505 /* filter coefficient */
506 switch (params_rate(params)) {
507 case SNDRV_PCM_RATE_8000:
508 adn |= 0x5 << 1;
509 break;
510 case SNDRV_PCM_RATE_11025:
511 adn |= 0x4 << 1;
512 break;
513 case SNDRV_PCM_RATE_16000:
514 adn |= 0x3 << 1;
515 break;
516 case SNDRV_PCM_RATE_22050:
517 adn |= 0x2 << 1;
518 break;
519 case SNDRV_PCM_RATE_32000:
520 adn |= 0x1 << 1;
521 break;
522 case SNDRV_PCM_RATE_44100:
523 break;
524 }
525
526 wm8974_write(codec, WM8974_IFACE, iface);
527 wm8974_write(codec, WM8974_ADD, adn);
528 return 0;
529}
530
531static int wm8974_mute(struct snd_soc_dai *dai, int mute)
532{
533 struct snd_soc_codec *codec = dai->codec;
534 u16 mute_reg = wm8974_read_reg_cache(codec, WM8974_DAC) & 0xffbf;
535
Mark Brown1a55b3f2009-05-23 11:31:40 +0100536 if (mute)
Mark Brown0a1bf552009-05-23 11:18:41 +0100537 wm8974_write(codec, WM8974_DAC, mute_reg | 0x40);
538 else
539 wm8974_write(codec, WM8974_DAC, mute_reg);
540 return 0;
541}
542
543/* liam need to make this lower power with dapm */
544static int wm8974_set_bias_level(struct snd_soc_codec *codec,
545 enum snd_soc_bias_level level)
546{
547 switch (level) {
548 case SND_SOC_BIAS_ON:
549 wm8974_write(codec, WM8974_POWER1, 0x1ff);
550 wm8974_write(codec, WM8974_POWER2, 0x1ff);
551 wm8974_write(codec, WM8974_POWER3, 0x1ff);
552 break;
553 case SND_SOC_BIAS_PREPARE:
554 break;
555 case SND_SOC_BIAS_STANDBY:
556 break;
557 case SND_SOC_BIAS_OFF:
558 wm8974_write(codec, WM8974_POWER1, 0x0);
559 wm8974_write(codec, WM8974_POWER2, 0x0);
560 wm8974_write(codec, WM8974_POWER3, 0x0);
561 break;
562 }
563 codec->bias_level = level;
564 return 0;
565}
566
Mark Brown1a55b3f2009-05-23 11:31:40 +0100567#define WM8974_RATES (SNDRV_PCM_RATE_8000_48000)
Mark Brown0a1bf552009-05-23 11:18:41 +0100568
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
Mark Brown4fcbbb62009-05-23 12:27:03 +0100626static int wm8974_probe(struct platform_device *pdev)
Mark Brown0a1bf552009-05-23 11:18:41 +0100627{
Mark Brown4fcbbb62009-05-23 12:27:03 +0100628 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
629 struct snd_soc_codec *codec;
Mark Brown0a1bf552009-05-23 11:18:41 +0100630 int ret = 0;
631
Mark Brown4fcbbb62009-05-23 12:27:03 +0100632 if (wm8974_codec == NULL) {
633 dev_err(&pdev->dev, "Codec device not registered\n");
634 return -ENODEV;
635 }
Mark Brown0a1bf552009-05-23 11:18:41 +0100636
Mark Brown4fcbbb62009-05-23 12:27:03 +0100637 socdev->card->codec = wm8974_codec;
638 codec = wm8974_codec;
Mark Brown0a1bf552009-05-23 11:18:41 +0100639
640 /* register pcms */
641 ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
Mark Brown1a55b3f2009-05-23 11:31:40 +0100642 if (ret < 0) {
Mark Brown4fcbbb62009-05-23 12:27:03 +0100643 dev_err(codec->dev, "failed to create pcms: %d\n", ret);
Mark Brown0a1bf552009-05-23 11:18:41 +0100644 goto pcm_err;
645 }
646
Mark Brown4fcbbb62009-05-23 12:27:03 +0100647 snd_soc_add_controls(codec, wm8974_snd_controls,
648 ARRAY_SIZE(wm8974_snd_controls));
Mark Brown0a1bf552009-05-23 11:18:41 +0100649 wm8974_add_widgets(codec);
650 ret = snd_soc_init_card(socdev);
651 if (ret < 0) {
Mark Brown4fcbbb62009-05-23 12:27:03 +0100652 dev_err(codec->dev, "failed to register card: %d\n", ret);
Mark Brown0a1bf552009-05-23 11:18:41 +0100653 goto card_err;
654 }
Mark Brown4fcbbb62009-05-23 12:27:03 +0100655
Mark Brown0a1bf552009-05-23 11:18:41 +0100656 return ret;
657
658card_err:
659 snd_soc_free_pcms(socdev);
660 snd_soc_dapm_free(socdev);
661pcm_err:
Mark Brown0a1bf552009-05-23 11:18:41 +0100662 return ret;
663}
664
665/* power down chip */
666static int wm8974_remove(struct platform_device *pdev)
667{
668 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
Mark Brown0a1bf552009-05-23 11:18:41 +0100669
670 snd_soc_free_pcms(socdev);
671 snd_soc_dapm_free(socdev);
Mark Brown0a1bf552009-05-23 11:18:41 +0100672
673 return 0;
674}
675
676struct snd_soc_codec_device soc_codec_dev_wm8974 = {
677 .probe = wm8974_probe,
678 .remove = wm8974_remove,
679 .suspend = wm8974_suspend,
680 .resume = wm8974_resume,
681};
682EXPORT_SYMBOL_GPL(soc_codec_dev_wm8974);
683
Mark Brown4fcbbb62009-05-23 12:27:03 +0100684static __devinit int wm8974_register(struct wm8974_priv *wm8974)
685{
686 int ret;
687 struct snd_soc_codec *codec = &wm8974->codec;
688
689 if (wm8974_codec) {
690 dev_err(codec->dev, "Another WM8974 is registered\n");
691 return -EINVAL;
692 }
693
694 mutex_init(&codec->mutex);
695 INIT_LIST_HEAD(&codec->dapm_widgets);
696 INIT_LIST_HEAD(&codec->dapm_paths);
697
698 codec->private_data = wm8974;
699 codec->name = "WM8974";
700 codec->owner = THIS_MODULE;
701 codec->read = wm8974_read_reg_cache;
702 codec->write = wm8974_write;
703 codec->bias_level = SND_SOC_BIAS_OFF;
704 codec->set_bias_level = wm8974_set_bias_level;
705 codec->dai = &wm8974_dai;
706 codec->num_dai = 1;
707 codec->reg_cache_size = WM8974_CACHEREGNUM;
708 codec->reg_cache = &wm8974->reg_cache;
709
710 memcpy(codec->reg_cache, wm8974_reg, sizeof(wm8974_reg));
711
712 ret = wm8974_reset(codec);
713 if (ret < 0) {
714 dev_err(codec->dev, "Failed to issue reset\n");
715 return ret;
716 }
717
718 wm8974_dai.dev = codec->dev;
719
720 wm8974_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
721
722 wm8974_codec = codec;
723
724 ret = snd_soc_register_codec(codec);
725 if (ret != 0) {
726 dev_err(codec->dev, "Failed to register codec: %d\n", ret);
727 return ret;
728 }
729
730 ret = snd_soc_register_dai(&wm8974_dai);
731 if (ret != 0) {
732 dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
733 snd_soc_unregister_codec(codec);
734 return ret;
735 }
736
737 return 0;
738}
739
740static __devexit void wm8974_unregister(struct wm8974_priv *wm8974)
741{
742 wm8974_set_bias_level(&wm8974->codec, SND_SOC_BIAS_OFF);
743 snd_soc_unregister_dai(&wm8974_dai);
744 snd_soc_unregister_codec(&wm8974->codec);
745 kfree(wm8974);
746 wm8974_codec = NULL;
747}
748
749static __devinit int wm8974_i2c_probe(struct i2c_client *i2c,
750 const struct i2c_device_id *id)
751{
752 struct wm8974_priv *wm8974;
753 struct snd_soc_codec *codec;
754
755 wm8974 = kzalloc(sizeof(struct wm8974_priv), GFP_KERNEL);
756 if (wm8974 == NULL)
757 return -ENOMEM;
758
759 codec = &wm8974->codec;
760 codec->hw_write = (hw_write_t)i2c_master_send;
761
762 i2c_set_clientdata(i2c, wm8974);
763 codec->control_data = i2c;
764
765 codec->dev = &i2c->dev;
766
767 return wm8974_register(wm8974);
768}
769
770static __devexit int wm8974_i2c_remove(struct i2c_client *client)
771{
772 struct wm8974_priv *wm8974 = i2c_get_clientdata(client);
773 wm8974_unregister(wm8974);
774 return 0;
775}
776
777static const struct i2c_device_id wm8974_i2c_id[] = {
778 { "wm8974", 0 },
779 { }
780};
781MODULE_DEVICE_TABLE(i2c, wm8974_i2c_id);
782
783static struct i2c_driver wm8974_i2c_driver = {
784 .driver = {
785 .name = "WM8974 I2C Codec",
786 .owner = THIS_MODULE,
787 },
788 .probe = wm8974_i2c_probe,
789 .remove = __devexit_p(wm8974_i2c_remove),
790 .id_table = wm8974_i2c_id,
791};
792
Mark Brown0a1bf552009-05-23 11:18:41 +0100793static int __init wm8974_modinit(void)
794{
Mark Brown4fcbbb62009-05-23 12:27:03 +0100795 return i2c_add_driver(&wm8974_i2c_driver);
Mark Brown0a1bf552009-05-23 11:18:41 +0100796}
797module_init(wm8974_modinit);
798
799static void __exit wm8974_exit(void)
800{
Mark Brown4fcbbb62009-05-23 12:27:03 +0100801 i2c_del_driver(&wm8974_i2c_driver);
Mark Brown0a1bf552009-05-23 11:18:41 +0100802}
803module_exit(wm8974_exit);
804
805MODULE_DESCRIPTION("ASoC WM8974 driver");
806MODULE_AUTHOR("Liam Girdwood");
807MODULE_LICENSE("GPL");