xref: /linux/sound/soc/codecs/wm8974.c (revision 84b315ee893676e9a9ce8ac42ab5ef44e2af3ee1)
10a1bf553SMark Brown /*
20a1bf553SMark Brown  * wm8974.c  --  WM8974 ALSA Soc Audio driver
30a1bf553SMark Brown  *
48b83a193SMark Brown  * Copyright 2006-2009 Wolfson Microelectronics PLC.
50a1bf553SMark Brown  *
69a185b9aSMark Brown  * Author: Liam Girdwood <Liam.Girdwood@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>
205a0e3ad6STejun Heo #include <linux/slab.h>
210a1bf553SMark Brown #include <sound/core.h>
220a1bf553SMark Brown #include <sound/pcm.h>
230a1bf553SMark Brown #include <sound/pcm_params.h>
240a1bf553SMark Brown #include <sound/soc.h>
250a1bf553SMark Brown #include <sound/initval.h>
26a5f8d2f1SMark Brown #include <sound/tlv.h>
270a1bf553SMark Brown 
280a1bf553SMark Brown #include "wm8974.h"
290a1bf553SMark Brown 
300a1bf553SMark Brown static const u16 wm8974_reg[WM8974_CACHEREGNUM] = {
310a1bf553SMark Brown 	0x0000, 0x0000, 0x0000, 0x0000,
320a1bf553SMark Brown 	0x0050, 0x0000, 0x0140, 0x0000,
330a1bf553SMark Brown 	0x0000, 0x0000, 0x0000, 0x00ff,
340a1bf553SMark Brown 	0x0000, 0x0000, 0x0100, 0x00ff,
350a1bf553SMark Brown 	0x0000, 0x0000, 0x012c, 0x002c,
360a1bf553SMark Brown 	0x002c, 0x002c, 0x002c, 0x0000,
370a1bf553SMark Brown 	0x0032, 0x0000, 0x0000, 0x0000,
380a1bf553SMark Brown 	0x0000, 0x0000, 0x0000, 0x0000,
390a1bf553SMark Brown 	0x0038, 0x000b, 0x0032, 0x0000,
400a1bf553SMark Brown 	0x0008, 0x000c, 0x0093, 0x00e9,
410a1bf553SMark Brown 	0x0000, 0x0000, 0x0000, 0x0000,
420a1bf553SMark Brown 	0x0003, 0x0010, 0x0000, 0x0000,
430a1bf553SMark Brown 	0x0000, 0x0002, 0x0000, 0x0000,
440a1bf553SMark Brown 	0x0000, 0x0000, 0x0039, 0x0000,
450a1bf553SMark Brown 	0x0000,
460a1bf553SMark Brown };
470a1bf553SMark Brown 
48df1ef7a3SMark Brown #define WM8974_POWER1_BIASEN  0x08
4948c03ce7SGuennadi Liakhovetski #define WM8974_POWER1_BUFIOEN 0x04
50df1ef7a3SMark Brown 
514fcbbb67SMark Brown struct wm8974_priv {
52f0fba2adSLiam Girdwood 	enum snd_soc_control_type control_type;
534fcbbb67SMark Brown };
544fcbbb67SMark Brown 
551e97f50bSMark Brown #define wm8974_reset(c)	snd_soc_write(c, WM8974_RESET, 0)
560a1bf553SMark Brown 
570a1bf553SMark Brown static const char *wm8974_companding[] = {"Off", "NC", "u-law", "A-law" };
580a1bf553SMark Brown static const char *wm8974_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz" };
590a1bf553SMark Brown static const char *wm8974_eqmode[] = {"Capture", "Playback" };
600a1bf553SMark Brown static const char *wm8974_bw[] = {"Narrow", "Wide" };
610a1bf553SMark Brown static const char *wm8974_eq1[] = {"80Hz", "105Hz", "135Hz", "175Hz" };
620a1bf553SMark Brown static const char *wm8974_eq2[] = {"230Hz", "300Hz", "385Hz", "500Hz" };
630a1bf553SMark Brown static const char *wm8974_eq3[] = {"650Hz", "850Hz", "1.1kHz", "1.4kHz" };
640a1bf553SMark Brown static const char *wm8974_eq4[] = {"1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz" };
650a1bf553SMark Brown static const char *wm8974_eq5[] = {"5.3kHz", "6.9kHz", "9kHz", "11.7kHz" };
660a1bf553SMark Brown static const char *wm8974_alc[] = {"ALC", "Limiter" };
670a1bf553SMark Brown 
680a1bf553SMark Brown static const struct soc_enum wm8974_enum[] = {
690a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_COMP, 1, 4, wm8974_companding), /* adc */
700a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_COMP, 3, 4, wm8974_companding), /* dac */
710a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_DAC,  4, 4, wm8974_deemp),
720a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_EQ1,  8, 2, wm8974_eqmode),
730a1bf553SMark Brown 
740a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_EQ1,  5, 4, wm8974_eq1),
750a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_EQ2,  8, 2, wm8974_bw),
760a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_EQ2,  5, 4, wm8974_eq2),
770a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_EQ3,  8, 2, wm8974_bw),
780a1bf553SMark Brown 
790a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_EQ3,  5, 4, wm8974_eq3),
800a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_EQ4,  8, 2, wm8974_bw),
810a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_EQ4,  5, 4, wm8974_eq4),
820a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_EQ5,  8, 2, wm8974_bw),
830a1bf553SMark Brown 
840a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_EQ5,  5, 4, wm8974_eq5),
850a1bf553SMark Brown 	SOC_ENUM_SINGLE(WM8974_ALC3,  8, 2, wm8974_alc),
860a1bf553SMark Brown };
870a1bf553SMark Brown 
888a123ee2SMark Brown static const char *wm8974_auxmode_text[] = { "Buffer", "Mixer" };
898a123ee2SMark Brown 
908a123ee2SMark Brown static const struct soc_enum wm8974_auxmode =
918a123ee2SMark Brown 	SOC_ENUM_SINGLE(WM8974_INPUT,  3, 2, wm8974_auxmode_text);
928a123ee2SMark Brown 
93a5f8d2f1SMark Brown static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1);
94a5f8d2f1SMark Brown static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
95a5f8d2f1SMark Brown static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1200, 75, 0);
96a5f8d2f1SMark Brown static const DECLARE_TLV_DB_SCALE(spk_tlv, -5700, 100, 0);
97a5f8d2f1SMark Brown 
980a1bf553SMark Brown static const struct snd_kcontrol_new wm8974_snd_controls[] = {
990a1bf553SMark Brown 
1000a1bf553SMark Brown SOC_SINGLE("Digital Loopback Switch", WM8974_COMP, 0, 1, 0),
1010a1bf553SMark Brown 
1020a1bf553SMark Brown SOC_ENUM("DAC Companding", wm8974_enum[1]),
1030a1bf553SMark Brown SOC_ENUM("ADC Companding", wm8974_enum[0]),
1040a1bf553SMark Brown 
1050a1bf553SMark Brown SOC_ENUM("Playback De-emphasis", wm8974_enum[2]),
1060a1bf553SMark Brown SOC_SINGLE("DAC Inversion Switch", WM8974_DAC, 0, 1, 0),
1070a1bf553SMark Brown 
108a5f8d2f1SMark Brown SOC_SINGLE_TLV("PCM Volume", WM8974_DACVOL, 0, 255, 0, digital_tlv),
1090a1bf553SMark Brown 
1100a1bf553SMark Brown SOC_SINGLE("High Pass Filter Switch", WM8974_ADC, 8, 1, 0),
1110a1bf553SMark Brown SOC_SINGLE("High Pass Cut Off", WM8974_ADC, 4, 7, 0),
11225cbf465Sjavier Martin SOC_SINGLE("ADC Inversion Switch", WM8974_ADC, 0, 1, 0),
1130a1bf553SMark Brown 
114a5f8d2f1SMark Brown SOC_SINGLE_TLV("Capture Volume", WM8974_ADCVOL,  0, 255, 0, digital_tlv),
1150a1bf553SMark Brown 
1160a1bf553SMark Brown SOC_ENUM("Equaliser Function", wm8974_enum[3]),
1170a1bf553SMark Brown SOC_ENUM("EQ1 Cut Off", wm8974_enum[4]),
118a5f8d2f1SMark Brown SOC_SINGLE_TLV("EQ1 Volume", WM8974_EQ1,  0, 24, 1, eq_tlv),
1190a1bf553SMark Brown 
1200a1bf553SMark Brown SOC_ENUM("Equaliser EQ2 Bandwith", wm8974_enum[5]),
1210a1bf553SMark Brown SOC_ENUM("EQ2 Cut Off", wm8974_enum[6]),
122a5f8d2f1SMark Brown SOC_SINGLE_TLV("EQ2 Volume", WM8974_EQ2,  0, 24, 1, eq_tlv),
1230a1bf553SMark Brown 
1240a1bf553SMark Brown SOC_ENUM("Equaliser EQ3 Bandwith", wm8974_enum[7]),
1250a1bf553SMark Brown SOC_ENUM("EQ3 Cut Off", wm8974_enum[8]),
126a5f8d2f1SMark Brown SOC_SINGLE_TLV("EQ3 Volume", WM8974_EQ3,  0, 24, 1, eq_tlv),
1270a1bf553SMark Brown 
1280a1bf553SMark Brown SOC_ENUM("Equaliser EQ4 Bandwith", wm8974_enum[9]),
1290a1bf553SMark Brown SOC_ENUM("EQ4 Cut Off", wm8974_enum[10]),
130a5f8d2f1SMark Brown SOC_SINGLE_TLV("EQ4 Volume", WM8974_EQ4,  0, 24, 1, eq_tlv),
1310a1bf553SMark Brown 
1320a1bf553SMark Brown SOC_ENUM("Equaliser EQ5 Bandwith", wm8974_enum[11]),
1330a1bf553SMark Brown SOC_ENUM("EQ5 Cut Off", wm8974_enum[12]),
134a5f8d2f1SMark Brown SOC_SINGLE_TLV("EQ5 Volume", WM8974_EQ5,  0, 24, 1, eq_tlv),
1350a1bf553SMark Brown 
1360a1bf553SMark Brown SOC_SINGLE("DAC Playback Limiter Switch", WM8974_DACLIM1,  8, 1, 0),
1370a1bf553SMark Brown SOC_SINGLE("DAC Playback Limiter Decay", WM8974_DACLIM1,  4, 15, 0),
1380a1bf553SMark Brown SOC_SINGLE("DAC Playback Limiter Attack", WM8974_DACLIM1,  0, 15, 0),
1390a1bf553SMark Brown 
1400a1bf553SMark Brown SOC_SINGLE("DAC Playback Limiter Threshold", WM8974_DACLIM2,  4, 7, 0),
1410a1bf553SMark Brown SOC_SINGLE("DAC Playback Limiter Boost", WM8974_DACLIM2,  0, 15, 0),
1420a1bf553SMark Brown 
1430a1bf553SMark Brown SOC_SINGLE("ALC Enable Switch", WM8974_ALC1,  8, 1, 0),
1440a1bf553SMark Brown SOC_SINGLE("ALC Capture Max Gain", WM8974_ALC1,  3, 7, 0),
1450a1bf553SMark Brown SOC_SINGLE("ALC Capture Min Gain", WM8974_ALC1,  0, 7, 0),
1460a1bf553SMark Brown 
1470a1bf553SMark Brown SOC_SINGLE("ALC Capture ZC Switch", WM8974_ALC2,  8, 1, 0),
1480a1bf553SMark Brown SOC_SINGLE("ALC Capture Hold", WM8974_ALC2,  4, 7, 0),
1490a1bf553SMark Brown SOC_SINGLE("ALC Capture Target", WM8974_ALC2,  0, 15, 0),
1500a1bf553SMark Brown 
1510a1bf553SMark Brown SOC_ENUM("ALC Capture Mode", wm8974_enum[13]),
1520a1bf553SMark Brown SOC_SINGLE("ALC Capture Decay", WM8974_ALC3,  4, 15, 0),
1530a1bf553SMark Brown SOC_SINGLE("ALC Capture Attack", WM8974_ALC3,  0, 15, 0),
1540a1bf553SMark Brown 
1550a1bf553SMark Brown SOC_SINGLE("ALC Capture Noise Gate Switch", WM8974_NGATE,  3, 1, 0),
1560a1bf553SMark Brown SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8974_NGATE,  0, 7, 0),
1570a1bf553SMark Brown 
1580a1bf553SMark Brown SOC_SINGLE("Capture PGA ZC Switch", WM8974_INPPGA,  7, 1, 0),
159a5f8d2f1SMark Brown SOC_SINGLE_TLV("Capture PGA Volume", WM8974_INPPGA,  0, 63, 0, inpga_tlv),
1600a1bf553SMark Brown 
1610a1bf553SMark Brown SOC_SINGLE("Speaker Playback ZC Switch", WM8974_SPKVOL,  7, 1, 0),
1620a1bf553SMark Brown SOC_SINGLE("Speaker Playback Switch", WM8974_SPKVOL,  6, 1, 1),
1638a123ee2SMark Brown SOC_SINGLE_TLV("Speaker Playback Volume", WM8974_SPKVOL,  0, 63, 0, spk_tlv),
1648a123ee2SMark Brown 
1658a123ee2SMark Brown SOC_ENUM("Aux Mode", wm8974_auxmode),
1660a1bf553SMark Brown 
1670a1bf553SMark Brown SOC_SINGLE("Capture Boost(+20dB)", WM8974_ADCBOOST,  8, 1, 0),
1688a123ee2SMark Brown SOC_SINGLE("Mono Playback Switch", WM8974_MONOMIX, 6, 1, 1),
169b2c3e923SGuennadi Liakhovetski 
170b2c3e923SGuennadi Liakhovetski /* DAC / ADC oversampling */
171b2c3e923SGuennadi Liakhovetski SOC_SINGLE("DAC 128x Oversampling Switch", WM8974_DAC, 8, 1, 0),
172b2c3e923SGuennadi Liakhovetski SOC_SINGLE("ADC 128x Oversampling Switch", WM8974_ADC, 8, 1, 0),
1730a1bf553SMark Brown };
1740a1bf553SMark Brown 
1750a1bf553SMark Brown /* Speaker Output Mixer */
1760a1bf553SMark Brown static const struct snd_kcontrol_new wm8974_speaker_mixer_controls[] = {
1770a1bf553SMark Brown SOC_DAPM_SINGLE("Line Bypass Switch", WM8974_SPKMIX, 1, 1, 0),
1780a1bf553SMark Brown SOC_DAPM_SINGLE("Aux Playback Switch", WM8974_SPKMIX, 5, 1, 0),
179759512fbSMark Brown SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_SPKMIX, 0, 1, 0),
1800a1bf553SMark Brown };
1810a1bf553SMark Brown 
1820a1bf553SMark Brown /* Mono Output Mixer */
1830a1bf553SMark Brown static const struct snd_kcontrol_new wm8974_mono_mixer_controls[] = {
1840a1bf553SMark Brown SOC_DAPM_SINGLE("Line Bypass Switch", WM8974_MONOMIX, 1, 1, 0),
1850a1bf553SMark Brown SOC_DAPM_SINGLE("Aux Playback Switch", WM8974_MONOMIX, 2, 1, 0),
1868a123ee2SMark Brown SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_MONOMIX, 0, 1, 0),
1878a123ee2SMark Brown };
1888a123ee2SMark Brown 
1898a123ee2SMark Brown /* Boost mixer */
1908a123ee2SMark Brown static const struct snd_kcontrol_new wm8974_boost_mixer[] = {
1918a123ee2SMark Brown SOC_DAPM_SINGLE("Aux Switch", WM8974_INPPGA, 6, 1, 0),
1928a123ee2SMark Brown };
1938a123ee2SMark Brown 
1948a123ee2SMark Brown /* Input PGA */
1958a123ee2SMark Brown static const struct snd_kcontrol_new wm8974_inpga[] = {
1968a123ee2SMark Brown SOC_DAPM_SINGLE("Aux Switch", WM8974_INPUT, 2, 1, 0),
1978a123ee2SMark Brown SOC_DAPM_SINGLE("MicN Switch", WM8974_INPUT, 1, 1, 0),
1988a123ee2SMark Brown SOC_DAPM_SINGLE("MicP Switch", WM8974_INPUT, 0, 1, 0),
1990a1bf553SMark Brown };
2000a1bf553SMark Brown 
2010a1bf553SMark Brown /* AUX Input boost vol */
2020a1bf553SMark Brown static const struct snd_kcontrol_new wm8974_aux_boost_controls =
2030a1bf553SMark Brown SOC_DAPM_SINGLE("Aux Volume", WM8974_ADCBOOST, 0, 7, 0);
2040a1bf553SMark Brown 
2050a1bf553SMark Brown /* Mic Input boost vol */
2060a1bf553SMark Brown static const struct snd_kcontrol_new wm8974_mic_boost_controls =
2070a1bf553SMark Brown SOC_DAPM_SINGLE("Mic Volume", WM8974_ADCBOOST, 4, 7, 0);
2080a1bf553SMark Brown 
2090a1bf553SMark Brown static const struct snd_soc_dapm_widget wm8974_dapm_widgets[] = {
2100a1bf553SMark Brown SND_SOC_DAPM_MIXER("Speaker Mixer", WM8974_POWER3, 2, 0,
2110a1bf553SMark Brown 	&wm8974_speaker_mixer_controls[0],
2120a1bf553SMark Brown 	ARRAY_SIZE(wm8974_speaker_mixer_controls)),
2130a1bf553SMark Brown SND_SOC_DAPM_MIXER("Mono Mixer", WM8974_POWER3, 3, 0,
2140a1bf553SMark Brown 	&wm8974_mono_mixer_controls[0],
2150a1bf553SMark Brown 	ARRAY_SIZE(wm8974_mono_mixer_controls)),
2160a1bf553SMark Brown SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8974_POWER3, 0, 0),
2178a123ee2SMark Brown SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8974_POWER2, 0, 0),
2180a1bf553SMark Brown SND_SOC_DAPM_PGA("Aux Input", WM8974_POWER1, 6, 0, NULL, 0),
2190a1bf553SMark Brown SND_SOC_DAPM_PGA("SpkN Out", WM8974_POWER3, 5, 0, NULL, 0),
2200a1bf553SMark Brown SND_SOC_DAPM_PGA("SpkP Out", WM8974_POWER3, 6, 0, NULL, 0),
2210a1bf553SMark Brown SND_SOC_DAPM_PGA("Mono Out", WM8974_POWER3, 7, 0, NULL, 0),
2220a1bf553SMark Brown 
2238a123ee2SMark Brown SND_SOC_DAPM_MIXER("Input PGA", WM8974_POWER2, 2, 0, wm8974_inpga,
2248a123ee2SMark Brown 		   ARRAY_SIZE(wm8974_inpga)),
2258a123ee2SMark Brown SND_SOC_DAPM_MIXER("Boost Mixer", WM8974_POWER2, 4, 0,
2268a123ee2SMark Brown 		   wm8974_boost_mixer, ARRAY_SIZE(wm8974_boost_mixer)),
2270a1bf553SMark Brown 
22848dd231bSMark Brown SND_SOC_DAPM_SUPPLY("Mic Bias", WM8974_POWER1, 4, 0, NULL, 0),
2290a1bf553SMark Brown 
2300a1bf553SMark Brown SND_SOC_DAPM_INPUT("MICN"),
2310a1bf553SMark Brown SND_SOC_DAPM_INPUT("MICP"),
2320a1bf553SMark Brown SND_SOC_DAPM_INPUT("AUX"),
2330a1bf553SMark Brown SND_SOC_DAPM_OUTPUT("MONOOUT"),
2340a1bf553SMark Brown SND_SOC_DAPM_OUTPUT("SPKOUTP"),
2350a1bf553SMark Brown SND_SOC_DAPM_OUTPUT("SPKOUTN"),
2360a1bf553SMark Brown };
2370a1bf553SMark Brown 
2380a1bf553SMark Brown static const struct snd_soc_dapm_route audio_map[] = {
2390a1bf553SMark Brown 	/* Mono output mixer */
2400a1bf553SMark Brown 	{"Mono Mixer", "PCM Playback Switch", "DAC"},
2410a1bf553SMark Brown 	{"Mono Mixer", "Aux Playback Switch", "Aux Input"},
2420a1bf553SMark Brown 	{"Mono Mixer", "Line Bypass Switch", "Boost Mixer"},
2430a1bf553SMark Brown 
2440a1bf553SMark Brown 	/* Speaker output mixer */
2450a1bf553SMark Brown 	{"Speaker Mixer", "PCM Playback Switch", "DAC"},
2460a1bf553SMark Brown 	{"Speaker Mixer", "Aux Playback Switch", "Aux Input"},
2470a1bf553SMark Brown 	{"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"},
2480a1bf553SMark Brown 
2490a1bf553SMark Brown 	/* Outputs */
2500a1bf553SMark Brown 	{"Mono Out", NULL, "Mono Mixer"},
2510a1bf553SMark Brown 	{"MONOOUT", NULL, "Mono Out"},
2520a1bf553SMark Brown 	{"SpkN Out", NULL, "Speaker Mixer"},
2530a1bf553SMark Brown 	{"SpkP Out", NULL, "Speaker Mixer"},
2540a1bf553SMark Brown 	{"SPKOUTN", NULL, "SpkN Out"},
2550a1bf553SMark Brown 	{"SPKOUTP", NULL, "SpkP Out"},
2560a1bf553SMark Brown 
2570a1bf553SMark Brown 	/* Boost Mixer */
2588a123ee2SMark Brown 	{"ADC", NULL, "Boost Mixer"},
2598a123ee2SMark Brown 	{"Boost Mixer", "Aux Switch", "Aux Input"},
2608a123ee2SMark Brown 	{"Boost Mixer", NULL, "Input PGA"},
2618a123ee2SMark Brown 	{"Boost Mixer", NULL, "MICP"},
2628a123ee2SMark Brown 
2638a123ee2SMark Brown 	/* Input PGA */
2648a123ee2SMark Brown 	{"Input PGA", "Aux Switch", "Aux Input"},
2658a123ee2SMark Brown 	{"Input PGA", "MicN Switch", "MICN"},
2668a123ee2SMark Brown 	{"Input PGA", "MicP Switch", "MICP"},
2670a1bf553SMark Brown 
2680a1bf553SMark Brown 	/* Inputs */
2698a123ee2SMark Brown 	{"Aux Input", NULL, "AUX"},
2700a1bf553SMark Brown };
2710a1bf553SMark Brown 
2720a1bf553SMark Brown static int wm8974_add_widgets(struct snd_soc_codec *codec)
2730a1bf553SMark Brown {
274ce6120ccSLiam Girdwood 	struct snd_soc_dapm_context *dapm = &codec->dapm;
2750a1bf553SMark Brown 
276ce6120ccSLiam Girdwood 	snd_soc_dapm_new_controls(dapm, wm8974_dapm_widgets,
277ce6120ccSLiam Girdwood 				  ARRAY_SIZE(wm8974_dapm_widgets));
278ce6120ccSLiam Girdwood 	snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
2790a1bf553SMark Brown 
2800a1bf553SMark Brown 	return 0;
2810a1bf553SMark Brown }
2820a1bf553SMark Brown 
2830a1bf553SMark Brown struct pll_ {
284c36b2fc7SMark Brown 	unsigned int pre_div:1;
2850a1bf553SMark Brown 	unsigned int n:4;
2860a1bf553SMark Brown 	unsigned int k;
2870a1bf553SMark Brown };
2880a1bf553SMark Brown 
28991d0c3ecSMark Brown /* The size in bits of the pll divide multiplied by 10
29091d0c3ecSMark Brown  * to allow rounding later */
29191d0c3ecSMark Brown #define FIXED_PLL_SIZE ((1 << 24) * 10)
29291d0c3ecSMark Brown 
293c36b2fc7SMark Brown static void pll_factors(struct pll_ *pll_div,
294c36b2fc7SMark Brown 			unsigned int target, unsigned int source)
29591d0c3ecSMark Brown {
29691d0c3ecSMark Brown 	unsigned long long Kpart;
29791d0c3ecSMark Brown 	unsigned int K, Ndiv, Nmod;
29891d0c3ecSMark Brown 
299c36b2fc7SMark Brown 	/* There is a fixed divide by 4 in the output path */
300c36b2fc7SMark Brown 	target *= 4;
301c36b2fc7SMark Brown 
30291d0c3ecSMark Brown 	Ndiv = target / source;
30391d0c3ecSMark Brown 	if (Ndiv < 6) {
304c36b2fc7SMark Brown 		source /= 2;
305c36b2fc7SMark Brown 		pll_div->pre_div = 1;
30691d0c3ecSMark Brown 		Ndiv = target / source;
30791d0c3ecSMark Brown 	} else
308c36b2fc7SMark Brown 		pll_div->pre_div = 0;
30991d0c3ecSMark Brown 
31091d0c3ecSMark Brown 	if ((Ndiv < 6) || (Ndiv > 12))
31191d0c3ecSMark Brown 		printk(KERN_WARNING
3128b83a193SMark Brown 			"WM8974 N value %u outwith recommended range!\n",
31391d0c3ecSMark Brown 			Ndiv);
31491d0c3ecSMark Brown 
315c36b2fc7SMark Brown 	pll_div->n = Ndiv;
31691d0c3ecSMark Brown 	Nmod = target % source;
31791d0c3ecSMark Brown 	Kpart = FIXED_PLL_SIZE * (long long)Nmod;
31891d0c3ecSMark Brown 
31991d0c3ecSMark Brown 	do_div(Kpart, source);
32091d0c3ecSMark Brown 
32191d0c3ecSMark Brown 	K = Kpart & 0xFFFFFFFF;
32291d0c3ecSMark Brown 
32391d0c3ecSMark Brown 	/* Check if we need to round */
32491d0c3ecSMark Brown 	if ((K % 10) >= 5)
32591d0c3ecSMark Brown 		K += 5;
32691d0c3ecSMark Brown 
32791d0c3ecSMark Brown 	/* Move down to proper range now rounding is done */
32891d0c3ecSMark Brown 	K /= 10;
32991d0c3ecSMark Brown 
330c36b2fc7SMark Brown 	pll_div->k = K;
33191d0c3ecSMark Brown }
3320a1bf553SMark Brown 
33385488037SMark Brown static int wm8974_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
33485488037SMark Brown 		int source, unsigned int freq_in, unsigned int freq_out)
3350a1bf553SMark Brown {
3360a1bf553SMark Brown 	struct snd_soc_codec *codec = codec_dai->codec;
337c36b2fc7SMark Brown 	struct pll_ pll_div;
3380a1bf553SMark Brown 	u16 reg;
3390a1bf553SMark Brown 
3400a1bf553SMark Brown 	if (freq_in == 0 || freq_out == 0) {
34191d0c3ecSMark Brown 		/* Clock CODEC directly from MCLK */
3421e97f50bSMark Brown 		reg = snd_soc_read(codec, WM8974_CLOCK);
3431e97f50bSMark Brown 		snd_soc_write(codec, WM8974_CLOCK, reg & 0x0ff);
34491d0c3ecSMark Brown 
34591d0c3ecSMark Brown 		/* Turn off PLL */
3461e97f50bSMark Brown 		reg = snd_soc_read(codec, WM8974_POWER1);
3471e97f50bSMark Brown 		snd_soc_write(codec, WM8974_POWER1, reg & 0x1df);
3480a1bf553SMark Brown 		return 0;
3490a1bf553SMark Brown 	}
3500a1bf553SMark Brown 
351c36b2fc7SMark Brown 	pll_factors(&pll_div, freq_out, freq_in);
35291d0c3ecSMark Brown 
3531e97f50bSMark Brown 	snd_soc_write(codec, WM8974_PLLN, (pll_div.pre_div << 4) | pll_div.n);
3541e97f50bSMark Brown 	snd_soc_write(codec, WM8974_PLLK1, pll_div.k >> 18);
3551e97f50bSMark Brown 	snd_soc_write(codec, WM8974_PLLK2, (pll_div.k >> 9) & 0x1ff);
3561e97f50bSMark Brown 	snd_soc_write(codec, WM8974_PLLK3, pll_div.k & 0x1ff);
3571e97f50bSMark Brown 	reg = snd_soc_read(codec, WM8974_POWER1);
3581e97f50bSMark Brown 	snd_soc_write(codec, WM8974_POWER1, reg | 0x020);
3591a55b3f6SMark Brown 
36091d0c3ecSMark Brown 	/* Run CODEC from PLL instead of MCLK */
3611e97f50bSMark Brown 	reg = snd_soc_read(codec, WM8974_CLOCK);
3621e97f50bSMark Brown 	snd_soc_write(codec, WM8974_CLOCK, reg | 0x100);
36391d0c3ecSMark Brown 
36491d0c3ecSMark Brown 	return 0;
3650a1bf553SMark Brown }
3660a1bf553SMark Brown 
3670a1bf553SMark Brown /*
3680a1bf553SMark Brown  * Configure WM8974 clock dividers.
3690a1bf553SMark Brown  */
3700a1bf553SMark Brown static int wm8974_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
3710a1bf553SMark Brown 		int div_id, int div)
3720a1bf553SMark Brown {
3730a1bf553SMark Brown 	struct snd_soc_codec *codec = codec_dai->codec;
3740a1bf553SMark Brown 	u16 reg;
3750a1bf553SMark Brown 
3760a1bf553SMark Brown 	switch (div_id) {
3770a1bf553SMark Brown 	case WM8974_OPCLKDIV:
3781e97f50bSMark Brown 		reg = snd_soc_read(codec, WM8974_GPIO) & 0x1cf;
3791e97f50bSMark Brown 		snd_soc_write(codec, WM8974_GPIO, reg | div);
3800a1bf553SMark Brown 		break;
3810a1bf553SMark Brown 	case WM8974_MCLKDIV:
3821e97f50bSMark Brown 		reg = snd_soc_read(codec, WM8974_CLOCK) & 0x11f;
3831e97f50bSMark Brown 		snd_soc_write(codec, WM8974_CLOCK, reg | div);
3840a1bf553SMark Brown 		break;
3850a1bf553SMark Brown 	case WM8974_BCLKDIV:
3861e97f50bSMark Brown 		reg = snd_soc_read(codec, WM8974_CLOCK) & 0x1e3;
3871e97f50bSMark Brown 		snd_soc_write(codec, WM8974_CLOCK, reg | div);
3880a1bf553SMark Brown 		break;
3890a1bf553SMark Brown 	default:
3900a1bf553SMark Brown 		return -EINVAL;
3910a1bf553SMark Brown 	}
3920a1bf553SMark Brown 
3930a1bf553SMark Brown 	return 0;
3940a1bf553SMark Brown }
3950a1bf553SMark Brown 
3960a1bf553SMark Brown static int wm8974_set_dai_fmt(struct snd_soc_dai *codec_dai,
3970a1bf553SMark Brown 		unsigned int fmt)
3980a1bf553SMark Brown {
3990a1bf553SMark Brown 	struct snd_soc_codec *codec = codec_dai->codec;
4000a1bf553SMark Brown 	u16 iface = 0;
4011e97f50bSMark Brown 	u16 clk = snd_soc_read(codec, WM8974_CLOCK) & 0x1fe;
4020a1bf553SMark Brown 
4030a1bf553SMark Brown 	/* set master/slave audio interface */
4040a1bf553SMark Brown 	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
4050a1bf553SMark Brown 	case SND_SOC_DAIFMT_CBM_CFM:
4060a1bf553SMark Brown 		clk |= 0x0001;
4070a1bf553SMark Brown 		break;
4080a1bf553SMark Brown 	case SND_SOC_DAIFMT_CBS_CFS:
4090a1bf553SMark Brown 		break;
4100a1bf553SMark Brown 	default:
4110a1bf553SMark Brown 		return -EINVAL;
4120a1bf553SMark Brown 	}
4130a1bf553SMark Brown 
4140a1bf553SMark Brown 	/* interface format */
4150a1bf553SMark Brown 	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
4160a1bf553SMark Brown 	case SND_SOC_DAIFMT_I2S:
4170a1bf553SMark Brown 		iface |= 0x0010;
4180a1bf553SMark Brown 		break;
4190a1bf553SMark Brown 	case SND_SOC_DAIFMT_RIGHT_J:
4200a1bf553SMark Brown 		break;
4210a1bf553SMark Brown 	case SND_SOC_DAIFMT_LEFT_J:
4220a1bf553SMark Brown 		iface |= 0x0008;
4230a1bf553SMark Brown 		break;
4240a1bf553SMark Brown 	case SND_SOC_DAIFMT_DSP_A:
4250a1bf553SMark Brown 		iface |= 0x00018;
4260a1bf553SMark Brown 		break;
4270a1bf553SMark Brown 	default:
4280a1bf553SMark Brown 		return -EINVAL;
4290a1bf553SMark Brown 	}
4300a1bf553SMark Brown 
4310a1bf553SMark Brown 	/* clock inversion */
4320a1bf553SMark Brown 	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
4330a1bf553SMark Brown 	case SND_SOC_DAIFMT_NB_NF:
4340a1bf553SMark Brown 		break;
4350a1bf553SMark Brown 	case SND_SOC_DAIFMT_IB_IF:
4360a1bf553SMark Brown 		iface |= 0x0180;
4370a1bf553SMark Brown 		break;
4380a1bf553SMark Brown 	case SND_SOC_DAIFMT_IB_NF:
4390a1bf553SMark Brown 		iface |= 0x0100;
4400a1bf553SMark Brown 		break;
4410a1bf553SMark Brown 	case SND_SOC_DAIFMT_NB_IF:
4420a1bf553SMark Brown 		iface |= 0x0080;
4430a1bf553SMark Brown 		break;
4440a1bf553SMark Brown 	default:
4450a1bf553SMark Brown 		return -EINVAL;
4460a1bf553SMark Brown 	}
4470a1bf553SMark Brown 
4481e97f50bSMark Brown 	snd_soc_write(codec, WM8974_IFACE, iface);
4491e97f50bSMark Brown 	snd_soc_write(codec, WM8974_CLOCK, clk);
4500a1bf553SMark Brown 	return 0;
4510a1bf553SMark Brown }
4520a1bf553SMark Brown 
4530a1bf553SMark Brown static int wm8974_pcm_hw_params(struct snd_pcm_substream *substream,
4540a1bf553SMark Brown 				struct snd_pcm_hw_params *params,
4550a1bf553SMark Brown 				struct snd_soc_dai *dai)
4560a1bf553SMark Brown {
4570a1bf553SMark Brown 	struct snd_soc_codec *codec = dai->codec;
4581e97f50bSMark Brown 	u16 iface = snd_soc_read(codec, WM8974_IFACE) & 0x19f;
4591e97f50bSMark Brown 	u16 adn = snd_soc_read(codec, WM8974_ADD) & 0x1f1;
4600a1bf553SMark Brown 
4610a1bf553SMark Brown 	/* bit size */
4620a1bf553SMark Brown 	switch (params_format(params)) {
4630a1bf553SMark Brown 	case SNDRV_PCM_FORMAT_S16_LE:
4640a1bf553SMark Brown 		break;
4650a1bf553SMark Brown 	case SNDRV_PCM_FORMAT_S20_3LE:
4660a1bf553SMark Brown 		iface |= 0x0020;
4670a1bf553SMark Brown 		break;
4680a1bf553SMark Brown 	case SNDRV_PCM_FORMAT_S24_LE:
4690a1bf553SMark Brown 		iface |= 0x0040;
4700a1bf553SMark Brown 		break;
4710a1bf553SMark Brown 	case SNDRV_PCM_FORMAT_S32_LE:
4720a1bf553SMark Brown 		iface |= 0x0060;
4730a1bf553SMark Brown 		break;
4740a1bf553SMark Brown 	}
4750a1bf553SMark Brown 
4760a1bf553SMark Brown 	/* filter coefficient */
4770a1bf553SMark Brown 	switch (params_rate(params)) {
478b3172f22SGuennadi Liakhovetski 	case 8000:
4790a1bf553SMark Brown 		adn |= 0x5 << 1;
4800a1bf553SMark Brown 		break;
481b3172f22SGuennadi Liakhovetski 	case 11025:
4820a1bf553SMark Brown 		adn |= 0x4 << 1;
4830a1bf553SMark Brown 		break;
484b3172f22SGuennadi Liakhovetski 	case 16000:
4850a1bf553SMark Brown 		adn |= 0x3 << 1;
4860a1bf553SMark Brown 		break;
487b3172f22SGuennadi Liakhovetski 	case 22050:
4880a1bf553SMark Brown 		adn |= 0x2 << 1;
4890a1bf553SMark Brown 		break;
490b3172f22SGuennadi Liakhovetski 	case 32000:
4910a1bf553SMark Brown 		adn |= 0x1 << 1;
4920a1bf553SMark Brown 		break;
493b3172f22SGuennadi Liakhovetski 	case 44100:
494b3172f22SGuennadi Liakhovetski 	case 48000:
4950a1bf553SMark Brown 		break;
4960a1bf553SMark Brown 	}
4970a1bf553SMark Brown 
4981e97f50bSMark Brown 	snd_soc_write(codec, WM8974_IFACE, iface);
4991e97f50bSMark Brown 	snd_soc_write(codec, WM8974_ADD, adn);
5000a1bf553SMark Brown 	return 0;
5010a1bf553SMark Brown }
5020a1bf553SMark Brown 
5030a1bf553SMark Brown static int wm8974_mute(struct snd_soc_dai *dai, int mute)
5040a1bf553SMark Brown {
5050a1bf553SMark Brown 	struct snd_soc_codec *codec = dai->codec;
5061e97f50bSMark Brown 	u16 mute_reg = snd_soc_read(codec, WM8974_DAC) & 0xffbf;
5070a1bf553SMark Brown 
5080a1bf553SMark Brown 	if (mute)
5091e97f50bSMark Brown 		snd_soc_write(codec, WM8974_DAC, mute_reg | 0x40);
5100a1bf553SMark Brown 	else
5111e97f50bSMark Brown 		snd_soc_write(codec, WM8974_DAC, mute_reg);
5120a1bf553SMark Brown 	return 0;
5130a1bf553SMark Brown }
5140a1bf553SMark Brown 
5150a1bf553SMark Brown /* liam need to make this lower power with dapm */
5160a1bf553SMark Brown static int wm8974_set_bias_level(struct snd_soc_codec *codec,
5170a1bf553SMark Brown 	enum snd_soc_bias_level level)
5180a1bf553SMark Brown {
5191e97f50bSMark Brown 	u16 power1 = snd_soc_read(codec, WM8974_POWER1) & ~0x3;
520df1ef7a3SMark Brown 
5210a1bf553SMark Brown 	switch (level) {
5220a1bf553SMark Brown 	case SND_SOC_BIAS_ON:
5230a1bf553SMark Brown 	case SND_SOC_BIAS_PREPARE:
524df1ef7a3SMark Brown 		power1 |= 0x1;  /* VMID 50k */
5251e97f50bSMark Brown 		snd_soc_write(codec, WM8974_POWER1, power1);
5260a1bf553SMark Brown 		break;
527df1ef7a3SMark Brown 
5280a1bf553SMark Brown 	case SND_SOC_BIAS_STANDBY:
529df1ef7a3SMark Brown 		power1 |= WM8974_POWER1_BIASEN | WM8974_POWER1_BUFIOEN;
530df1ef7a3SMark Brown 
531ce6120ccSLiam Girdwood 		if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
5320bad3d84SAxel Lin 			snd_soc_cache_sync(codec);
5330bad3d84SAxel Lin 
534df1ef7a3SMark Brown 			/* Initial cap charge at VMID 5k */
5351e97f50bSMark Brown 			snd_soc_write(codec, WM8974_POWER1, power1 | 0x3);
536df1ef7a3SMark Brown 			mdelay(100);
537df1ef7a3SMark Brown 		}
538df1ef7a3SMark Brown 
539df1ef7a3SMark Brown 		power1 |= 0x2;  /* VMID 500k */
5401e97f50bSMark Brown 		snd_soc_write(codec, WM8974_POWER1, power1);
5410a1bf553SMark Brown 		break;
542df1ef7a3SMark Brown 
5430a1bf553SMark Brown 	case SND_SOC_BIAS_OFF:
5441e97f50bSMark Brown 		snd_soc_write(codec, WM8974_POWER1, 0);
5451e97f50bSMark Brown 		snd_soc_write(codec, WM8974_POWER2, 0);
5461e97f50bSMark Brown 		snd_soc_write(codec, WM8974_POWER3, 0);
5470a1bf553SMark Brown 		break;
5480a1bf553SMark Brown 	}
549df1ef7a3SMark Brown 
550ce6120ccSLiam Girdwood 	codec->dapm.bias_level = level;
5510a1bf553SMark Brown 	return 0;
5520a1bf553SMark Brown }
5530a1bf553SMark Brown 
5541a55b3f6SMark Brown #define WM8974_RATES (SNDRV_PCM_RATE_8000_48000)
5550a1bf553SMark Brown 
5560a1bf553SMark Brown #define WM8974_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
5570a1bf553SMark Brown 	SNDRV_PCM_FMTBIT_S24_LE)
5580a1bf553SMark Brown 
55985e7652dSLars-Peter Clausen static const struct snd_soc_dai_ops wm8974_ops = {
5600a1bf553SMark Brown 	.hw_params = wm8974_pcm_hw_params,
5610a1bf553SMark Brown 	.digital_mute = wm8974_mute,
5620a1bf553SMark Brown 	.set_fmt = wm8974_set_dai_fmt,
5630a1bf553SMark Brown 	.set_clkdiv = wm8974_set_dai_clkdiv,
5640a1bf553SMark Brown 	.set_pll = wm8974_set_dai_pll,
5650a1bf553SMark Brown };
5660a1bf553SMark Brown 
567f0fba2adSLiam Girdwood static struct snd_soc_dai_driver wm8974_dai = {
568f0fba2adSLiam Girdwood 	.name = "wm8974-hifi",
5690a1bf553SMark Brown 	.playback = {
5700a1bf553SMark Brown 		.stream_name = "Playback",
5710a1bf553SMark Brown 		.channels_min = 1,
57233d81af4SMark Brown 		.channels_max = 2,   /* Only 1 channel of data */
5730a1bf553SMark Brown 		.rates = WM8974_RATES,
5740a1bf553SMark Brown 		.formats = WM8974_FORMATS,},
5750a1bf553SMark Brown 	.capture = {
5760a1bf553SMark Brown 		.stream_name = "Capture",
5770a1bf553SMark Brown 		.channels_min = 1,
57833d81af4SMark Brown 		.channels_max = 2,   /* Only 1 channel of data */
5790a1bf553SMark Brown 		.rates = WM8974_RATES,
5800a1bf553SMark Brown 		.formats = WM8974_FORMATS,},
5810a1bf553SMark Brown 	.ops = &wm8974_ops,
582cb11d39eSMark Brown 	.symmetric_rates = 1,
5830a1bf553SMark Brown };
5840a1bf553SMark Brown 
585*84b315eeSLars-Peter Clausen static int wm8974_suspend(struct snd_soc_codec *codec)
5860a1bf553SMark Brown {
5870a1bf553SMark Brown 	wm8974_set_bias_level(codec, SND_SOC_BIAS_OFF);
5880a1bf553SMark Brown 	return 0;
5890a1bf553SMark Brown }
5900a1bf553SMark Brown 
591f0fba2adSLiam Girdwood static int wm8974_resume(struct snd_soc_codec *codec)
5920a1bf553SMark Brown {
5930a1bf553SMark Brown 	wm8974_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
5940a1bf553SMark Brown 	return 0;
5950a1bf553SMark Brown }
5960a1bf553SMark Brown 
597f0fba2adSLiam Girdwood static int wm8974_probe(struct snd_soc_codec *codec)
5980a1bf553SMark Brown {
5990a1bf553SMark Brown 	int ret = 0;
6000a1bf553SMark Brown 
601f0fba2adSLiam Girdwood 	ret = snd_soc_codec_set_cache_io(codec, 7, 9, SND_SOC_I2C);
6020a1bf553SMark Brown 	if (ret < 0) {
603f0fba2adSLiam Girdwood 		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
604f0fba2adSLiam Girdwood 		return ret;
6050a1bf553SMark Brown 	}
6060a1bf553SMark Brown 
607f0fba2adSLiam Girdwood 	ret = wm8974_reset(codec);
608f0fba2adSLiam Girdwood 	if (ret < 0) {
609f0fba2adSLiam Girdwood 		dev_err(codec->dev, "Failed to issue reset\n");
610f0fba2adSLiam Girdwood 		return ret;
611f0fba2adSLiam Girdwood 	}
612f0fba2adSLiam Girdwood 
613f0fba2adSLiam Girdwood 	wm8974_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
6144fcbbb67SMark Brown 	snd_soc_add_controls(codec, wm8974_snd_controls,
6154fcbbb67SMark Brown 			     ARRAY_SIZE(wm8974_snd_controls));
6160a1bf553SMark Brown 	wm8974_add_widgets(codec);
6174fcbbb67SMark Brown 
6180a1bf553SMark Brown 	return ret;
6190a1bf553SMark Brown }
6200a1bf553SMark Brown 
6210a1bf553SMark Brown /* power down chip */
622f0fba2adSLiam Girdwood static int wm8974_remove(struct snd_soc_codec *codec)
6230a1bf553SMark Brown {
624f0fba2adSLiam Girdwood 	wm8974_set_bias_level(codec, SND_SOC_BIAS_OFF);
6250a1bf553SMark Brown 	return 0;
6260a1bf553SMark Brown }
6270a1bf553SMark Brown 
628f0fba2adSLiam Girdwood static struct snd_soc_codec_driver soc_codec_dev_wm8974 = {
6290a1bf553SMark Brown 	.probe = 	wm8974_probe,
6300a1bf553SMark Brown 	.remove = 	wm8974_remove,
6310a1bf553SMark Brown 	.suspend = 	wm8974_suspend,
6320a1bf553SMark Brown 	.resume =	wm8974_resume,
633f0fba2adSLiam Girdwood 	.set_bias_level = wm8974_set_bias_level,
634f0fba2adSLiam Girdwood 	.reg_cache_size = ARRAY_SIZE(wm8974_reg),
635f0fba2adSLiam Girdwood 	.reg_word_size = sizeof(u16),
636f0fba2adSLiam Girdwood 	.reg_cache_default = wm8974_reg,
6370a1bf553SMark Brown };
6380a1bf553SMark Brown 
639f0fba2adSLiam Girdwood #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
6404fcbbb67SMark Brown static __devinit int wm8974_i2c_probe(struct i2c_client *i2c,
6414fcbbb67SMark Brown 				      const struct i2c_device_id *id)
6424fcbbb67SMark Brown {
6434fcbbb67SMark Brown 	struct wm8974_priv *wm8974;
644f0fba2adSLiam Girdwood 	int ret;
6454fcbbb67SMark Brown 
6464fcbbb67SMark Brown 	wm8974 = kzalloc(sizeof(struct wm8974_priv), GFP_KERNEL);
6474fcbbb67SMark Brown 	if (wm8974 == NULL)
6484fcbbb67SMark Brown 		return -ENOMEM;
6494fcbbb67SMark Brown 
6504fcbbb67SMark Brown 	i2c_set_clientdata(i2c, wm8974);
6514fcbbb67SMark Brown 
652f0fba2adSLiam Girdwood 	ret = snd_soc_register_codec(&i2c->dev,
653f0fba2adSLiam Girdwood 			&soc_codec_dev_wm8974, &wm8974_dai, 1);
654f0fba2adSLiam Girdwood 	if (ret < 0)
655f0fba2adSLiam Girdwood 		kfree(wm8974);
656f0fba2adSLiam Girdwood 	return ret;
6574fcbbb67SMark Brown }
6584fcbbb67SMark Brown 
6594fcbbb67SMark Brown static __devexit int wm8974_i2c_remove(struct i2c_client *client)
6604fcbbb67SMark Brown {
661f0fba2adSLiam Girdwood 	snd_soc_unregister_codec(&client->dev);
662f0fba2adSLiam Girdwood 	kfree(i2c_get_clientdata(client));
6634fcbbb67SMark Brown 	return 0;
6644fcbbb67SMark Brown }
6654fcbbb67SMark Brown 
6664fcbbb67SMark Brown static const struct i2c_device_id wm8974_i2c_id[] = {
6674fcbbb67SMark Brown 	{ "wm8974", 0 },
6684fcbbb67SMark Brown 	{ }
6694fcbbb67SMark Brown };
6704fcbbb67SMark Brown MODULE_DEVICE_TABLE(i2c, wm8974_i2c_id);
6714fcbbb67SMark Brown 
6724fcbbb67SMark Brown static struct i2c_driver wm8974_i2c_driver = {
6734fcbbb67SMark Brown 	.driver = {
674f0fba2adSLiam Girdwood 		.name = "wm8974-codec",
6754fcbbb67SMark Brown 		.owner = THIS_MODULE,
6764fcbbb67SMark Brown 	},
6774fcbbb67SMark Brown 	.probe =    wm8974_i2c_probe,
6784fcbbb67SMark Brown 	.remove =   __devexit_p(wm8974_i2c_remove),
6794fcbbb67SMark Brown 	.id_table = wm8974_i2c_id,
6804fcbbb67SMark Brown };
681f0fba2adSLiam Girdwood #endif
6824fcbbb67SMark Brown 
6830a1bf553SMark Brown static int __init wm8974_modinit(void)
6840a1bf553SMark Brown {
685f0fba2adSLiam Girdwood 	int ret = 0;
686f0fba2adSLiam Girdwood #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
687f0fba2adSLiam Girdwood 	ret = i2c_add_driver(&wm8974_i2c_driver);
688f0fba2adSLiam Girdwood 	if (ret != 0) {
689f0fba2adSLiam Girdwood 		printk(KERN_ERR "Failed to register wm8974 I2C driver: %d\n",
690f0fba2adSLiam Girdwood 		       ret);
691f0fba2adSLiam Girdwood 	}
692f0fba2adSLiam Girdwood #endif
693f0fba2adSLiam Girdwood 	return ret;
6940a1bf553SMark Brown }
6950a1bf553SMark Brown module_init(wm8974_modinit);
6960a1bf553SMark Brown 
6970a1bf553SMark Brown static void __exit wm8974_exit(void)
6980a1bf553SMark Brown {
699f0fba2adSLiam Girdwood #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
7004fcbbb67SMark Brown 	i2c_del_driver(&wm8974_i2c_driver);
701f0fba2adSLiam Girdwood #endif
7020a1bf553SMark Brown }
7030a1bf553SMark Brown module_exit(wm8974_exit);
7040a1bf553SMark Brown 
7050a1bf553SMark Brown MODULE_DESCRIPTION("ASoC WM8974 driver");
7060a1bf553SMark Brown MODULE_AUTHOR("Liam Girdwood");
7070a1bf553SMark Brown MODULE_LICENSE("GPL");
708