blob: f30f86b3bda05a8276ed21de4771c7b9684c26be [file] [log] [blame]
Mark Brown0a1bf552009-05-23 11:18:41 +01001/*
2 * wm8974.c -- WM8974 ALSA Soc Audio driver
3 *
Mark Brown8b83a192009-06-30 19:37:02 +01004 * Copyright 2006-2009 Wolfson Microelectronics PLC.
Mark Brown0a1bf552009-05-23 11:18:41 +01005 *
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>
Mark Browna5f8d2f2009-06-30 19:30:33 +010028#include <sound/tlv.h>
Mark Brown0a1bf552009-05-23 11:18:41 +010029
30#include "wm8974.h"
31
Mark Brown0a1bf552009-05-23 11:18:41 +010032static const u16 wm8974_reg[WM8974_CACHEREGNUM] = {
Mark Brown1a55b3f2009-05-23 11:31:40 +010033 0x0000, 0x0000, 0x0000, 0x0000,
34 0x0050, 0x0000, 0x0140, 0x0000,
35 0x0000, 0x0000, 0x0000, 0x00ff,
36 0x0000, 0x0000, 0x0100, 0x00ff,
37 0x0000, 0x0000, 0x012c, 0x002c,
38 0x002c, 0x002c, 0x002c, 0x0000,
39 0x0032, 0x0000, 0x0000, 0x0000,
40 0x0000, 0x0000, 0x0000, 0x0000,
41 0x0038, 0x000b, 0x0032, 0x0000,
42 0x0008, 0x000c, 0x0093, 0x00e9,
43 0x0000, 0x0000, 0x0000, 0x0000,
44 0x0003, 0x0010, 0x0000, 0x0000,
45 0x0000, 0x0002, 0x0000, 0x0000,
46 0x0000, 0x0000, 0x0039, 0x0000,
47 0x0000,
Mark Brown0a1bf552009-05-23 11:18:41 +010048};
49
Mark Browndf1ef7a2009-06-30 19:01:09 +010050#define WM8974_POWER1_BIASEN 0x08
51#define WM8974_POWER1_BUFIOEN 0x10
52
Mark Brown4fcbbb62009-05-23 12:27:03 +010053struct wm8974_priv {
54 struct snd_soc_codec codec;
55 u16 reg_cache[WM8974_CACHEREGNUM];
56};
57
58static struct snd_soc_codec *wm8974_codec;
59
Mark Brown0a1bf552009-05-23 11:18:41 +010060/*
61 * read wm8974 register cache
62 */
Mark Brown1a55b3f2009-05-23 11:31:40 +010063static inline unsigned int wm8974_read_reg_cache(struct snd_soc_codec *codec,
Mark Brown0a1bf552009-05-23 11:18:41 +010064 unsigned int reg)
65{
66 u16 *cache = codec->reg_cache;
67 if (reg == WM8974_RESET)
68 return 0;
69 if (reg >= WM8974_CACHEREGNUM)
70 return -1;
71 return cache[reg];
72}
73
74/*
75 * write wm8974 register cache
76 */
77static inline void wm8974_write_reg_cache(struct snd_soc_codec *codec,
78 u16 reg, unsigned int value)
79{
80 u16 *cache = codec->reg_cache;
81 if (reg >= WM8974_CACHEREGNUM)
82 return;
83 cache[reg] = value;
84}
85
86/*
87 * write to the WM8974 register space
88 */
89static int wm8974_write(struct snd_soc_codec *codec, unsigned int reg,
90 unsigned int value)
91{
92 u8 data[2];
93
94 /* data is
95 * D15..D9 WM8974 register offset
96 * D8...D0 register data
97 */
98 data[0] = (reg << 1) | ((value >> 8) & 0x0001);
99 data[1] = value & 0x00ff;
100
Mark Brown1a55b3f2009-05-23 11:31:40 +0100101 wm8974_write_reg_cache(codec, reg, value);
Mark Brown0a1bf552009-05-23 11:18:41 +0100102 if (codec->hw_write(codec->control_data, data, 2) == 2)
103 return 0;
104 else
105 return -EIO;
106}
107
108#define wm8974_reset(c) wm8974_write(c, WM8974_RESET, 0)
109
110static const char *wm8974_companding[] = {"Off", "NC", "u-law", "A-law" };
111static const char *wm8974_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz" };
112static const char *wm8974_eqmode[] = {"Capture", "Playback" };
113static const char *wm8974_bw[] = {"Narrow", "Wide" };
114static const char *wm8974_eq1[] = {"80Hz", "105Hz", "135Hz", "175Hz" };
115static const char *wm8974_eq2[] = {"230Hz", "300Hz", "385Hz", "500Hz" };
116static const char *wm8974_eq3[] = {"650Hz", "850Hz", "1.1kHz", "1.4kHz" };
117static const char *wm8974_eq4[] = {"1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz" };
118static const char *wm8974_eq5[] = {"5.3kHz", "6.9kHz", "9kHz", "11.7kHz" };
119static const char *wm8974_alc[] = {"ALC", "Limiter" };
120
121static const struct soc_enum wm8974_enum[] = {
122 SOC_ENUM_SINGLE(WM8974_COMP, 1, 4, wm8974_companding), /* adc */
123 SOC_ENUM_SINGLE(WM8974_COMP, 3, 4, wm8974_companding), /* dac */
124 SOC_ENUM_SINGLE(WM8974_DAC, 4, 4, wm8974_deemp),
125 SOC_ENUM_SINGLE(WM8974_EQ1, 8, 2, wm8974_eqmode),
126
127 SOC_ENUM_SINGLE(WM8974_EQ1, 5, 4, wm8974_eq1),
128 SOC_ENUM_SINGLE(WM8974_EQ2, 8, 2, wm8974_bw),
129 SOC_ENUM_SINGLE(WM8974_EQ2, 5, 4, wm8974_eq2),
130 SOC_ENUM_SINGLE(WM8974_EQ3, 8, 2, wm8974_bw),
131
132 SOC_ENUM_SINGLE(WM8974_EQ3, 5, 4, wm8974_eq3),
133 SOC_ENUM_SINGLE(WM8974_EQ4, 8, 2, wm8974_bw),
134 SOC_ENUM_SINGLE(WM8974_EQ4, 5, 4, wm8974_eq4),
135 SOC_ENUM_SINGLE(WM8974_EQ5, 8, 2, wm8974_bw),
136
137 SOC_ENUM_SINGLE(WM8974_EQ5, 5, 4, wm8974_eq5),
138 SOC_ENUM_SINGLE(WM8974_ALC3, 8, 2, wm8974_alc),
139};
140
Mark Brown8a123ee2009-06-30 21:10:34 +0100141static const char *wm8974_auxmode_text[] = { "Buffer", "Mixer" };
142
143static const struct soc_enum wm8974_auxmode =
144 SOC_ENUM_SINGLE(WM8974_INPUT, 3, 2, wm8974_auxmode_text);
145
Mark Browna5f8d2f2009-06-30 19:30:33 +0100146static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1);
147static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
148static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1200, 75, 0);
149static const DECLARE_TLV_DB_SCALE(spk_tlv, -5700, 100, 0);
150
Mark Brown0a1bf552009-05-23 11:18:41 +0100151static const struct snd_kcontrol_new wm8974_snd_controls[] = {
152
153SOC_SINGLE("Digital Loopback Switch", WM8974_COMP, 0, 1, 0),
154
155SOC_ENUM("DAC Companding", wm8974_enum[1]),
156SOC_ENUM("ADC Companding", wm8974_enum[0]),
157
158SOC_ENUM("Playback De-emphasis", wm8974_enum[2]),
159SOC_SINGLE("DAC Inversion Switch", WM8974_DAC, 0, 1, 0),
160
Mark Browna5f8d2f2009-06-30 19:30:33 +0100161SOC_SINGLE_TLV("PCM Volume", WM8974_DACVOL, 0, 255, 0, digital_tlv),
Mark Brown0a1bf552009-05-23 11:18:41 +0100162
163SOC_SINGLE("High Pass Filter Switch", WM8974_ADC, 8, 1, 0),
164SOC_SINGLE("High Pass Cut Off", WM8974_ADC, 4, 7, 0),
javier Martin25cbf462009-07-21 11:15:06 +0200165SOC_SINGLE("ADC Inversion Switch", WM8974_ADC, 0, 1, 0),
Mark Brown0a1bf552009-05-23 11:18:41 +0100166
Mark Browna5f8d2f2009-06-30 19:30:33 +0100167SOC_SINGLE_TLV("Capture Volume", WM8974_ADCVOL, 0, 255, 0, digital_tlv),
Mark Brown0a1bf552009-05-23 11:18:41 +0100168
169SOC_ENUM("Equaliser Function", wm8974_enum[3]),
170SOC_ENUM("EQ1 Cut Off", wm8974_enum[4]),
Mark Browna5f8d2f2009-06-30 19:30:33 +0100171SOC_SINGLE_TLV("EQ1 Volume", WM8974_EQ1, 0, 24, 1, eq_tlv),
Mark Brown0a1bf552009-05-23 11:18:41 +0100172
173SOC_ENUM("Equaliser EQ2 Bandwith", wm8974_enum[5]),
174SOC_ENUM("EQ2 Cut Off", wm8974_enum[6]),
Mark Browna5f8d2f2009-06-30 19:30:33 +0100175SOC_SINGLE_TLV("EQ2 Volume", WM8974_EQ2, 0, 24, 1, eq_tlv),
Mark Brown0a1bf552009-05-23 11:18:41 +0100176
177SOC_ENUM("Equaliser EQ3 Bandwith", wm8974_enum[7]),
178SOC_ENUM("EQ3 Cut Off", wm8974_enum[8]),
Mark Browna5f8d2f2009-06-30 19:30:33 +0100179SOC_SINGLE_TLV("EQ3 Volume", WM8974_EQ3, 0, 24, 1, eq_tlv),
Mark Brown0a1bf552009-05-23 11:18:41 +0100180
181SOC_ENUM("Equaliser EQ4 Bandwith", wm8974_enum[9]),
182SOC_ENUM("EQ4 Cut Off", wm8974_enum[10]),
Mark Browna5f8d2f2009-06-30 19:30:33 +0100183SOC_SINGLE_TLV("EQ4 Volume", WM8974_EQ4, 0, 24, 1, eq_tlv),
Mark Brown0a1bf552009-05-23 11:18:41 +0100184
185SOC_ENUM("Equaliser EQ5 Bandwith", wm8974_enum[11]),
186SOC_ENUM("EQ5 Cut Off", wm8974_enum[12]),
Mark Browna5f8d2f2009-06-30 19:30:33 +0100187SOC_SINGLE_TLV("EQ5 Volume", WM8974_EQ5, 0, 24, 1, eq_tlv),
Mark Brown0a1bf552009-05-23 11:18:41 +0100188
189SOC_SINGLE("DAC Playback Limiter Switch", WM8974_DACLIM1, 8, 1, 0),
190SOC_SINGLE("DAC Playback Limiter Decay", WM8974_DACLIM1, 4, 15, 0),
191SOC_SINGLE("DAC Playback Limiter Attack", WM8974_DACLIM1, 0, 15, 0),
192
193SOC_SINGLE("DAC Playback Limiter Threshold", WM8974_DACLIM2, 4, 7, 0),
194SOC_SINGLE("DAC Playback Limiter Boost", WM8974_DACLIM2, 0, 15, 0),
195
196SOC_SINGLE("ALC Enable Switch", WM8974_ALC1, 8, 1, 0),
197SOC_SINGLE("ALC Capture Max Gain", WM8974_ALC1, 3, 7, 0),
198SOC_SINGLE("ALC Capture Min Gain", WM8974_ALC1, 0, 7, 0),
199
200SOC_SINGLE("ALC Capture ZC Switch", WM8974_ALC2, 8, 1, 0),
201SOC_SINGLE("ALC Capture Hold", WM8974_ALC2, 4, 7, 0),
202SOC_SINGLE("ALC Capture Target", WM8974_ALC2, 0, 15, 0),
203
204SOC_ENUM("ALC Capture Mode", wm8974_enum[13]),
205SOC_SINGLE("ALC Capture Decay", WM8974_ALC3, 4, 15, 0),
206SOC_SINGLE("ALC Capture Attack", WM8974_ALC3, 0, 15, 0),
207
208SOC_SINGLE("ALC Capture Noise Gate Switch", WM8974_NGATE, 3, 1, 0),
209SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8974_NGATE, 0, 7, 0),
210
211SOC_SINGLE("Capture PGA ZC Switch", WM8974_INPPGA, 7, 1, 0),
Mark Browna5f8d2f2009-06-30 19:30:33 +0100212SOC_SINGLE_TLV("Capture PGA Volume", WM8974_INPPGA, 0, 63, 0, inpga_tlv),
Mark Brown0a1bf552009-05-23 11:18:41 +0100213
214SOC_SINGLE("Speaker Playback ZC Switch", WM8974_SPKVOL, 7, 1, 0),
215SOC_SINGLE("Speaker Playback Switch", WM8974_SPKVOL, 6, 1, 1),
Mark Brown8a123ee2009-06-30 21:10:34 +0100216SOC_SINGLE_TLV("Speaker Playback Volume", WM8974_SPKVOL, 0, 63, 0, spk_tlv),
217
218SOC_ENUM("Aux Mode", wm8974_auxmode),
Mark Brown0a1bf552009-05-23 11:18:41 +0100219
220SOC_SINGLE("Capture Boost(+20dB)", WM8974_ADCBOOST, 8, 1, 0),
Mark Brown8a123ee2009-06-30 21:10:34 +0100221SOC_SINGLE("Mono Playback Switch", WM8974_MONOMIX, 6, 1, 1),
Mark Brown0a1bf552009-05-23 11:18:41 +0100222};
223
Mark Brown0a1bf552009-05-23 11:18:41 +0100224/* 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),
Mark Brown8a123ee2009-06-30 21:10:34 +0100235SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_MONOMIX, 0, 1, 0),
236};
237
238/* Boost mixer */
239static const struct snd_kcontrol_new wm8974_boost_mixer[] = {
240SOC_DAPM_SINGLE("Aux Switch", WM8974_INPPGA, 6, 1, 0),
241};
242
243/* Input PGA */
244static const struct snd_kcontrol_new wm8974_inpga[] = {
245SOC_DAPM_SINGLE("Aux Switch", WM8974_INPUT, 2, 1, 0),
246SOC_DAPM_SINGLE("MicN Switch", WM8974_INPUT, 1, 1, 0),
247SOC_DAPM_SINGLE("MicP Switch", WM8974_INPUT, 0, 1, 0),
Mark Brown0a1bf552009-05-23 11:18:41 +0100248};
249
250/* AUX Input boost vol */
251static const struct snd_kcontrol_new wm8974_aux_boost_controls =
252SOC_DAPM_SINGLE("Aux Volume", WM8974_ADCBOOST, 0, 7, 0);
253
254/* Mic Input boost vol */
255static const struct snd_kcontrol_new wm8974_mic_boost_controls =
256SOC_DAPM_SINGLE("Mic Volume", WM8974_ADCBOOST, 4, 7, 0);
257
Mark Brown0a1bf552009-05-23 11:18:41 +0100258static const struct snd_soc_dapm_widget wm8974_dapm_widgets[] = {
259SND_SOC_DAPM_MIXER("Speaker Mixer", WM8974_POWER3, 2, 0,
260 &wm8974_speaker_mixer_controls[0],
261 ARRAY_SIZE(wm8974_speaker_mixer_controls)),
262SND_SOC_DAPM_MIXER("Mono Mixer", WM8974_POWER3, 3, 0,
263 &wm8974_mono_mixer_controls[0],
264 ARRAY_SIZE(wm8974_mono_mixer_controls)),
265SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8974_POWER3, 0, 0),
Mark Brown8a123ee2009-06-30 21:10:34 +0100266SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8974_POWER2, 0, 0),
Mark Brown0a1bf552009-05-23 11:18:41 +0100267SND_SOC_DAPM_PGA("Aux Input", WM8974_POWER1, 6, 0, NULL, 0),
268SND_SOC_DAPM_PGA("SpkN Out", WM8974_POWER3, 5, 0, NULL, 0),
269SND_SOC_DAPM_PGA("SpkP Out", WM8974_POWER3, 6, 0, NULL, 0),
270SND_SOC_DAPM_PGA("Mono Out", WM8974_POWER3, 7, 0, NULL, 0),
Mark Brown0a1bf552009-05-23 11:18:41 +0100271
Mark Brown8a123ee2009-06-30 21:10:34 +0100272SND_SOC_DAPM_MIXER("Input PGA", WM8974_POWER2, 2, 0, wm8974_inpga,
273 ARRAY_SIZE(wm8974_inpga)),
274SND_SOC_DAPM_MIXER("Boost Mixer", WM8974_POWER2, 4, 0,
275 wm8974_boost_mixer, ARRAY_SIZE(wm8974_boost_mixer)),
Mark Brown0a1bf552009-05-23 11:18:41 +0100276
277SND_SOC_DAPM_MICBIAS("Mic Bias", WM8974_POWER1, 4, 0),
278
279SND_SOC_DAPM_INPUT("MICN"),
280SND_SOC_DAPM_INPUT("MICP"),
281SND_SOC_DAPM_INPUT("AUX"),
282SND_SOC_DAPM_OUTPUT("MONOOUT"),
283SND_SOC_DAPM_OUTPUT("SPKOUTP"),
284SND_SOC_DAPM_OUTPUT("SPKOUTN"),
285};
286
287static const struct snd_soc_dapm_route audio_map[] = {
288 /* Mono output mixer */
289 {"Mono Mixer", "PCM Playback Switch", "DAC"},
290 {"Mono Mixer", "Aux Playback Switch", "Aux Input"},
291 {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"},
292
293 /* Speaker output mixer */
294 {"Speaker Mixer", "PCM Playback Switch", "DAC"},
295 {"Speaker Mixer", "Aux Playback Switch", "Aux Input"},
296 {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"},
297
298 /* Outputs */
299 {"Mono Out", NULL, "Mono Mixer"},
300 {"MONOOUT", NULL, "Mono Out"},
301 {"SpkN Out", NULL, "Speaker Mixer"},
302 {"SpkP Out", NULL, "Speaker Mixer"},
303 {"SPKOUTN", NULL, "SpkN Out"},
304 {"SPKOUTP", NULL, "SpkP Out"},
305
306 /* Boost Mixer */
Mark Brown8a123ee2009-06-30 21:10:34 +0100307 {"ADC", NULL, "Boost Mixer"},
308 {"Boost Mixer", "Aux Switch", "Aux Input"},
309 {"Boost Mixer", NULL, "Input PGA"},
310 {"Boost Mixer", NULL, "MICP"},
311
312 /* Input PGA */
313 {"Input PGA", "Aux Switch", "Aux Input"},
314 {"Input PGA", "MicN Switch", "MICN"},
315 {"Input PGA", "MicP Switch", "MICP"},
Mark Brown0a1bf552009-05-23 11:18:41 +0100316
317 /* Inputs */
Mark Brown8a123ee2009-06-30 21:10:34 +0100318 {"Aux Input", NULL, "AUX"},
Mark Brown0a1bf552009-05-23 11:18:41 +0100319};
320
321static int wm8974_add_widgets(struct snd_soc_codec *codec)
322{
323 snd_soc_dapm_new_controls(codec, wm8974_dapm_widgets,
324 ARRAY_SIZE(wm8974_dapm_widgets));
325
326 snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
327
328 snd_soc_dapm_new_widgets(codec);
329 return 0;
330}
331
332struct pll_ {
Mark Brownc36b2fc2009-09-30 14:31:38 +0100333 unsigned int pre_div:1;
Mark Brown0a1bf552009-05-23 11:18:41 +0100334 unsigned int n:4;
335 unsigned int k;
336};
337
Mark Brown91d0c3e2009-06-30 19:02:32 +0100338/* The size in bits of the pll divide multiplied by 10
339 * to allow rounding later */
340#define FIXED_PLL_SIZE ((1 << 24) * 10)
341
Mark Brownc36b2fc2009-09-30 14:31:38 +0100342static void pll_factors(struct pll_ *pll_div,
343 unsigned int target, unsigned int source)
Mark Brown91d0c3e2009-06-30 19:02:32 +0100344{
345 unsigned long long Kpart;
346 unsigned int K, Ndiv, Nmod;
347
Mark Brownc36b2fc2009-09-30 14:31:38 +0100348 /* There is a fixed divide by 4 in the output path */
349 target *= 4;
350
Mark Brown91d0c3e2009-06-30 19:02:32 +0100351 Ndiv = target / source;
352 if (Ndiv < 6) {
Mark Brownc36b2fc2009-09-30 14:31:38 +0100353 source /= 2;
354 pll_div->pre_div = 1;
Mark Brown91d0c3e2009-06-30 19:02:32 +0100355 Ndiv = target / source;
356 } else
Mark Brownc36b2fc2009-09-30 14:31:38 +0100357 pll_div->pre_div = 0;
Mark Brown91d0c3e2009-06-30 19:02:32 +0100358
359 if ((Ndiv < 6) || (Ndiv > 12))
360 printk(KERN_WARNING
Mark Brown8b83a192009-06-30 19:37:02 +0100361 "WM8974 N value %u outwith recommended range!\n",
Mark Brown91d0c3e2009-06-30 19:02:32 +0100362 Ndiv);
363
Mark Brownc36b2fc2009-09-30 14:31:38 +0100364 pll_div->n = Ndiv;
Mark Brown91d0c3e2009-06-30 19:02:32 +0100365 Nmod = target % source;
366 Kpart = FIXED_PLL_SIZE * (long long)Nmod;
367
368 do_div(Kpart, source);
369
370 K = Kpart & 0xFFFFFFFF;
371
372 /* Check if we need to round */
373 if ((K % 10) >= 5)
374 K += 5;
375
376 /* Move down to proper range now rounding is done */
377 K /= 10;
378
Mark Brownc36b2fc2009-09-30 14:31:38 +0100379 pll_div->k = K;
Mark Brown91d0c3e2009-06-30 19:02:32 +0100380}
Mark Brown0a1bf552009-05-23 11:18:41 +0100381
382static int wm8974_set_dai_pll(struct snd_soc_dai *codec_dai,
383 int pll_id, unsigned int freq_in, unsigned int freq_out)
384{
385 struct snd_soc_codec *codec = codec_dai->codec;
Mark Brownc36b2fc2009-09-30 14:31:38 +0100386 struct pll_ pll_div;
Mark Brown0a1bf552009-05-23 11:18:41 +0100387 u16 reg;
388
Mark Brown1a55b3f2009-05-23 11:31:40 +0100389 if (freq_in == 0 || freq_out == 0) {
Mark Brown91d0c3e2009-06-30 19:02:32 +0100390 /* Clock CODEC directly from MCLK */
391 reg = wm8974_read_reg_cache(codec, WM8974_CLOCK);
392 wm8974_write(codec, WM8974_CLOCK, reg & 0x0ff);
393
394 /* Turn off PLL */
Mark Brown0a1bf552009-05-23 11:18:41 +0100395 reg = wm8974_read_reg_cache(codec, WM8974_POWER1);
396 wm8974_write(codec, WM8974_POWER1, reg & 0x1df);
397 return 0;
398 }
399
Mark Brownc36b2fc2009-09-30 14:31:38 +0100400 pll_factors(&pll_div, freq_out, freq_in);
Mark Brown1a55b3f2009-05-23 11:31:40 +0100401
Mark Brown91d0c3e2009-06-30 19:02:32 +0100402 wm8974_write(codec, WM8974_PLLN, (pll_div.pre_div << 4) | pll_div.n);
403 wm8974_write(codec, WM8974_PLLK1, pll_div.k >> 18);
404 wm8974_write(codec, WM8974_PLLK2, (pll_div.k >> 9) & 0x1ff);
405 wm8974_write(codec, WM8974_PLLK3, pll_div.k & 0x1ff);
406 reg = wm8974_read_reg_cache(codec, WM8974_POWER1);
407 wm8974_write(codec, WM8974_POWER1, reg | 0x020);
408
409 /* Run CODEC from PLL instead of MCLK */
410 reg = wm8974_read_reg_cache(codec, WM8974_CLOCK);
411 wm8974_write(codec, WM8974_CLOCK, reg | 0x100);
412
413 return 0;
Mark Brown0a1bf552009-05-23 11:18:41 +0100414}
415
416/*
417 * Configure WM8974 clock dividers.
418 */
419static int wm8974_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
420 int div_id, int div)
421{
422 struct snd_soc_codec *codec = codec_dai->codec;
423 u16 reg;
424
425 switch (div_id) {
426 case WM8974_OPCLKDIV:
Mark Brown1a55b3f2009-05-23 11:31:40 +0100427 reg = wm8974_read_reg_cache(codec, WM8974_GPIO) & 0x1cf;
Mark Brown0a1bf552009-05-23 11:18:41 +0100428 wm8974_write(codec, WM8974_GPIO, reg | div);
429 break;
430 case WM8974_MCLKDIV:
431 reg = wm8974_read_reg_cache(codec, WM8974_CLOCK) & 0x11f;
432 wm8974_write(codec, WM8974_CLOCK, reg | div);
433 break;
434 case WM8974_ADCCLK:
435 reg = wm8974_read_reg_cache(codec, WM8974_ADC) & 0x1f7;
436 wm8974_write(codec, WM8974_ADC, reg | div);
437 break;
438 case WM8974_DACCLK:
439 reg = wm8974_read_reg_cache(codec, WM8974_DAC) & 0x1f7;
440 wm8974_write(codec, WM8974_DAC, reg | div);
441 break;
442 case WM8974_BCLKDIV:
443 reg = wm8974_read_reg_cache(codec, WM8974_CLOCK) & 0x1e3;
444 wm8974_write(codec, WM8974_CLOCK, reg | div);
445 break;
446 default:
447 return -EINVAL;
448 }
449
450 return 0;
451}
452
453static int wm8974_set_dai_fmt(struct snd_soc_dai *codec_dai,
454 unsigned int fmt)
455{
456 struct snd_soc_codec *codec = codec_dai->codec;
457 u16 iface = 0;
458 u16 clk = wm8974_read_reg_cache(codec, WM8974_CLOCK) & 0x1fe;
459
460 /* set master/slave audio interface */
461 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
462 case SND_SOC_DAIFMT_CBM_CFM:
463 clk |= 0x0001;
464 break;
465 case SND_SOC_DAIFMT_CBS_CFS:
466 break;
467 default:
468 return -EINVAL;
469 }
470
471 /* interface format */
472 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
473 case SND_SOC_DAIFMT_I2S:
474 iface |= 0x0010;
475 break;
476 case SND_SOC_DAIFMT_RIGHT_J:
477 break;
478 case SND_SOC_DAIFMT_LEFT_J:
479 iface |= 0x0008;
480 break;
481 case SND_SOC_DAIFMT_DSP_A:
482 iface |= 0x00018;
483 break;
484 default:
485 return -EINVAL;
486 }
487
488 /* clock inversion */
489 switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
490 case SND_SOC_DAIFMT_NB_NF:
491 break;
492 case SND_SOC_DAIFMT_IB_IF:
493 iface |= 0x0180;
494 break;
495 case SND_SOC_DAIFMT_IB_NF:
496 iface |= 0x0100;
497 break;
498 case SND_SOC_DAIFMT_NB_IF:
499 iface |= 0x0080;
500 break;
501 default:
502 return -EINVAL;
503 }
504
505 wm8974_write(codec, WM8974_IFACE, iface);
506 wm8974_write(codec, WM8974_CLOCK, clk);
507 return 0;
508}
509
510static int wm8974_pcm_hw_params(struct snd_pcm_substream *substream,
511 struct snd_pcm_hw_params *params,
512 struct snd_soc_dai *dai)
513{
514 struct snd_soc_codec *codec = dai->codec;
515 u16 iface = wm8974_read_reg_cache(codec, WM8974_IFACE) & 0x19f;
516 u16 adn = wm8974_read_reg_cache(codec, WM8974_ADD) & 0x1f1;
517
518 /* bit size */
519 switch (params_format(params)) {
520 case SNDRV_PCM_FORMAT_S16_LE:
521 break;
522 case SNDRV_PCM_FORMAT_S20_3LE:
523 iface |= 0x0020;
524 break;
525 case SNDRV_PCM_FORMAT_S24_LE:
526 iface |= 0x0040;
527 break;
528 case SNDRV_PCM_FORMAT_S32_LE:
529 iface |= 0x0060;
530 break;
531 }
532
533 /* filter coefficient */
534 switch (params_rate(params)) {
535 case SNDRV_PCM_RATE_8000:
536 adn |= 0x5 << 1;
537 break;
538 case SNDRV_PCM_RATE_11025:
539 adn |= 0x4 << 1;
540 break;
541 case SNDRV_PCM_RATE_16000:
542 adn |= 0x3 << 1;
543 break;
544 case SNDRV_PCM_RATE_22050:
545 adn |= 0x2 << 1;
546 break;
547 case SNDRV_PCM_RATE_32000:
548 adn |= 0x1 << 1;
549 break;
550 case SNDRV_PCM_RATE_44100:
Mark Brown8b83a192009-06-30 19:37:02 +0100551 case SNDRV_PCM_RATE_48000:
Mark Brown0a1bf552009-05-23 11:18:41 +0100552 break;
553 }
554
555 wm8974_write(codec, WM8974_IFACE, iface);
556 wm8974_write(codec, WM8974_ADD, adn);
557 return 0;
558}
559
560static int wm8974_mute(struct snd_soc_dai *dai, int mute)
561{
562 struct snd_soc_codec *codec = dai->codec;
563 u16 mute_reg = wm8974_read_reg_cache(codec, WM8974_DAC) & 0xffbf;
564
Mark Brown1a55b3f2009-05-23 11:31:40 +0100565 if (mute)
Mark Brown0a1bf552009-05-23 11:18:41 +0100566 wm8974_write(codec, WM8974_DAC, mute_reg | 0x40);
567 else
568 wm8974_write(codec, WM8974_DAC, mute_reg);
569 return 0;
570}
571
572/* liam need to make this lower power with dapm */
573static int wm8974_set_bias_level(struct snd_soc_codec *codec,
574 enum snd_soc_bias_level level)
575{
Mark Browndf1ef7a2009-06-30 19:01:09 +0100576 u16 power1 = wm8974_read_reg_cache(codec, WM8974_POWER1) & ~0x3;
577
Mark Brown0a1bf552009-05-23 11:18:41 +0100578 switch (level) {
579 case SND_SOC_BIAS_ON:
Mark Brown0a1bf552009-05-23 11:18:41 +0100580 case SND_SOC_BIAS_PREPARE:
Mark Browndf1ef7a2009-06-30 19:01:09 +0100581 power1 |= 0x1; /* VMID 50k */
582 wm8974_write(codec, WM8974_POWER1, power1);
Mark Brown0a1bf552009-05-23 11:18:41 +0100583 break;
Mark Browndf1ef7a2009-06-30 19:01:09 +0100584
Mark Brown0a1bf552009-05-23 11:18:41 +0100585 case SND_SOC_BIAS_STANDBY:
Mark Browndf1ef7a2009-06-30 19:01:09 +0100586 power1 |= WM8974_POWER1_BIASEN | WM8974_POWER1_BUFIOEN;
587
588 if (codec->bias_level == SND_SOC_BIAS_OFF) {
589 /* Initial cap charge at VMID 5k */
590 wm8974_write(codec, WM8974_POWER1, power1 | 0x3);
591 mdelay(100);
592 }
593
594 power1 |= 0x2; /* VMID 500k */
595 wm8974_write(codec, WM8974_POWER1, power1);
Mark Brown0a1bf552009-05-23 11:18:41 +0100596 break;
Mark Browndf1ef7a2009-06-30 19:01:09 +0100597
Mark Brown0a1bf552009-05-23 11:18:41 +0100598 case SND_SOC_BIAS_OFF:
Mark Browndf1ef7a2009-06-30 19:01:09 +0100599 wm8974_write(codec, WM8974_POWER1, 0);
600 wm8974_write(codec, WM8974_POWER2, 0);
601 wm8974_write(codec, WM8974_POWER3, 0);
Mark Brown0a1bf552009-05-23 11:18:41 +0100602 break;
603 }
Mark Browndf1ef7a2009-06-30 19:01:09 +0100604
Mark Brown0a1bf552009-05-23 11:18:41 +0100605 codec->bias_level = level;
606 return 0;
607}
608
Mark Brown1a55b3f2009-05-23 11:31:40 +0100609#define WM8974_RATES (SNDRV_PCM_RATE_8000_48000)
Mark Brown0a1bf552009-05-23 11:18:41 +0100610
611#define WM8974_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
612 SNDRV_PCM_FMTBIT_S24_LE)
613
614static struct snd_soc_dai_ops wm8974_ops = {
615 .hw_params = wm8974_pcm_hw_params,
616 .digital_mute = wm8974_mute,
617 .set_fmt = wm8974_set_dai_fmt,
618 .set_clkdiv = wm8974_set_dai_clkdiv,
619 .set_pll = wm8974_set_dai_pll,
620};
621
622struct snd_soc_dai wm8974_dai = {
623 .name = "WM8974 HiFi",
624 .playback = {
625 .stream_name = "Playback",
626 .channels_min = 1,
Mark Brown33d81af2009-06-30 19:01:52 +0100627 .channels_max = 2, /* Only 1 channel of data */
Mark Brown0a1bf552009-05-23 11:18:41 +0100628 .rates = WM8974_RATES,
629 .formats = WM8974_FORMATS,},
630 .capture = {
631 .stream_name = "Capture",
632 .channels_min = 1,
Mark Brown33d81af2009-06-30 19:01:52 +0100633 .channels_max = 2, /* Only 1 channel of data */
Mark Brown0a1bf552009-05-23 11:18:41 +0100634 .rates = WM8974_RATES,
635 .formats = WM8974_FORMATS,},
636 .ops = &wm8974_ops,
Mark Browncb11d392009-06-30 19:36:39 +0100637 .symmetric_rates = 1,
Mark Brown0a1bf552009-05-23 11:18:41 +0100638};
639EXPORT_SYMBOL_GPL(wm8974_dai);
640
641static int wm8974_suspend(struct platform_device *pdev, pm_message_t state)
642{
643 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
644 struct snd_soc_codec *codec = socdev->card->codec;
645
646 wm8974_set_bias_level(codec, SND_SOC_BIAS_OFF);
647 return 0;
648}
649
650static int wm8974_resume(struct platform_device *pdev)
651{
652 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
653 struct snd_soc_codec *codec = socdev->card->codec;
654 int i;
655 u8 data[2];
656 u16 *cache = codec->reg_cache;
657
658 /* Sync reg_cache with the hardware */
659 for (i = 0; i < ARRAY_SIZE(wm8974_reg); i++) {
660 data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
661 data[1] = cache[i] & 0x00ff;
662 codec->hw_write(codec->control_data, data, 2);
663 }
664 wm8974_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
665 wm8974_set_bias_level(codec, codec->suspend_bias_level);
666 return 0;
667}
668
Mark Brown4fcbbb62009-05-23 12:27:03 +0100669static int wm8974_probe(struct platform_device *pdev)
Mark Brown0a1bf552009-05-23 11:18:41 +0100670{
Mark Brown4fcbbb62009-05-23 12:27:03 +0100671 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
672 struct snd_soc_codec *codec;
Mark Brown0a1bf552009-05-23 11:18:41 +0100673 int ret = 0;
674
Mark Brown4fcbbb62009-05-23 12:27:03 +0100675 if (wm8974_codec == NULL) {
676 dev_err(&pdev->dev, "Codec device not registered\n");
677 return -ENODEV;
678 }
Mark Brown0a1bf552009-05-23 11:18:41 +0100679
Mark Brown4fcbbb62009-05-23 12:27:03 +0100680 socdev->card->codec = wm8974_codec;
681 codec = wm8974_codec;
Mark Brown0a1bf552009-05-23 11:18:41 +0100682
683 /* register pcms */
684 ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
Mark Brown1a55b3f2009-05-23 11:31:40 +0100685 if (ret < 0) {
Mark Brown4fcbbb62009-05-23 12:27:03 +0100686 dev_err(codec->dev, "failed to create pcms: %d\n", ret);
Mark Brown0a1bf552009-05-23 11:18:41 +0100687 goto pcm_err;
688 }
689
Mark Brown4fcbbb62009-05-23 12:27:03 +0100690 snd_soc_add_controls(codec, wm8974_snd_controls,
691 ARRAY_SIZE(wm8974_snd_controls));
Mark Brown0a1bf552009-05-23 11:18:41 +0100692 wm8974_add_widgets(codec);
693 ret = snd_soc_init_card(socdev);
694 if (ret < 0) {
Mark Brown4fcbbb62009-05-23 12:27:03 +0100695 dev_err(codec->dev, "failed to register card: %d\n", ret);
Mark Brown0a1bf552009-05-23 11:18:41 +0100696 goto card_err;
697 }
Mark Brown4fcbbb62009-05-23 12:27:03 +0100698
Mark Brown0a1bf552009-05-23 11:18:41 +0100699 return ret;
700
701card_err:
702 snd_soc_free_pcms(socdev);
703 snd_soc_dapm_free(socdev);
704pcm_err:
Mark Brown0a1bf552009-05-23 11:18:41 +0100705 return ret;
706}
707
708/* power down chip */
709static int wm8974_remove(struct platform_device *pdev)
710{
711 struct snd_soc_device *socdev = platform_get_drvdata(pdev);
Mark Brown0a1bf552009-05-23 11:18:41 +0100712
713 snd_soc_free_pcms(socdev);
714 snd_soc_dapm_free(socdev);
Mark Brown0a1bf552009-05-23 11:18:41 +0100715
716 return 0;
717}
718
719struct snd_soc_codec_device soc_codec_dev_wm8974 = {
720 .probe = wm8974_probe,
721 .remove = wm8974_remove,
722 .suspend = wm8974_suspend,
723 .resume = wm8974_resume,
724};
725EXPORT_SYMBOL_GPL(soc_codec_dev_wm8974);
726
Mark Brown4fcbbb62009-05-23 12:27:03 +0100727static __devinit int wm8974_register(struct wm8974_priv *wm8974)
728{
729 int ret;
730 struct snd_soc_codec *codec = &wm8974->codec;
731
732 if (wm8974_codec) {
733 dev_err(codec->dev, "Another WM8974 is registered\n");
734 return -EINVAL;
735 }
736
737 mutex_init(&codec->mutex);
738 INIT_LIST_HEAD(&codec->dapm_widgets);
739 INIT_LIST_HEAD(&codec->dapm_paths);
740
741 codec->private_data = wm8974;
742 codec->name = "WM8974";
743 codec->owner = THIS_MODULE;
744 codec->read = wm8974_read_reg_cache;
745 codec->write = wm8974_write;
746 codec->bias_level = SND_SOC_BIAS_OFF;
747 codec->set_bias_level = wm8974_set_bias_level;
748 codec->dai = &wm8974_dai;
749 codec->num_dai = 1;
750 codec->reg_cache_size = WM8974_CACHEREGNUM;
751 codec->reg_cache = &wm8974->reg_cache;
752
753 memcpy(codec->reg_cache, wm8974_reg, sizeof(wm8974_reg));
754
755 ret = wm8974_reset(codec);
756 if (ret < 0) {
757 dev_err(codec->dev, "Failed to issue reset\n");
758 return ret;
759 }
760
761 wm8974_dai.dev = codec->dev;
762
763 wm8974_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
764
765 wm8974_codec = codec;
766
767 ret = snd_soc_register_codec(codec);
768 if (ret != 0) {
769 dev_err(codec->dev, "Failed to register codec: %d\n", ret);
770 return ret;
771 }
772
773 ret = snd_soc_register_dai(&wm8974_dai);
774 if (ret != 0) {
775 dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
776 snd_soc_unregister_codec(codec);
777 return ret;
778 }
779
780 return 0;
781}
782
783static __devexit void wm8974_unregister(struct wm8974_priv *wm8974)
784{
785 wm8974_set_bias_level(&wm8974->codec, SND_SOC_BIAS_OFF);
786 snd_soc_unregister_dai(&wm8974_dai);
787 snd_soc_unregister_codec(&wm8974->codec);
788 kfree(wm8974);
789 wm8974_codec = NULL;
790}
791
792static __devinit int wm8974_i2c_probe(struct i2c_client *i2c,
793 const struct i2c_device_id *id)
794{
795 struct wm8974_priv *wm8974;
796 struct snd_soc_codec *codec;
797
798 wm8974 = kzalloc(sizeof(struct wm8974_priv), GFP_KERNEL);
799 if (wm8974 == NULL)
800 return -ENOMEM;
801
802 codec = &wm8974->codec;
803 codec->hw_write = (hw_write_t)i2c_master_send;
804
805 i2c_set_clientdata(i2c, wm8974);
806 codec->control_data = i2c;
807
808 codec->dev = &i2c->dev;
809
810 return wm8974_register(wm8974);
811}
812
813static __devexit int wm8974_i2c_remove(struct i2c_client *client)
814{
815 struct wm8974_priv *wm8974 = i2c_get_clientdata(client);
816 wm8974_unregister(wm8974);
817 return 0;
818}
819
820static const struct i2c_device_id wm8974_i2c_id[] = {
821 { "wm8974", 0 },
822 { }
823};
824MODULE_DEVICE_TABLE(i2c, wm8974_i2c_id);
825
826static struct i2c_driver wm8974_i2c_driver = {
827 .driver = {
Mark Brown8b83a192009-06-30 19:37:02 +0100828 .name = "WM8974",
Mark Brown4fcbbb62009-05-23 12:27:03 +0100829 .owner = THIS_MODULE,
830 },
831 .probe = wm8974_i2c_probe,
832 .remove = __devexit_p(wm8974_i2c_remove),
833 .id_table = wm8974_i2c_id,
834};
835
Mark Brown0a1bf552009-05-23 11:18:41 +0100836static int __init wm8974_modinit(void)
837{
Mark Brown4fcbbb62009-05-23 12:27:03 +0100838 return i2c_add_driver(&wm8974_i2c_driver);
Mark Brown0a1bf552009-05-23 11:18:41 +0100839}
840module_init(wm8974_modinit);
841
842static void __exit wm8974_exit(void)
843{
Mark Brown4fcbbb62009-05-23 12:27:03 +0100844 i2c_del_driver(&wm8974_i2c_driver);
Mark Brown0a1bf552009-05-23 11:18:41 +0100845}
846module_exit(wm8974_exit);
847
848MODULE_DESCRIPTION("ASoC WM8974 driver");
849MODULE_AUTHOR("Liam Girdwood");
850MODULE_LICENSE("GPL");