xref: /linux/sound/soc/codecs/wm8974.c (revision ce6120cca2589ede530200c7cfe11ac9f144333c)
10a1bf553SMark Brown /*
20a1bf553SMark Brown  * wm8974.c  --  WM8974 ALSA Soc Audio driver
30a1bf553SMark Brown  *
48b83a193SMark Brown  * Copyright 2006-2009 Wolfson Microelectronics PLC.
50a1bf553SMark Brown  *
64fcbbb67SMark Brown  * Author: Liam Girdwood <linux@wolfsonmicro.com>
70a1bf553SMark Brown  *
80a1bf553SMark Brown  * This program is free software; you can redistribute it and/or modify
90a1bf553SMark Brown  * it under the terms of the GNU General Public License version 2 as
100a1bf553SMark Brown  * published by the Free Software Foundation.
110a1bf553SMark Brown  */
120a1bf553SMark Brown 
130a1bf553SMark Brown #include <linux/module.h>
140a1bf553SMark Brown #include <linux/moduleparam.h>
150a1bf553SMark Brown #include <linux/kernel.h>
160a1bf553SMark Brown #include <linux/init.h>
170a1bf553SMark Brown #include <linux/delay.h>
180a1bf553SMark Brown #include <linux/pm.h>
190a1bf553SMark Brown #include <linux/i2c.h>
200a1bf553SMark Brown #include <linux/platform_device.h>
215a0e3ad6STejun Heo #include <linux/slab.h>
220a1bf553SMark Brown #include <sound/core.h>
230a1bf553SMark Brown #include <sound/pcm.h>
240a1bf553SMark Brown #include <sound/pcm_params.h>
250a1bf553SMark Brown #include <sound/soc.h>
260a1bf553SMark Brown #include <sound/soc-dapm.h>
270a1bf553SMark Brown #include <sound/initval.h>
28a5f8d2f1SMark Brown #include <sound/tlv.h>
290a1bf553SMark Brown 
300a1bf553SMark Brown #include "wm8974.h"
310a1bf553SMark Brown 
320a1bf553SMark Brown static const u16 wm8974_reg[WM8974_CACHEREGNUM] = {
330a1bf553SMark Brown 	0x0000, 0x0000, 0x0000, 0x0000,
340a1bf553SMark Brown 	0x0050, 0x0000, 0x0140, 0x0000,
350a1bf553SMark Brown 	0x0000, 0x0000, 0x0000, 0x00ff,
360a1bf553SMark Brown 	0x0000, 0x0000, 0x0100, 0x00ff,
370a1bf553SMark Brown 	0x0000, 0x0000, 0x012c, 0x002c,
380a1bf553SMark Brown 	0x002c, 0x002c, 0x002c, 0x0000,
390a1bf553SMark Brown 	0x0032, 0x0000, 0x0000, 0x0000,
400a1bf553SMark Brown 	0x0000, 0x0000, 0x0000, 0x0000,
410a1bf553SMark Brown 	0x0038, 0x000b, 0x0032, 0x0000,
420a1bf553SMark Brown 	0x0008, 0x000c, 0x0093, 0x00e9,
430a1bf553SMark Brown 	0x0000, 0x0000, 0x0000, 0x0000,
440a1bf553SMark Brown 	0x0003, 0x0010, 0x0000, 0x0000,
450a1bf553SMark Brown 	0x0000, 0x0002, 0x0000, 0x0000,
460a1bf553SMark Brown 	0x0000, 0x0000, 0x0039, 0x0000,
470a1bf553SMark Brown 	0x0000,
480a1bf553SMark Brown };
490a1bf553SMark Brown 
50df1ef7a3SMark Brown #define WM8974_POWER1_BIASEN  0x08
5148c03ce7SGuennadi Liakhovetski #define WM8974_POWER1_BUFIOEN 0x04
52df1ef7a3SMark Brown 
534fcbbb67SMark Brown struct wm8974_priv {
54f0fba2adSLiam Girdwood 	enum snd_soc_control_type control_type;
554fcbbb67SMark Brown 	u16 reg_cache[WM8974_CACHEREGNUM];
564fcbbb67SMark Brown };
574fcbbb67SMark Brown 
581e97f50bSMark Brown #define wm8974_reset(c)	snd_soc_write(c, WM8974_RESET, 0)
590a1bf553SMark Brown 
600a1bf553SMark Brown static const char *wm8974_companding[] = {"Off", "NC", "u-law", "A-law" };
610a1bf553SMark Brown static const char *wm8974_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz" };
620a1bf553SMark Brown static const char *wm8974_eqmode[] = {"Capture", "Playback" };
630a1bf553SMark Brown static const char *wm8974_bw[] = {"Narrow", "Wide" };
640a1bf553SMark Brown static const char *wm8974_eq1[] = {"80Hz", "105Hz", "135Hz", "175Hz" };
650a1bf553SMark Brown static const char *wm8974_eq2[] = {"230Hz", "300Hz", "385Hz", "500Hz" };
660a1bf553SMark Brown static const char *wm8974_eq3[] = {"650Hz", "850Hz", "1.1kHz", "1.4kHz" };
670a1bf553SMark Brown static const char *wm8974_eq4[] = {"1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz" };
680a1bf553SMark Brown static const char *wm8974_eq5[] = {"5.3kHz", "6.9kHz", "9kHz", "11.7kHz" };
690a1bf553SMark Brown static const char *wm8974_alc[] = {"ALC", "Limiter" };
700a1bf553SMark Brown 
710a1bf553SMark Brown static const struct soc_enum wm8974_enum[] = {
720a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_COMP, 1, 4, wm8974_companding), /* adc */
730a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_COMP, 3, 4, wm8974_companding), /* dac */
740a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_DAC,  4, 4, wm8974_deemp),
750a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_EQ1,  8, 2, wm8974_eqmode),
760a1bf553SMark Brown 
770a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_EQ1,  5, 4, wm8974_eq1),
780a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_EQ2,  8, 2, wm8974_bw),
790a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_EQ2,  5, 4, wm8974_eq2),
800a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_EQ3,  8, 2, wm8974_bw),
810a1bf553SMark Brown 
820a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_EQ3,  5, 4, wm8974_eq3),
830a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_EQ4,  8, 2, wm8974_bw),
840a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_EQ4,  5, 4, wm8974_eq4),
850a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_EQ5,  8, 2, wm8974_bw),
860a1bf553SMark Brown 
870a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_EQ5,  5, 4, wm8974_eq5),
880a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_ALC3,  8, 2, wm8974_alc),
890a1bf553SMark Brown };
900a1bf553SMark Brown 
918a123ee2SMark Brown static const char *wm8974_auxmode_text[] = { "Buffer", "Mixer" };
928a123ee2SMark Brown 
938a123ee2SMark Brown static const struct soc_enum wm8974_auxmode =
948a123ee2SMark Brown 	SOC_ENUM_SINGLE(WM8974_INPUT,  3, 2, wm8974_auxmode_text);
958a123ee2SMark Brown 
96a5f8d2f1SMark Brown static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1);
97a5f8d2f1SMark Brown static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
98a5f8d2f1SMark Brown static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1200, 75, 0);
99a5f8d2f1SMark Brown static const DECLARE_TLV_DB_SCALE(spk_tlv, -5700, 100, 0);
100a5f8d2f1SMark Brown 
1010a1bf553SMark Brown static const struct snd_kcontrol_new wm8974_snd_controls[] = {
1020a1bf553SMark Brown 
1030a1bf553SMark Brown SOC_SINGLE("Digital Loopback Switch", WM8974_COMP, 0, 1, 0),
1040a1bf553SMark Brown 
1050a1bf553SMark Brown SOC_ENUM("DAC Companding", wm8974_enum[1]),
1060a1bf553SMark Brown SOC_ENUM("ADC Companding", wm8974_enum[0]),
1070a1bf553SMark Brown 
1080a1bf553SMark Brown SOC_ENUM("Playback De-emphasis", wm8974_enum[2]),
1090a1bf553SMark Brown SOC_SINGLE("DAC Inversion Switch", WM8974_DAC, 0, 1, 0),
1100a1bf553SMark Brown 
111a5f8d2f1SMark Brown SOC_SINGLE_TLV("PCM Volume", WM8974_DACVOL, 0, 255, 0, digital_tlv),
1120a1bf553SMark Brown 
1130a1bf553SMark Brown SOC_SINGLE("High Pass Filter Switch", WM8974_ADC, 8, 1, 0),
1140a1bf553SMark Brown SOC_SINGLE("High Pass Cut Off", WM8974_ADC, 4, 7, 0),
11525cbf465Sjavier Martin SOC_SINGLE("ADC Inversion Switch", WM8974_ADC, 0, 1, 0),
1160a1bf553SMark Brown 
117a5f8d2f1SMark Brown SOC_SINGLE_TLV("Capture Volume", WM8974_ADCVOL,  0, 255, 0, digital_tlv),
1180a1bf553SMark Brown 
1190a1bf553SMark Brown SOC_ENUM("Equaliser Function", wm8974_enum[3]),
1200a1bf553SMark Brown SOC_ENUM("EQ1 Cut Off", wm8974_enum[4]),
121a5f8d2f1SMark Brown SOC_SINGLE_TLV("EQ1 Volume", WM8974_EQ1,  0, 24, 1, eq_tlv),
1220a1bf553SMark Brown 
1230a1bf553SMark Brown SOC_ENUM("Equaliser EQ2 Bandwith", wm8974_enum[5]),
1240a1bf553SMark Brown SOC_ENUM("EQ2 Cut Off", wm8974_enum[6]),
125a5f8d2f1SMark Brown SOC_SINGLE_TLV("EQ2 Volume", WM8974_EQ2,  0, 24, 1, eq_tlv),
1260a1bf553SMark Brown 
1270a1bf553SMark Brown SOC_ENUM("Equaliser EQ3 Bandwith", wm8974_enum[7]),
1280a1bf553SMark Brown SOC_ENUM("EQ3 Cut Off", wm8974_enum[8]),
129a5f8d2f1SMark Brown SOC_SINGLE_TLV("EQ3 Volume", WM8974_EQ3,  0, 24, 1, eq_tlv),
1300a1bf553SMark Brown 
1310a1bf553SMark Brown SOC_ENUM("Equaliser EQ4 Bandwith", wm8974_enum[9]),
1320a1bf553SMark Brown SOC_ENUM("EQ4 Cut Off", wm8974_enum[10]),
133a5f8d2f1SMark Brown SOC_SINGLE_TLV("EQ4 Volume", WM8974_EQ4,  0, 24, 1, eq_tlv),
1340a1bf553SMark Brown 
1350a1bf553SMark Brown SOC_ENUM("Equaliser EQ5 Bandwith", wm8974_enum[11]),
1360a1bf553SMark Brown SOC_ENUM("EQ5 Cut Off", wm8974_enum[12]),
137a5f8d2f1SMark Brown SOC_SINGLE_TLV("EQ5 Volume", WM8974_EQ5,  0, 24, 1, eq_tlv),
1380a1bf553SMark Brown 
1390a1bf553SMark Brown SOC_SINGLE("DAC Playback Limiter Switch", WM8974_DACLIM1,  8, 1, 0),
1400a1bf553SMark Brown SOC_SINGLE("DAC Playback Limiter Decay", WM8974_DACLIM1,  4, 15, 0),
1410a1bf553SMark Brown SOC_SINGLE("DAC Playback Limiter Attack", WM8974_DACLIM1,  0, 15, 0),
1420a1bf553SMark Brown 
1430a1bf553SMark Brown SOC_SINGLE("DAC Playback Limiter Threshold", WM8974_DACLIM2,  4, 7, 0),
1440a1bf553SMark Brown SOC_SINGLE("DAC Playback Limiter Boost", WM8974_DACLIM2,  0, 15, 0),
1450a1bf553SMark Brown 
1460a1bf553SMark Brown SOC_SINGLE("ALC Enable Switch", WM8974_ALC1,  8, 1, 0),
1470a1bf553SMark Brown SOC_SINGLE("ALC Capture Max Gain", WM8974_ALC1,  3, 7, 0),
1480a1bf553SMark Brown SOC_SINGLE("ALC Capture Min Gain", WM8974_ALC1,  0, 7, 0),
1490a1bf553SMark Brown 
1500a1bf553SMark Brown SOC_SINGLE("ALC Capture ZC Switch", WM8974_ALC2,  8, 1, 0),
1510a1bf553SMark Brown SOC_SINGLE("ALC Capture Hold", WM8974_ALC2,  4, 7, 0),
1520a1bf553SMark Brown SOC_SINGLE("ALC Capture Target", WM8974_ALC2,  0, 15, 0),
1530a1bf553SMark Brown 
1540a1bf553SMark Brown SOC_ENUM("ALC Capture Mode", wm8974_enum[13]),
1550a1bf553SMark Brown SOC_SINGLE("ALC Capture Decay", WM8974_ALC3,  4, 15, 0),
1560a1bf553SMark Brown SOC_SINGLE("ALC Capture Attack", WM8974_ALC3,  0, 15, 0),
1570a1bf553SMark Brown 
1580a1bf553SMark Brown SOC_SINGLE("ALC Capture Noise Gate Switch", WM8974_NGATE,  3, 1, 0),
1590a1bf553SMark Brown SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8974_NGATE,  0, 7, 0),
1600a1bf553SMark Brown 
1610a1bf553SMark Brown SOC_SINGLE("Capture PGA ZC Switch", WM8974_INPPGA,  7, 1, 0),
162a5f8d2f1SMark Brown SOC_SINGLE_TLV("Capture PGA Volume", WM8974_INPPGA,  0, 63, 0, inpga_tlv),
1630a1bf553SMark Brown 
1640a1bf553SMark Brown SOC_SINGLE("Speaker Playback ZC Switch", WM8974_SPKVOL,  7, 1, 0),
1650a1bf553SMark Brown SOC_SINGLE("Speaker Playback Switch", WM8974_SPKVOL,  6, 1, 1),
1668a123ee2SMark Brown SOC_SINGLE_TLV("Speaker Playback Volume", WM8974_SPKVOL,  0, 63, 0, spk_tlv),
1678a123ee2SMark Brown 
1688a123ee2SMark Brown SOC_ENUM("Aux Mode", wm8974_auxmode),
1690a1bf553SMark Brown 
1700a1bf553SMark Brown SOC_SINGLE("Capture Boost(+20dB)", WM8974_ADCBOOST,  8, 1, 0),
1718a123ee2SMark Brown SOC_SINGLE("Mono Playback Switch", WM8974_MONOMIX, 6, 1, 1),
172b2c3e923SGuennadi Liakhovetski 
173b2c3e923SGuennadi Liakhovetski /* DAC / ADC oversampling */
174b2c3e923SGuennadi Liakhovetski SOC_SINGLE("DAC 128x Oversampling Switch", WM8974_DAC, 8, 1, 0),
175b2c3e923SGuennadi Liakhovetski SOC_SINGLE("ADC 128x Oversampling Switch", WM8974_ADC, 8, 1, 0),
1760a1bf553SMark Brown };
1770a1bf553SMark Brown 
1780a1bf553SMark Brown /* Speaker Output Mixer */
1790a1bf553SMark Brown static const struct snd_kcontrol_new wm8974_speaker_mixer_controls[] = {
1800a1bf553SMark Brown SOC_DAPM_SINGLE("Line Bypass Switch", WM8974_SPKMIX, 1, 1, 0),
1810a1bf553SMark Brown SOC_DAPM_SINGLE("Aux Playback Switch", WM8974_SPKMIX, 5, 1, 0),
182759512fbSMark Brown SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_SPKMIX, 0, 1, 0),
1830a1bf553SMark Brown };
1840a1bf553SMark Brown 
1850a1bf553SMark Brown /* Mono Output Mixer */
1860a1bf553SMark Brown static const struct snd_kcontrol_new wm8974_mono_mixer_controls[] = {
1870a1bf553SMark Brown SOC_DAPM_SINGLE("Line Bypass Switch", WM8974_MONOMIX, 1, 1, 0),
1880a1bf553SMark Brown SOC_DAPM_SINGLE("Aux Playback Switch", WM8974_MONOMIX, 2, 1, 0),
1898a123ee2SMark Brown SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_MONOMIX, 0, 1, 0),
1908a123ee2SMark Brown };
1918a123ee2SMark Brown 
1928a123ee2SMark Brown /* Boost mixer */
1938a123ee2SMark Brown static const struct snd_kcontrol_new wm8974_boost_mixer[] = {
1948a123ee2SMark Brown SOC_DAPM_SINGLE("Aux Switch", WM8974_INPPGA, 6, 1, 0),
1958a123ee2SMark Brown };
1968a123ee2SMark Brown 
1978a123ee2SMark Brown /* Input PGA */
1988a123ee2SMark Brown static const struct snd_kcontrol_new wm8974_inpga[] = {
1998a123ee2SMark Brown SOC_DAPM_SINGLE("Aux Switch", WM8974_INPUT, 2, 1, 0),
2008a123ee2SMark Brown SOC_DAPM_SINGLE("MicN Switch", WM8974_INPUT, 1, 1, 0),
2018a123ee2SMark Brown SOC_DAPM_SINGLE("MicP Switch", WM8974_INPUT, 0, 1, 0),
2020a1bf553SMark Brown };
2030a1bf553SMark Brown 
2040a1bf553SMark Brown /* AUX Input boost vol */
2050a1bf553SMark Brown static const struct snd_kcontrol_new wm8974_aux_boost_controls =
2060a1bf553SMark Brown SOC_DAPM_SINGLE("Aux Volume", WM8974_ADCBOOST, 0, 7, 0);
2070a1bf553SMark Brown 
2080a1bf553SMark Brown /* Mic Input boost vol */
2090a1bf553SMark Brown static const struct snd_kcontrol_new wm8974_mic_boost_controls =
2100a1bf553SMark Brown SOC_DAPM_SINGLE("Mic Volume", WM8974_ADCBOOST, 4, 7, 0);
2110a1bf553SMark Brown 
2120a1bf553SMark Brown static const struct snd_soc_dapm_widget wm8974_dapm_widgets[] = {
2130a1bf553SMark Brown SND_SOC_DAPM_MIXER("Speaker Mixer", WM8974_POWER3, 2, 0,
2140a1bf553SMark Brown 	&wm8974_speaker_mixer_controls[0],
2150a1bf553SMark Brown 	ARRAY_SIZE(wm8974_speaker_mixer_controls)),
2160a1bf553SMark Brown SND_SOC_DAPM_MIXER("Mono Mixer", WM8974_POWER3, 3, 0,
2170a1bf553SMark Brown 	&wm8974_mono_mixer_controls[0],
2180a1bf553SMark Brown 	ARRAY_SIZE(wm8974_mono_mixer_controls)),
2190a1bf553SMark Brown SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8974_POWER3, 0, 0),
2208a123ee2SMark Brown SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8974_POWER2, 0, 0),
2210a1bf553SMark Brown SND_SOC_DAPM_PGA("Aux Input", WM8974_POWER1, 6, 0, NULL, 0),
2220a1bf553SMark Brown SND_SOC_DAPM_PGA("SpkN Out", WM8974_POWER3, 5, 0, NULL, 0),
2230a1bf553SMark Brown SND_SOC_DAPM_PGA("SpkP Out", WM8974_POWER3, 6, 0, NULL, 0),
2240a1bf553SMark Brown SND_SOC_DAPM_PGA("Mono Out", WM8974_POWER3, 7, 0, NULL, 0),
2250a1bf553SMark Brown 
2268a123ee2SMark Brown SND_SOC_DAPM_MIXER("Input PGA", WM8974_POWER2, 2, 0, wm8974_inpga,
2278a123ee2SMark Brown 		   ARRAY_SIZE(wm8974_inpga)),
2288a123ee2SMark Brown SND_SOC_DAPM_MIXER("Boost Mixer", WM8974_POWER2, 4, 0,
2298a123ee2SMark Brown 		   wm8974_boost_mixer, ARRAY_SIZE(wm8974_boost_mixer)),
2300a1bf553SMark Brown 
2310a1bf553SMark Brown SND_SOC_DAPM_MICBIAS("Mic Bias", WM8974_POWER1, 4, 0),
2320a1bf553SMark Brown 
2330a1bf553SMark Brown SND_SOC_DAPM_INPUT("MICN"),
2340a1bf553SMark Brown SND_SOC_DAPM_INPUT("MICP"),
2350a1bf553SMark Brown SND_SOC_DAPM_INPUT("AUX"),
2360a1bf553SMark Brown SND_SOC_DAPM_OUTPUT("MONOOUT"),
2370a1bf553SMark Brown SND_SOC_DAPM_OUTPUT("SPKOUTP"),
2380a1bf553SMark Brown SND_SOC_DAPM_OUTPUT("SPKOUTN"),
2390a1bf553SMark Brown };
2400a1bf553SMark Brown 
2410a1bf553SMark Brown static const struct snd_soc_dapm_route audio_map[] = {
2420a1bf553SMark Brown 	/* Mono output mixer */
2430a1bf553SMark Brown 	{"Mono Mixer", "PCM Playback Switch", "DAC"},
2440a1bf553SMark Brown 	{"Mono Mixer", "Aux Playback Switch", "Aux Input"},
2450a1bf553SMark Brown 	{"Mono Mixer", "Line Bypass Switch", "Boost Mixer"},
2460a1bf553SMark Brown 
2470a1bf553SMark Brown 	/* Speaker output mixer */
2480a1bf553SMark Brown 	{"Speaker Mixer", "PCM Playback Switch", "DAC"},
2490a1bf553SMark Brown 	{"Speaker Mixer", "Aux Playback Switch", "Aux Input"},
2500a1bf553SMark Brown 	{"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"},
2510a1bf553SMark Brown 
2520a1bf553SMark Brown 	/* Outputs */
2530a1bf553SMark Brown 	{"Mono Out", NULL, "Mono Mixer"},
2540a1bf553SMark Brown 	{"MONOOUT", NULL, "Mono Out"},
2550a1bf553SMark Brown 	{"SpkN Out", NULL, "Speaker Mixer"},
2560a1bf553SMark Brown 	{"SpkP Out", NULL, "Speaker Mixer"},
2570a1bf553SMark Brown 	{"SPKOUTN", NULL, "SpkN Out"},
2580a1bf553SMark Brown 	{"SPKOUTP", NULL, "SpkP Out"},
2590a1bf553SMark Brown 
2600a1bf553SMark Brown 	/* Boost Mixer */
2618a123ee2SMark Brown 	{"ADC", NULL, "Boost Mixer"},
2628a123ee2SMark Brown 	{"Boost Mixer", "Aux Switch", "Aux Input"},
2638a123ee2SMark Brown 	{"Boost Mixer", NULL, "Input PGA"},
2648a123ee2SMark Brown 	{"Boost Mixer", NULL, "MICP"},
2658a123ee2SMark Brown 
2668a123ee2SMark Brown 	/* Input PGA */
2678a123ee2SMark Brown 	{"Input PGA", "Aux Switch", "Aux Input"},
2688a123ee2SMark Brown 	{"Input PGA", "MicN Switch", "MICN"},
2698a123ee2SMark Brown 	{"Input PGA", "MicP Switch", "MICP"},
2700a1bf553SMark Brown 
2710a1bf553SMark Brown 	/* Inputs */
2728a123ee2SMark Brown 	{"Aux Input", NULL, "AUX"},
2730a1bf553SMark Brown };
2740a1bf553SMark Brown 
2750a1bf553SMark Brown static int wm8974_add_widgets(struct snd_soc_codec *codec)
2760a1bf553SMark Brown {
277*ce6120ccSLiam Girdwood 	struct snd_soc_dapm_context *dapm = &codec->dapm;
2780a1bf553SMark Brown 
279*ce6120ccSLiam Girdwood 	snd_soc_dapm_new_controls(dapm, wm8974_dapm_widgets,
280*ce6120ccSLiam Girdwood 				  ARRAY_SIZE(wm8974_dapm_widgets));
281*ce6120ccSLiam Girdwood 	snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
2820a1bf553SMark Brown 
2830a1bf553SMark Brown 	return 0;
2840a1bf553SMark Brown }
2850a1bf553SMark Brown 
2860a1bf553SMark Brown struct pll_ {
287c36b2fc7SMark Brown 	unsigned int pre_div:1;
2880a1bf553SMark Brown 	unsigned int n:4;
2890a1bf553SMark Brown 	unsigned int k;
2900a1bf553SMark Brown };
2910a1bf553SMark Brown 
29291d0c3ecSMark Brown /* The size in bits of the pll divide multiplied by 10
29391d0c3ecSMark Brown  * to allow rounding later */
29491d0c3ecSMark Brown #define FIXED_PLL_SIZE ((1 << 24) * 10)
29591d0c3ecSMark Brown 
296c36b2fc7SMark Brown static void pll_factors(struct pll_ *pll_div,
297c36b2fc7SMark Brown 			unsigned int target, unsigned int source)
29891d0c3ecSMark Brown {
29991d0c3ecSMark Brown 	unsigned long long Kpart;
30091d0c3ecSMark Brown 	unsigned int K, Ndiv, Nmod;
30191d0c3ecSMark Brown 
302c36b2fc7SMark Brown 	/* There is a fixed divide by 4 in the output path */
303c36b2fc7SMark Brown 	target *= 4;
304c36b2fc7SMark Brown 
30591d0c3ecSMark Brown 	Ndiv = target / source;
30691d0c3ecSMark Brown 	if (Ndiv < 6) {
307c36b2fc7SMark Brown 		source /= 2;
308c36b2fc7SMark Brown 		pll_div->pre_div = 1;
30991d0c3ecSMark Brown 		Ndiv = target / source;
31091d0c3ecSMark Brown 	} else
311c36b2fc7SMark Brown 		pll_div->pre_div = 0;
31291d0c3ecSMark Brown 
31391d0c3ecSMark Brown 	if ((Ndiv < 6) || (Ndiv > 12))
31491d0c3ecSMark Brown 		printk(KERN_WARNING
3158b83a193SMark Brown 			"WM8974 N value %u outwith recommended range!\n",
31691d0c3ecSMark Brown 			Ndiv);
31791d0c3ecSMark Brown 
318c36b2fc7SMark Brown 	pll_div->n = Ndiv;
31991d0c3ecSMark Brown 	Nmod = target % source;
32091d0c3ecSMark Brown 	Kpart = FIXED_PLL_SIZE * (long long)Nmod;
32191d0c3ecSMark Brown 
32291d0c3ecSMark Brown 	do_div(Kpart, source);
32391d0c3ecSMark Brown 
32491d0c3ecSMark Brown 	K = Kpart & 0xFFFFFFFF;
32591d0c3ecSMark Brown 
32691d0c3ecSMark Brown 	/* Check if we need to round */
32791d0c3ecSMark Brown 	if ((K % 10) >= 5)
32891d0c3ecSMark Brown 		K += 5;
32991d0c3ecSMark Brown 
33091d0c3ecSMark Brown 	/* Move down to proper range now rounding is done */
33191d0c3ecSMark Brown 	K /= 10;
33291d0c3ecSMark Brown 
333c36b2fc7SMark Brown 	pll_div->k = K;
33491d0c3ecSMark Brown }
3350a1bf553SMark Brown 
33685488037SMark Brown static int wm8974_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
33785488037SMark Brown 		int source, unsigned int freq_in, unsigned int freq_out)
3380a1bf553SMark Brown {
3390a1bf553SMark Brown 	struct snd_soc_codec *codec = codec_dai->codec;
340c36b2fc7SMark Brown 	struct pll_ pll_div;
3410a1bf553SMark Brown 	u16 reg;
3420a1bf553SMark Brown 
3430a1bf553SMark Brown 	if (freq_in == 0 || freq_out == 0) {
34491d0c3ecSMark Brown 		/* Clock CODEC directly from MCLK */
3451e97f50bSMark Brown 		reg = snd_soc_read(codec, WM8974_CLOCK);
3461e97f50bSMark Brown 		snd_soc_write(codec, WM8974_CLOCK, reg & 0x0ff);
34791d0c3ecSMark Brown 
34891d0c3ecSMark Brown 		/* Turn off PLL */
3491e97f50bSMark Brown 		reg = snd_soc_read(codec, WM8974_POWER1);
3501e97f50bSMark Brown 		snd_soc_write(codec, WM8974_POWER1, reg & 0x1df);
3510a1bf553SMark Brown 		return 0;
3520a1bf553SMark Brown 	}
3530a1bf553SMark Brown 
354c36b2fc7SMark Brown 	pll_factors(&pll_div, freq_out, freq_in);
35591d0c3ecSMark Brown 
3561e97f50bSMark Brown 	snd_soc_write(codec, WM8974_PLLN, (pll_div.pre_div << 4) | pll_div.n);
3571e97f50bSMark Brown 	snd_soc_write(codec, WM8974_PLLK1, pll_div.k >> 18);
3581e97f50bSMark Brown 	snd_soc_write(codec, WM8974_PLLK2, (pll_div.k >> 9) & 0x1ff);
3591e97f50bSMark Brown 	snd_soc_write(codec, WM8974_PLLK3, pll_div.k & 0x1ff);
3601e97f50bSMark Brown 	reg = snd_soc_read(codec, WM8974_POWER1);
3611e97f50bSMark Brown 	snd_soc_write(codec, WM8974_POWER1, reg | 0x020);
3621a55b3f6SMark Brown 
36391d0c3ecSMark Brown 	/* Run CODEC from PLL instead of MCLK */
3641e97f50bSMark Brown 	reg = snd_soc_read(codec, WM8974_CLOCK);
3651e97f50bSMark Brown 	snd_soc_write(codec, WM8974_CLOCK, reg | 0x100);
36691d0c3ecSMark Brown 
36791d0c3ecSMark Brown 	return 0;
3680a1bf553SMark Brown }
3690a1bf553SMark Brown 
3700a1bf553SMark Brown /*
3710a1bf553SMark Brown  * Configure WM8974 clock dividers.
3720a1bf553SMark Brown  */
3730a1bf553SMark Brown static int wm8974_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
3740a1bf553SMark Brown 		int div_id, int div)
3750a1bf553SMark Brown {
3760a1bf553SMark Brown 	struct snd_soc_codec *codec = codec_dai->codec;
3770a1bf553SMark Brown 	u16 reg;
3780a1bf553SMark Brown 
3790a1bf553SMark Brown 	switch (div_id) {
3800a1bf553SMark Brown 	case WM8974_OPCLKDIV:
3811e97f50bSMark Brown 		reg = snd_soc_read(codec, WM8974_GPIO) & 0x1cf;
3821e97f50bSMark Brown 		snd_soc_write(codec, WM8974_GPIO, reg | div);
3830a1bf553SMark Brown 		break;
3840a1bf553SMark Brown 	case WM8974_MCLKDIV:
3851e97f50bSMark Brown 		reg = snd_soc_read(codec, WM8974_CLOCK) & 0x11f;
3861e97f50bSMark Brown 		snd_soc_write(codec, WM8974_CLOCK, reg | div);
3870a1bf553SMark Brown 		break;
3880a1bf553SMark Brown 	case WM8974_BCLKDIV:
3891e97f50bSMark Brown 		reg = snd_soc_read(codec, WM8974_CLOCK) & 0x1e3;
3901e97f50bSMark Brown 		snd_soc_write(codec, WM8974_CLOCK, reg | div);
3910a1bf553SMark Brown 		break;
3920a1bf553SMark Brown 	default:
3930a1bf553SMark Brown 		return -EINVAL;
3940a1bf553SMark Brown 	}
3950a1bf553SMark Brown 
3960a1bf553SMark Brown 	return 0;
3970a1bf553SMark Brown }
3980a1bf553SMark Brown 
3990a1bf553SMark Brown static int wm8974_set_dai_fmt(struct snd_soc_dai *codec_dai,
4000a1bf553SMark Brown 		unsigned int fmt)
4010a1bf553SMark Brown {
4020a1bf553SMark Brown 	struct snd_soc_codec *codec = codec_dai->codec;
4030a1bf553SMark Brown 	u16 iface = 0;
4041e97f50bSMark Brown 	u16 clk = snd_soc_read(codec, WM8974_CLOCK) & 0x1fe;
4050a1bf553SMark Brown 
4060a1bf553SMark Brown 	/* set master/slave audio interface */
4070a1bf553SMark Brown 	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
4080a1bf553SMark Brown 	case SND_SOC_DAIFMT_CBM_CFM:
4090a1bf553SMark Brown 		clk |= 0x0001;
4100a1bf553SMark Brown 		break;
4110a1bf553SMark Brown 	case SND_SOC_DAIFMT_CBS_CFS:
4120a1bf553SMark Brown 		break;
4130a1bf553SMark Brown 	default:
4140a1bf553SMark Brown 		return -EINVAL;
4150a1bf553SMark Brown 	}
4160a1bf553SMark Brown 
4170a1bf553SMark Brown 	/* interface format */
4180a1bf553SMark Brown 	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
4190a1bf553SMark Brown 	case SND_SOC_DAIFMT_I2S:
4200a1bf553SMark Brown 		iface |= 0x0010;
4210a1bf553SMark Brown 		break;
4220a1bf553SMark Brown 	case SND_SOC_DAIFMT_RIGHT_J:
4230a1bf553SMark Brown 		break;
4240a1bf553SMark Brown 	case SND_SOC_DAIFMT_LEFT_J:
4250a1bf553SMark Brown 		iface |= 0x0008;
4260a1bf553SMark Brown 		break;
4270a1bf553SMark Brown 	case SND_SOC_DAIFMT_DSP_A:
4280a1bf553SMark Brown 		iface |= 0x00018;
4290a1bf553SMark Brown 		break;
4300a1bf553SMark Brown 	default:
4310a1bf553SMark Brown 		return -EINVAL;
4320a1bf553SMark Brown 	}
4330a1bf553SMark Brown 
4340a1bf553SMark Brown 	/* clock inversion */
4350a1bf553SMark Brown 	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
4360a1bf553SMark Brown 	case SND_SOC_DAIFMT_NB_NF:
4370a1bf553SMark Brown 		break;
4380a1bf553SMark Brown 	case SND_SOC_DAIFMT_IB_IF:
4390a1bf553SMark Brown 		iface |= 0x0180;
4400a1bf553SMark Brown 		break;
4410a1bf553SMark Brown 	case SND_SOC_DAIFMT_IB_NF:
4420a1bf553SMark Brown 		iface |= 0x0100;
4430a1bf553SMark Brown 		break;
4440a1bf553SMark Brown 	case SND_SOC_DAIFMT_NB_IF:
4450a1bf553SMark Brown 		iface |= 0x0080;
4460a1bf553SMark Brown 		break;
4470a1bf553SMark Brown 	default:
4480a1bf553SMark Brown 		return -EINVAL;
4490a1bf553SMark Brown 	}
4500a1bf553SMark Brown 
4511e97f50bSMark Brown 	snd_soc_write(codec, WM8974_IFACE, iface);
4521e97f50bSMark Brown 	snd_soc_write(codec, WM8974_CLOCK, clk);
4530a1bf553SMark Brown 	return 0;
4540a1bf553SMark Brown }
4550a1bf553SMark Brown 
4560a1bf553SMark Brown static int wm8974_pcm_hw_params(struct snd_pcm_substream *substream,
4570a1bf553SMark Brown 				struct snd_pcm_hw_params *params,
4580a1bf553SMark Brown 				struct snd_soc_dai *dai)
4590a1bf553SMark Brown {
4600a1bf553SMark Brown 	struct snd_soc_codec *codec = dai->codec;
4611e97f50bSMark Brown 	u16 iface = snd_soc_read(codec, WM8974_IFACE) & 0x19f;
4621e97f50bSMark Brown 	u16 adn = snd_soc_read(codec, WM8974_ADD) & 0x1f1;
4630a1bf553SMark Brown 
4640a1bf553SMark Brown 	/* bit size */
4650a1bf553SMark Brown 	switch (params_format(params)) {
4660a1bf553SMark Brown 	case SNDRV_PCM_FORMAT_S16_LE:
4670a1bf553SMark Brown 		break;
4680a1bf553SMark Brown 	case SNDRV_PCM_FORMAT_S20_3LE:
4690a1bf553SMark Brown 		iface |= 0x0020;
4700a1bf553SMark Brown 		break;
4710a1bf553SMark Brown 	case SNDRV_PCM_FORMAT_S24_LE:
4720a1bf553SMark Brown 		iface |= 0x0040;
4730a1bf553SMark Brown 		break;
4740a1bf553SMark Brown 	case SNDRV_PCM_FORMAT_S32_LE:
4750a1bf553SMark Brown 		iface |= 0x0060;
4760a1bf553SMark Brown 		break;
4770a1bf553SMark Brown 	}
4780a1bf553SMark Brown 
4790a1bf553SMark Brown 	/* filter coefficient */
4800a1bf553SMark Brown 	switch (params_rate(params)) {
481b3172f22SGuennadi Liakhovetski 	case 8000:
4820a1bf553SMark Brown 		adn |= 0x5 << 1;
4830a1bf553SMark Brown 		break;
484b3172f22SGuennadi Liakhovetski 	case 11025:
4850a1bf553SMark Brown 		adn |= 0x4 << 1;
4860a1bf553SMark Brown 		break;
487b3172f22SGuennadi Liakhovetski 	case 16000:
4880a1bf553SMark Brown 		adn |= 0x3 << 1;
4890a1bf553SMark Brown 		break;
490b3172f22SGuennadi Liakhovetski 	case 22050:
4910a1bf553SMark Brown 		adn |= 0x2 << 1;
4920a1bf553SMark Brown 		break;
493b3172f22SGuennadi Liakhovetski 	case 32000:
4940a1bf553SMark Brown 		adn |= 0x1 << 1;
4950a1bf553SMark Brown 		break;
496b3172f22SGuennadi Liakhovetski 	case 44100:
497b3172f22SGuennadi Liakhovetski 	case 48000:
4980a1bf553SMark Brown 		break;
4990a1bf553SMark Brown 	}
5000a1bf553SMark Brown 
5011e97f50bSMark Brown 	snd_soc_write(codec, WM8974_IFACE, iface);
5021e97f50bSMark Brown 	snd_soc_write(codec, WM8974_ADD, adn);
5030a1bf553SMark Brown 	return 0;
5040a1bf553SMark Brown }
5050a1bf553SMark Brown 
5060a1bf553SMark Brown static int wm8974_mute(struct snd_soc_dai *dai, int mute)
5070a1bf553SMark Brown {
5080a1bf553SMark Brown 	struct snd_soc_codec *codec = dai->codec;
5091e97f50bSMark Brown 	u16 mute_reg = snd_soc_read(codec, WM8974_DAC) & 0xffbf;
5100a1bf553SMark Brown 
5110a1bf553SMark Brown 	if (mute)
5121e97f50bSMark Brown 		snd_soc_write(codec, WM8974_DAC, mute_reg | 0x40);
5130a1bf553SMark Brown 	else
5141e97f50bSMark Brown 		snd_soc_write(codec, WM8974_DAC, mute_reg);
5150a1bf553SMark Brown 	return 0;
5160a1bf553SMark Brown }
5170a1bf553SMark Brown 
5180a1bf553SMark Brown /* liam need to make this lower power with dapm */
5190a1bf553SMark Brown static int wm8974_set_bias_level(struct snd_soc_codec *codec,
5200a1bf553SMark Brown 	enum snd_soc_bias_level level)
5210a1bf553SMark Brown {
5221e97f50bSMark Brown 	u16 power1 = snd_soc_read(codec, WM8974_POWER1) & ~0x3;
523df1ef7a3SMark Brown 
5240a1bf553SMark Brown 	switch (level) {
5250a1bf553SMark Brown 	case SND_SOC_BIAS_ON:
5260a1bf553SMark Brown 	case SND_SOC_BIAS_PREPARE:
527df1ef7a3SMark Brown 		power1 |= 0x1;  /* VMID 50k */
5281e97f50bSMark Brown 		snd_soc_write(codec, WM8974_POWER1, power1);
5290a1bf553SMark Brown 		break;
530df1ef7a3SMark Brown 
5310a1bf553SMark Brown 	case SND_SOC_BIAS_STANDBY:
532df1ef7a3SMark Brown 		power1 |= WM8974_POWER1_BIASEN | WM8974_POWER1_BUFIOEN;
533df1ef7a3SMark Brown 
534*ce6120ccSLiam Girdwood 		if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
535df1ef7a3SMark Brown 			/* Initial cap charge at VMID 5k */
5361e97f50bSMark Brown 			snd_soc_write(codec, WM8974_POWER1, power1 | 0x3);
537df1ef7a3SMark Brown 			mdelay(100);
538df1ef7a3SMark Brown 		}
539df1ef7a3SMark Brown 
540df1ef7a3SMark Brown 		power1 |= 0x2;  /* VMID 500k */
5411e97f50bSMark Brown 		snd_soc_write(codec, WM8974_POWER1, power1);
5420a1bf553SMark Brown 		break;
543df1ef7a3SMark Brown 
5440a1bf553SMark Brown 	case SND_SOC_BIAS_OFF:
5451e97f50bSMark Brown 		snd_soc_write(codec, WM8974_POWER1, 0);
5461e97f50bSMark Brown 		snd_soc_write(codec, WM8974_POWER2, 0);
5471e97f50bSMark Brown 		snd_soc_write(codec, WM8974_POWER3, 0);
5480a1bf553SMark Brown 		break;
5490a1bf553SMark Brown 	}
550df1ef7a3SMark Brown 
551*ce6120ccSLiam Girdwood 	codec->dapm.bias_level = level;
5520a1bf553SMark Brown 	return 0;
5530a1bf553SMark Brown }
5540a1bf553SMark Brown 
5551a55b3f6SMark Brown #define WM8974_RATES (SNDRV_PCM_RATE_8000_48000)
5560a1bf553SMark Brown 
5570a1bf553SMark Brown #define WM8974_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
5580a1bf553SMark Brown 	SNDRV_PCM_FMTBIT_S24_LE)
5590a1bf553SMark Brown 
5600a1bf553SMark Brown static struct snd_soc_dai_ops wm8974_ops = {
5610a1bf553SMark Brown 	.hw_params = wm8974_pcm_hw_params,
5620a1bf553SMark Brown 	.digital_mute = wm8974_mute,
5630a1bf553SMark Brown 	.set_fmt = wm8974_set_dai_fmt,
5640a1bf553SMark Brown 	.set_clkdiv = wm8974_set_dai_clkdiv,
5650a1bf553SMark Brown 	.set_pll = wm8974_set_dai_pll,
5660a1bf553SMark Brown };
5670a1bf553SMark Brown 
568f0fba2adSLiam Girdwood static struct snd_soc_dai_driver wm8974_dai = {
569f0fba2adSLiam Girdwood 	.name = "wm8974-hifi",
5700a1bf553SMark Brown 	.playback = {
5710a1bf553SMark Brown 		.stream_name = "Playback",
5720a1bf553SMark Brown 		.channels_min = 1,
57333d81af4SMark Brown 		.channels_max = 2,   /* Only 1 channel of data */
5740a1bf553SMark Brown 		.rates = WM8974_RATES,
5750a1bf553SMark Brown 		.formats = WM8974_FORMATS,},
5760a1bf553SMark Brown 	.capture = {
5770a1bf553SMark Brown 		.stream_name = "Capture",
5780a1bf553SMark Brown 		.channels_min = 1,
57933d81af4SMark Brown 		.channels_max = 2,   /* Only 1 channel of data */
5800a1bf553SMark Brown 		.rates = WM8974_RATES,
5810a1bf553SMark Brown 		.formats = WM8974_FORMATS,},
5820a1bf553SMark Brown 	.ops = &wm8974_ops,
583cb11d39eSMark Brown 	.symmetric_rates = 1,
5840a1bf553SMark Brown };
5850a1bf553SMark Brown 
586f0fba2adSLiam Girdwood static int wm8974_suspend(struct snd_soc_codec *codec, pm_message_t state)
5870a1bf553SMark Brown {
5880a1bf553SMark Brown 	wm8974_set_bias_level(codec, SND_SOC_BIAS_OFF);
5890a1bf553SMark Brown 	return 0;
5900a1bf553SMark Brown }
5910a1bf553SMark Brown 
592f0fba2adSLiam Girdwood static int wm8974_resume(struct snd_soc_codec *codec)
5930a1bf553SMark Brown {
5940a1bf553SMark Brown 	int i;
5950a1bf553SMark Brown 	u8 data[2];
5960a1bf553SMark Brown 	u16 *cache = codec->reg_cache;
5970a1bf553SMark Brown 
5980a1bf553SMark Brown 	/* Sync reg_cache with the hardware */
5990a1bf553SMark Brown 	for (i = 0; i < ARRAY_SIZE(wm8974_reg); i++) {
6000a1bf553SMark Brown 		data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
6010a1bf553SMark Brown 		data[1] = cache[i] & 0x00ff;
6020a1bf553SMark Brown 		codec->hw_write(codec->control_data, data, 2);
6030a1bf553SMark Brown 	}
6040a1bf553SMark Brown 	wm8974_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
60529e189c2SMark Brown 
6060a1bf553SMark Brown 	return 0;
6070a1bf553SMark Brown }
6080a1bf553SMark Brown 
609f0fba2adSLiam Girdwood static int wm8974_probe(struct snd_soc_codec *codec)
6100a1bf553SMark Brown {
6110a1bf553SMark Brown 	int ret = 0;
6120a1bf553SMark Brown 
613f0fba2adSLiam Girdwood 	ret = snd_soc_codec_set_cache_io(codec, 7, 9, SND_SOC_I2C);
6140a1bf553SMark Brown 	if (ret < 0) {
615f0fba2adSLiam Girdwood 		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
616f0fba2adSLiam Girdwood 		return ret;
6170a1bf553SMark Brown 	}
6180a1bf553SMark Brown 
619f0fba2adSLiam Girdwood 	ret = wm8974_reset(codec);
620f0fba2adSLiam Girdwood 	if (ret < 0) {
621f0fba2adSLiam Girdwood 		dev_err(codec->dev, "Failed to issue reset\n");
622f0fba2adSLiam Girdwood 		return ret;
623f0fba2adSLiam Girdwood 	}
624f0fba2adSLiam Girdwood 
625f0fba2adSLiam Girdwood 	wm8974_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
6264fcbbb67SMark Brown 	snd_soc_add_controls(codec, wm8974_snd_controls,
6274fcbbb67SMark Brown 			     ARRAY_SIZE(wm8974_snd_controls));
6280a1bf553SMark Brown 	wm8974_add_widgets(codec);
6294fcbbb67SMark Brown 
6300a1bf553SMark Brown 	return ret;
6310a1bf553SMark Brown }
6320a1bf553SMark Brown 
6330a1bf553SMark Brown /* power down chip */
634f0fba2adSLiam Girdwood static int wm8974_remove(struct snd_soc_codec *codec)
6350a1bf553SMark Brown {
636f0fba2adSLiam Girdwood 	wm8974_set_bias_level(codec, SND_SOC_BIAS_OFF);
6370a1bf553SMark Brown 	return 0;
6380a1bf553SMark Brown }
6390a1bf553SMark Brown 
640f0fba2adSLiam Girdwood static struct snd_soc_codec_driver soc_codec_dev_wm8974 = {
6410a1bf553SMark Brown 	.probe = 	wm8974_probe,
6420a1bf553SMark Brown 	.remove = 	wm8974_remove,
6430a1bf553SMark Brown 	.suspend = 	wm8974_suspend,
6440a1bf553SMark Brown 	.resume =	wm8974_resume,
645f0fba2adSLiam Girdwood 	.set_bias_level = wm8974_set_bias_level,
646f0fba2adSLiam Girdwood 	.reg_cache_size = ARRAY_SIZE(wm8974_reg),
647f0fba2adSLiam Girdwood 	.reg_word_size = sizeof(u16),
648f0fba2adSLiam Girdwood 	.reg_cache_default = wm8974_reg,
6490a1bf553SMark Brown };
6500a1bf553SMark Brown 
651f0fba2adSLiam Girdwood #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
6524fcbbb67SMark Brown static __devinit int wm8974_i2c_probe(struct i2c_client *i2c,
6534fcbbb67SMark Brown 				      const struct i2c_device_id *id)
6544fcbbb67SMark Brown {
6554fcbbb67SMark Brown 	struct wm8974_priv *wm8974;
656f0fba2adSLiam Girdwood 	int ret;
6574fcbbb67SMark Brown 
6584fcbbb67SMark Brown 	wm8974 = kzalloc(sizeof(struct wm8974_priv), GFP_KERNEL);
6594fcbbb67SMark Brown 	if (wm8974 == NULL)
6604fcbbb67SMark Brown 		return -ENOMEM;
6614fcbbb67SMark Brown 
6624fcbbb67SMark Brown 	i2c_set_clientdata(i2c, wm8974);
6634fcbbb67SMark Brown 
664f0fba2adSLiam Girdwood 	ret = snd_soc_register_codec(&i2c->dev,
665f0fba2adSLiam Girdwood 			&soc_codec_dev_wm8974, &wm8974_dai, 1);
666f0fba2adSLiam Girdwood 	if (ret < 0)
667f0fba2adSLiam Girdwood 		kfree(wm8974);
668f0fba2adSLiam Girdwood 	return ret;
6694fcbbb67SMark Brown }
6704fcbbb67SMark Brown 
6714fcbbb67SMark Brown static __devexit int wm8974_i2c_remove(struct i2c_client *client)
6724fcbbb67SMark Brown {
673f0fba2adSLiam Girdwood 	snd_soc_unregister_codec(&client->dev);
674f0fba2adSLiam Girdwood 	kfree(i2c_get_clientdata(client));
6754fcbbb67SMark Brown 	return 0;
6764fcbbb67SMark Brown }
6774fcbbb67SMark Brown 
6784fcbbb67SMark Brown static const struct i2c_device_id wm8974_i2c_id[] = {
6794fcbbb67SMark Brown 	{ "wm8974", 0 },
6804fcbbb67SMark Brown 	{ }
6814fcbbb67SMark Brown };
6824fcbbb67SMark Brown MODULE_DEVICE_TABLE(i2c, wm8974_i2c_id);
6834fcbbb67SMark Brown 
6844fcbbb67SMark Brown static struct i2c_driver wm8974_i2c_driver = {
6854fcbbb67SMark Brown 	.driver = {
686f0fba2adSLiam Girdwood 		.name = "wm8974-codec",
6874fcbbb67SMark Brown 		.owner = THIS_MODULE,
6884fcbbb67SMark Brown 	},
6894fcbbb67SMark Brown 	.probe =    wm8974_i2c_probe,
6904fcbbb67SMark Brown 	.remove =   __devexit_p(wm8974_i2c_remove),
6914fcbbb67SMark Brown 	.id_table = wm8974_i2c_id,
6924fcbbb67SMark Brown };
693f0fba2adSLiam Girdwood #endif
6944fcbbb67SMark Brown 
6950a1bf553SMark Brown static int __init wm8974_modinit(void)
6960a1bf553SMark Brown {
697f0fba2adSLiam Girdwood 	int ret = 0;
698f0fba2adSLiam Girdwood #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
699f0fba2adSLiam Girdwood 	ret = i2c_add_driver(&wm8974_i2c_driver);
700f0fba2adSLiam Girdwood 	if (ret != 0) {
701f0fba2adSLiam Girdwood 		printk(KERN_ERR "Failed to register wm8974 I2C driver: %d\n",
702f0fba2adSLiam Girdwood 		       ret);
703f0fba2adSLiam Girdwood 	}
704f0fba2adSLiam Girdwood #endif
705f0fba2adSLiam Girdwood 	return ret;
7060a1bf553SMark Brown }
7070a1bf553SMark Brown module_init(wm8974_modinit);
7080a1bf553SMark Brown 
7090a1bf553SMark Brown static void __exit wm8974_exit(void)
7100a1bf553SMark Brown {
711f0fba2adSLiam Girdwood #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
7124fcbbb67SMark Brown 	i2c_del_driver(&wm8974_i2c_driver);
713f0fba2adSLiam Girdwood #endif
7140a1bf553SMark Brown }
7150a1bf553SMark Brown module_exit(wm8974_exit);
7160a1bf553SMark Brown 
7170a1bf553SMark Brown MODULE_DESCRIPTION("ASoC WM8974 driver");
7180a1bf553SMark Brown MODULE_AUTHOR("Liam Girdwood");
7190a1bf553SMark Brown MODULE_LICENSE("GPL");
720