xref: /openbmc/linux/sound/soc/codecs/wm9713.c (revision 656baaebf92ae9b16644c7e10a273d8dfe1ba1f6)
183ac08c0SLiam Girdwood /*
283ac08c0SLiam Girdwood  * wm9713.c  --  ALSA Soc WM9713 codec support
383ac08c0SLiam Girdwood  *
4*656baaebSMark Brown  * Copyright 2006-10 Wolfson Microelectronics PLC.
5d331124dSLiam Girdwood  * Author: Liam Girdwood <lrg@slimlogic.co.uk>
683ac08c0SLiam Girdwood  *
783ac08c0SLiam Girdwood  *  This program is free software; you can redistribute  it and/or modify it
883ac08c0SLiam Girdwood  *  under  the terms of  the GNU General  Public License as published by the
983ac08c0SLiam Girdwood  *  Free Software Foundation;  either version 2 of the  License, or (at your
1083ac08c0SLiam Girdwood  *  option) any later version.
1183ac08c0SLiam Girdwood  *
1283ac08c0SLiam Girdwood  *  Features:-
1383ac08c0SLiam Girdwood  *
1483ac08c0SLiam Girdwood  *   o Support for AC97 Codec, Voice DAC and Aux DAC
1583ac08c0SLiam Girdwood  *   o Support for DAPM
1683ac08c0SLiam Girdwood  */
1783ac08c0SLiam Girdwood 
1883ac08c0SLiam Girdwood #include <linux/init.h>
195a0e3ad6STejun Heo #include <linux/slab.h>
2083ac08c0SLiam Girdwood #include <linux/module.h>
2183ac08c0SLiam Girdwood #include <linux/device.h>
2283ac08c0SLiam Girdwood #include <sound/core.h>
2383ac08c0SLiam Girdwood #include <sound/pcm.h>
2483ac08c0SLiam Girdwood #include <sound/ac97_codec.h>
2583ac08c0SLiam Girdwood #include <sound/initval.h>
2683ac08c0SLiam Girdwood #include <sound/pcm_params.h>
27e03a8d2cSMark Brown #include <sound/tlv.h>
2883ac08c0SLiam Girdwood #include <sound/soc.h>
2983ac08c0SLiam Girdwood 
3083ac08c0SLiam Girdwood #include "wm9713.h"
3183ac08c0SLiam Girdwood 
3283ac08c0SLiam Girdwood struct wm9713_priv {
3383ac08c0SLiam Girdwood 	u32 pll_in; /* PLL input frequency */
3483ac08c0SLiam Girdwood };
3583ac08c0SLiam Girdwood 
3683ac08c0SLiam Girdwood static unsigned int ac97_read(struct snd_soc_codec *codec,
3783ac08c0SLiam Girdwood 	unsigned int reg);
3883ac08c0SLiam Girdwood static int ac97_write(struct snd_soc_codec *codec,
3983ac08c0SLiam Girdwood 	unsigned int reg, unsigned int val);
4083ac08c0SLiam Girdwood 
4183ac08c0SLiam Girdwood /*
4283ac08c0SLiam Girdwood  * WM9713 register cache
4383ac08c0SLiam Girdwood  * Reg 0x3c bit 15 is used by touch driver.
4483ac08c0SLiam Girdwood  */
4583ac08c0SLiam Girdwood static const u16 wm9713_reg[] = {
4683ac08c0SLiam Girdwood 	0x6174, 0x8080, 0x8080, 0x8080,
4783ac08c0SLiam Girdwood 	0xc880, 0xe808, 0xe808, 0x0808,
4883ac08c0SLiam Girdwood 	0x00da, 0x8000, 0xd600, 0xaaa0,
4983ac08c0SLiam Girdwood 	0xaaa0, 0xaaa0, 0x0000, 0x0000,
5083ac08c0SLiam Girdwood 	0x0f0f, 0x0040, 0x0000, 0x7f00,
5183ac08c0SLiam Girdwood 	0x0405, 0x0410, 0xbb80, 0xbb80,
5283ac08c0SLiam Girdwood 	0x0000, 0xbb80, 0x0000, 0x4523,
5383ac08c0SLiam Girdwood 	0x0000, 0x2000, 0x7eff, 0xffff,
5483ac08c0SLiam Girdwood 	0x0000, 0x0000, 0x0080, 0x0000,
5583ac08c0SLiam Girdwood 	0x0000, 0x0000, 0xfffe, 0xffff,
5683ac08c0SLiam Girdwood 	0x0000, 0x0000, 0x0000, 0xfffe,
5783ac08c0SLiam Girdwood 	0x4000, 0x0000, 0x0000, 0x0000,
5883ac08c0SLiam Girdwood 	0xb032, 0x3e00, 0x0000, 0x0000,
5983ac08c0SLiam Girdwood 	0x0000, 0x0000, 0x0000, 0x0000,
6083ac08c0SLiam Girdwood 	0x0000, 0x0000, 0x0000, 0x0006,
6183ac08c0SLiam Girdwood 	0x0001, 0x0000, 0x574d, 0x4c13,
6283ac08c0SLiam Girdwood 	0x0000, 0x0000, 0x0000
6383ac08c0SLiam Girdwood };
6483ac08c0SLiam Girdwood 
6583ac08c0SLiam Girdwood /* virtual HP mixers regs */
6683ac08c0SLiam Girdwood #define HPL_MIXER	0x80
6783ac08c0SLiam Girdwood #define HPR_MIXER	0x82
6883ac08c0SLiam Girdwood #define MICB_MUX	0x82
6983ac08c0SLiam Girdwood 
7083ac08c0SLiam Girdwood static const char *wm9713_mic_mixer[] = {"Stereo", "Mic 1", "Mic 2", "Mute"};
7183ac08c0SLiam Girdwood static const char *wm9713_rec_mux[] = {"Stereo", "Left", "Right", "Mute"};
7283ac08c0SLiam Girdwood static const char *wm9713_rec_src[] =
7383ac08c0SLiam Girdwood 	{"Mic 1", "Mic 2", "Line", "Mono In", "Headphone", "Speaker",
7483ac08c0SLiam Girdwood 	"Mono Out", "Zh"};
7583ac08c0SLiam Girdwood static const char *wm9713_rec_gain[] = {"+1.5dB Steps", "+0.75dB Steps"};
7683ac08c0SLiam Girdwood static const char *wm9713_alc_select[] = {"None", "Left", "Right", "Stereo"};
7783ac08c0SLiam Girdwood static const char *wm9713_mono_pga[] = {"Vmid", "Zh", "Mono", "Inv",
7883ac08c0SLiam Girdwood 	"Mono Vmid", "Inv Vmid"};
7983ac08c0SLiam Girdwood static const char *wm9713_spk_pga[] =
8083ac08c0SLiam Girdwood 	{"Vmid", "Zh", "Headphone", "Speaker", "Inv", "Headphone Vmid",
8183ac08c0SLiam Girdwood 	"Speaker Vmid", "Inv Vmid"};
8283ac08c0SLiam Girdwood static const char *wm9713_hp_pga[] = {"Vmid", "Zh", "Headphone",
8383ac08c0SLiam Girdwood 	"Headphone Vmid"};
8483ac08c0SLiam Girdwood static const char *wm9713_out3_pga[] = {"Vmid", "Zh", "Inv 1", "Inv 1 Vmid"};
8583ac08c0SLiam Girdwood static const char *wm9713_out4_pga[] = {"Vmid", "Zh", "Inv 2", "Inv 2 Vmid"};
8683ac08c0SLiam Girdwood static const char *wm9713_dac_inv[] =
8783ac08c0SLiam Girdwood 	{"Off", "Mono", "Speaker", "Left Headphone", "Right Headphone",
8883ac08c0SLiam Girdwood 	"Headphone Mono", "NC", "Vmid"};
8983ac08c0SLiam Girdwood static const char *wm9713_bass[] = {"Linear Control", "Adaptive Boost"};
9083ac08c0SLiam Girdwood static const char *wm9713_ng_type[] = {"Constant Gain", "Mute"};
9183ac08c0SLiam Girdwood static const char *wm9713_mic_select[] = {"Mic 1", "Mic 2 A", "Mic 2 B"};
9283ac08c0SLiam Girdwood static const char *wm9713_micb_select[] = {"MPB", "MPA"};
9383ac08c0SLiam Girdwood 
9483ac08c0SLiam Girdwood static const struct soc_enum wm9713_enum[] = {
9583ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_LINE, 3, 4, wm9713_mic_mixer), /* record mic mixer 0 */
9683ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_VIDEO, 14, 4, wm9713_rec_mux), /* record mux hp 1 */
9783ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_VIDEO, 9, 4, wm9713_rec_mux),  /* record mux mono 2 */
9883ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_VIDEO, 3, 8, wm9713_rec_src),  /* record mux left 3 */
9983ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_VIDEO, 0, 8, wm9713_rec_src),  /* record mux right 4*/
10083ac08c0SLiam Girdwood SOC_ENUM_DOUBLE(AC97_CD, 14, 6, 2, wm9713_rec_gain), /* record step size 5 */
10183ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_PCI_SVID, 14, 4, wm9713_alc_select), /* alc source select 6*/
10283ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_REC_GAIN, 14, 4, wm9713_mono_pga), /* mono input select 7 */
10383ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_REC_GAIN, 11, 8, wm9713_spk_pga), /* speaker left input select 8 */
10483ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_REC_GAIN, 8, 8, wm9713_spk_pga), /* speaker right input select 9 */
10583ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_REC_GAIN, 6, 3, wm9713_hp_pga), /* headphone left input 10 */
10683ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_REC_GAIN, 4, 3, wm9713_hp_pga), /* headphone right input 11 */
10783ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_REC_GAIN, 2, 4, wm9713_out3_pga), /* out 3 source 12 */
10883ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_REC_GAIN, 0, 4, wm9713_out4_pga), /* out 4 source 13 */
10983ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_REC_GAIN_MIC, 13, 8, wm9713_dac_inv), /* dac invert 1 14 */
11083ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_REC_GAIN_MIC, 10, 8, wm9713_dac_inv), /* dac invert 2 15 */
11183ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, wm9713_bass), /* bass control 16 */
11283ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9713_ng_type), /* noise gate type 17 */
11383ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_3D_CONTROL, 12, 3, wm9713_mic_select), /* mic selection 18 */
11483ac08c0SLiam Girdwood SOC_ENUM_SINGLE(MICB_MUX, 0, 2, wm9713_micb_select), /* mic selection 19 */
11583ac08c0SLiam Girdwood };
11683ac08c0SLiam Girdwood 
117e03a8d2cSMark Brown static const DECLARE_TLV_DB_SCALE(out_tlv, -4650, 150, 0);
118e03a8d2cSMark Brown static const DECLARE_TLV_DB_SCALE(main_tlv, -3450, 150, 0);
119e03a8d2cSMark Brown static const DECLARE_TLV_DB_SCALE(misc_tlv, -1500, 300, 0);
120e03a8d2cSMark Brown static unsigned int mic_tlv[] = {
121e03a8d2cSMark Brown 	TLV_DB_RANGE_HEAD(2),
122e03a8d2cSMark Brown 	0, 2, TLV_DB_SCALE_ITEM(1200, 600, 0),
123e03a8d2cSMark Brown 	3, 3, TLV_DB_SCALE_ITEM(3000, 0, 0),
124e03a8d2cSMark Brown };
125e03a8d2cSMark Brown 
12683ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_snd_ac97_controls[] = {
127e03a8d2cSMark Brown SOC_DOUBLE_TLV("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1, out_tlv),
12883ac08c0SLiam Girdwood SOC_DOUBLE("Speaker Playback Switch", AC97_MASTER, 15, 7, 1, 1),
129e03a8d2cSMark Brown SOC_DOUBLE_TLV("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1,
130e03a8d2cSMark Brown 	       out_tlv),
13183ac08c0SLiam Girdwood SOC_DOUBLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 7, 1, 1),
132e03a8d2cSMark Brown SOC_DOUBLE_TLV("Line In Volume", AC97_PC_BEEP, 8, 0, 31, 1, main_tlv),
133e03a8d2cSMark Brown SOC_DOUBLE_TLV("PCM Playback Volume", AC97_PHONE, 8, 0, 31, 1, main_tlv),
134e03a8d2cSMark Brown SOC_SINGLE_TLV("Mic 1 Volume", AC97_MIC, 8, 31, 1, main_tlv),
135e03a8d2cSMark Brown SOC_SINGLE_TLV("Mic 2 Volume", AC97_MIC, 0, 31, 1, main_tlv),
136e03a8d2cSMark Brown SOC_SINGLE_TLV("Mic 1 Preamp Volume", AC97_3D_CONTROL, 10, 3, 0, mic_tlv),
137e03a8d2cSMark Brown SOC_SINGLE_TLV("Mic 2 Preamp Volume", AC97_3D_CONTROL, 12, 3, 0, mic_tlv),
13883ac08c0SLiam Girdwood 
13983ac08c0SLiam Girdwood SOC_SINGLE("Mic Boost (+20dB) Switch", AC97_LINE, 5, 1, 0),
14083ac08c0SLiam Girdwood SOC_SINGLE("Mic Headphone Mixer Volume", AC97_LINE, 0, 7, 1),
14183ac08c0SLiam Girdwood 
14283ac08c0SLiam Girdwood SOC_SINGLE("Capture Switch", AC97_CD, 15, 1, 1),
14383ac08c0SLiam Girdwood SOC_ENUM("Capture Volume Steps", wm9713_enum[5]),
14483ac08c0SLiam Girdwood SOC_DOUBLE("Capture Volume", AC97_CD, 8, 0, 31, 0),
14583ac08c0SLiam Girdwood SOC_SINGLE("Capture ZC Switch", AC97_CD, 7, 1, 0),
14683ac08c0SLiam Girdwood 
147e03a8d2cSMark Brown SOC_SINGLE_TLV("Capture to Headphone Volume", AC97_VIDEO, 11, 7, 1, misc_tlv),
14883ac08c0SLiam Girdwood SOC_SINGLE("Capture to Mono Boost (+20dB) Switch", AC97_VIDEO, 8, 1, 0),
14983ac08c0SLiam Girdwood SOC_SINGLE("Capture ADC Boost (+20dB) Switch", AC97_VIDEO, 6, 1, 0),
15083ac08c0SLiam Girdwood 
15183ac08c0SLiam Girdwood SOC_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0),
15283ac08c0SLiam Girdwood SOC_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0),
15383ac08c0SLiam Girdwood SOC_SINGLE("ALC Decay Time", AC97_CODEC_CLASS_REV, 4, 15, 0),
15483ac08c0SLiam Girdwood SOC_SINGLE("ALC Attack Time", AC97_CODEC_CLASS_REV, 0, 15, 0),
15583ac08c0SLiam Girdwood SOC_ENUM("ALC Function", wm9713_enum[6]),
15683ac08c0SLiam Girdwood SOC_SINGLE("ALC Max Volume", AC97_PCI_SVID, 11, 7, 0),
15783ac08c0SLiam Girdwood SOC_SINGLE("ALC ZC Timeout", AC97_PCI_SVID, 9, 3, 0),
15883ac08c0SLiam Girdwood SOC_SINGLE("ALC ZC Switch", AC97_PCI_SVID, 8, 1, 0),
15983ac08c0SLiam Girdwood SOC_SINGLE("ALC NG Switch", AC97_PCI_SVID, 7, 1, 0),
16083ac08c0SLiam Girdwood SOC_ENUM("ALC NG Type", wm9713_enum[17]),
16183ac08c0SLiam Girdwood SOC_SINGLE("ALC NG Threshold", AC97_PCI_SVID, 0, 31, 0),
16283ac08c0SLiam Girdwood 
16383ac08c0SLiam Girdwood SOC_DOUBLE("Speaker Playback ZC Switch", AC97_MASTER, 14, 6, 1, 0),
16483ac08c0SLiam Girdwood SOC_DOUBLE("Headphone Playback ZC Switch", AC97_HEADPHONE, 14, 6, 1, 0),
16583ac08c0SLiam Girdwood 
16683ac08c0SLiam Girdwood SOC_SINGLE("Out4 Playback Switch", AC97_MASTER_MONO, 15, 1, 1),
16783ac08c0SLiam Girdwood SOC_SINGLE("Out4 Playback ZC Switch", AC97_MASTER_MONO, 14, 1, 0),
168e03a8d2cSMark Brown SOC_SINGLE_TLV("Out4 Playback Volume", AC97_MASTER_MONO, 8, 31, 1, out_tlv),
16983ac08c0SLiam Girdwood 
17083ac08c0SLiam Girdwood SOC_SINGLE("Out3 Playback Switch", AC97_MASTER_MONO, 7, 1, 1),
17183ac08c0SLiam Girdwood SOC_SINGLE("Out3 Playback ZC Switch", AC97_MASTER_MONO, 6, 1, 0),
172e03a8d2cSMark Brown SOC_SINGLE_TLV("Out3 Playback Volume", AC97_MASTER_MONO, 0, 31, 1, out_tlv),
17383ac08c0SLiam Girdwood 
174e03a8d2cSMark Brown SOC_SINGLE_TLV("Mono Capture Volume", AC97_MASTER_TONE, 8, 31, 1, main_tlv),
17583ac08c0SLiam Girdwood SOC_SINGLE("Mono Playback Switch", AC97_MASTER_TONE, 7, 1, 1),
17683ac08c0SLiam Girdwood SOC_SINGLE("Mono Playback ZC Switch", AC97_MASTER_TONE, 6, 1, 0),
177e03a8d2cSMark Brown SOC_SINGLE_TLV("Mono Playback Volume", AC97_MASTER_TONE, 0, 31, 1, out_tlv),
17883ac08c0SLiam Girdwood 
179e03a8d2cSMark Brown SOC_SINGLE_TLV("Headphone Mixer Beep Playback Volume", AC97_AUX, 12, 7, 1,
180e03a8d2cSMark Brown 	       misc_tlv),
181e03a8d2cSMark Brown SOC_SINGLE_TLV("Speaker Mixer Beep Playback Volume", AC97_AUX, 8, 7, 1,
182e03a8d2cSMark Brown 	       misc_tlv),
183e03a8d2cSMark Brown SOC_SINGLE_TLV("Mono Mixer Beep Playback Volume", AC97_AUX, 4, 7, 1, misc_tlv),
18483ac08c0SLiam Girdwood 
185e03a8d2cSMark Brown SOC_SINGLE_TLV("Voice Playback Headphone Volume", AC97_PCM, 12, 7, 1,
186e03a8d2cSMark Brown 	       misc_tlv),
18783ac08c0SLiam Girdwood SOC_SINGLE("Voice Playback Master Volume", AC97_PCM, 8, 7, 1),
18883ac08c0SLiam Girdwood SOC_SINGLE("Voice Playback Mono Volume", AC97_PCM, 4, 7, 1),
18983ac08c0SLiam Girdwood 
190e03a8d2cSMark Brown SOC_SINGLE_TLV("Headphone Mixer Aux Playback Volume", AC97_REC_SEL, 12, 7, 1,
191e03a8d2cSMark Brown 	       misc_tlv),
192e03a8d2cSMark Brown 
193e03a8d2cSMark Brown SOC_SINGLE_TLV("Speaker Mixer Voice Playback Volume", AC97_PCM, 8, 7, 1,
194e03a8d2cSMark Brown 	       misc_tlv),
195e03a8d2cSMark Brown SOC_SINGLE_TLV("Speaker Mixer Aux Playback Volume", AC97_REC_SEL, 8, 7, 1,
196e03a8d2cSMark Brown 	       misc_tlv),
197e03a8d2cSMark Brown 
198e03a8d2cSMark Brown SOC_SINGLE_TLV("Mono Mixer Voice Playback Volume", AC97_PCM, 4, 7, 1,
199e03a8d2cSMark Brown 	       misc_tlv),
200e03a8d2cSMark Brown SOC_SINGLE_TLV("Mono Mixer Aux Playback Volume", AC97_REC_SEL, 4, 7, 1,
201e03a8d2cSMark Brown 	       misc_tlv),
202e03a8d2cSMark Brown 
20383ac08c0SLiam Girdwood SOC_SINGLE("Aux Playback Headphone Volume", AC97_REC_SEL, 12, 7, 1),
20483ac08c0SLiam Girdwood SOC_SINGLE("Aux Playback Master Volume", AC97_REC_SEL, 8, 7, 1),
20583ac08c0SLiam Girdwood 
20683ac08c0SLiam Girdwood SOC_ENUM("Bass Control", wm9713_enum[16]),
20783ac08c0SLiam Girdwood SOC_SINGLE("Bass Cut-off Switch", AC97_GENERAL_PURPOSE, 12, 1, 1),
20883ac08c0SLiam Girdwood SOC_SINGLE("Tone Cut-off Switch", AC97_GENERAL_PURPOSE, 4, 1, 1),
20983ac08c0SLiam Girdwood SOC_SINGLE("Playback Attenuate (-6dB) Switch", AC97_GENERAL_PURPOSE, 6, 1, 0),
21083ac08c0SLiam Girdwood SOC_SINGLE("Bass Volume", AC97_GENERAL_PURPOSE, 8, 15, 1),
21183ac08c0SLiam Girdwood SOC_SINGLE("Tone Volume", AC97_GENERAL_PURPOSE, 0, 15, 1),
21283ac08c0SLiam Girdwood 
21383ac08c0SLiam Girdwood SOC_SINGLE("3D Upper Cut-off Switch", AC97_REC_GAIN_MIC, 5, 1, 0),
21483ac08c0SLiam Girdwood SOC_SINGLE("3D Lower Cut-off Switch", AC97_REC_GAIN_MIC, 4, 1, 0),
21583ac08c0SLiam Girdwood SOC_SINGLE("3D Depth", AC97_REC_GAIN_MIC, 0, 15, 1),
21683ac08c0SLiam Girdwood };
21783ac08c0SLiam Girdwood 
2186bbcb459SMark Brown static int wm9713_voice_shutdown(struct snd_soc_dapm_widget *w,
2196bbcb459SMark Brown 				 struct snd_kcontrol *kcontrol, int event)
2206bbcb459SMark Brown {
2216bbcb459SMark Brown 	struct snd_soc_codec *codec = w->codec;
2226bbcb459SMark Brown 	u16 status, rate;
2236bbcb459SMark Brown 
2246bbcb459SMark Brown 	BUG_ON(event != SND_SOC_DAPM_PRE_PMD);
2256bbcb459SMark Brown 
2266bbcb459SMark Brown 	/* Gracefully shut down the voice interface. */
2276bbcb459SMark Brown 	status = ac97_read(codec, AC97_EXTENDED_MID) | 0x1000;
2286bbcb459SMark Brown 	rate = ac97_read(codec, AC97_HANDSET_RATE) & 0xF0FF;
2296bbcb459SMark Brown 	ac97_write(codec, AC97_HANDSET_RATE, rate | 0x0200);
2306bbcb459SMark Brown 	schedule_timeout_interruptible(msecs_to_jiffies(1));
2316bbcb459SMark Brown 	ac97_write(codec, AC97_HANDSET_RATE, rate | 0x0F00);
2326bbcb459SMark Brown 	ac97_write(codec, AC97_EXTENDED_MID, status);
2336bbcb459SMark Brown 
2346bbcb459SMark Brown 	return 0;
2356bbcb459SMark Brown }
2366bbcb459SMark Brown 
2376bbcb459SMark Brown 
23883ac08c0SLiam Girdwood /* We have to create a fake left and right HP mixers because
23983ac08c0SLiam Girdwood  * the codec only has a single control that is shared by both channels.
24083ac08c0SLiam Girdwood  * This makes it impossible to determine the audio path using the current
24183ac08c0SLiam Girdwood  * register map, thus we add a new (virtual) register to help determine the
24283ac08c0SLiam Girdwood  * audio route within the device.
24383ac08c0SLiam Girdwood  */
24483ac08c0SLiam Girdwood static int mixer_event(struct snd_soc_dapm_widget *w,
24583ac08c0SLiam Girdwood 	struct snd_kcontrol *kcontrol, int event)
24683ac08c0SLiam Girdwood {
24783ac08c0SLiam Girdwood 	u16 l, r, beep, tone, phone, rec, pcm, aux;
24883ac08c0SLiam Girdwood 
24983ac08c0SLiam Girdwood 	l = ac97_read(w->codec, HPL_MIXER);
25083ac08c0SLiam Girdwood 	r = ac97_read(w->codec, HPR_MIXER);
25183ac08c0SLiam Girdwood 	beep = ac97_read(w->codec, AC97_PC_BEEP);
25283ac08c0SLiam Girdwood 	tone = ac97_read(w->codec, AC97_MASTER_TONE);
25383ac08c0SLiam Girdwood 	phone = ac97_read(w->codec, AC97_PHONE);
25483ac08c0SLiam Girdwood 	rec = ac97_read(w->codec, AC97_REC_SEL);
25583ac08c0SLiam Girdwood 	pcm = ac97_read(w->codec, AC97_PCM);
25683ac08c0SLiam Girdwood 	aux = ac97_read(w->codec, AC97_AUX);
25783ac08c0SLiam Girdwood 
25883ac08c0SLiam Girdwood 	if (event & SND_SOC_DAPM_PRE_REG)
25983ac08c0SLiam Girdwood 		return 0;
26083ac08c0SLiam Girdwood 	if ((l & 0x1) || (r & 0x1))
26183ac08c0SLiam Girdwood 		ac97_write(w->codec, AC97_PC_BEEP, beep & 0x7fff);
26283ac08c0SLiam Girdwood 	else
26383ac08c0SLiam Girdwood 		ac97_write(w->codec, AC97_PC_BEEP, beep | 0x8000);
26483ac08c0SLiam Girdwood 
26583ac08c0SLiam Girdwood 	if ((l & 0x2) || (r & 0x2))
26683ac08c0SLiam Girdwood 		ac97_write(w->codec, AC97_MASTER_TONE, tone & 0x7fff);
26783ac08c0SLiam Girdwood 	else
26883ac08c0SLiam Girdwood 		ac97_write(w->codec, AC97_MASTER_TONE, tone | 0x8000);
26983ac08c0SLiam Girdwood 
27083ac08c0SLiam Girdwood 	if ((l & 0x4) || (r & 0x4))
27183ac08c0SLiam Girdwood 		ac97_write(w->codec, AC97_PHONE, phone & 0x7fff);
27283ac08c0SLiam Girdwood 	else
27383ac08c0SLiam Girdwood 		ac97_write(w->codec, AC97_PHONE, phone | 0x8000);
27483ac08c0SLiam Girdwood 
27583ac08c0SLiam Girdwood 	if ((l & 0x8) || (r & 0x8))
27683ac08c0SLiam Girdwood 		ac97_write(w->codec, AC97_REC_SEL, rec & 0x7fff);
27783ac08c0SLiam Girdwood 	else
27883ac08c0SLiam Girdwood 		ac97_write(w->codec, AC97_REC_SEL, rec | 0x8000);
27983ac08c0SLiam Girdwood 
28083ac08c0SLiam Girdwood 	if ((l & 0x10) || (r & 0x10))
28183ac08c0SLiam Girdwood 		ac97_write(w->codec, AC97_PCM, pcm & 0x7fff);
28283ac08c0SLiam Girdwood 	else
28383ac08c0SLiam Girdwood 		ac97_write(w->codec, AC97_PCM, pcm | 0x8000);
28483ac08c0SLiam Girdwood 
28583ac08c0SLiam Girdwood 	if ((l & 0x20) || (r & 0x20))
28683ac08c0SLiam Girdwood 		ac97_write(w->codec, AC97_AUX, aux & 0x7fff);
28783ac08c0SLiam Girdwood 	else
28883ac08c0SLiam Girdwood 		ac97_write(w->codec, AC97_AUX, aux | 0x8000);
28983ac08c0SLiam Girdwood 
29083ac08c0SLiam Girdwood 	return 0;
29183ac08c0SLiam Girdwood }
29283ac08c0SLiam Girdwood 
29383ac08c0SLiam Girdwood /* Left Headphone Mixers */
29483ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_hpl_mixer_controls[] = {
295d355c82aSJaroslav Kysela SOC_DAPM_SINGLE("Beep Playback Switch", HPL_MIXER, 5, 1, 0),
29683ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Voice Playback Switch", HPL_MIXER, 4, 1, 0),
29783ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Aux Playback Switch", HPL_MIXER, 3, 1, 0),
29883ac08c0SLiam Girdwood SOC_DAPM_SINGLE("PCM Playback Switch", HPL_MIXER, 2, 1, 0),
29983ac08c0SLiam Girdwood SOC_DAPM_SINGLE("MonoIn Playback Switch", HPL_MIXER, 1, 1, 0),
30083ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Bypass Playback Switch", HPL_MIXER, 0, 1, 0),
30183ac08c0SLiam Girdwood };
30283ac08c0SLiam Girdwood 
30383ac08c0SLiam Girdwood /* Right Headphone Mixers */
30483ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_hpr_mixer_controls[] = {
305d355c82aSJaroslav Kysela SOC_DAPM_SINGLE("Beep Playback Switch", HPR_MIXER, 5, 1, 0),
30683ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Voice Playback Switch", HPR_MIXER, 4, 1, 0),
30783ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Aux Playback Switch", HPR_MIXER, 3, 1, 0),
30883ac08c0SLiam Girdwood SOC_DAPM_SINGLE("PCM Playback Switch", HPR_MIXER, 2, 1, 0),
30983ac08c0SLiam Girdwood SOC_DAPM_SINGLE("MonoIn Playback Switch", HPR_MIXER, 1, 1, 0),
31083ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Bypass Playback Switch", HPR_MIXER, 0, 1, 0),
31183ac08c0SLiam Girdwood };
31283ac08c0SLiam Girdwood 
31383ac08c0SLiam Girdwood /* headphone capture mux */
31483ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_hp_rec_mux_controls =
31583ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[1]);
31683ac08c0SLiam Girdwood 
31783ac08c0SLiam Girdwood /* headphone mic mux */
31883ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_hp_mic_mux_controls =
31983ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[0]);
32083ac08c0SLiam Girdwood 
32183ac08c0SLiam Girdwood /* Speaker Mixer */
32283ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_speaker_mixer_controls[] = {
323d355c82aSJaroslav Kysela SOC_DAPM_SINGLE("Beep Playback Switch", AC97_AUX, 11, 1, 1),
32483ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Voice Playback Switch", AC97_PCM, 11, 1, 1),
32583ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Aux Playback Switch", AC97_REC_SEL, 11, 1, 1),
32683ac08c0SLiam Girdwood SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PHONE, 14, 1, 1),
32783ac08c0SLiam Girdwood SOC_DAPM_SINGLE("MonoIn Playback Switch", AC97_MASTER_TONE, 14, 1, 1),
32883ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Bypass Playback Switch", AC97_PC_BEEP, 14, 1, 1),
32983ac08c0SLiam Girdwood };
33083ac08c0SLiam Girdwood 
33183ac08c0SLiam Girdwood /* Mono Mixer */
33283ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_mono_mixer_controls[] = {
333d355c82aSJaroslav Kysela SOC_DAPM_SINGLE("Beep Playback Switch", AC97_AUX, 7, 1, 1),
33483ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Voice Playback Switch", AC97_PCM, 7, 1, 1),
33583ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Aux Playback Switch", AC97_REC_SEL, 7, 1, 1),
33683ac08c0SLiam Girdwood SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PHONE, 13, 1, 1),
33783ac08c0SLiam Girdwood SOC_DAPM_SINGLE("MonoIn Playback Switch", AC97_MASTER_TONE, 13, 1, 1),
33883ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Bypass Playback Switch", AC97_PC_BEEP, 13, 1, 1),
33983ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Mic 1 Sidetone Switch", AC97_LINE, 7, 1, 1),
34083ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Mic 2 Sidetone Switch", AC97_LINE, 6, 1, 1),
34183ac08c0SLiam Girdwood };
34283ac08c0SLiam Girdwood 
34383ac08c0SLiam Girdwood /* mono mic mux */
34483ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_mono_mic_mux_controls =
34583ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[2]);
34683ac08c0SLiam Girdwood 
34783ac08c0SLiam Girdwood /* mono output mux */
34883ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_mono_mux_controls =
34983ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[7]);
35083ac08c0SLiam Girdwood 
35183ac08c0SLiam Girdwood /* speaker left output mux */
35283ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_hp_spkl_mux_controls =
35383ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[8]);
35483ac08c0SLiam Girdwood 
35583ac08c0SLiam Girdwood /* speaker right output mux */
35683ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_hp_spkr_mux_controls =
35783ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[9]);
35883ac08c0SLiam Girdwood 
35983ac08c0SLiam Girdwood /* headphone left output mux */
36083ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_hpl_out_mux_controls =
36183ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[10]);
36283ac08c0SLiam Girdwood 
36383ac08c0SLiam Girdwood /* headphone right output mux */
36483ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_hpr_out_mux_controls =
36583ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[11]);
36683ac08c0SLiam Girdwood 
36783ac08c0SLiam Girdwood /* Out3 mux */
36883ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_out3_mux_controls =
36983ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[12]);
37083ac08c0SLiam Girdwood 
37183ac08c0SLiam Girdwood /* Out4 mux */
37283ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_out4_mux_controls =
37383ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[13]);
37483ac08c0SLiam Girdwood 
37583ac08c0SLiam Girdwood /* DAC inv mux 1 */
37683ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_dac_inv1_mux_controls =
37783ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[14]);
37883ac08c0SLiam Girdwood 
37983ac08c0SLiam Girdwood /* DAC inv mux 2 */
38083ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_dac_inv2_mux_controls =
38183ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[15]);
38283ac08c0SLiam Girdwood 
38383ac08c0SLiam Girdwood /* Capture source left */
38483ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_rec_srcl_mux_controls =
38583ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[3]);
38683ac08c0SLiam Girdwood 
38783ac08c0SLiam Girdwood /* Capture source right */
38883ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_rec_srcr_mux_controls =
38983ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[4]);
39083ac08c0SLiam Girdwood 
39183ac08c0SLiam Girdwood /* mic source */
39283ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_mic_sel_mux_controls =
39383ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[18]);
39483ac08c0SLiam Girdwood 
39583ac08c0SLiam Girdwood /* mic source B virtual control */
39683ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_micb_sel_mux_controls =
39783ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[19]);
39883ac08c0SLiam Girdwood 
39983ac08c0SLiam Girdwood static const struct snd_soc_dapm_widget wm9713_dapm_widgets[] = {
40083ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Capture Headphone Mux", SND_SOC_NOPM, 0, 0,
40183ac08c0SLiam Girdwood 	&wm9713_hp_rec_mux_controls),
40283ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Sidetone Mux", SND_SOC_NOPM, 0, 0,
40383ac08c0SLiam Girdwood 	&wm9713_hp_mic_mux_controls),
40483ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Capture Mono Mux", SND_SOC_NOPM, 0, 0,
40583ac08c0SLiam Girdwood 	&wm9713_mono_mic_mux_controls),
40683ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Mono Out Mux", SND_SOC_NOPM, 0, 0,
40783ac08c0SLiam Girdwood 	&wm9713_mono_mux_controls),
40883ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Left Speaker Out Mux", SND_SOC_NOPM, 0, 0,
40983ac08c0SLiam Girdwood 	&wm9713_hp_spkl_mux_controls),
41083ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Right Speaker Out Mux", SND_SOC_NOPM, 0, 0,
41183ac08c0SLiam Girdwood 	&wm9713_hp_spkr_mux_controls),
41283ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Left Headphone Out Mux", SND_SOC_NOPM, 0, 0,
41383ac08c0SLiam Girdwood 	&wm9713_hpl_out_mux_controls),
41483ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Right Headphone Out Mux", SND_SOC_NOPM, 0, 0,
41583ac08c0SLiam Girdwood 	&wm9713_hpr_out_mux_controls),
41683ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Out 3 Mux", SND_SOC_NOPM, 0, 0,
41783ac08c0SLiam Girdwood 	&wm9713_out3_mux_controls),
41883ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Out 4 Mux", SND_SOC_NOPM, 0, 0,
41983ac08c0SLiam Girdwood 	&wm9713_out4_mux_controls),
42083ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("DAC Inv Mux 1", SND_SOC_NOPM, 0, 0,
42183ac08c0SLiam Girdwood 	&wm9713_dac_inv1_mux_controls),
42283ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("DAC Inv Mux 2", SND_SOC_NOPM, 0, 0,
42383ac08c0SLiam Girdwood 	&wm9713_dac_inv2_mux_controls),
42483ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Left Capture Source", SND_SOC_NOPM, 0, 0,
42583ac08c0SLiam Girdwood 	&wm9713_rec_srcl_mux_controls),
42683ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Right Capture Source", SND_SOC_NOPM, 0, 0,
42783ac08c0SLiam Girdwood 	&wm9713_rec_srcr_mux_controls),
42883ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Mic A Source", SND_SOC_NOPM, 0, 0,
42983ac08c0SLiam Girdwood 	&wm9713_mic_sel_mux_controls),
43083ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Mic B Source", SND_SOC_NOPM, 0, 0,
43183ac08c0SLiam Girdwood 	&wm9713_micb_sel_mux_controls),
43283ac08c0SLiam Girdwood SND_SOC_DAPM_MIXER_E("Left HP Mixer", AC97_EXTENDED_MID, 3, 1,
43383ac08c0SLiam Girdwood 	&wm9713_hpl_mixer_controls[0], ARRAY_SIZE(wm9713_hpl_mixer_controls),
43483ac08c0SLiam Girdwood 	mixer_event, SND_SOC_DAPM_POST_REG),
43583ac08c0SLiam Girdwood SND_SOC_DAPM_MIXER_E("Right HP Mixer", AC97_EXTENDED_MID, 2, 1,
43683ac08c0SLiam Girdwood 	&wm9713_hpr_mixer_controls[0], ARRAY_SIZE(wm9713_hpr_mixer_controls),
43783ac08c0SLiam Girdwood 	mixer_event, SND_SOC_DAPM_POST_REG),
43883ac08c0SLiam Girdwood SND_SOC_DAPM_MIXER("Mono Mixer", AC97_EXTENDED_MID, 0, 1,
43983ac08c0SLiam Girdwood 	&wm9713_mono_mixer_controls[0], ARRAY_SIZE(wm9713_mono_mixer_controls)),
44083ac08c0SLiam Girdwood SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_EXTENDED_MID, 1, 1,
44183ac08c0SLiam Girdwood 	&wm9713_speaker_mixer_controls[0],
44283ac08c0SLiam Girdwood 	ARRAY_SIZE(wm9713_speaker_mixer_controls)),
44383ac08c0SLiam Girdwood SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", AC97_EXTENDED_MID, 7, 1),
44483ac08c0SLiam Girdwood SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", AC97_EXTENDED_MID, 6, 1),
44583ac08c0SLiam Girdwood SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
44683ac08c0SLiam Girdwood SND_SOC_DAPM_MIXER("HP Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
44783ac08c0SLiam Girdwood SND_SOC_DAPM_MIXER("Line Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
44883ac08c0SLiam Girdwood SND_SOC_DAPM_MIXER("Capture Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
4496bbcb459SMark Brown SND_SOC_DAPM_DAC_E("Voice DAC", "Voice Playback", AC97_EXTENDED_MID, 12, 1,
4506bbcb459SMark Brown 		   wm9713_voice_shutdown, SND_SOC_DAPM_PRE_PMD),
45183ac08c0SLiam Girdwood SND_SOC_DAPM_DAC("Aux DAC", "Aux Playback", AC97_EXTENDED_MID, 11, 1),
452ccbc301eSMark Brown SND_SOC_DAPM_PGA("Left ADC", AC97_EXTENDED_MID, 5, 1, NULL, 0),
453ccbc301eSMark Brown SND_SOC_DAPM_PGA("Right ADC", AC97_EXTENDED_MID, 4, 1, NULL, 0),
454ccbc301eSMark Brown SND_SOC_DAPM_ADC("Left HiFi ADC", "Left HiFi Capture", SND_SOC_NOPM, 0, 0),
455ccbc301eSMark Brown SND_SOC_DAPM_ADC("Right HiFi ADC", "Right HiFi Capture", SND_SOC_NOPM, 0, 0),
456ccbc301eSMark Brown SND_SOC_DAPM_ADC("Left Voice ADC", "Left Voice Capture", SND_SOC_NOPM, 0, 0),
457ccbc301eSMark Brown SND_SOC_DAPM_ADC("Right Voice ADC", "Right Voice Capture", SND_SOC_NOPM, 0, 0),
45883ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Left Headphone", AC97_EXTENDED_MSTATUS, 10, 1, NULL, 0),
45983ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Right Headphone", AC97_EXTENDED_MSTATUS, 9, 1, NULL, 0),
46083ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Left Speaker", AC97_EXTENDED_MSTATUS, 8, 1, NULL, 0),
46183ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Right Speaker", AC97_EXTENDED_MSTATUS, 7, 1, NULL, 0),
46283ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Out 3", AC97_EXTENDED_MSTATUS, 11, 1, NULL, 0),
46383ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Out 4", AC97_EXTENDED_MSTATUS, 12, 1, NULL, 0),
46483ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Mono Out", AC97_EXTENDED_MSTATUS, 13, 1, NULL, 0),
46583ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Left Line In", AC97_EXTENDED_MSTATUS, 6, 1, NULL, 0),
46683ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Right Line In", AC97_EXTENDED_MSTATUS, 5, 1, NULL, 0),
46783ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Mono In", AC97_EXTENDED_MSTATUS, 4, 1, NULL, 0),
46883ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Mic A PGA", AC97_EXTENDED_MSTATUS, 3, 1, NULL, 0),
46983ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Mic B PGA", AC97_EXTENDED_MSTATUS, 2, 1, NULL, 0),
47083ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Mic A Pre Amp", AC97_EXTENDED_MSTATUS, 1, 1, NULL, 0),
47183ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Mic B Pre Amp", AC97_EXTENDED_MSTATUS, 0, 1, NULL, 0),
47283ac08c0SLiam Girdwood SND_SOC_DAPM_MICBIAS("Mic Bias", AC97_EXTENDED_MSTATUS, 14, 1),
47383ac08c0SLiam Girdwood SND_SOC_DAPM_OUTPUT("MONO"),
47483ac08c0SLiam Girdwood SND_SOC_DAPM_OUTPUT("HPL"),
47583ac08c0SLiam Girdwood SND_SOC_DAPM_OUTPUT("HPR"),
47683ac08c0SLiam Girdwood SND_SOC_DAPM_OUTPUT("SPKL"),
47783ac08c0SLiam Girdwood SND_SOC_DAPM_OUTPUT("SPKR"),
47883ac08c0SLiam Girdwood SND_SOC_DAPM_OUTPUT("OUT3"),
47983ac08c0SLiam Girdwood SND_SOC_DAPM_OUTPUT("OUT4"),
48083ac08c0SLiam Girdwood SND_SOC_DAPM_INPUT("LINEL"),
48183ac08c0SLiam Girdwood SND_SOC_DAPM_INPUT("LINER"),
48283ac08c0SLiam Girdwood SND_SOC_DAPM_INPUT("MONOIN"),
48383ac08c0SLiam Girdwood SND_SOC_DAPM_INPUT("PCBEEP"),
48483ac08c0SLiam Girdwood SND_SOC_DAPM_INPUT("MIC1"),
48583ac08c0SLiam Girdwood SND_SOC_DAPM_INPUT("MIC2A"),
48683ac08c0SLiam Girdwood SND_SOC_DAPM_INPUT("MIC2B"),
48783ac08c0SLiam Girdwood SND_SOC_DAPM_VMID("VMID"),
48883ac08c0SLiam Girdwood };
48983ac08c0SLiam Girdwood 
4907ad7dd12SLu Guanqun static const struct snd_soc_dapm_route wm9713_audio_map[] = {
49183ac08c0SLiam Girdwood 	/* left HP mixer */
492d355c82aSJaroslav Kysela 	{"Left HP Mixer", "Beep Playback Switch",    "PCBEEP"},
49383ac08c0SLiam Girdwood 	{"Left HP Mixer", "Voice Playback Switch",   "Voice DAC"},
49483ac08c0SLiam Girdwood 	{"Left HP Mixer", "Aux Playback Switch",     "Aux DAC"},
49583ac08c0SLiam Girdwood 	{"Left HP Mixer", "Bypass Playback Switch",  "Left Line In"},
49683ac08c0SLiam Girdwood 	{"Left HP Mixer", "PCM Playback Switch",     "Left DAC"},
49783ac08c0SLiam Girdwood 	{"Left HP Mixer", "MonoIn Playback Switch",  "Mono In"},
49883ac08c0SLiam Girdwood 	{"Left HP Mixer", NULL,  "Capture Headphone Mux"},
49983ac08c0SLiam Girdwood 
50083ac08c0SLiam Girdwood 	/* right HP mixer */
501d355c82aSJaroslav Kysela 	{"Right HP Mixer", "Beep Playback Switch",    "PCBEEP"},
50283ac08c0SLiam Girdwood 	{"Right HP Mixer", "Voice Playback Switch",   "Voice DAC"},
50383ac08c0SLiam Girdwood 	{"Right HP Mixer", "Aux Playback Switch",     "Aux DAC"},
50483ac08c0SLiam Girdwood 	{"Right HP Mixer", "Bypass Playback Switch",  "Right Line In"},
50583ac08c0SLiam Girdwood 	{"Right HP Mixer", "PCM Playback Switch",     "Right DAC"},
50683ac08c0SLiam Girdwood 	{"Right HP Mixer", "MonoIn Playback Switch",  "Mono In"},
50783ac08c0SLiam Girdwood 	{"Right HP Mixer", NULL,  "Capture Headphone Mux"},
50883ac08c0SLiam Girdwood 
50983ac08c0SLiam Girdwood 	/* virtual mixer - mixes left & right channels for spk and mono */
51083ac08c0SLiam Girdwood 	{"AC97 Mixer", NULL, "Left DAC"},
51183ac08c0SLiam Girdwood 	{"AC97 Mixer", NULL, "Right DAC"},
51283ac08c0SLiam Girdwood 	{"Line Mixer", NULL, "Right Line In"},
51383ac08c0SLiam Girdwood 	{"Line Mixer", NULL, "Left Line In"},
51483ac08c0SLiam Girdwood 	{"HP Mixer", NULL, "Left HP Mixer"},
51583ac08c0SLiam Girdwood 	{"HP Mixer", NULL, "Right HP Mixer"},
51683ac08c0SLiam Girdwood 	{"Capture Mixer", NULL, "Left Capture Source"},
51783ac08c0SLiam Girdwood 	{"Capture Mixer", NULL, "Right Capture Source"},
51883ac08c0SLiam Girdwood 
51983ac08c0SLiam Girdwood 	/* speaker mixer */
520d355c82aSJaroslav Kysela 	{"Speaker Mixer", "Beep Playback Switch",    "PCBEEP"},
52183ac08c0SLiam Girdwood 	{"Speaker Mixer", "Voice Playback Switch",   "Voice DAC"},
52283ac08c0SLiam Girdwood 	{"Speaker Mixer", "Aux Playback Switch",     "Aux DAC"},
52383ac08c0SLiam Girdwood 	{"Speaker Mixer", "Bypass Playback Switch",  "Line Mixer"},
52483ac08c0SLiam Girdwood 	{"Speaker Mixer", "PCM Playback Switch",     "AC97 Mixer"},
52583ac08c0SLiam Girdwood 	{"Speaker Mixer", "MonoIn Playback Switch",  "Mono In"},
52683ac08c0SLiam Girdwood 
52783ac08c0SLiam Girdwood 	/* mono mixer */
528d355c82aSJaroslav Kysela 	{"Mono Mixer", "Beep Playback Switch",    "PCBEEP"},
52983ac08c0SLiam Girdwood 	{"Mono Mixer", "Voice Playback Switch",   "Voice DAC"},
53083ac08c0SLiam Girdwood 	{"Mono Mixer", "Aux Playback Switch",     "Aux DAC"},
53183ac08c0SLiam Girdwood 	{"Mono Mixer", "Bypass Playback Switch",  "Line Mixer"},
53283ac08c0SLiam Girdwood 	{"Mono Mixer", "PCM Playback Switch",     "AC97 Mixer"},
533c0bbf48dSRobert Jarzmik 	{"Mono Mixer", "Mic 1 Sidetone Switch", "Mic A PGA"},
534c0bbf48dSRobert Jarzmik 	{"Mono Mixer", "Mic 2 Sidetone Switch", "Mic B PGA"},
53583ac08c0SLiam Girdwood 	{"Mono Mixer", NULL,  "Capture Mono Mux"},
53683ac08c0SLiam Girdwood 
53783ac08c0SLiam Girdwood 	/* DAC inv mux 1 */
53883ac08c0SLiam Girdwood 	{"DAC Inv Mux 1", "Mono", "Mono Mixer"},
53983ac08c0SLiam Girdwood 	{"DAC Inv Mux 1", "Speaker", "Speaker Mixer"},
54083ac08c0SLiam Girdwood 	{"DAC Inv Mux 1", "Left Headphone", "Left HP Mixer"},
54183ac08c0SLiam Girdwood 	{"DAC Inv Mux 1", "Right Headphone", "Right HP Mixer"},
54283ac08c0SLiam Girdwood 	{"DAC Inv Mux 1", "Headphone Mono", "HP Mixer"},
54383ac08c0SLiam Girdwood 
54483ac08c0SLiam Girdwood 	/* DAC inv mux 2 */
54583ac08c0SLiam Girdwood 	{"DAC Inv Mux 2", "Mono", "Mono Mixer"},
54683ac08c0SLiam Girdwood 	{"DAC Inv Mux 2", "Speaker", "Speaker Mixer"},
54783ac08c0SLiam Girdwood 	{"DAC Inv Mux 2", "Left Headphone", "Left HP Mixer"},
54883ac08c0SLiam Girdwood 	{"DAC Inv Mux 2", "Right Headphone", "Right HP Mixer"},
54983ac08c0SLiam Girdwood 	{"DAC Inv Mux 2", "Headphone Mono", "HP Mixer"},
55083ac08c0SLiam Girdwood 
55183ac08c0SLiam Girdwood 	/* headphone left mux */
55283ac08c0SLiam Girdwood 	{"Left Headphone Out Mux", "Headphone", "Left HP Mixer"},
55383ac08c0SLiam Girdwood 
55483ac08c0SLiam Girdwood 	/* headphone right mux */
55583ac08c0SLiam Girdwood 	{"Right Headphone Out Mux", "Headphone", "Right HP Mixer"},
55683ac08c0SLiam Girdwood 
55783ac08c0SLiam Girdwood 	/* speaker left mux */
55883ac08c0SLiam Girdwood 	{"Left Speaker Out Mux", "Headphone", "Left HP Mixer"},
55983ac08c0SLiam Girdwood 	{"Left Speaker Out Mux", "Speaker", "Speaker Mixer"},
56083ac08c0SLiam Girdwood 	{"Left Speaker Out Mux", "Inv", "DAC Inv Mux 1"},
56183ac08c0SLiam Girdwood 
56283ac08c0SLiam Girdwood 	/* speaker right mux */
56383ac08c0SLiam Girdwood 	{"Right Speaker Out Mux", "Headphone", "Right HP Mixer"},
56483ac08c0SLiam Girdwood 	{"Right Speaker Out Mux", "Speaker", "Speaker Mixer"},
56583ac08c0SLiam Girdwood 	{"Right Speaker Out Mux", "Inv", "DAC Inv Mux 2"},
56683ac08c0SLiam Girdwood 
56783ac08c0SLiam Girdwood 	/* mono mux */
56883ac08c0SLiam Girdwood 	{"Mono Out Mux", "Mono", "Mono Mixer"},
56983ac08c0SLiam Girdwood 	{"Mono Out Mux", "Inv", "DAC Inv Mux 1"},
57083ac08c0SLiam Girdwood 
57183ac08c0SLiam Girdwood 	/* out 3 mux */
57283ac08c0SLiam Girdwood 	{"Out 3 Mux", "Inv 1", "DAC Inv Mux 1"},
57383ac08c0SLiam Girdwood 
57483ac08c0SLiam Girdwood 	/* out 4 mux */
57583ac08c0SLiam Girdwood 	{"Out 4 Mux", "Inv 2", "DAC Inv Mux 2"},
57683ac08c0SLiam Girdwood 
57783ac08c0SLiam Girdwood 	/* output pga */
57883ac08c0SLiam Girdwood 	{"HPL", NULL, "Left Headphone"},
57983ac08c0SLiam Girdwood 	{"Left Headphone", NULL, "Left Headphone Out Mux"},
58083ac08c0SLiam Girdwood 	{"HPR", NULL, "Right Headphone"},
58183ac08c0SLiam Girdwood 	{"Right Headphone", NULL, "Right Headphone Out Mux"},
58283ac08c0SLiam Girdwood 	{"OUT3", NULL, "Out 3"},
58383ac08c0SLiam Girdwood 	{"Out 3", NULL, "Out 3 Mux"},
58483ac08c0SLiam Girdwood 	{"OUT4", NULL, "Out 4"},
58583ac08c0SLiam Girdwood 	{"Out 4", NULL, "Out 4 Mux"},
58683ac08c0SLiam Girdwood 	{"SPKL", NULL, "Left Speaker"},
58783ac08c0SLiam Girdwood 	{"Left Speaker", NULL, "Left Speaker Out Mux"},
58883ac08c0SLiam Girdwood 	{"SPKR", NULL, "Right Speaker"},
58983ac08c0SLiam Girdwood 	{"Right Speaker", NULL, "Right Speaker Out Mux"},
59083ac08c0SLiam Girdwood 	{"MONO", NULL, "Mono Out"},
59183ac08c0SLiam Girdwood 	{"Mono Out", NULL, "Mono Out Mux"},
59283ac08c0SLiam Girdwood 
59383ac08c0SLiam Girdwood 	/* input pga */
59483ac08c0SLiam Girdwood 	{"Left Line In", NULL, "LINEL"},
59583ac08c0SLiam Girdwood 	{"Right Line In", NULL, "LINER"},
59683ac08c0SLiam Girdwood 	{"Mono In", NULL, "MONOIN"},
59783ac08c0SLiam Girdwood 	{"Mic A PGA", NULL, "Mic A Pre Amp"},
59883ac08c0SLiam Girdwood 	{"Mic B PGA", NULL, "Mic B Pre Amp"},
59983ac08c0SLiam Girdwood 
60083ac08c0SLiam Girdwood 	/* left capture select */
60183ac08c0SLiam Girdwood 	{"Left Capture Source", "Mic 1", "Mic A Pre Amp"},
60283ac08c0SLiam Girdwood 	{"Left Capture Source", "Mic 2", "Mic B Pre Amp"},
60383ac08c0SLiam Girdwood 	{"Left Capture Source", "Line", "LINEL"},
60483ac08c0SLiam Girdwood 	{"Left Capture Source", "Mono In", "MONOIN"},
60583ac08c0SLiam Girdwood 	{"Left Capture Source", "Headphone", "Left HP Mixer"},
60683ac08c0SLiam Girdwood 	{"Left Capture Source", "Speaker", "Speaker Mixer"},
60783ac08c0SLiam Girdwood 	{"Left Capture Source", "Mono Out", "Mono Mixer"},
60883ac08c0SLiam Girdwood 
60983ac08c0SLiam Girdwood 	/* right capture select */
61083ac08c0SLiam Girdwood 	{"Right Capture Source", "Mic 1", "Mic A Pre Amp"},
61183ac08c0SLiam Girdwood 	{"Right Capture Source", "Mic 2", "Mic B Pre Amp"},
61283ac08c0SLiam Girdwood 	{"Right Capture Source", "Line", "LINER"},
61383ac08c0SLiam Girdwood 	{"Right Capture Source", "Mono In", "MONOIN"},
61483ac08c0SLiam Girdwood 	{"Right Capture Source", "Headphone", "Right HP Mixer"},
61583ac08c0SLiam Girdwood 	{"Right Capture Source", "Speaker", "Speaker Mixer"},
61683ac08c0SLiam Girdwood 	{"Right Capture Source", "Mono Out", "Mono Mixer"},
61783ac08c0SLiam Girdwood 
61883ac08c0SLiam Girdwood 	/* left ADC */
61983ac08c0SLiam Girdwood 	{"Left ADC", NULL, "Left Capture Source"},
620ccbc301eSMark Brown 	{"Left Voice ADC", NULL, "Left ADC"},
621ccbc301eSMark Brown 	{"Left HiFi ADC", NULL, "Left ADC"},
62283ac08c0SLiam Girdwood 
62383ac08c0SLiam Girdwood 	/* right ADC */
62483ac08c0SLiam Girdwood 	{"Right ADC", NULL, "Right Capture Source"},
625ccbc301eSMark Brown 	{"Right Voice ADC", NULL, "Right ADC"},
626ccbc301eSMark Brown 	{"Right HiFi ADC", NULL, "Right ADC"},
62783ac08c0SLiam Girdwood 
62883ac08c0SLiam Girdwood 	/* mic */
62983ac08c0SLiam Girdwood 	{"Mic A Pre Amp", NULL, "Mic A Source"},
63083ac08c0SLiam Girdwood 	{"Mic A Source", "Mic 1", "MIC1"},
63183ac08c0SLiam Girdwood 	{"Mic A Source", "Mic 2 A", "MIC2A"},
63283ac08c0SLiam Girdwood 	{"Mic A Source", "Mic 2 B", "Mic B Source"},
63383ac08c0SLiam Girdwood 	{"Mic B Pre Amp", "MPB", "Mic B Source"},
63483ac08c0SLiam Girdwood 	{"Mic B Source", NULL, "MIC2B"},
63583ac08c0SLiam Girdwood 
63683ac08c0SLiam Girdwood 	/* headphone capture */
63783ac08c0SLiam Girdwood 	{"Capture Headphone Mux", "Stereo", "Capture Mixer"},
63883ac08c0SLiam Girdwood 	{"Capture Headphone Mux", "Left", "Left Capture Source"},
63983ac08c0SLiam Girdwood 	{"Capture Headphone Mux", "Right", "Right Capture Source"},
64083ac08c0SLiam Girdwood 
64183ac08c0SLiam Girdwood 	/* mono capture */
64283ac08c0SLiam Girdwood 	{"Capture Mono Mux", "Stereo", "Capture Mixer"},
64383ac08c0SLiam Girdwood 	{"Capture Mono Mux", "Left", "Left Capture Source"},
64483ac08c0SLiam Girdwood 	{"Capture Mono Mux", "Right", "Right Capture Source"},
64583ac08c0SLiam Girdwood };
64683ac08c0SLiam Girdwood 
64783ac08c0SLiam Girdwood static unsigned int ac97_read(struct snd_soc_codec *codec,
64883ac08c0SLiam Girdwood 	unsigned int reg)
64983ac08c0SLiam Girdwood {
65083ac08c0SLiam Girdwood 	u16 *cache = codec->reg_cache;
65183ac08c0SLiam Girdwood 
65283ac08c0SLiam Girdwood 	if (reg == AC97_RESET || reg == AC97_GPIO_STATUS ||
65383ac08c0SLiam Girdwood 		reg == AC97_VENDOR_ID1 || reg == AC97_VENDOR_ID2 ||
65483ac08c0SLiam Girdwood 		reg == AC97_CD)
65583ac08c0SLiam Girdwood 		return soc_ac97_ops.read(codec->ac97, reg);
65683ac08c0SLiam Girdwood 	else {
65783ac08c0SLiam Girdwood 		reg = reg >> 1;
65883ac08c0SLiam Girdwood 
65991432e97SIan Molton 		if (reg >= (ARRAY_SIZE(wm9713_reg)))
66083ac08c0SLiam Girdwood 			return -EIO;
66183ac08c0SLiam Girdwood 
66283ac08c0SLiam Girdwood 		return cache[reg];
66383ac08c0SLiam Girdwood 	}
66483ac08c0SLiam Girdwood }
66583ac08c0SLiam Girdwood 
66683ac08c0SLiam Girdwood static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
66783ac08c0SLiam Girdwood 	unsigned int val)
66883ac08c0SLiam Girdwood {
66983ac08c0SLiam Girdwood 	u16 *cache = codec->reg_cache;
67083ac08c0SLiam Girdwood 	if (reg < 0x7c)
67183ac08c0SLiam Girdwood 		soc_ac97_ops.write(codec->ac97, reg, val);
67283ac08c0SLiam Girdwood 	reg = reg >> 1;
67391432e97SIan Molton 	if (reg < (ARRAY_SIZE(wm9713_reg)))
67483ac08c0SLiam Girdwood 		cache[reg] = val;
67583ac08c0SLiam Girdwood 
67683ac08c0SLiam Girdwood 	return 0;
67783ac08c0SLiam Girdwood }
67883ac08c0SLiam Girdwood 
67983ac08c0SLiam Girdwood /* PLL divisors */
68083ac08c0SLiam Girdwood struct _pll_div {
68183ac08c0SLiam Girdwood 	u32 divsel:1;
68283ac08c0SLiam Girdwood 	u32 divctl:1;
68383ac08c0SLiam Girdwood 	u32 lf:1;
68483ac08c0SLiam Girdwood 	u32 n:4;
68583ac08c0SLiam Girdwood 	u32 k:24;
68683ac08c0SLiam Girdwood };
68783ac08c0SLiam Girdwood 
68883ac08c0SLiam Girdwood /* The size in bits of the PLL divide multiplied by 10
68983ac08c0SLiam Girdwood  * to allow rounding later */
69083ac08c0SLiam Girdwood #define FIXED_PLL_SIZE ((1 << 22) * 10)
69183ac08c0SLiam Girdwood 
69283ac08c0SLiam Girdwood static void pll_factors(struct _pll_div *pll_div, unsigned int source)
69383ac08c0SLiam Girdwood {
69483ac08c0SLiam Girdwood 	u64 Kpart;
69583ac08c0SLiam Girdwood 	unsigned int K, Ndiv, Nmod, target;
69683ac08c0SLiam Girdwood 
69783ac08c0SLiam Girdwood 	/* The the PLL output is always 98.304MHz. */
69883ac08c0SLiam Girdwood 	target = 98304000;
69983ac08c0SLiam Girdwood 
70083ac08c0SLiam Girdwood 	/* If the input frequency is over 14.4MHz then scale it down. */
70183ac08c0SLiam Girdwood 	if (source > 14400000) {
70283ac08c0SLiam Girdwood 		source >>= 1;
70383ac08c0SLiam Girdwood 		pll_div->divsel = 1;
70483ac08c0SLiam Girdwood 
70583ac08c0SLiam Girdwood 		if (source > 14400000) {
70683ac08c0SLiam Girdwood 			source >>= 1;
70783ac08c0SLiam Girdwood 			pll_div->divctl = 1;
70883ac08c0SLiam Girdwood 		} else
70983ac08c0SLiam Girdwood 			pll_div->divctl = 0;
71083ac08c0SLiam Girdwood 
71183ac08c0SLiam Girdwood 	} else {
71283ac08c0SLiam Girdwood 		pll_div->divsel = 0;
71383ac08c0SLiam Girdwood 		pll_div->divctl = 0;
71483ac08c0SLiam Girdwood 	}
71583ac08c0SLiam Girdwood 
71683ac08c0SLiam Girdwood 	/* Low frequency sources require an additional divide in the
71783ac08c0SLiam Girdwood 	 * loop.
71883ac08c0SLiam Girdwood 	 */
71983ac08c0SLiam Girdwood 	if (source < 8192000) {
72083ac08c0SLiam Girdwood 		pll_div->lf = 1;
72183ac08c0SLiam Girdwood 		target >>= 2;
72283ac08c0SLiam Girdwood 	} else
72383ac08c0SLiam Girdwood 		pll_div->lf = 0;
72483ac08c0SLiam Girdwood 
72583ac08c0SLiam Girdwood 	Ndiv = target / source;
72683ac08c0SLiam Girdwood 	if ((Ndiv < 5) || (Ndiv > 12))
72783ac08c0SLiam Girdwood 		printk(KERN_WARNING
728449bd54dSRoel Kluin 			"WM9713 PLL N value %u out of recommended range!\n",
72983ac08c0SLiam Girdwood 			Ndiv);
73083ac08c0SLiam Girdwood 
73183ac08c0SLiam Girdwood 	pll_div->n = Ndiv;
73283ac08c0SLiam Girdwood 	Nmod = target % source;
73383ac08c0SLiam Girdwood 	Kpart = FIXED_PLL_SIZE * (long long)Nmod;
73483ac08c0SLiam Girdwood 
73583ac08c0SLiam Girdwood 	do_div(Kpart, source);
73683ac08c0SLiam Girdwood 
73783ac08c0SLiam Girdwood 	K = Kpart & 0xFFFFFFFF;
73883ac08c0SLiam Girdwood 
73983ac08c0SLiam Girdwood 	/* Check if we need to round */
74083ac08c0SLiam Girdwood 	if ((K % 10) >= 5)
74183ac08c0SLiam Girdwood 		K += 5;
74283ac08c0SLiam Girdwood 
74383ac08c0SLiam Girdwood 	/* Move down to proper range now rounding is done */
74483ac08c0SLiam Girdwood 	K /= 10;
74583ac08c0SLiam Girdwood 
74683ac08c0SLiam Girdwood 	pll_div->k = K;
74783ac08c0SLiam Girdwood }
74883ac08c0SLiam Girdwood 
74983ac08c0SLiam Girdwood /**
75083ac08c0SLiam Girdwood  * Please note that changing the PLL input frequency may require
75183ac08c0SLiam Girdwood  * resynchronisation with the AC97 controller.
75283ac08c0SLiam Girdwood  */
75383ac08c0SLiam Girdwood static int wm9713_set_pll(struct snd_soc_codec *codec,
75483ac08c0SLiam Girdwood 	int pll_id, unsigned int freq_in, unsigned int freq_out)
75583ac08c0SLiam Girdwood {
756b2c812e2SMark Brown 	struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec);
75783ac08c0SLiam Girdwood 	u16 reg, reg2;
75883ac08c0SLiam Girdwood 	struct _pll_div pll_div;
75983ac08c0SLiam Girdwood 
76083ac08c0SLiam Girdwood 	/* turn PLL off ? */
761c42f69bbSMark Brown 	if (freq_in == 0) {
76283ac08c0SLiam Girdwood 		/* disable PLL power and select ext source */
76383ac08c0SLiam Girdwood 		reg = ac97_read(codec, AC97_HANDSET_RATE);
76483ac08c0SLiam Girdwood 		ac97_write(codec, AC97_HANDSET_RATE, reg | 0x0080);
76583ac08c0SLiam Girdwood 		reg = ac97_read(codec, AC97_EXTENDED_MID);
76683ac08c0SLiam Girdwood 		ac97_write(codec, AC97_EXTENDED_MID, reg | 0x0200);
767c42f69bbSMark Brown 		wm9713->pll_in = 0;
76883ac08c0SLiam Girdwood 		return 0;
76983ac08c0SLiam Girdwood 	}
77083ac08c0SLiam Girdwood 
77183ac08c0SLiam Girdwood 	pll_factors(&pll_div, freq_in);
77283ac08c0SLiam Girdwood 
77383ac08c0SLiam Girdwood 	if (pll_div.k == 0) {
77483ac08c0SLiam Girdwood 		reg = (pll_div.n << 12) | (pll_div.lf << 11) |
77583ac08c0SLiam Girdwood 			(pll_div.divsel << 9) | (pll_div.divctl << 8);
77683ac08c0SLiam Girdwood 		ac97_write(codec, AC97_LINE1_LEVEL, reg);
77783ac08c0SLiam Girdwood 	} else {
77883ac08c0SLiam Girdwood 		/* write the fractional k to the reg 0x46 pages */
77983ac08c0SLiam Girdwood 		reg2 = (pll_div.n << 12) | (pll_div.lf << 11) | (1 << 10) |
78083ac08c0SLiam Girdwood 			(pll_div.divsel << 9) | (pll_div.divctl << 8);
78183ac08c0SLiam Girdwood 
78283ac08c0SLiam Girdwood 		/* K [21:20] */
78383ac08c0SLiam Girdwood 		reg = reg2 | (0x5 << 4) | (pll_div.k >> 20);
78483ac08c0SLiam Girdwood 		ac97_write(codec, AC97_LINE1_LEVEL, reg);
78583ac08c0SLiam Girdwood 
78683ac08c0SLiam Girdwood 		/* K [19:16] */
78783ac08c0SLiam Girdwood 		reg = reg2 | (0x4 << 4) | ((pll_div.k >> 16) & 0xf);
78883ac08c0SLiam Girdwood 		ac97_write(codec, AC97_LINE1_LEVEL, reg);
78983ac08c0SLiam Girdwood 
79083ac08c0SLiam Girdwood 		/* K [15:12] */
79183ac08c0SLiam Girdwood 		reg = reg2 | (0x3 << 4) | ((pll_div.k >> 12) & 0xf);
79283ac08c0SLiam Girdwood 		ac97_write(codec, AC97_LINE1_LEVEL, reg);
79383ac08c0SLiam Girdwood 
79483ac08c0SLiam Girdwood 		/* K [11:8] */
79583ac08c0SLiam Girdwood 		reg = reg2 | (0x2 << 4) | ((pll_div.k >> 8) & 0xf);
79683ac08c0SLiam Girdwood 		ac97_write(codec, AC97_LINE1_LEVEL, reg);
79783ac08c0SLiam Girdwood 
79883ac08c0SLiam Girdwood 		/* K [7:4] */
79983ac08c0SLiam Girdwood 		reg = reg2 | (0x1 << 4) | ((pll_div.k >> 4) & 0xf);
80083ac08c0SLiam Girdwood 		ac97_write(codec, AC97_LINE1_LEVEL, reg);
80183ac08c0SLiam Girdwood 
80283ac08c0SLiam Girdwood 		reg = reg2 | (0x0 << 4) | (pll_div.k & 0xf); /* K [3:0] */
80383ac08c0SLiam Girdwood 		ac97_write(codec, AC97_LINE1_LEVEL, reg);
80483ac08c0SLiam Girdwood 	}
80583ac08c0SLiam Girdwood 
80683ac08c0SLiam Girdwood 	/* turn PLL on and select as source */
80783ac08c0SLiam Girdwood 	reg = ac97_read(codec, AC97_EXTENDED_MID);
80883ac08c0SLiam Girdwood 	ac97_write(codec, AC97_EXTENDED_MID, reg & 0xfdff);
80983ac08c0SLiam Girdwood 	reg = ac97_read(codec, AC97_HANDSET_RATE);
81083ac08c0SLiam Girdwood 	ac97_write(codec, AC97_HANDSET_RATE, reg & 0xff7f);
81183ac08c0SLiam Girdwood 	wm9713->pll_in = freq_in;
81283ac08c0SLiam Girdwood 
81383ac08c0SLiam Girdwood 	/* wait 10ms AC97 link frames for the link to stabilise */
81483ac08c0SLiam Girdwood 	schedule_timeout_interruptible(msecs_to_jiffies(10));
81583ac08c0SLiam Girdwood 	return 0;
81683ac08c0SLiam Girdwood }
81783ac08c0SLiam Girdwood 
81885488037SMark Brown static int wm9713_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
81985488037SMark Brown 		int source, unsigned int freq_in, unsigned int freq_out)
82083ac08c0SLiam Girdwood {
82183ac08c0SLiam Girdwood 	struct snd_soc_codec *codec = codec_dai->codec;
82283ac08c0SLiam Girdwood 	return wm9713_set_pll(codec, pll_id, freq_in, freq_out);
82383ac08c0SLiam Girdwood }
82483ac08c0SLiam Girdwood 
82583ac08c0SLiam Girdwood /*
82683ac08c0SLiam Girdwood  * Tristate the PCM DAI lines, tristate can be disabled by calling
82783ac08c0SLiam Girdwood  * wm9713_set_dai_fmt()
82883ac08c0SLiam Girdwood  */
829e550e17fSLiam Girdwood static int wm9713_set_dai_tristate(struct snd_soc_dai *codec_dai,
83083ac08c0SLiam Girdwood 	int tristate)
83183ac08c0SLiam Girdwood {
83283ac08c0SLiam Girdwood 	struct snd_soc_codec *codec = codec_dai->codec;
83383ac08c0SLiam Girdwood 	u16 reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0x9fff;
83483ac08c0SLiam Girdwood 
83583ac08c0SLiam Girdwood 	if (tristate)
83683ac08c0SLiam Girdwood 		ac97_write(codec, AC97_CENTER_LFE_MASTER, reg);
83783ac08c0SLiam Girdwood 
83883ac08c0SLiam Girdwood 	return 0;
83983ac08c0SLiam Girdwood }
84083ac08c0SLiam Girdwood 
84183ac08c0SLiam Girdwood /*
84283ac08c0SLiam Girdwood  * Configure WM9713 clock dividers.
84383ac08c0SLiam Girdwood  * Voice DAC needs 256 FS
84483ac08c0SLiam Girdwood  */
845e550e17fSLiam Girdwood static int wm9713_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
84683ac08c0SLiam Girdwood 		int div_id, int div)
84783ac08c0SLiam Girdwood {
84883ac08c0SLiam Girdwood 	struct snd_soc_codec *codec = codec_dai->codec;
84983ac08c0SLiam Girdwood 	u16 reg;
85083ac08c0SLiam Girdwood 
85183ac08c0SLiam Girdwood 	switch (div_id) {
85283ac08c0SLiam Girdwood 	case WM9713_PCMCLK_DIV:
85383ac08c0SLiam Girdwood 		reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xf0ff;
85483ac08c0SLiam Girdwood 		ac97_write(codec, AC97_HANDSET_RATE, reg | div);
85583ac08c0SLiam Girdwood 		break;
85683ac08c0SLiam Girdwood 	case WM9713_CLKA_MULT:
85783ac08c0SLiam Girdwood 		reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xfffd;
85883ac08c0SLiam Girdwood 		ac97_write(codec, AC97_HANDSET_RATE, reg | div);
85983ac08c0SLiam Girdwood 		break;
86083ac08c0SLiam Girdwood 	case WM9713_CLKB_MULT:
86183ac08c0SLiam Girdwood 		reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xfffb;
86283ac08c0SLiam Girdwood 		ac97_write(codec, AC97_HANDSET_RATE, reg | div);
86383ac08c0SLiam Girdwood 		break;
86483ac08c0SLiam Girdwood 	case WM9713_HIFI_DIV:
86583ac08c0SLiam Girdwood 		reg = ac97_read(codec, AC97_HANDSET_RATE) & 0x8fff;
86683ac08c0SLiam Girdwood 		ac97_write(codec, AC97_HANDSET_RATE, reg | div);
86783ac08c0SLiam Girdwood 		break;
86883ac08c0SLiam Girdwood 	case WM9713_PCMBCLK_DIV:
86983ac08c0SLiam Girdwood 		reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0xf1ff;
87083ac08c0SLiam Girdwood 		ac97_write(codec, AC97_CENTER_LFE_MASTER, reg | div);
87183ac08c0SLiam Girdwood 		break;
87283ac08c0SLiam Girdwood 	case WM9713_PCMCLK_PLL_DIV:
87383ac08c0SLiam Girdwood 		reg = ac97_read(codec, AC97_LINE1_LEVEL) & 0xff80;
87483ac08c0SLiam Girdwood 		ac97_write(codec, AC97_LINE1_LEVEL, reg | 0x60 | div);
87583ac08c0SLiam Girdwood 		break;
87683ac08c0SLiam Girdwood 	case WM9713_HIFI_PLL_DIV:
87783ac08c0SLiam Girdwood 		reg = ac97_read(codec, AC97_LINE1_LEVEL) & 0xff80;
87883ac08c0SLiam Girdwood 		ac97_write(codec, AC97_LINE1_LEVEL, reg | 0x70 | div);
87983ac08c0SLiam Girdwood 		break;
88083ac08c0SLiam Girdwood 	default:
88183ac08c0SLiam Girdwood 		return -EINVAL;
88283ac08c0SLiam Girdwood 	}
88383ac08c0SLiam Girdwood 
88483ac08c0SLiam Girdwood 	return 0;
88583ac08c0SLiam Girdwood }
88683ac08c0SLiam Girdwood 
887e550e17fSLiam Girdwood static int wm9713_set_dai_fmt(struct snd_soc_dai *codec_dai,
88883ac08c0SLiam Girdwood 		unsigned int fmt)
88983ac08c0SLiam Girdwood {
89083ac08c0SLiam Girdwood 	struct snd_soc_codec *codec = codec_dai->codec;
89183ac08c0SLiam Girdwood 	u16 gpio = ac97_read(codec, AC97_GPIO_CFG) & 0xffc5;
89283ac08c0SLiam Girdwood 	u16 reg = 0x8000;
89383ac08c0SLiam Girdwood 
89483ac08c0SLiam Girdwood 	/* clock masters */
89583ac08c0SLiam Girdwood 	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
89683ac08c0SLiam Girdwood 	case SND_SOC_DAIFMT_CBM_CFM:
89783ac08c0SLiam Girdwood 		reg |= 0x4000;
89883ac08c0SLiam Girdwood 		gpio |= 0x0010;
89983ac08c0SLiam Girdwood 		break;
90083ac08c0SLiam Girdwood 	case SND_SOC_DAIFMT_CBM_CFS:
90183ac08c0SLiam Girdwood 		reg |= 0x6000;
90283ac08c0SLiam Girdwood 		gpio |= 0x0018;
90383ac08c0SLiam Girdwood 		break;
90483ac08c0SLiam Girdwood 	case SND_SOC_DAIFMT_CBS_CFS:
905d133b0ceSMark Brown 		reg |= 0x2000;
90683ac08c0SLiam Girdwood 		gpio |= 0x001a;
90783ac08c0SLiam Girdwood 		break;
90883ac08c0SLiam Girdwood 	case SND_SOC_DAIFMT_CBS_CFM:
90983ac08c0SLiam Girdwood 		gpio |= 0x0012;
91083ac08c0SLiam Girdwood 		break;
91183ac08c0SLiam Girdwood 	}
91283ac08c0SLiam Girdwood 
91383ac08c0SLiam Girdwood 	/* clock inversion */
91483ac08c0SLiam Girdwood 	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
91583ac08c0SLiam Girdwood 	case SND_SOC_DAIFMT_IB_IF:
91683ac08c0SLiam Girdwood 		reg |= 0x00c0;
91783ac08c0SLiam Girdwood 		break;
91883ac08c0SLiam Girdwood 	case SND_SOC_DAIFMT_IB_NF:
91983ac08c0SLiam Girdwood 		reg |= 0x0080;
92083ac08c0SLiam Girdwood 		break;
92183ac08c0SLiam Girdwood 	case SND_SOC_DAIFMT_NB_IF:
92283ac08c0SLiam Girdwood 		reg |= 0x0040;
92383ac08c0SLiam Girdwood 		break;
92483ac08c0SLiam Girdwood 	}
92583ac08c0SLiam Girdwood 
92683ac08c0SLiam Girdwood 	/* DAI format */
92783ac08c0SLiam Girdwood 	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
92883ac08c0SLiam Girdwood 	case SND_SOC_DAIFMT_I2S:
92983ac08c0SLiam Girdwood 		reg |= 0x0002;
93083ac08c0SLiam Girdwood 		break;
93183ac08c0SLiam Girdwood 	case SND_SOC_DAIFMT_RIGHT_J:
93283ac08c0SLiam Girdwood 		break;
93383ac08c0SLiam Girdwood 	case SND_SOC_DAIFMT_LEFT_J:
93483ac08c0SLiam Girdwood 		reg |= 0x0001;
93583ac08c0SLiam Girdwood 		break;
93683ac08c0SLiam Girdwood 	case SND_SOC_DAIFMT_DSP_A:
93783ac08c0SLiam Girdwood 		reg |= 0x0003;
93883ac08c0SLiam Girdwood 		break;
93983ac08c0SLiam Girdwood 	case SND_SOC_DAIFMT_DSP_B:
94083ac08c0SLiam Girdwood 		reg |= 0x0043;
94183ac08c0SLiam Girdwood 		break;
94283ac08c0SLiam Girdwood 	}
94383ac08c0SLiam Girdwood 
94483ac08c0SLiam Girdwood 	ac97_write(codec, AC97_GPIO_CFG, gpio);
94583ac08c0SLiam Girdwood 	ac97_write(codec, AC97_CENTER_LFE_MASTER, reg);
94683ac08c0SLiam Girdwood 	return 0;
94783ac08c0SLiam Girdwood }
94883ac08c0SLiam Girdwood 
94983ac08c0SLiam Girdwood static int wm9713_pcm_hw_params(struct snd_pcm_substream *substream,
950dee89c4dSMark Brown 				struct snd_pcm_hw_params *params,
951dee89c4dSMark Brown 				struct snd_soc_dai *dai)
95283ac08c0SLiam Girdwood {
95355b8bac5SMark Brown 	struct snd_soc_codec *codec = dai->codec;
95483ac08c0SLiam Girdwood 	u16 reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0xfff3;
95583ac08c0SLiam Girdwood 
95683ac08c0SLiam Girdwood 	switch (params_format(params)) {
95783ac08c0SLiam Girdwood 	case SNDRV_PCM_FORMAT_S16_LE:
95883ac08c0SLiam Girdwood 		break;
95983ac08c0SLiam Girdwood 	case SNDRV_PCM_FORMAT_S20_3LE:
96083ac08c0SLiam Girdwood 		reg |= 0x0004;
96183ac08c0SLiam Girdwood 		break;
96283ac08c0SLiam Girdwood 	case SNDRV_PCM_FORMAT_S24_LE:
96383ac08c0SLiam Girdwood 		reg |= 0x0008;
96483ac08c0SLiam Girdwood 		break;
96583ac08c0SLiam Girdwood 	case SNDRV_PCM_FORMAT_S32_LE:
96683ac08c0SLiam Girdwood 		reg |= 0x000c;
96783ac08c0SLiam Girdwood 		break;
96883ac08c0SLiam Girdwood 	}
96983ac08c0SLiam Girdwood 
97083ac08c0SLiam Girdwood 	/* enable PCM interface in master mode */
97183ac08c0SLiam Girdwood 	ac97_write(codec, AC97_CENTER_LFE_MASTER, reg);
97283ac08c0SLiam Girdwood 	return 0;
97383ac08c0SLiam Girdwood }
97483ac08c0SLiam Girdwood 
975dee89c4dSMark Brown static int ac97_hifi_prepare(struct snd_pcm_substream *substream,
976dee89c4dSMark Brown 			     struct snd_soc_dai *dai)
97783ac08c0SLiam Girdwood {
97855b8bac5SMark Brown 	struct snd_soc_codec *codec = dai->codec;
97983ac08c0SLiam Girdwood 	struct snd_pcm_runtime *runtime = substream->runtime;
98083ac08c0SLiam Girdwood 	int reg;
98183ac08c0SLiam Girdwood 	u16 vra;
98283ac08c0SLiam Girdwood 
98383ac08c0SLiam Girdwood 	vra = ac97_read(codec, AC97_EXTENDED_STATUS);
98483ac08c0SLiam Girdwood 	ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
98583ac08c0SLiam Girdwood 
98683ac08c0SLiam Girdwood 	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
98783ac08c0SLiam Girdwood 		reg = AC97_PCM_FRONT_DAC_RATE;
98883ac08c0SLiam Girdwood 	else
98983ac08c0SLiam Girdwood 		reg = AC97_PCM_LR_ADC_RATE;
99083ac08c0SLiam Girdwood 
99183ac08c0SLiam Girdwood 	return ac97_write(codec, reg, runtime->rate);
99283ac08c0SLiam Girdwood }
99383ac08c0SLiam Girdwood 
994dee89c4dSMark Brown static int ac97_aux_prepare(struct snd_pcm_substream *substream,
995dee89c4dSMark Brown 			    struct snd_soc_dai *dai)
99683ac08c0SLiam Girdwood {
99755b8bac5SMark Brown 	struct snd_soc_codec *codec = dai->codec;
99883ac08c0SLiam Girdwood 	struct snd_pcm_runtime *runtime = substream->runtime;
99983ac08c0SLiam Girdwood 	u16 vra, xsle;
100083ac08c0SLiam Girdwood 
100183ac08c0SLiam Girdwood 	vra = ac97_read(codec, AC97_EXTENDED_STATUS);
100283ac08c0SLiam Girdwood 	ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
100383ac08c0SLiam Girdwood 	xsle = ac97_read(codec, AC97_PCI_SID);
100483ac08c0SLiam Girdwood 	ac97_write(codec, AC97_PCI_SID, xsle | 0x8000);
100583ac08c0SLiam Girdwood 
100683ac08c0SLiam Girdwood 	if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
100783ac08c0SLiam Girdwood 		return -ENODEV;
100883ac08c0SLiam Girdwood 
100983ac08c0SLiam Girdwood 	return ac97_write(codec, AC97_PCM_SURR_DAC_RATE, runtime->rate);
101083ac08c0SLiam Girdwood }
101183ac08c0SLiam Girdwood 
10125415552dSMark Brown #define WM9713_RATES (SNDRV_PCM_RATE_8000  |	\
10135415552dSMark Brown 		      SNDRV_PCM_RATE_11025 |	\
10145415552dSMark Brown 		      SNDRV_PCM_RATE_22050 |	\
10155415552dSMark Brown 		      SNDRV_PCM_RATE_44100 |	\
10165415552dSMark Brown 		      SNDRV_PCM_RATE_48000)
10175415552dSMark Brown 
10185415552dSMark Brown #define WM9713_PCM_RATES (SNDRV_PCM_RATE_8000  |	\
10195415552dSMark Brown 			  SNDRV_PCM_RATE_11025 |	\
10205415552dSMark Brown 			  SNDRV_PCM_RATE_16000 |	\
10215415552dSMark Brown 			  SNDRV_PCM_RATE_22050 |	\
10225415552dSMark Brown 			  SNDRV_PCM_RATE_44100 |	\
102383ac08c0SLiam Girdwood 			  SNDRV_PCM_RATE_48000)
102483ac08c0SLiam Girdwood 
102583ac08c0SLiam Girdwood #define WM9713_PCM_FORMATS \
102683ac08c0SLiam Girdwood 	(SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \
102783ac08c0SLiam Girdwood 	 SNDRV_PCM_FORMAT_S24_LE)
102883ac08c0SLiam Girdwood 
102985e7652dSLars-Peter Clausen static const struct snd_soc_dai_ops wm9713_dai_ops_hifi = {
10306335d055SEric Miao 	.prepare	= ac97_hifi_prepare,
10316335d055SEric Miao 	.set_clkdiv	= wm9713_set_dai_clkdiv,
10326335d055SEric Miao 	.set_pll	= wm9713_set_dai_pll,
10336335d055SEric Miao };
10346335d055SEric Miao 
103585e7652dSLars-Peter Clausen static const struct snd_soc_dai_ops wm9713_dai_ops_aux = {
10366335d055SEric Miao 	.prepare	= ac97_aux_prepare,
10376335d055SEric Miao 	.set_clkdiv	= wm9713_set_dai_clkdiv,
10386335d055SEric Miao 	.set_pll	= wm9713_set_dai_pll,
10396335d055SEric Miao };
10406335d055SEric Miao 
104185e7652dSLars-Peter Clausen static const struct snd_soc_dai_ops wm9713_dai_ops_voice = {
10426335d055SEric Miao 	.hw_params	= wm9713_pcm_hw_params,
10436335d055SEric Miao 	.set_clkdiv	= wm9713_set_dai_clkdiv,
10446335d055SEric Miao 	.set_pll	= wm9713_set_dai_pll,
10456335d055SEric Miao 	.set_fmt	= wm9713_set_dai_fmt,
10466335d055SEric Miao 	.set_tristate	= wm9713_set_dai_tristate,
10476335d055SEric Miao };
10486335d055SEric Miao 
1049f0fba2adSLiam Girdwood static struct snd_soc_dai_driver wm9713_dai[] = {
105083ac08c0SLiam Girdwood {
1051f0fba2adSLiam Girdwood 	.name = "wm9713-hifi",
10523ba9e10aSMark Brown 	.ac97_control = 1,
105383ac08c0SLiam Girdwood 	.playback = {
105483ac08c0SLiam Girdwood 		.stream_name = "HiFi Playback",
105583ac08c0SLiam Girdwood 		.channels_min = 1,
105683ac08c0SLiam Girdwood 		.channels_max = 2,
105783ac08c0SLiam Girdwood 		.rates = WM9713_RATES,
105833f503c9SMark Brown 		.formats = SND_SOC_STD_AC97_FMTS,},
105983ac08c0SLiam Girdwood 	.capture = {
106083ac08c0SLiam Girdwood 		.stream_name = "HiFi Capture",
106183ac08c0SLiam Girdwood 		.channels_min = 1,
106283ac08c0SLiam Girdwood 		.channels_max = 2,
106383ac08c0SLiam Girdwood 		.rates = WM9713_RATES,
106433f503c9SMark Brown 		.formats = SND_SOC_STD_AC97_FMTS,},
10656335d055SEric Miao 	.ops = &wm9713_dai_ops_hifi,
106683ac08c0SLiam Girdwood 	},
106783ac08c0SLiam Girdwood 	{
1068f0fba2adSLiam Girdwood 	.name = "wm9713-aux",
106983ac08c0SLiam Girdwood 	.playback = {
107083ac08c0SLiam Girdwood 		.stream_name = "Aux Playback",
107183ac08c0SLiam Girdwood 		.channels_min = 1,
107283ac08c0SLiam Girdwood 		.channels_max = 1,
107383ac08c0SLiam Girdwood 		.rates = WM9713_RATES,
107433f503c9SMark Brown 		.formats = SND_SOC_STD_AC97_FMTS,},
10756335d055SEric Miao 	.ops = &wm9713_dai_ops_aux,
107683ac08c0SLiam Girdwood 	},
107783ac08c0SLiam Girdwood 	{
1078f0fba2adSLiam Girdwood 	.name = "wm9713-voice",
107983ac08c0SLiam Girdwood 	.playback = {
108083ac08c0SLiam Girdwood 		.stream_name = "Voice Playback",
108183ac08c0SLiam Girdwood 		.channels_min = 1,
108283ac08c0SLiam Girdwood 		.channels_max = 1,
10835415552dSMark Brown 		.rates = WM9713_PCM_RATES,
108483ac08c0SLiam Girdwood 		.formats = WM9713_PCM_FORMATS,},
108583ac08c0SLiam Girdwood 	.capture = {
108683ac08c0SLiam Girdwood 		.stream_name = "Voice Capture",
108783ac08c0SLiam Girdwood 		.channels_min = 1,
108883ac08c0SLiam Girdwood 		.channels_max = 2,
10895415552dSMark Brown 		.rates = WM9713_PCM_RATES,
109083ac08c0SLiam Girdwood 		.formats = WM9713_PCM_FORMATS,},
10916335d055SEric Miao 	.ops = &wm9713_dai_ops_voice,
1092f4976116SMark Brown 	.symmetric_rates = 1,
109383ac08c0SLiam Girdwood 	},
109483ac08c0SLiam Girdwood };
109583ac08c0SLiam Girdwood 
109683ac08c0SLiam Girdwood int wm9713_reset(struct snd_soc_codec *codec, int try_warm)
109783ac08c0SLiam Girdwood {
109883ac08c0SLiam Girdwood 	if (try_warm && soc_ac97_ops.warm_reset) {
109983ac08c0SLiam Girdwood 		soc_ac97_ops.warm_reset(codec->ac97);
1100abb68c26SMark Brown 		if (ac97_read(codec, 0) == wm9713_reg[0])
110183ac08c0SLiam Girdwood 			return 1;
110283ac08c0SLiam Girdwood 	}
110383ac08c0SLiam Girdwood 
110483ac08c0SLiam Girdwood 	soc_ac97_ops.reset(codec->ac97);
1105e775f6c0SMark Brown 	if (soc_ac97_ops.warm_reset)
1106e775f6c0SMark Brown 		soc_ac97_ops.warm_reset(codec->ac97);
1107abb68c26SMark Brown 	if (ac97_read(codec, 0) != wm9713_reg[0])
110883ac08c0SLiam Girdwood 		return -EIO;
110983ac08c0SLiam Girdwood 	return 0;
111083ac08c0SLiam Girdwood }
111183ac08c0SLiam Girdwood EXPORT_SYMBOL_GPL(wm9713_reset);
111283ac08c0SLiam Girdwood 
11130be9898aSMark Brown static int wm9713_set_bias_level(struct snd_soc_codec *codec,
11140be9898aSMark Brown 				 enum snd_soc_bias_level level)
111583ac08c0SLiam Girdwood {
111683ac08c0SLiam Girdwood 	u16 reg;
111783ac08c0SLiam Girdwood 
11180be9898aSMark Brown 	switch (level) {
11190be9898aSMark Brown 	case SND_SOC_BIAS_ON:
112083ac08c0SLiam Girdwood 		/* enable thermal shutdown */
112183ac08c0SLiam Girdwood 		reg = ac97_read(codec, AC97_EXTENDED_MID) & 0x1bff;
112283ac08c0SLiam Girdwood 		ac97_write(codec, AC97_EXTENDED_MID, reg);
112383ac08c0SLiam Girdwood 		break;
11240be9898aSMark Brown 	case SND_SOC_BIAS_PREPARE:
112583ac08c0SLiam Girdwood 		break;
11260be9898aSMark Brown 	case SND_SOC_BIAS_STANDBY:
112783ac08c0SLiam Girdwood 		/* enable master bias and vmid */
112883ac08c0SLiam Girdwood 		reg = ac97_read(codec, AC97_EXTENDED_MID) & 0x3bff;
112983ac08c0SLiam Girdwood 		ac97_write(codec, AC97_EXTENDED_MID, reg);
113083ac08c0SLiam Girdwood 		ac97_write(codec, AC97_POWERDOWN, 0x0000);
113183ac08c0SLiam Girdwood 		break;
11320be9898aSMark Brown 	case SND_SOC_BIAS_OFF:
113383ac08c0SLiam Girdwood 		/* disable everything including AC link */
113483ac08c0SLiam Girdwood 		ac97_write(codec, AC97_EXTENDED_MID, 0xffff);
113583ac08c0SLiam Girdwood 		ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff);
113683ac08c0SLiam Girdwood 		ac97_write(codec, AC97_POWERDOWN, 0xffff);
113783ac08c0SLiam Girdwood 		break;
113883ac08c0SLiam Girdwood 	}
1139ce6120ccSLiam Girdwood 	codec->dapm.bias_level = level;
114083ac08c0SLiam Girdwood 	return 0;
114183ac08c0SLiam Girdwood }
114283ac08c0SLiam Girdwood 
114384b315eeSLars-Peter Clausen static int wm9713_soc_suspend(struct snd_soc_codec *codec)
114483ac08c0SLiam Girdwood {
114587b57fe2SMark Brown 	u16 reg;
114683ac08c0SLiam Girdwood 
114787b57fe2SMark Brown 	/* Disable everything except touchpanel - that will be handled
114887b57fe2SMark Brown 	 * by the touch driver and left disabled if touch is not in
114987b57fe2SMark Brown 	 * use. */
115087b57fe2SMark Brown 	reg = ac97_read(codec, AC97_EXTENDED_MID);
115187b57fe2SMark Brown 	ac97_write(codec, AC97_EXTENDED_MID, reg | 0x7fff);
115287b57fe2SMark Brown 	ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff);
115387b57fe2SMark Brown 	ac97_write(codec, AC97_POWERDOWN, 0x6f00);
115487b57fe2SMark Brown 	ac97_write(codec, AC97_POWERDOWN, 0xffff);
115587b57fe2SMark Brown 
115683ac08c0SLiam Girdwood 	return 0;
115783ac08c0SLiam Girdwood }
115883ac08c0SLiam Girdwood 
1159f0fba2adSLiam Girdwood static int wm9713_soc_resume(struct snd_soc_codec *codec)
116083ac08c0SLiam Girdwood {
1161b2c812e2SMark Brown 	struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec);
116283ac08c0SLiam Girdwood 	int i, ret;
116383ac08c0SLiam Girdwood 	u16 *cache = codec->reg_cache;
116483ac08c0SLiam Girdwood 
116583ac08c0SLiam Girdwood 	ret = wm9713_reset(codec, 1);
116683ac08c0SLiam Girdwood 	if (ret < 0) {
116783ac08c0SLiam Girdwood 		printk(KERN_ERR "could not reset AC97 codec\n");
116883ac08c0SLiam Girdwood 		return ret;
116983ac08c0SLiam Girdwood 	}
117083ac08c0SLiam Girdwood 
11710be9898aSMark Brown 	wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
117283ac08c0SLiam Girdwood 
117383ac08c0SLiam Girdwood 	/* do we need to re-start the PLL ? */
1174c42f69bbSMark Brown 	if (wm9713->pll_in)
1175c42f69bbSMark Brown 		wm9713_set_pll(codec, 0, wm9713->pll_in, 0);
117683ac08c0SLiam Girdwood 
117783ac08c0SLiam Girdwood 	/* only synchronise the codec if warm reset failed */
117883ac08c0SLiam Girdwood 	if (ret == 0) {
117983ac08c0SLiam Girdwood 		for (i = 2; i < ARRAY_SIZE(wm9713_reg) << 1; i += 2) {
118083ac08c0SLiam Girdwood 			if (i == AC97_POWERDOWN || i == AC97_EXTENDED_MID ||
118183ac08c0SLiam Girdwood 				i == AC97_EXTENDED_MSTATUS || i > 0x66)
118283ac08c0SLiam Girdwood 				continue;
118383ac08c0SLiam Girdwood 			soc_ac97_ops.write(codec->ac97, i, cache[i>>1]);
118483ac08c0SLiam Girdwood 		}
118583ac08c0SLiam Girdwood 	}
118683ac08c0SLiam Girdwood 
118783ac08c0SLiam Girdwood 	return ret;
118883ac08c0SLiam Girdwood }
118983ac08c0SLiam Girdwood 
1190f0fba2adSLiam Girdwood static int wm9713_soc_probe(struct snd_soc_codec *codec)
119183ac08c0SLiam Girdwood {
1192f0fba2adSLiam Girdwood 	struct wm9713_priv *wm9713;
119383ac08c0SLiam Girdwood 	int ret = 0, reg;
119483ac08c0SLiam Girdwood 
1195f0fba2adSLiam Girdwood 	wm9713 = kzalloc(sizeof(struct wm9713_priv), GFP_KERNEL);
1196f0fba2adSLiam Girdwood 	if (wm9713 == NULL)
119783ac08c0SLiam Girdwood 		return -ENOMEM;
1198f0fba2adSLiam Girdwood 	snd_soc_codec_set_drvdata(codec, wm9713);
119983ac08c0SLiam Girdwood 
120083ac08c0SLiam Girdwood 	ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0);
120183ac08c0SLiam Girdwood 	if (ret < 0)
120283ac08c0SLiam Girdwood 		goto codec_err;
120383ac08c0SLiam Girdwood 
120483ac08c0SLiam Girdwood 	/* do a cold reset for the controller and then try
120583ac08c0SLiam Girdwood 	 * a warm reset followed by an optional cold reset for codec */
120683ac08c0SLiam Girdwood 	wm9713_reset(codec, 0);
120783ac08c0SLiam Girdwood 	ret = wm9713_reset(codec, 1);
120883ac08c0SLiam Girdwood 	if (ret < 0) {
120939639fabSMark Brown 		printk(KERN_ERR "Failed to reset WM9713: AC97 link error\n");
121083ac08c0SLiam Girdwood 		goto reset_err;
121183ac08c0SLiam Girdwood 	}
121283ac08c0SLiam Girdwood 
12130be9898aSMark Brown 	wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
121483ac08c0SLiam Girdwood 
121583ac08c0SLiam Girdwood 	/* unmute the adc - move to kcontrol */
121683ac08c0SLiam Girdwood 	reg = ac97_read(codec, AC97_CD) & 0x7fff;
121783ac08c0SLiam Girdwood 	ac97_write(codec, AC97_CD, reg);
121883ac08c0SLiam Girdwood 
1219022658beSLiam Girdwood 	snd_soc_add_codec_controls(codec, wm9713_snd_ac97_controls,
12203e8e1952SIan Molton 				ARRAY_SIZE(wm9713_snd_ac97_controls));
122183ac08c0SLiam Girdwood 
122283ac08c0SLiam Girdwood 	return 0;
122383ac08c0SLiam Girdwood 
122483ac08c0SLiam Girdwood reset_err:
122583ac08c0SLiam Girdwood 	snd_soc_free_ac97_codec(codec);
122683ac08c0SLiam Girdwood codec_err:
1227f0fba2adSLiam Girdwood 	kfree(wm9713);
122883ac08c0SLiam Girdwood 	return ret;
122983ac08c0SLiam Girdwood }
123083ac08c0SLiam Girdwood 
1231f0fba2adSLiam Girdwood static int wm9713_soc_remove(struct snd_soc_codec *codec)
123283ac08c0SLiam Girdwood {
1233f0fba2adSLiam Girdwood 	struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec);
123483ac08c0SLiam Girdwood 	snd_soc_free_ac97_codec(codec);
1235f0fba2adSLiam Girdwood 	kfree(wm9713);
123683ac08c0SLiam Girdwood 	return 0;
123783ac08c0SLiam Girdwood }
123883ac08c0SLiam Girdwood 
1239f0fba2adSLiam Girdwood static struct snd_soc_codec_driver soc_codec_dev_wm9713 = {
124083ac08c0SLiam Girdwood 	.probe = 	wm9713_soc_probe,
124183ac08c0SLiam Girdwood 	.remove = 	wm9713_soc_remove,
124283ac08c0SLiam Girdwood 	.suspend =	wm9713_soc_suspend,
124383ac08c0SLiam Girdwood 	.resume = 	wm9713_soc_resume,
1244f0fba2adSLiam Girdwood 	.read = ac97_read,
1245f0fba2adSLiam Girdwood 	.write = ac97_write,
1246f0fba2adSLiam Girdwood 	.set_bias_level = wm9713_set_bias_level,
1247e5eec34cSDimitris Papastamos 	.reg_cache_size = ARRAY_SIZE(wm9713_reg),
1248f0fba2adSLiam Girdwood 	.reg_word_size = sizeof(u16),
1249f0fba2adSLiam Girdwood 	.reg_cache_step = 2,
1250f0fba2adSLiam Girdwood 	.reg_cache_default = wm9713_reg,
12517ad7dd12SLu Guanqun 	.dapm_widgets = wm9713_dapm_widgets,
12527ad7dd12SLu Guanqun 	.num_dapm_widgets = ARRAY_SIZE(wm9713_dapm_widgets),
12537ad7dd12SLu Guanqun 	.dapm_routes = wm9713_audio_map,
12547ad7dd12SLu Guanqun 	.num_dapm_routes = ARRAY_SIZE(wm9713_audio_map),
125583ac08c0SLiam Girdwood };
1256f0fba2adSLiam Girdwood 
1257f0fba2adSLiam Girdwood static __devinit int wm9713_probe(struct platform_device *pdev)
1258f0fba2adSLiam Girdwood {
1259f0fba2adSLiam Girdwood 	return snd_soc_register_codec(&pdev->dev,
1260f0fba2adSLiam Girdwood 			&soc_codec_dev_wm9713, wm9713_dai, ARRAY_SIZE(wm9713_dai));
1261f0fba2adSLiam Girdwood }
1262f0fba2adSLiam Girdwood 
1263f0fba2adSLiam Girdwood static int __devexit wm9713_remove(struct platform_device *pdev)
1264f0fba2adSLiam Girdwood {
1265f0fba2adSLiam Girdwood 	snd_soc_unregister_codec(&pdev->dev);
1266f0fba2adSLiam Girdwood 	return 0;
1267f0fba2adSLiam Girdwood }
1268f0fba2adSLiam Girdwood 
1269f0fba2adSLiam Girdwood static struct platform_driver wm9713_codec_driver = {
1270f0fba2adSLiam Girdwood 	.driver = {
1271f0fba2adSLiam Girdwood 			.name = "wm9713-codec",
1272f0fba2adSLiam Girdwood 			.owner = THIS_MODULE,
1273f0fba2adSLiam Girdwood 	},
1274f0fba2adSLiam Girdwood 
1275f0fba2adSLiam Girdwood 	.probe = wm9713_probe,
1276f0fba2adSLiam Girdwood 	.remove = __devexit_p(wm9713_remove),
1277f0fba2adSLiam Girdwood };
1278f0fba2adSLiam Girdwood 
12795bbcc3c0SMark Brown module_platform_driver(wm9713_codec_driver);
128083ac08c0SLiam Girdwood 
128183ac08c0SLiam Girdwood MODULE_DESCRIPTION("ASoC WM9713/WM9714 driver");
128283ac08c0SLiam Girdwood MODULE_AUTHOR("Liam Girdwood");
128383ac08c0SLiam Girdwood MODULE_LICENSE("GPL");
1284