183ac08c0SLiam Girdwood /* 283ac08c0SLiam Girdwood * wm9713.c -- ALSA Soc WM9713 codec support 383ac08c0SLiam Girdwood * 483ac08c0SLiam Girdwood * Copyright 2006 Wolfson Microelectronics PLC. 583ac08c0SLiam Girdwood * Author: Liam Girdwood 683ac08c0SLiam Girdwood * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com 783ac08c0SLiam Girdwood * 883ac08c0SLiam Girdwood * This program is free software; you can redistribute it and/or modify it 983ac08c0SLiam Girdwood * under the terms of the GNU General Public License as published by the 1083ac08c0SLiam Girdwood * Free Software Foundation; either version 2 of the License, or (at your 1183ac08c0SLiam Girdwood * option) any later version. 1283ac08c0SLiam Girdwood * 1383ac08c0SLiam Girdwood * Features:- 1483ac08c0SLiam Girdwood * 1583ac08c0SLiam Girdwood * o Support for AC97 Codec, Voice DAC and Aux DAC 1683ac08c0SLiam Girdwood * o Support for DAPM 1783ac08c0SLiam Girdwood */ 1883ac08c0SLiam Girdwood 1983ac08c0SLiam Girdwood #include <linux/init.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> 2783ac08c0SLiam Girdwood #include <sound/soc.h> 2883ac08c0SLiam Girdwood #include <sound/soc-dapm.h> 2983ac08c0SLiam Girdwood 3083ac08c0SLiam Girdwood #include "wm9713.h" 3183ac08c0SLiam Girdwood 3283ac08c0SLiam Girdwood #define WM9713_VERSION "0.15" 3383ac08c0SLiam Girdwood 3483ac08c0SLiam Girdwood struct wm9713_priv { 3583ac08c0SLiam Girdwood u32 pll_in; /* PLL input frequency */ 3683ac08c0SLiam Girdwood u32 pll_out; /* PLL output frequency */ 3783ac08c0SLiam Girdwood }; 3883ac08c0SLiam Girdwood 3983ac08c0SLiam Girdwood static unsigned int ac97_read(struct snd_soc_codec *codec, 4083ac08c0SLiam Girdwood unsigned int reg); 4183ac08c0SLiam Girdwood static int ac97_write(struct snd_soc_codec *codec, 4283ac08c0SLiam Girdwood unsigned int reg, unsigned int val); 4383ac08c0SLiam Girdwood 4483ac08c0SLiam Girdwood /* 4583ac08c0SLiam Girdwood * WM9713 register cache 4683ac08c0SLiam Girdwood * Reg 0x3c bit 15 is used by touch driver. 4783ac08c0SLiam Girdwood */ 4883ac08c0SLiam Girdwood static const u16 wm9713_reg[] = { 4983ac08c0SLiam Girdwood 0x6174, 0x8080, 0x8080, 0x8080, 5083ac08c0SLiam Girdwood 0xc880, 0xe808, 0xe808, 0x0808, 5183ac08c0SLiam Girdwood 0x00da, 0x8000, 0xd600, 0xaaa0, 5283ac08c0SLiam Girdwood 0xaaa0, 0xaaa0, 0x0000, 0x0000, 5383ac08c0SLiam Girdwood 0x0f0f, 0x0040, 0x0000, 0x7f00, 5483ac08c0SLiam Girdwood 0x0405, 0x0410, 0xbb80, 0xbb80, 5583ac08c0SLiam Girdwood 0x0000, 0xbb80, 0x0000, 0x4523, 5683ac08c0SLiam Girdwood 0x0000, 0x2000, 0x7eff, 0xffff, 5783ac08c0SLiam Girdwood 0x0000, 0x0000, 0x0080, 0x0000, 5883ac08c0SLiam Girdwood 0x0000, 0x0000, 0xfffe, 0xffff, 5983ac08c0SLiam Girdwood 0x0000, 0x0000, 0x0000, 0xfffe, 6083ac08c0SLiam Girdwood 0x4000, 0x0000, 0x0000, 0x0000, 6183ac08c0SLiam Girdwood 0xb032, 0x3e00, 0x0000, 0x0000, 6283ac08c0SLiam Girdwood 0x0000, 0x0000, 0x0000, 0x0000, 6383ac08c0SLiam Girdwood 0x0000, 0x0000, 0x0000, 0x0006, 6483ac08c0SLiam Girdwood 0x0001, 0x0000, 0x574d, 0x4c13, 6583ac08c0SLiam Girdwood 0x0000, 0x0000, 0x0000 6683ac08c0SLiam Girdwood }; 6783ac08c0SLiam Girdwood 6883ac08c0SLiam Girdwood /* virtual HP mixers regs */ 6983ac08c0SLiam Girdwood #define HPL_MIXER 0x80 7083ac08c0SLiam Girdwood #define HPR_MIXER 0x82 7183ac08c0SLiam Girdwood #define MICB_MUX 0x82 7283ac08c0SLiam Girdwood 7383ac08c0SLiam Girdwood static const char *wm9713_mic_mixer[] = {"Stereo", "Mic 1", "Mic 2", "Mute"}; 7483ac08c0SLiam Girdwood static const char *wm9713_rec_mux[] = {"Stereo", "Left", "Right", "Mute"}; 7583ac08c0SLiam Girdwood static const char *wm9713_rec_src[] = 7683ac08c0SLiam Girdwood {"Mic 1", "Mic 2", "Line", "Mono In", "Headphone", "Speaker", 7783ac08c0SLiam Girdwood "Mono Out", "Zh"}; 7883ac08c0SLiam Girdwood static const char *wm9713_rec_gain[] = {"+1.5dB Steps", "+0.75dB Steps"}; 7983ac08c0SLiam Girdwood static const char *wm9713_alc_select[] = {"None", "Left", "Right", "Stereo"}; 8083ac08c0SLiam Girdwood static const char *wm9713_mono_pga[] = {"Vmid", "Zh", "Mono", "Inv", 8183ac08c0SLiam Girdwood "Mono Vmid", "Inv Vmid"}; 8283ac08c0SLiam Girdwood static const char *wm9713_spk_pga[] = 8383ac08c0SLiam Girdwood {"Vmid", "Zh", "Headphone", "Speaker", "Inv", "Headphone Vmid", 8483ac08c0SLiam Girdwood "Speaker Vmid", "Inv Vmid"}; 8583ac08c0SLiam Girdwood static const char *wm9713_hp_pga[] = {"Vmid", "Zh", "Headphone", 8683ac08c0SLiam Girdwood "Headphone Vmid"}; 8783ac08c0SLiam Girdwood static const char *wm9713_out3_pga[] = {"Vmid", "Zh", "Inv 1", "Inv 1 Vmid"}; 8883ac08c0SLiam Girdwood static const char *wm9713_out4_pga[] = {"Vmid", "Zh", "Inv 2", "Inv 2 Vmid"}; 8983ac08c0SLiam Girdwood static const char *wm9713_dac_inv[] = 9083ac08c0SLiam Girdwood {"Off", "Mono", "Speaker", "Left Headphone", "Right Headphone", 9183ac08c0SLiam Girdwood "Headphone Mono", "NC", "Vmid"}; 9283ac08c0SLiam Girdwood static const char *wm9713_bass[] = {"Linear Control", "Adaptive Boost"}; 9383ac08c0SLiam Girdwood static const char *wm9713_ng_type[] = {"Constant Gain", "Mute"}; 9483ac08c0SLiam Girdwood static const char *wm9713_mic_select[] = {"Mic 1", "Mic 2 A", "Mic 2 B"}; 9583ac08c0SLiam Girdwood static const char *wm9713_micb_select[] = {"MPB", "MPA"}; 9683ac08c0SLiam Girdwood 9783ac08c0SLiam Girdwood static const struct soc_enum wm9713_enum[] = { 9883ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_LINE, 3, 4, wm9713_mic_mixer), /* record mic mixer 0 */ 9983ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_VIDEO, 14, 4, wm9713_rec_mux), /* record mux hp 1 */ 10083ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_VIDEO, 9, 4, wm9713_rec_mux), /* record mux mono 2 */ 10183ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_VIDEO, 3, 8, wm9713_rec_src), /* record mux left 3 */ 10283ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_VIDEO, 0, 8, wm9713_rec_src), /* record mux right 4*/ 10383ac08c0SLiam Girdwood SOC_ENUM_DOUBLE(AC97_CD, 14, 6, 2, wm9713_rec_gain), /* record step size 5 */ 10483ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_PCI_SVID, 14, 4, wm9713_alc_select), /* alc source select 6*/ 10583ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_REC_GAIN, 14, 4, wm9713_mono_pga), /* mono input select 7 */ 10683ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_REC_GAIN, 11, 8, wm9713_spk_pga), /* speaker left input select 8 */ 10783ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_REC_GAIN, 8, 8, wm9713_spk_pga), /* speaker right input select 9 */ 10883ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_REC_GAIN, 6, 3, wm9713_hp_pga), /* headphone left input 10 */ 10983ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_REC_GAIN, 4, 3, wm9713_hp_pga), /* headphone right input 11 */ 11083ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_REC_GAIN, 2, 4, wm9713_out3_pga), /* out 3 source 12 */ 11183ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_REC_GAIN, 0, 4, wm9713_out4_pga), /* out 4 source 13 */ 11283ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_REC_GAIN_MIC, 13, 8, wm9713_dac_inv), /* dac invert 1 14 */ 11383ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_REC_GAIN_MIC, 10, 8, wm9713_dac_inv), /* dac invert 2 15 */ 11483ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, wm9713_bass), /* bass control 16 */ 11583ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9713_ng_type), /* noise gate type 17 */ 11683ac08c0SLiam Girdwood SOC_ENUM_SINGLE(AC97_3D_CONTROL, 12, 3, wm9713_mic_select), /* mic selection 18 */ 11783ac08c0SLiam Girdwood SOC_ENUM_SINGLE(MICB_MUX, 0, 2, wm9713_micb_select), /* mic selection 19 */ 11883ac08c0SLiam Girdwood }; 11983ac08c0SLiam Girdwood 12083ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_snd_ac97_controls[] = { 12183ac08c0SLiam Girdwood SOC_DOUBLE("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1), 12283ac08c0SLiam Girdwood SOC_DOUBLE("Speaker Playback Switch", AC97_MASTER, 15, 7, 1, 1), 12383ac08c0SLiam Girdwood SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1), 12483ac08c0SLiam Girdwood SOC_DOUBLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 7, 1, 1), 12583ac08c0SLiam Girdwood SOC_DOUBLE("Line In Volume", AC97_PC_BEEP, 8, 0, 31, 1), 12683ac08c0SLiam Girdwood SOC_DOUBLE("PCM Playback Volume", AC97_PHONE, 8, 0, 31, 1), 12783ac08c0SLiam Girdwood SOC_SINGLE("Mic 1 Volume", AC97_MIC, 8, 31, 1), 12883ac08c0SLiam Girdwood SOC_SINGLE("Mic 2 Volume", AC97_MIC, 0, 31, 1), 12983ac08c0SLiam Girdwood 13083ac08c0SLiam Girdwood SOC_SINGLE("Mic Boost (+20dB) Switch", AC97_LINE, 5, 1, 0), 13183ac08c0SLiam Girdwood SOC_SINGLE("Mic Headphone Mixer Volume", AC97_LINE, 0, 7, 1), 13283ac08c0SLiam Girdwood 13383ac08c0SLiam Girdwood SOC_SINGLE("Capture Switch", AC97_CD, 15, 1, 1), 13483ac08c0SLiam Girdwood SOC_ENUM("Capture Volume Steps", wm9713_enum[5]), 13583ac08c0SLiam Girdwood SOC_DOUBLE("Capture Volume", AC97_CD, 8, 0, 31, 0), 13683ac08c0SLiam Girdwood SOC_SINGLE("Capture ZC Switch", AC97_CD, 7, 1, 0), 13783ac08c0SLiam Girdwood 13883ac08c0SLiam Girdwood SOC_SINGLE("Capture to Headphone Volume", AC97_VIDEO, 11, 7, 1), 13983ac08c0SLiam Girdwood SOC_SINGLE("Capture to Mono Boost (+20dB) Switch", AC97_VIDEO, 8, 1, 0), 14083ac08c0SLiam Girdwood SOC_SINGLE("Capture ADC Boost (+20dB) Switch", AC97_VIDEO, 6, 1, 0), 14183ac08c0SLiam Girdwood 14283ac08c0SLiam Girdwood SOC_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0), 14383ac08c0SLiam Girdwood SOC_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0), 14483ac08c0SLiam Girdwood SOC_SINGLE("ALC Decay Time ", AC97_CODEC_CLASS_REV, 4, 15, 0), 14583ac08c0SLiam Girdwood SOC_SINGLE("ALC Attack Time", AC97_CODEC_CLASS_REV, 0, 15, 0), 14683ac08c0SLiam Girdwood SOC_ENUM("ALC Function", wm9713_enum[6]), 14783ac08c0SLiam Girdwood SOC_SINGLE("ALC Max Volume", AC97_PCI_SVID, 11, 7, 0), 14883ac08c0SLiam Girdwood SOC_SINGLE("ALC ZC Timeout", AC97_PCI_SVID, 9, 3, 0), 14983ac08c0SLiam Girdwood SOC_SINGLE("ALC ZC Switch", AC97_PCI_SVID, 8, 1, 0), 15083ac08c0SLiam Girdwood SOC_SINGLE("ALC NG Switch", AC97_PCI_SVID, 7, 1, 0), 15183ac08c0SLiam Girdwood SOC_ENUM("ALC NG Type", wm9713_enum[17]), 15283ac08c0SLiam Girdwood SOC_SINGLE("ALC NG Threshold", AC97_PCI_SVID, 0, 31, 0), 15383ac08c0SLiam Girdwood 15483ac08c0SLiam Girdwood SOC_DOUBLE("Speaker Playback ZC Switch", AC97_MASTER, 14, 6, 1, 0), 15583ac08c0SLiam Girdwood SOC_DOUBLE("Headphone Playback ZC Switch", AC97_HEADPHONE, 14, 6, 1, 0), 15683ac08c0SLiam Girdwood 15783ac08c0SLiam Girdwood SOC_SINGLE("Out4 Playback Switch", AC97_MASTER_MONO, 15, 1, 1), 15883ac08c0SLiam Girdwood SOC_SINGLE("Out4 Playback ZC Switch", AC97_MASTER_MONO, 14, 1, 0), 15983ac08c0SLiam Girdwood SOC_SINGLE("Out4 Playback Volume", AC97_MASTER_MONO, 8, 63, 1), 16083ac08c0SLiam Girdwood 16183ac08c0SLiam Girdwood SOC_SINGLE("Out3 Playback Switch", AC97_MASTER_MONO, 7, 1, 1), 16283ac08c0SLiam Girdwood SOC_SINGLE("Out3 Playback ZC Switch", AC97_MASTER_MONO, 6, 1, 0), 16383ac08c0SLiam Girdwood SOC_SINGLE("Out3 Playback Volume", AC97_MASTER_MONO, 0, 63, 1), 16483ac08c0SLiam Girdwood 16583ac08c0SLiam Girdwood SOC_SINGLE("Mono Capture Volume", AC97_MASTER_TONE, 8, 31, 1), 16683ac08c0SLiam Girdwood SOC_SINGLE("Mono Playback Switch", AC97_MASTER_TONE, 7, 1, 1), 16783ac08c0SLiam Girdwood SOC_SINGLE("Mono Playback ZC Switch", AC97_MASTER_TONE, 6, 1, 0), 16883ac08c0SLiam Girdwood SOC_SINGLE("Mono Playback Volume", AC97_MASTER_TONE, 0, 31, 1), 16983ac08c0SLiam Girdwood 17083ac08c0SLiam Girdwood SOC_SINGLE("PC Beep Playback Headphone Volume", AC97_AUX, 12, 7, 1), 17183ac08c0SLiam Girdwood SOC_SINGLE("PC Beep Playback Speaker Volume", AC97_AUX, 8, 7, 1), 17283ac08c0SLiam Girdwood SOC_SINGLE("PC Beep Playback Mono Volume", AC97_AUX, 4, 7, 1), 17383ac08c0SLiam Girdwood 17483ac08c0SLiam Girdwood SOC_SINGLE("Voice Playback Headphone Volume", AC97_PCM, 12, 7, 1), 17583ac08c0SLiam Girdwood SOC_SINGLE("Voice Playback Master Volume", AC97_PCM, 8, 7, 1), 17683ac08c0SLiam Girdwood SOC_SINGLE("Voice Playback Mono Volume", AC97_PCM, 4, 7, 1), 17783ac08c0SLiam Girdwood 17883ac08c0SLiam Girdwood SOC_SINGLE("Aux Playback Headphone Volume", AC97_REC_SEL, 12, 7, 1), 17983ac08c0SLiam Girdwood SOC_SINGLE("Aux Playback Master Volume", AC97_REC_SEL, 8, 7, 1), 18083ac08c0SLiam Girdwood SOC_SINGLE("Aux Playback Mono Volume", AC97_REC_SEL, 4, 7, 1), 18183ac08c0SLiam Girdwood 18283ac08c0SLiam Girdwood SOC_ENUM("Bass Control", wm9713_enum[16]), 18383ac08c0SLiam Girdwood SOC_SINGLE("Bass Cut-off Switch", AC97_GENERAL_PURPOSE, 12, 1, 1), 18483ac08c0SLiam Girdwood SOC_SINGLE("Tone Cut-off Switch", AC97_GENERAL_PURPOSE, 4, 1, 1), 18583ac08c0SLiam Girdwood SOC_SINGLE("Playback Attenuate (-6dB) Switch", AC97_GENERAL_PURPOSE, 6, 1, 0), 18683ac08c0SLiam Girdwood SOC_SINGLE("Bass Volume", AC97_GENERAL_PURPOSE, 8, 15, 1), 18783ac08c0SLiam Girdwood SOC_SINGLE("Tone Volume", AC97_GENERAL_PURPOSE, 0, 15, 1), 18883ac08c0SLiam Girdwood 18983ac08c0SLiam Girdwood SOC_SINGLE("3D Upper Cut-off Switch", AC97_REC_GAIN_MIC, 5, 1, 0), 19083ac08c0SLiam Girdwood SOC_SINGLE("3D Lower Cut-off Switch", AC97_REC_GAIN_MIC, 4, 1, 0), 19183ac08c0SLiam Girdwood SOC_SINGLE("3D Depth", AC97_REC_GAIN_MIC, 0, 15, 1), 19283ac08c0SLiam Girdwood }; 19383ac08c0SLiam Girdwood 19483ac08c0SLiam Girdwood /* add non dapm controls */ 19583ac08c0SLiam Girdwood static int wm9713_add_controls(struct snd_soc_codec *codec) 19683ac08c0SLiam Girdwood { 19783ac08c0SLiam Girdwood int err, i; 19883ac08c0SLiam Girdwood 19983ac08c0SLiam Girdwood for (i = 0; i < ARRAY_SIZE(wm9713_snd_ac97_controls); i++) { 20083ac08c0SLiam Girdwood err = snd_ctl_add(codec->card, 20183ac08c0SLiam Girdwood snd_soc_cnew(&wm9713_snd_ac97_controls[i], 20283ac08c0SLiam Girdwood codec, NULL)); 20383ac08c0SLiam Girdwood if (err < 0) 20483ac08c0SLiam Girdwood return err; 20583ac08c0SLiam Girdwood } 20683ac08c0SLiam Girdwood return 0; 20783ac08c0SLiam Girdwood } 20883ac08c0SLiam Girdwood 20983ac08c0SLiam Girdwood /* We have to create a fake left and right HP mixers because 21083ac08c0SLiam Girdwood * the codec only has a single control that is shared by both channels. 21183ac08c0SLiam Girdwood * This makes it impossible to determine the audio path using the current 21283ac08c0SLiam Girdwood * register map, thus we add a new (virtual) register to help determine the 21383ac08c0SLiam Girdwood * audio route within the device. 21483ac08c0SLiam Girdwood */ 21583ac08c0SLiam Girdwood static int mixer_event(struct snd_soc_dapm_widget *w, 21683ac08c0SLiam Girdwood struct snd_kcontrol *kcontrol, int event) 21783ac08c0SLiam Girdwood { 21883ac08c0SLiam Girdwood u16 l, r, beep, tone, phone, rec, pcm, aux; 21983ac08c0SLiam Girdwood 22083ac08c0SLiam Girdwood l = ac97_read(w->codec, HPL_MIXER); 22183ac08c0SLiam Girdwood r = ac97_read(w->codec, HPR_MIXER); 22283ac08c0SLiam Girdwood beep = ac97_read(w->codec, AC97_PC_BEEP); 22383ac08c0SLiam Girdwood tone = ac97_read(w->codec, AC97_MASTER_TONE); 22483ac08c0SLiam Girdwood phone = ac97_read(w->codec, AC97_PHONE); 22583ac08c0SLiam Girdwood rec = ac97_read(w->codec, AC97_REC_SEL); 22683ac08c0SLiam Girdwood pcm = ac97_read(w->codec, AC97_PCM); 22783ac08c0SLiam Girdwood aux = ac97_read(w->codec, AC97_AUX); 22883ac08c0SLiam Girdwood 22983ac08c0SLiam Girdwood if (event & SND_SOC_DAPM_PRE_REG) 23083ac08c0SLiam Girdwood return 0; 23183ac08c0SLiam Girdwood if ((l & 0x1) || (r & 0x1)) 23283ac08c0SLiam Girdwood ac97_write(w->codec, AC97_PC_BEEP, beep & 0x7fff); 23383ac08c0SLiam Girdwood else 23483ac08c0SLiam Girdwood ac97_write(w->codec, AC97_PC_BEEP, beep | 0x8000); 23583ac08c0SLiam Girdwood 23683ac08c0SLiam Girdwood if ((l & 0x2) || (r & 0x2)) 23783ac08c0SLiam Girdwood ac97_write(w->codec, AC97_MASTER_TONE, tone & 0x7fff); 23883ac08c0SLiam Girdwood else 23983ac08c0SLiam Girdwood ac97_write(w->codec, AC97_MASTER_TONE, tone | 0x8000); 24083ac08c0SLiam Girdwood 24183ac08c0SLiam Girdwood if ((l & 0x4) || (r & 0x4)) 24283ac08c0SLiam Girdwood ac97_write(w->codec, AC97_PHONE, phone & 0x7fff); 24383ac08c0SLiam Girdwood else 24483ac08c0SLiam Girdwood ac97_write(w->codec, AC97_PHONE, phone | 0x8000); 24583ac08c0SLiam Girdwood 24683ac08c0SLiam Girdwood if ((l & 0x8) || (r & 0x8)) 24783ac08c0SLiam Girdwood ac97_write(w->codec, AC97_REC_SEL, rec & 0x7fff); 24883ac08c0SLiam Girdwood else 24983ac08c0SLiam Girdwood ac97_write(w->codec, AC97_REC_SEL, rec | 0x8000); 25083ac08c0SLiam Girdwood 25183ac08c0SLiam Girdwood if ((l & 0x10) || (r & 0x10)) 25283ac08c0SLiam Girdwood ac97_write(w->codec, AC97_PCM, pcm & 0x7fff); 25383ac08c0SLiam Girdwood else 25483ac08c0SLiam Girdwood ac97_write(w->codec, AC97_PCM, pcm | 0x8000); 25583ac08c0SLiam Girdwood 25683ac08c0SLiam Girdwood if ((l & 0x20) || (r & 0x20)) 25783ac08c0SLiam Girdwood ac97_write(w->codec, AC97_AUX, aux & 0x7fff); 25883ac08c0SLiam Girdwood else 25983ac08c0SLiam Girdwood ac97_write(w->codec, AC97_AUX, aux | 0x8000); 26083ac08c0SLiam Girdwood 26183ac08c0SLiam Girdwood return 0; 26283ac08c0SLiam Girdwood } 26383ac08c0SLiam Girdwood 26483ac08c0SLiam Girdwood /* Left Headphone Mixers */ 26583ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_hpl_mixer_controls[] = { 26683ac08c0SLiam Girdwood SOC_DAPM_SINGLE("PC Beep Playback Switch", HPL_MIXER, 5, 1, 0), 26783ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Voice Playback Switch", HPL_MIXER, 4, 1, 0), 26883ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Aux Playback Switch", HPL_MIXER, 3, 1, 0), 26983ac08c0SLiam Girdwood SOC_DAPM_SINGLE("PCM Playback Switch", HPL_MIXER, 2, 1, 0), 27083ac08c0SLiam Girdwood SOC_DAPM_SINGLE("MonoIn Playback Switch", HPL_MIXER, 1, 1, 0), 27183ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Bypass Playback Switch", HPL_MIXER, 0, 1, 0), 27283ac08c0SLiam Girdwood }; 27383ac08c0SLiam Girdwood 27483ac08c0SLiam Girdwood /* Right Headphone Mixers */ 27583ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_hpr_mixer_controls[] = { 27683ac08c0SLiam Girdwood SOC_DAPM_SINGLE("PC Beep Playback Switch", HPR_MIXER, 5, 1, 0), 27783ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Voice Playback Switch", HPR_MIXER, 4, 1, 0), 27883ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Aux Playback Switch", HPR_MIXER, 3, 1, 0), 27983ac08c0SLiam Girdwood SOC_DAPM_SINGLE("PCM Playback Switch", HPR_MIXER, 2, 1, 0), 28083ac08c0SLiam Girdwood SOC_DAPM_SINGLE("MonoIn Playback Switch", HPR_MIXER, 1, 1, 0), 28183ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Bypass Playback Switch", HPR_MIXER, 0, 1, 0), 28283ac08c0SLiam Girdwood }; 28383ac08c0SLiam Girdwood 28483ac08c0SLiam Girdwood /* headphone capture mux */ 28583ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_hp_rec_mux_controls = 28683ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[1]); 28783ac08c0SLiam Girdwood 28883ac08c0SLiam Girdwood /* headphone mic mux */ 28983ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_hp_mic_mux_controls = 29083ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[0]); 29183ac08c0SLiam Girdwood 29283ac08c0SLiam Girdwood /* Speaker Mixer */ 29383ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_speaker_mixer_controls[] = { 29483ac08c0SLiam Girdwood SOC_DAPM_SINGLE("PC Beep Playback Switch", AC97_AUX, 11, 1, 1), 29583ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Voice Playback Switch", AC97_PCM, 11, 1, 1), 29683ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Aux Playback Switch", AC97_REC_SEL, 11, 1, 1), 29783ac08c0SLiam Girdwood SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PHONE, 14, 1, 1), 29883ac08c0SLiam Girdwood SOC_DAPM_SINGLE("MonoIn Playback Switch", AC97_MASTER_TONE, 14, 1, 1), 29983ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Bypass Playback Switch", AC97_PC_BEEP, 14, 1, 1), 30083ac08c0SLiam Girdwood }; 30183ac08c0SLiam Girdwood 30283ac08c0SLiam Girdwood /* Mono Mixer */ 30383ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_mono_mixer_controls[] = { 30483ac08c0SLiam Girdwood SOC_DAPM_SINGLE("PC Beep Playback Switch", AC97_AUX, 7, 1, 1), 30583ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Voice Playback Switch", AC97_PCM, 7, 1, 1), 30683ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Aux Playback Switch", AC97_REC_SEL, 7, 1, 1), 30783ac08c0SLiam Girdwood SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PHONE, 13, 1, 1), 30883ac08c0SLiam Girdwood SOC_DAPM_SINGLE("MonoIn Playback Switch", AC97_MASTER_TONE, 13, 1, 1), 30983ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Bypass Playback Switch", AC97_PC_BEEP, 13, 1, 1), 31083ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Mic 1 Sidetone Switch", AC97_LINE, 7, 1, 1), 31183ac08c0SLiam Girdwood SOC_DAPM_SINGLE("Mic 2 Sidetone Switch", AC97_LINE, 6, 1, 1), 31283ac08c0SLiam Girdwood }; 31383ac08c0SLiam Girdwood 31483ac08c0SLiam Girdwood /* mono mic mux */ 31583ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_mono_mic_mux_controls = 31683ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[2]); 31783ac08c0SLiam Girdwood 31883ac08c0SLiam Girdwood /* mono output mux */ 31983ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_mono_mux_controls = 32083ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[7]); 32183ac08c0SLiam Girdwood 32283ac08c0SLiam Girdwood /* speaker left output mux */ 32383ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_hp_spkl_mux_controls = 32483ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[8]); 32583ac08c0SLiam Girdwood 32683ac08c0SLiam Girdwood /* speaker right output mux */ 32783ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_hp_spkr_mux_controls = 32883ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[9]); 32983ac08c0SLiam Girdwood 33083ac08c0SLiam Girdwood /* headphone left output mux */ 33183ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_hpl_out_mux_controls = 33283ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[10]); 33383ac08c0SLiam Girdwood 33483ac08c0SLiam Girdwood /* headphone right output mux */ 33583ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_hpr_out_mux_controls = 33683ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[11]); 33783ac08c0SLiam Girdwood 33883ac08c0SLiam Girdwood /* Out3 mux */ 33983ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_out3_mux_controls = 34083ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[12]); 34183ac08c0SLiam Girdwood 34283ac08c0SLiam Girdwood /* Out4 mux */ 34383ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_out4_mux_controls = 34483ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[13]); 34583ac08c0SLiam Girdwood 34683ac08c0SLiam Girdwood /* DAC inv mux 1 */ 34783ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_dac_inv1_mux_controls = 34883ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[14]); 34983ac08c0SLiam Girdwood 35083ac08c0SLiam Girdwood /* DAC inv mux 2 */ 35183ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_dac_inv2_mux_controls = 35283ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[15]); 35383ac08c0SLiam Girdwood 35483ac08c0SLiam Girdwood /* Capture source left */ 35583ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_rec_srcl_mux_controls = 35683ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[3]); 35783ac08c0SLiam Girdwood 35883ac08c0SLiam Girdwood /* Capture source right */ 35983ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_rec_srcr_mux_controls = 36083ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[4]); 36183ac08c0SLiam Girdwood 36283ac08c0SLiam Girdwood /* mic source */ 36383ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_mic_sel_mux_controls = 36483ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[18]); 36583ac08c0SLiam Girdwood 36683ac08c0SLiam Girdwood /* mic source B virtual control */ 36783ac08c0SLiam Girdwood static const struct snd_kcontrol_new wm9713_micb_sel_mux_controls = 36883ac08c0SLiam Girdwood SOC_DAPM_ENUM("Route", wm9713_enum[19]); 36983ac08c0SLiam Girdwood 37083ac08c0SLiam Girdwood static const struct snd_soc_dapm_widget wm9713_dapm_widgets[] = { 37183ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Capture Headphone Mux", SND_SOC_NOPM, 0, 0, 37283ac08c0SLiam Girdwood &wm9713_hp_rec_mux_controls), 37383ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Sidetone Mux", SND_SOC_NOPM, 0, 0, 37483ac08c0SLiam Girdwood &wm9713_hp_mic_mux_controls), 37583ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Capture Mono Mux", SND_SOC_NOPM, 0, 0, 37683ac08c0SLiam Girdwood &wm9713_mono_mic_mux_controls), 37783ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Mono Out Mux", SND_SOC_NOPM, 0, 0, 37883ac08c0SLiam Girdwood &wm9713_mono_mux_controls), 37983ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Left Speaker Out Mux", SND_SOC_NOPM, 0, 0, 38083ac08c0SLiam Girdwood &wm9713_hp_spkl_mux_controls), 38183ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Right Speaker Out Mux", SND_SOC_NOPM, 0, 0, 38283ac08c0SLiam Girdwood &wm9713_hp_spkr_mux_controls), 38383ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Left Headphone Out Mux", SND_SOC_NOPM, 0, 0, 38483ac08c0SLiam Girdwood &wm9713_hpl_out_mux_controls), 38583ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Right Headphone Out Mux", SND_SOC_NOPM, 0, 0, 38683ac08c0SLiam Girdwood &wm9713_hpr_out_mux_controls), 38783ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Out 3 Mux", SND_SOC_NOPM, 0, 0, 38883ac08c0SLiam Girdwood &wm9713_out3_mux_controls), 38983ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Out 4 Mux", SND_SOC_NOPM, 0, 0, 39083ac08c0SLiam Girdwood &wm9713_out4_mux_controls), 39183ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("DAC Inv Mux 1", SND_SOC_NOPM, 0, 0, 39283ac08c0SLiam Girdwood &wm9713_dac_inv1_mux_controls), 39383ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("DAC Inv Mux 2", SND_SOC_NOPM, 0, 0, 39483ac08c0SLiam Girdwood &wm9713_dac_inv2_mux_controls), 39583ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Left Capture Source", SND_SOC_NOPM, 0, 0, 39683ac08c0SLiam Girdwood &wm9713_rec_srcl_mux_controls), 39783ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Right Capture Source", SND_SOC_NOPM, 0, 0, 39883ac08c0SLiam Girdwood &wm9713_rec_srcr_mux_controls), 39983ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Mic A Source", SND_SOC_NOPM, 0, 0, 40083ac08c0SLiam Girdwood &wm9713_mic_sel_mux_controls), 40183ac08c0SLiam Girdwood SND_SOC_DAPM_MUX("Mic B Source", SND_SOC_NOPM, 0, 0, 40283ac08c0SLiam Girdwood &wm9713_micb_sel_mux_controls), 40383ac08c0SLiam Girdwood SND_SOC_DAPM_MIXER_E("Left HP Mixer", AC97_EXTENDED_MID, 3, 1, 40483ac08c0SLiam Girdwood &wm9713_hpl_mixer_controls[0], ARRAY_SIZE(wm9713_hpl_mixer_controls), 40583ac08c0SLiam Girdwood mixer_event, SND_SOC_DAPM_POST_REG), 40683ac08c0SLiam Girdwood SND_SOC_DAPM_MIXER_E("Right HP Mixer", AC97_EXTENDED_MID, 2, 1, 40783ac08c0SLiam Girdwood &wm9713_hpr_mixer_controls[0], ARRAY_SIZE(wm9713_hpr_mixer_controls), 40883ac08c0SLiam Girdwood mixer_event, SND_SOC_DAPM_POST_REG), 40983ac08c0SLiam Girdwood SND_SOC_DAPM_MIXER("Mono Mixer", AC97_EXTENDED_MID, 0, 1, 41083ac08c0SLiam Girdwood &wm9713_mono_mixer_controls[0], ARRAY_SIZE(wm9713_mono_mixer_controls)), 41183ac08c0SLiam Girdwood SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_EXTENDED_MID, 1, 1, 41283ac08c0SLiam Girdwood &wm9713_speaker_mixer_controls[0], 41383ac08c0SLiam Girdwood ARRAY_SIZE(wm9713_speaker_mixer_controls)), 41483ac08c0SLiam Girdwood SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", AC97_EXTENDED_MID, 7, 1), 41583ac08c0SLiam Girdwood SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", AC97_EXTENDED_MID, 6, 1), 41683ac08c0SLiam Girdwood SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), 41783ac08c0SLiam Girdwood SND_SOC_DAPM_MIXER("HP Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), 41883ac08c0SLiam Girdwood SND_SOC_DAPM_MIXER("Line Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), 41983ac08c0SLiam Girdwood SND_SOC_DAPM_MIXER("Capture Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), 42083ac08c0SLiam Girdwood SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback", AC97_EXTENDED_MID, 12, 1), 42183ac08c0SLiam Girdwood SND_SOC_DAPM_DAC("Aux DAC", "Aux Playback", AC97_EXTENDED_MID, 11, 1), 42283ac08c0SLiam Girdwood SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture", AC97_EXTENDED_MID, 5, 1), 42383ac08c0SLiam Girdwood SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture", AC97_EXTENDED_MID, 4, 1), 42483ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Left Headphone", AC97_EXTENDED_MSTATUS, 10, 1, NULL, 0), 42583ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Right Headphone", AC97_EXTENDED_MSTATUS, 9, 1, NULL, 0), 42683ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Left Speaker", AC97_EXTENDED_MSTATUS, 8, 1, NULL, 0), 42783ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Right Speaker", AC97_EXTENDED_MSTATUS, 7, 1, NULL, 0), 42883ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Out 3", AC97_EXTENDED_MSTATUS, 11, 1, NULL, 0), 42983ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Out 4", AC97_EXTENDED_MSTATUS, 12, 1, NULL, 0), 43083ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Mono Out", AC97_EXTENDED_MSTATUS, 13, 1, NULL, 0), 43183ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Left Line In", AC97_EXTENDED_MSTATUS, 6, 1, NULL, 0), 43283ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Right Line In", AC97_EXTENDED_MSTATUS, 5, 1, NULL, 0), 43383ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Mono In", AC97_EXTENDED_MSTATUS, 4, 1, NULL, 0), 43483ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Mic A PGA", AC97_EXTENDED_MSTATUS, 3, 1, NULL, 0), 43583ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Mic B PGA", AC97_EXTENDED_MSTATUS, 2, 1, NULL, 0), 43683ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Mic A Pre Amp", AC97_EXTENDED_MSTATUS, 1, 1, NULL, 0), 43783ac08c0SLiam Girdwood SND_SOC_DAPM_PGA("Mic B Pre Amp", AC97_EXTENDED_MSTATUS, 0, 1, NULL, 0), 43883ac08c0SLiam Girdwood SND_SOC_DAPM_MICBIAS("Mic Bias", AC97_EXTENDED_MSTATUS, 14, 1), 43983ac08c0SLiam Girdwood SND_SOC_DAPM_OUTPUT("MONO"), 44083ac08c0SLiam Girdwood SND_SOC_DAPM_OUTPUT("HPL"), 44183ac08c0SLiam Girdwood SND_SOC_DAPM_OUTPUT("HPR"), 44283ac08c0SLiam Girdwood SND_SOC_DAPM_OUTPUT("SPKL"), 44383ac08c0SLiam Girdwood SND_SOC_DAPM_OUTPUT("SPKR"), 44483ac08c0SLiam Girdwood SND_SOC_DAPM_OUTPUT("OUT3"), 44583ac08c0SLiam Girdwood SND_SOC_DAPM_OUTPUT("OUT4"), 44683ac08c0SLiam Girdwood SND_SOC_DAPM_INPUT("LINEL"), 44783ac08c0SLiam Girdwood SND_SOC_DAPM_INPUT("LINER"), 44883ac08c0SLiam Girdwood SND_SOC_DAPM_INPUT("MONOIN"), 44983ac08c0SLiam Girdwood SND_SOC_DAPM_INPUT("PCBEEP"), 45083ac08c0SLiam Girdwood SND_SOC_DAPM_INPUT("MIC1"), 45183ac08c0SLiam Girdwood SND_SOC_DAPM_INPUT("MIC2A"), 45283ac08c0SLiam Girdwood SND_SOC_DAPM_INPUT("MIC2B"), 45383ac08c0SLiam Girdwood SND_SOC_DAPM_VMID("VMID"), 45483ac08c0SLiam Girdwood }; 45583ac08c0SLiam Girdwood 456*a65f0568SMark Brown static const struct snd_soc_dapm_route audio_map[] = { 45783ac08c0SLiam Girdwood /* left HP mixer */ 45883ac08c0SLiam Girdwood {"Left HP Mixer", "PC Beep Playback Switch", "PCBEEP"}, 45983ac08c0SLiam Girdwood {"Left HP Mixer", "Voice Playback Switch", "Voice DAC"}, 46083ac08c0SLiam Girdwood {"Left HP Mixer", "Aux Playback Switch", "Aux DAC"}, 46183ac08c0SLiam Girdwood {"Left HP Mixer", "Bypass Playback Switch", "Left Line In"}, 46283ac08c0SLiam Girdwood {"Left HP Mixer", "PCM Playback Switch", "Left DAC"}, 46383ac08c0SLiam Girdwood {"Left HP Mixer", "MonoIn Playback Switch", "Mono In"}, 46483ac08c0SLiam Girdwood {"Left HP Mixer", NULL, "Capture Headphone Mux"}, 46583ac08c0SLiam Girdwood 46683ac08c0SLiam Girdwood /* right HP mixer */ 46783ac08c0SLiam Girdwood {"Right HP Mixer", "PC Beep Playback Switch", "PCBEEP"}, 46883ac08c0SLiam Girdwood {"Right HP Mixer", "Voice Playback Switch", "Voice DAC"}, 46983ac08c0SLiam Girdwood {"Right HP Mixer", "Aux Playback Switch", "Aux DAC"}, 47083ac08c0SLiam Girdwood {"Right HP Mixer", "Bypass Playback Switch", "Right Line In"}, 47183ac08c0SLiam Girdwood {"Right HP Mixer", "PCM Playback Switch", "Right DAC"}, 47283ac08c0SLiam Girdwood {"Right HP Mixer", "MonoIn Playback Switch", "Mono In"}, 47383ac08c0SLiam Girdwood {"Right HP Mixer", NULL, "Capture Headphone Mux"}, 47483ac08c0SLiam Girdwood 47583ac08c0SLiam Girdwood /* virtual mixer - mixes left & right channels for spk and mono */ 47683ac08c0SLiam Girdwood {"AC97 Mixer", NULL, "Left DAC"}, 47783ac08c0SLiam Girdwood {"AC97 Mixer", NULL, "Right DAC"}, 47883ac08c0SLiam Girdwood {"Line Mixer", NULL, "Right Line In"}, 47983ac08c0SLiam Girdwood {"Line Mixer", NULL, "Left Line In"}, 48083ac08c0SLiam Girdwood {"HP Mixer", NULL, "Left HP Mixer"}, 48183ac08c0SLiam Girdwood {"HP Mixer", NULL, "Right HP Mixer"}, 48283ac08c0SLiam Girdwood {"Capture Mixer", NULL, "Left Capture Source"}, 48383ac08c0SLiam Girdwood {"Capture Mixer", NULL, "Right Capture Source"}, 48483ac08c0SLiam Girdwood 48583ac08c0SLiam Girdwood /* speaker mixer */ 48683ac08c0SLiam Girdwood {"Speaker Mixer", "PC Beep Playback Switch", "PCBEEP"}, 48783ac08c0SLiam Girdwood {"Speaker Mixer", "Voice Playback Switch", "Voice DAC"}, 48883ac08c0SLiam Girdwood {"Speaker Mixer", "Aux Playback Switch", "Aux DAC"}, 48983ac08c0SLiam Girdwood {"Speaker Mixer", "Bypass Playback Switch", "Line Mixer"}, 49083ac08c0SLiam Girdwood {"Speaker Mixer", "PCM Playback Switch", "AC97 Mixer"}, 49183ac08c0SLiam Girdwood {"Speaker Mixer", "MonoIn Playback Switch", "Mono In"}, 49283ac08c0SLiam Girdwood 49383ac08c0SLiam Girdwood /* mono mixer */ 49483ac08c0SLiam Girdwood {"Mono Mixer", "PC Beep Playback Switch", "PCBEEP"}, 49583ac08c0SLiam Girdwood {"Mono Mixer", "Voice Playback Switch", "Voice DAC"}, 49683ac08c0SLiam Girdwood {"Mono Mixer", "Aux Playback Switch", "Aux DAC"}, 49783ac08c0SLiam Girdwood {"Mono Mixer", "Bypass Playback Switch", "Line Mixer"}, 49883ac08c0SLiam Girdwood {"Mono Mixer", "PCM Playback Switch", "AC97 Mixer"}, 499c0bbf48dSRobert Jarzmik {"Mono Mixer", "Mic 1 Sidetone Switch", "Mic A PGA"}, 500c0bbf48dSRobert Jarzmik {"Mono Mixer", "Mic 2 Sidetone Switch", "Mic B PGA"}, 50183ac08c0SLiam Girdwood {"Mono Mixer", NULL, "Capture Mono Mux"}, 50283ac08c0SLiam Girdwood 50383ac08c0SLiam Girdwood /* DAC inv mux 1 */ 50483ac08c0SLiam Girdwood {"DAC Inv Mux 1", "Mono", "Mono Mixer"}, 50583ac08c0SLiam Girdwood {"DAC Inv Mux 1", "Speaker", "Speaker Mixer"}, 50683ac08c0SLiam Girdwood {"DAC Inv Mux 1", "Left Headphone", "Left HP Mixer"}, 50783ac08c0SLiam Girdwood {"DAC Inv Mux 1", "Right Headphone", "Right HP Mixer"}, 50883ac08c0SLiam Girdwood {"DAC Inv Mux 1", "Headphone Mono", "HP Mixer"}, 50983ac08c0SLiam Girdwood 51083ac08c0SLiam Girdwood /* DAC inv mux 2 */ 51183ac08c0SLiam Girdwood {"DAC Inv Mux 2", "Mono", "Mono Mixer"}, 51283ac08c0SLiam Girdwood {"DAC Inv Mux 2", "Speaker", "Speaker Mixer"}, 51383ac08c0SLiam Girdwood {"DAC Inv Mux 2", "Left Headphone", "Left HP Mixer"}, 51483ac08c0SLiam Girdwood {"DAC Inv Mux 2", "Right Headphone", "Right HP Mixer"}, 51583ac08c0SLiam Girdwood {"DAC Inv Mux 2", "Headphone Mono", "HP Mixer"}, 51683ac08c0SLiam Girdwood 51783ac08c0SLiam Girdwood /* headphone left mux */ 51883ac08c0SLiam Girdwood {"Left Headphone Out Mux", "Headphone", "Left HP Mixer"}, 51983ac08c0SLiam Girdwood 52083ac08c0SLiam Girdwood /* headphone right mux */ 52183ac08c0SLiam Girdwood {"Right Headphone Out Mux", "Headphone", "Right HP Mixer"}, 52283ac08c0SLiam Girdwood 52383ac08c0SLiam Girdwood /* speaker left mux */ 52483ac08c0SLiam Girdwood {"Left Speaker Out Mux", "Headphone", "Left HP Mixer"}, 52583ac08c0SLiam Girdwood {"Left Speaker Out Mux", "Speaker", "Speaker Mixer"}, 52683ac08c0SLiam Girdwood {"Left Speaker Out Mux", "Inv", "DAC Inv Mux 1"}, 52783ac08c0SLiam Girdwood 52883ac08c0SLiam Girdwood /* speaker right mux */ 52983ac08c0SLiam Girdwood {"Right Speaker Out Mux", "Headphone", "Right HP Mixer"}, 53083ac08c0SLiam Girdwood {"Right Speaker Out Mux", "Speaker", "Speaker Mixer"}, 53183ac08c0SLiam Girdwood {"Right Speaker Out Mux", "Inv", "DAC Inv Mux 2"}, 53283ac08c0SLiam Girdwood 53383ac08c0SLiam Girdwood /* mono mux */ 53483ac08c0SLiam Girdwood {"Mono Out Mux", "Mono", "Mono Mixer"}, 53583ac08c0SLiam Girdwood {"Mono Out Mux", "Inv", "DAC Inv Mux 1"}, 53683ac08c0SLiam Girdwood 53783ac08c0SLiam Girdwood /* out 3 mux */ 53883ac08c0SLiam Girdwood {"Out 3 Mux", "Inv 1", "DAC Inv Mux 1"}, 53983ac08c0SLiam Girdwood 54083ac08c0SLiam Girdwood /* out 4 mux */ 54183ac08c0SLiam Girdwood {"Out 4 Mux", "Inv 2", "DAC Inv Mux 2"}, 54283ac08c0SLiam Girdwood 54383ac08c0SLiam Girdwood /* output pga */ 54483ac08c0SLiam Girdwood {"HPL", NULL, "Left Headphone"}, 54583ac08c0SLiam Girdwood {"Left Headphone", NULL, "Left Headphone Out Mux"}, 54683ac08c0SLiam Girdwood {"HPR", NULL, "Right Headphone"}, 54783ac08c0SLiam Girdwood {"Right Headphone", NULL, "Right Headphone Out Mux"}, 54883ac08c0SLiam Girdwood {"OUT3", NULL, "Out 3"}, 54983ac08c0SLiam Girdwood {"Out 3", NULL, "Out 3 Mux"}, 55083ac08c0SLiam Girdwood {"OUT4", NULL, "Out 4"}, 55183ac08c0SLiam Girdwood {"Out 4", NULL, "Out 4 Mux"}, 55283ac08c0SLiam Girdwood {"SPKL", NULL, "Left Speaker"}, 55383ac08c0SLiam Girdwood {"Left Speaker", NULL, "Left Speaker Out Mux"}, 55483ac08c0SLiam Girdwood {"SPKR", NULL, "Right Speaker"}, 55583ac08c0SLiam Girdwood {"Right Speaker", NULL, "Right Speaker Out Mux"}, 55683ac08c0SLiam Girdwood {"MONO", NULL, "Mono Out"}, 55783ac08c0SLiam Girdwood {"Mono Out", NULL, "Mono Out Mux"}, 55883ac08c0SLiam Girdwood 55983ac08c0SLiam Girdwood /* input pga */ 56083ac08c0SLiam Girdwood {"Left Line In", NULL, "LINEL"}, 56183ac08c0SLiam Girdwood {"Right Line In", NULL, "LINER"}, 56283ac08c0SLiam Girdwood {"Mono In", NULL, "MONOIN"}, 56383ac08c0SLiam Girdwood {"Mic A PGA", NULL, "Mic A Pre Amp"}, 56483ac08c0SLiam Girdwood {"Mic B PGA", NULL, "Mic B Pre Amp"}, 56583ac08c0SLiam Girdwood 56683ac08c0SLiam Girdwood /* left capture select */ 56783ac08c0SLiam Girdwood {"Left Capture Source", "Mic 1", "Mic A Pre Amp"}, 56883ac08c0SLiam Girdwood {"Left Capture Source", "Mic 2", "Mic B Pre Amp"}, 56983ac08c0SLiam Girdwood {"Left Capture Source", "Line", "LINEL"}, 57083ac08c0SLiam Girdwood {"Left Capture Source", "Mono In", "MONOIN"}, 57183ac08c0SLiam Girdwood {"Left Capture Source", "Headphone", "Left HP Mixer"}, 57283ac08c0SLiam Girdwood {"Left Capture Source", "Speaker", "Speaker Mixer"}, 57383ac08c0SLiam Girdwood {"Left Capture Source", "Mono Out", "Mono Mixer"}, 57483ac08c0SLiam Girdwood 57583ac08c0SLiam Girdwood /* right capture select */ 57683ac08c0SLiam Girdwood {"Right Capture Source", "Mic 1", "Mic A Pre Amp"}, 57783ac08c0SLiam Girdwood {"Right Capture Source", "Mic 2", "Mic B Pre Amp"}, 57883ac08c0SLiam Girdwood {"Right Capture Source", "Line", "LINER"}, 57983ac08c0SLiam Girdwood {"Right Capture Source", "Mono In", "MONOIN"}, 58083ac08c0SLiam Girdwood {"Right Capture Source", "Headphone", "Right HP Mixer"}, 58183ac08c0SLiam Girdwood {"Right Capture Source", "Speaker", "Speaker Mixer"}, 58283ac08c0SLiam Girdwood {"Right Capture Source", "Mono Out", "Mono Mixer"}, 58383ac08c0SLiam Girdwood 58483ac08c0SLiam Girdwood /* left ADC */ 58583ac08c0SLiam Girdwood {"Left ADC", NULL, "Left Capture Source"}, 58683ac08c0SLiam Girdwood 58783ac08c0SLiam Girdwood /* right ADC */ 58883ac08c0SLiam Girdwood {"Right ADC", NULL, "Right Capture Source"}, 58983ac08c0SLiam Girdwood 59083ac08c0SLiam Girdwood /* mic */ 59183ac08c0SLiam Girdwood {"Mic A Pre Amp", NULL, "Mic A Source"}, 59283ac08c0SLiam Girdwood {"Mic A Source", "Mic 1", "MIC1"}, 59383ac08c0SLiam Girdwood {"Mic A Source", "Mic 2 A", "MIC2A"}, 59483ac08c0SLiam Girdwood {"Mic A Source", "Mic 2 B", "Mic B Source"}, 59583ac08c0SLiam Girdwood {"Mic B Pre Amp", "MPB", "Mic B Source"}, 59683ac08c0SLiam Girdwood {"Mic B Source", NULL, "MIC2B"}, 59783ac08c0SLiam Girdwood 59883ac08c0SLiam Girdwood /* headphone capture */ 59983ac08c0SLiam Girdwood {"Capture Headphone Mux", "Stereo", "Capture Mixer"}, 60083ac08c0SLiam Girdwood {"Capture Headphone Mux", "Left", "Left Capture Source"}, 60183ac08c0SLiam Girdwood {"Capture Headphone Mux", "Right", "Right Capture Source"}, 60283ac08c0SLiam Girdwood 60383ac08c0SLiam Girdwood /* mono capture */ 60483ac08c0SLiam Girdwood {"Capture Mono Mux", "Stereo", "Capture Mixer"}, 60583ac08c0SLiam Girdwood {"Capture Mono Mux", "Left", "Left Capture Source"}, 60683ac08c0SLiam Girdwood {"Capture Mono Mux", "Right", "Right Capture Source"}, 60783ac08c0SLiam Girdwood }; 60883ac08c0SLiam Girdwood 60983ac08c0SLiam Girdwood static int wm9713_add_widgets(struct snd_soc_codec *codec) 61083ac08c0SLiam Girdwood { 611*a65f0568SMark Brown snd_soc_dapm_new_controls(codec, wm9713_dapm_widgets, 612*a65f0568SMark Brown ARRAY_SIZE(wm9713_dapm_widgets)); 61383ac08c0SLiam Girdwood 614*a65f0568SMark Brown snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); 61583ac08c0SLiam Girdwood 61683ac08c0SLiam Girdwood snd_soc_dapm_new_widgets(codec); 61783ac08c0SLiam Girdwood return 0; 61883ac08c0SLiam Girdwood } 61983ac08c0SLiam Girdwood 62083ac08c0SLiam Girdwood static unsigned int ac97_read(struct snd_soc_codec *codec, 62183ac08c0SLiam Girdwood unsigned int reg) 62283ac08c0SLiam Girdwood { 62383ac08c0SLiam Girdwood u16 *cache = codec->reg_cache; 62483ac08c0SLiam Girdwood 62583ac08c0SLiam Girdwood if (reg == AC97_RESET || reg == AC97_GPIO_STATUS || 62683ac08c0SLiam Girdwood reg == AC97_VENDOR_ID1 || reg == AC97_VENDOR_ID2 || 62783ac08c0SLiam Girdwood reg == AC97_CD) 62883ac08c0SLiam Girdwood return soc_ac97_ops.read(codec->ac97, reg); 62983ac08c0SLiam Girdwood else { 63083ac08c0SLiam Girdwood reg = reg >> 1; 63183ac08c0SLiam Girdwood 63283ac08c0SLiam Girdwood if (reg > (ARRAY_SIZE(wm9713_reg))) 63383ac08c0SLiam Girdwood return -EIO; 63483ac08c0SLiam Girdwood 63583ac08c0SLiam Girdwood return cache[reg]; 63683ac08c0SLiam Girdwood } 63783ac08c0SLiam Girdwood } 63883ac08c0SLiam Girdwood 63983ac08c0SLiam Girdwood static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, 64083ac08c0SLiam Girdwood unsigned int val) 64183ac08c0SLiam Girdwood { 64283ac08c0SLiam Girdwood u16 *cache = codec->reg_cache; 64383ac08c0SLiam Girdwood if (reg < 0x7c) 64483ac08c0SLiam Girdwood soc_ac97_ops.write(codec->ac97, reg, val); 64583ac08c0SLiam Girdwood reg = reg >> 1; 64683ac08c0SLiam Girdwood if (reg <= (ARRAY_SIZE(wm9713_reg))) 64783ac08c0SLiam Girdwood cache[reg] = val; 64883ac08c0SLiam Girdwood 64983ac08c0SLiam Girdwood return 0; 65083ac08c0SLiam Girdwood } 65183ac08c0SLiam Girdwood 65283ac08c0SLiam Girdwood /* PLL divisors */ 65383ac08c0SLiam Girdwood struct _pll_div { 65483ac08c0SLiam Girdwood u32 divsel:1; 65583ac08c0SLiam Girdwood u32 divctl:1; 65683ac08c0SLiam Girdwood u32 lf:1; 65783ac08c0SLiam Girdwood u32 n:4; 65883ac08c0SLiam Girdwood u32 k:24; 65983ac08c0SLiam Girdwood }; 66083ac08c0SLiam Girdwood 66183ac08c0SLiam Girdwood /* The size in bits of the PLL divide multiplied by 10 66283ac08c0SLiam Girdwood * to allow rounding later */ 66383ac08c0SLiam Girdwood #define FIXED_PLL_SIZE ((1 << 22) * 10) 66483ac08c0SLiam Girdwood 66583ac08c0SLiam Girdwood static void pll_factors(struct _pll_div *pll_div, unsigned int source) 66683ac08c0SLiam Girdwood { 66783ac08c0SLiam Girdwood u64 Kpart; 66883ac08c0SLiam Girdwood unsigned int K, Ndiv, Nmod, target; 66983ac08c0SLiam Girdwood 67083ac08c0SLiam Girdwood /* The the PLL output is always 98.304MHz. */ 67183ac08c0SLiam Girdwood target = 98304000; 67283ac08c0SLiam Girdwood 67383ac08c0SLiam Girdwood /* If the input frequency is over 14.4MHz then scale it down. */ 67483ac08c0SLiam Girdwood if (source > 14400000) { 67583ac08c0SLiam Girdwood source >>= 1; 67683ac08c0SLiam Girdwood pll_div->divsel = 1; 67783ac08c0SLiam Girdwood 67883ac08c0SLiam Girdwood if (source > 14400000) { 67983ac08c0SLiam Girdwood source >>= 1; 68083ac08c0SLiam Girdwood pll_div->divctl = 1; 68183ac08c0SLiam Girdwood } else 68283ac08c0SLiam Girdwood pll_div->divctl = 0; 68383ac08c0SLiam Girdwood 68483ac08c0SLiam Girdwood } else { 68583ac08c0SLiam Girdwood pll_div->divsel = 0; 68683ac08c0SLiam Girdwood pll_div->divctl = 0; 68783ac08c0SLiam Girdwood } 68883ac08c0SLiam Girdwood 68983ac08c0SLiam Girdwood /* Low frequency sources require an additional divide in the 69083ac08c0SLiam Girdwood * loop. 69183ac08c0SLiam Girdwood */ 69283ac08c0SLiam Girdwood if (source < 8192000) { 69383ac08c0SLiam Girdwood pll_div->lf = 1; 69483ac08c0SLiam Girdwood target >>= 2; 69583ac08c0SLiam Girdwood } else 69683ac08c0SLiam Girdwood pll_div->lf = 0; 69783ac08c0SLiam Girdwood 69883ac08c0SLiam Girdwood Ndiv = target / source; 69983ac08c0SLiam Girdwood if ((Ndiv < 5) || (Ndiv > 12)) 70083ac08c0SLiam Girdwood printk(KERN_WARNING 70183ac08c0SLiam Girdwood "WM9713 PLL N value %d out of recommended range!\n", 70283ac08c0SLiam Girdwood Ndiv); 70383ac08c0SLiam Girdwood 70483ac08c0SLiam Girdwood pll_div->n = Ndiv; 70583ac08c0SLiam Girdwood Nmod = target % source; 70683ac08c0SLiam Girdwood Kpart = FIXED_PLL_SIZE * (long long)Nmod; 70783ac08c0SLiam Girdwood 70883ac08c0SLiam Girdwood do_div(Kpart, source); 70983ac08c0SLiam Girdwood 71083ac08c0SLiam Girdwood K = Kpart & 0xFFFFFFFF; 71183ac08c0SLiam Girdwood 71283ac08c0SLiam Girdwood /* Check if we need to round */ 71383ac08c0SLiam Girdwood if ((K % 10) >= 5) 71483ac08c0SLiam Girdwood K += 5; 71583ac08c0SLiam Girdwood 71683ac08c0SLiam Girdwood /* Move down to proper range now rounding is done */ 71783ac08c0SLiam Girdwood K /= 10; 71883ac08c0SLiam Girdwood 71983ac08c0SLiam Girdwood pll_div->k = K; 72083ac08c0SLiam Girdwood } 72183ac08c0SLiam Girdwood 72283ac08c0SLiam Girdwood /** 72383ac08c0SLiam Girdwood * Please note that changing the PLL input frequency may require 72483ac08c0SLiam Girdwood * resynchronisation with the AC97 controller. 72583ac08c0SLiam Girdwood */ 72683ac08c0SLiam Girdwood static int wm9713_set_pll(struct snd_soc_codec *codec, 72783ac08c0SLiam Girdwood int pll_id, unsigned int freq_in, unsigned int freq_out) 72883ac08c0SLiam Girdwood { 72983ac08c0SLiam Girdwood struct wm9713_priv *wm9713 = codec->private_data; 73083ac08c0SLiam Girdwood u16 reg, reg2; 73183ac08c0SLiam Girdwood struct _pll_div pll_div; 73283ac08c0SLiam Girdwood 73383ac08c0SLiam Girdwood /* turn PLL off ? */ 73483ac08c0SLiam Girdwood if (freq_in == 0 || freq_out == 0) { 73583ac08c0SLiam Girdwood /* disable PLL power and select ext source */ 73683ac08c0SLiam Girdwood reg = ac97_read(codec, AC97_HANDSET_RATE); 73783ac08c0SLiam Girdwood ac97_write(codec, AC97_HANDSET_RATE, reg | 0x0080); 73883ac08c0SLiam Girdwood reg = ac97_read(codec, AC97_EXTENDED_MID); 73983ac08c0SLiam Girdwood ac97_write(codec, AC97_EXTENDED_MID, reg | 0x0200); 74083ac08c0SLiam Girdwood wm9713->pll_out = 0; 74183ac08c0SLiam Girdwood return 0; 74283ac08c0SLiam Girdwood } 74383ac08c0SLiam Girdwood 74483ac08c0SLiam Girdwood pll_factors(&pll_div, freq_in); 74583ac08c0SLiam Girdwood 74683ac08c0SLiam Girdwood if (pll_div.k == 0) { 74783ac08c0SLiam Girdwood reg = (pll_div.n << 12) | (pll_div.lf << 11) | 74883ac08c0SLiam Girdwood (pll_div.divsel << 9) | (pll_div.divctl << 8); 74983ac08c0SLiam Girdwood ac97_write(codec, AC97_LINE1_LEVEL, reg); 75083ac08c0SLiam Girdwood } else { 75183ac08c0SLiam Girdwood /* write the fractional k to the reg 0x46 pages */ 75283ac08c0SLiam Girdwood reg2 = (pll_div.n << 12) | (pll_div.lf << 11) | (1 << 10) | 75383ac08c0SLiam Girdwood (pll_div.divsel << 9) | (pll_div.divctl << 8); 75483ac08c0SLiam Girdwood 75583ac08c0SLiam Girdwood /* K [21:20] */ 75683ac08c0SLiam Girdwood reg = reg2 | (0x5 << 4) | (pll_div.k >> 20); 75783ac08c0SLiam Girdwood ac97_write(codec, AC97_LINE1_LEVEL, reg); 75883ac08c0SLiam Girdwood 75983ac08c0SLiam Girdwood /* K [19:16] */ 76083ac08c0SLiam Girdwood reg = reg2 | (0x4 << 4) | ((pll_div.k >> 16) & 0xf); 76183ac08c0SLiam Girdwood ac97_write(codec, AC97_LINE1_LEVEL, reg); 76283ac08c0SLiam Girdwood 76383ac08c0SLiam Girdwood /* K [15:12] */ 76483ac08c0SLiam Girdwood reg = reg2 | (0x3 << 4) | ((pll_div.k >> 12) & 0xf); 76583ac08c0SLiam Girdwood ac97_write(codec, AC97_LINE1_LEVEL, reg); 76683ac08c0SLiam Girdwood 76783ac08c0SLiam Girdwood /* K [11:8] */ 76883ac08c0SLiam Girdwood reg = reg2 | (0x2 << 4) | ((pll_div.k >> 8) & 0xf); 76983ac08c0SLiam Girdwood ac97_write(codec, AC97_LINE1_LEVEL, reg); 77083ac08c0SLiam Girdwood 77183ac08c0SLiam Girdwood /* K [7:4] */ 77283ac08c0SLiam Girdwood reg = reg2 | (0x1 << 4) | ((pll_div.k >> 4) & 0xf); 77383ac08c0SLiam Girdwood ac97_write(codec, AC97_LINE1_LEVEL, reg); 77483ac08c0SLiam Girdwood 77583ac08c0SLiam Girdwood reg = reg2 | (0x0 << 4) | (pll_div.k & 0xf); /* K [3:0] */ 77683ac08c0SLiam Girdwood ac97_write(codec, AC97_LINE1_LEVEL, reg); 77783ac08c0SLiam Girdwood } 77883ac08c0SLiam Girdwood 77983ac08c0SLiam Girdwood /* turn PLL on and select as source */ 78083ac08c0SLiam Girdwood reg = ac97_read(codec, AC97_EXTENDED_MID); 78183ac08c0SLiam Girdwood ac97_write(codec, AC97_EXTENDED_MID, reg & 0xfdff); 78283ac08c0SLiam Girdwood reg = ac97_read(codec, AC97_HANDSET_RATE); 78383ac08c0SLiam Girdwood ac97_write(codec, AC97_HANDSET_RATE, reg & 0xff7f); 78483ac08c0SLiam Girdwood wm9713->pll_out = freq_out; 78583ac08c0SLiam Girdwood wm9713->pll_in = freq_in; 78683ac08c0SLiam Girdwood 78783ac08c0SLiam Girdwood /* wait 10ms AC97 link frames for the link to stabilise */ 78883ac08c0SLiam Girdwood schedule_timeout_interruptible(msecs_to_jiffies(10)); 78983ac08c0SLiam Girdwood return 0; 79083ac08c0SLiam Girdwood } 79183ac08c0SLiam Girdwood 79283ac08c0SLiam Girdwood static int wm9713_set_dai_pll(struct snd_soc_codec_dai *codec_dai, 79383ac08c0SLiam Girdwood int pll_id, unsigned int freq_in, unsigned int freq_out) 79483ac08c0SLiam Girdwood { 79583ac08c0SLiam Girdwood struct snd_soc_codec *codec = codec_dai->codec; 79683ac08c0SLiam Girdwood return wm9713_set_pll(codec, pll_id, freq_in, freq_out); 79783ac08c0SLiam Girdwood } 79883ac08c0SLiam Girdwood 79983ac08c0SLiam Girdwood /* 80083ac08c0SLiam Girdwood * Tristate the PCM DAI lines, tristate can be disabled by calling 80183ac08c0SLiam Girdwood * wm9713_set_dai_fmt() 80283ac08c0SLiam Girdwood */ 80383ac08c0SLiam Girdwood static int wm9713_set_dai_tristate(struct snd_soc_codec_dai *codec_dai, 80483ac08c0SLiam Girdwood int tristate) 80583ac08c0SLiam Girdwood { 80683ac08c0SLiam Girdwood struct snd_soc_codec *codec = codec_dai->codec; 80783ac08c0SLiam Girdwood u16 reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0x9fff; 80883ac08c0SLiam Girdwood 80983ac08c0SLiam Girdwood if (tristate) 81083ac08c0SLiam Girdwood ac97_write(codec, AC97_CENTER_LFE_MASTER, reg); 81183ac08c0SLiam Girdwood 81283ac08c0SLiam Girdwood return 0; 81383ac08c0SLiam Girdwood } 81483ac08c0SLiam Girdwood 81583ac08c0SLiam Girdwood /* 81683ac08c0SLiam Girdwood * Configure WM9713 clock dividers. 81783ac08c0SLiam Girdwood * Voice DAC needs 256 FS 81883ac08c0SLiam Girdwood */ 81983ac08c0SLiam Girdwood static int wm9713_set_dai_clkdiv(struct snd_soc_codec_dai *codec_dai, 82083ac08c0SLiam Girdwood int div_id, int div) 82183ac08c0SLiam Girdwood { 82283ac08c0SLiam Girdwood struct snd_soc_codec *codec = codec_dai->codec; 82383ac08c0SLiam Girdwood u16 reg; 82483ac08c0SLiam Girdwood 82583ac08c0SLiam Girdwood switch (div_id) { 82683ac08c0SLiam Girdwood case WM9713_PCMCLK_DIV: 82783ac08c0SLiam Girdwood reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xf0ff; 82883ac08c0SLiam Girdwood ac97_write(codec, AC97_HANDSET_RATE, reg | div); 82983ac08c0SLiam Girdwood break; 83083ac08c0SLiam Girdwood case WM9713_CLKA_MULT: 83183ac08c0SLiam Girdwood reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xfffd; 83283ac08c0SLiam Girdwood ac97_write(codec, AC97_HANDSET_RATE, reg | div); 83383ac08c0SLiam Girdwood break; 83483ac08c0SLiam Girdwood case WM9713_CLKB_MULT: 83583ac08c0SLiam Girdwood reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xfffb; 83683ac08c0SLiam Girdwood ac97_write(codec, AC97_HANDSET_RATE, reg | div); 83783ac08c0SLiam Girdwood break; 83883ac08c0SLiam Girdwood case WM9713_HIFI_DIV: 83983ac08c0SLiam Girdwood reg = ac97_read(codec, AC97_HANDSET_RATE) & 0x8fff; 84083ac08c0SLiam Girdwood ac97_write(codec, AC97_HANDSET_RATE, reg | div); 84183ac08c0SLiam Girdwood break; 84283ac08c0SLiam Girdwood case WM9713_PCMBCLK_DIV: 84383ac08c0SLiam Girdwood reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0xf1ff; 84483ac08c0SLiam Girdwood ac97_write(codec, AC97_CENTER_LFE_MASTER, reg | div); 84583ac08c0SLiam Girdwood break; 84683ac08c0SLiam Girdwood case WM9713_PCMCLK_PLL_DIV: 84783ac08c0SLiam Girdwood reg = ac97_read(codec, AC97_LINE1_LEVEL) & 0xff80; 84883ac08c0SLiam Girdwood ac97_write(codec, AC97_LINE1_LEVEL, reg | 0x60 | div); 84983ac08c0SLiam Girdwood break; 85083ac08c0SLiam Girdwood case WM9713_HIFI_PLL_DIV: 85183ac08c0SLiam Girdwood reg = ac97_read(codec, AC97_LINE1_LEVEL) & 0xff80; 85283ac08c0SLiam Girdwood ac97_write(codec, AC97_LINE1_LEVEL, reg | 0x70 | div); 85383ac08c0SLiam Girdwood break; 85483ac08c0SLiam Girdwood default: 85583ac08c0SLiam Girdwood return -EINVAL; 85683ac08c0SLiam Girdwood } 85783ac08c0SLiam Girdwood 85883ac08c0SLiam Girdwood return 0; 85983ac08c0SLiam Girdwood } 86083ac08c0SLiam Girdwood 86183ac08c0SLiam Girdwood static int wm9713_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, 86283ac08c0SLiam Girdwood unsigned int fmt) 86383ac08c0SLiam Girdwood { 86483ac08c0SLiam Girdwood struct snd_soc_codec *codec = codec_dai->codec; 86583ac08c0SLiam Girdwood u16 gpio = ac97_read(codec, AC97_GPIO_CFG) & 0xffc5; 86683ac08c0SLiam Girdwood u16 reg = 0x8000; 86783ac08c0SLiam Girdwood 86883ac08c0SLiam Girdwood /* clock masters */ 86983ac08c0SLiam Girdwood switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { 87083ac08c0SLiam Girdwood case SND_SOC_DAIFMT_CBM_CFM: 87183ac08c0SLiam Girdwood reg |= 0x4000; 87283ac08c0SLiam Girdwood gpio |= 0x0010; 87383ac08c0SLiam Girdwood break; 87483ac08c0SLiam Girdwood case SND_SOC_DAIFMT_CBM_CFS: 87583ac08c0SLiam Girdwood reg |= 0x6000; 87683ac08c0SLiam Girdwood gpio |= 0x0018; 87783ac08c0SLiam Girdwood break; 87883ac08c0SLiam Girdwood case SND_SOC_DAIFMT_CBS_CFS: 87983ac08c0SLiam Girdwood reg |= 0x0200; 88083ac08c0SLiam Girdwood gpio |= 0x001a; 88183ac08c0SLiam Girdwood break; 88283ac08c0SLiam Girdwood case SND_SOC_DAIFMT_CBS_CFM: 88383ac08c0SLiam Girdwood gpio |= 0x0012; 88483ac08c0SLiam Girdwood break; 88583ac08c0SLiam Girdwood } 88683ac08c0SLiam Girdwood 88783ac08c0SLiam Girdwood /* clock inversion */ 88883ac08c0SLiam Girdwood switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 88983ac08c0SLiam Girdwood case SND_SOC_DAIFMT_IB_IF: 89083ac08c0SLiam Girdwood reg |= 0x00c0; 89183ac08c0SLiam Girdwood break; 89283ac08c0SLiam Girdwood case SND_SOC_DAIFMT_IB_NF: 89383ac08c0SLiam Girdwood reg |= 0x0080; 89483ac08c0SLiam Girdwood break; 89583ac08c0SLiam Girdwood case SND_SOC_DAIFMT_NB_IF: 89683ac08c0SLiam Girdwood reg |= 0x0040; 89783ac08c0SLiam Girdwood break; 89883ac08c0SLiam Girdwood } 89983ac08c0SLiam Girdwood 90083ac08c0SLiam Girdwood /* DAI format */ 90183ac08c0SLiam Girdwood switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 90283ac08c0SLiam Girdwood case SND_SOC_DAIFMT_I2S: 90383ac08c0SLiam Girdwood reg |= 0x0002; 90483ac08c0SLiam Girdwood break; 90583ac08c0SLiam Girdwood case SND_SOC_DAIFMT_RIGHT_J: 90683ac08c0SLiam Girdwood break; 90783ac08c0SLiam Girdwood case SND_SOC_DAIFMT_LEFT_J: 90883ac08c0SLiam Girdwood reg |= 0x0001; 90983ac08c0SLiam Girdwood break; 91083ac08c0SLiam Girdwood case SND_SOC_DAIFMT_DSP_A: 91183ac08c0SLiam Girdwood reg |= 0x0003; 91283ac08c0SLiam Girdwood break; 91383ac08c0SLiam Girdwood case SND_SOC_DAIFMT_DSP_B: 91483ac08c0SLiam Girdwood reg |= 0x0043; 91583ac08c0SLiam Girdwood break; 91683ac08c0SLiam Girdwood } 91783ac08c0SLiam Girdwood 91883ac08c0SLiam Girdwood ac97_write(codec, AC97_GPIO_CFG, gpio); 91983ac08c0SLiam Girdwood ac97_write(codec, AC97_CENTER_LFE_MASTER, reg); 92083ac08c0SLiam Girdwood return 0; 92183ac08c0SLiam Girdwood } 92283ac08c0SLiam Girdwood 92383ac08c0SLiam Girdwood static int wm9713_pcm_hw_params(struct snd_pcm_substream *substream, 92483ac08c0SLiam Girdwood struct snd_pcm_hw_params *params) 92583ac08c0SLiam Girdwood { 92683ac08c0SLiam Girdwood struct snd_soc_pcm_runtime *rtd = substream->private_data; 92783ac08c0SLiam Girdwood struct snd_soc_device *socdev = rtd->socdev; 92883ac08c0SLiam Girdwood struct snd_soc_codec *codec = socdev->codec; 92983ac08c0SLiam Girdwood u16 reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0xfff3; 93083ac08c0SLiam Girdwood 93183ac08c0SLiam Girdwood switch (params_format(params)) { 93283ac08c0SLiam Girdwood case SNDRV_PCM_FORMAT_S16_LE: 93383ac08c0SLiam Girdwood break; 93483ac08c0SLiam Girdwood case SNDRV_PCM_FORMAT_S20_3LE: 93583ac08c0SLiam Girdwood reg |= 0x0004; 93683ac08c0SLiam Girdwood break; 93783ac08c0SLiam Girdwood case SNDRV_PCM_FORMAT_S24_LE: 93883ac08c0SLiam Girdwood reg |= 0x0008; 93983ac08c0SLiam Girdwood break; 94083ac08c0SLiam Girdwood case SNDRV_PCM_FORMAT_S32_LE: 94183ac08c0SLiam Girdwood reg |= 0x000c; 94283ac08c0SLiam Girdwood break; 94383ac08c0SLiam Girdwood } 94483ac08c0SLiam Girdwood 94583ac08c0SLiam Girdwood /* enable PCM interface in master mode */ 94683ac08c0SLiam Girdwood ac97_write(codec, AC97_CENTER_LFE_MASTER, reg); 94783ac08c0SLiam Girdwood return 0; 94883ac08c0SLiam Girdwood } 94983ac08c0SLiam Girdwood 95083ac08c0SLiam Girdwood static void wm9713_voiceshutdown(struct snd_pcm_substream *substream) 95183ac08c0SLiam Girdwood { 95283ac08c0SLiam Girdwood struct snd_soc_pcm_runtime *rtd = substream->private_data; 95383ac08c0SLiam Girdwood struct snd_soc_device *socdev = rtd->socdev; 95483ac08c0SLiam Girdwood struct snd_soc_codec *codec = socdev->codec; 95583ac08c0SLiam Girdwood u16 status; 95683ac08c0SLiam Girdwood 95783ac08c0SLiam Girdwood /* Gracefully shut down the voice interface. */ 95883ac08c0SLiam Girdwood status = ac97_read(codec, AC97_EXTENDED_STATUS) | 0x1000; 95983ac08c0SLiam Girdwood ac97_write(codec, AC97_HANDSET_RATE, 0x0280); 96083ac08c0SLiam Girdwood schedule_timeout_interruptible(msecs_to_jiffies(1)); 96183ac08c0SLiam Girdwood ac97_write(codec, AC97_HANDSET_RATE, 0x0F80); 96283ac08c0SLiam Girdwood ac97_write(codec, AC97_EXTENDED_MID, status); 96383ac08c0SLiam Girdwood } 96483ac08c0SLiam Girdwood 96583ac08c0SLiam Girdwood static int ac97_hifi_prepare(struct snd_pcm_substream *substream) 96683ac08c0SLiam Girdwood { 96783ac08c0SLiam Girdwood struct snd_pcm_runtime *runtime = substream->runtime; 96883ac08c0SLiam Girdwood struct snd_soc_pcm_runtime *rtd = substream->private_data; 96983ac08c0SLiam Girdwood struct snd_soc_device *socdev = rtd->socdev; 97083ac08c0SLiam Girdwood struct snd_soc_codec *codec = socdev->codec; 97183ac08c0SLiam Girdwood int reg; 97283ac08c0SLiam Girdwood u16 vra; 97383ac08c0SLiam Girdwood 97483ac08c0SLiam Girdwood vra = ac97_read(codec, AC97_EXTENDED_STATUS); 97583ac08c0SLiam Girdwood ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1); 97683ac08c0SLiam Girdwood 97783ac08c0SLiam Girdwood if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 97883ac08c0SLiam Girdwood reg = AC97_PCM_FRONT_DAC_RATE; 97983ac08c0SLiam Girdwood else 98083ac08c0SLiam Girdwood reg = AC97_PCM_LR_ADC_RATE; 98183ac08c0SLiam Girdwood 98283ac08c0SLiam Girdwood return ac97_write(codec, reg, runtime->rate); 98383ac08c0SLiam Girdwood } 98483ac08c0SLiam Girdwood 98583ac08c0SLiam Girdwood static int ac97_aux_prepare(struct snd_pcm_substream *substream) 98683ac08c0SLiam Girdwood { 98783ac08c0SLiam Girdwood struct snd_pcm_runtime *runtime = substream->runtime; 98883ac08c0SLiam Girdwood struct snd_soc_pcm_runtime *rtd = substream->private_data; 98983ac08c0SLiam Girdwood struct snd_soc_device *socdev = rtd->socdev; 99083ac08c0SLiam Girdwood struct snd_soc_codec *codec = socdev->codec; 99183ac08c0SLiam Girdwood u16 vra, xsle; 99283ac08c0SLiam Girdwood 99383ac08c0SLiam Girdwood vra = ac97_read(codec, AC97_EXTENDED_STATUS); 99483ac08c0SLiam Girdwood ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1); 99583ac08c0SLiam Girdwood xsle = ac97_read(codec, AC97_PCI_SID); 99683ac08c0SLiam Girdwood ac97_write(codec, AC97_PCI_SID, xsle | 0x8000); 99783ac08c0SLiam Girdwood 99883ac08c0SLiam Girdwood if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) 99983ac08c0SLiam Girdwood return -ENODEV; 100083ac08c0SLiam Girdwood 100183ac08c0SLiam Girdwood return ac97_write(codec, AC97_PCM_SURR_DAC_RATE, runtime->rate); 100283ac08c0SLiam Girdwood } 100383ac08c0SLiam Girdwood 100483ac08c0SLiam Girdwood #define WM9713_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ 100583ac08c0SLiam Girdwood SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\ 100683ac08c0SLiam Girdwood SNDRV_PCM_RATE_48000) 100783ac08c0SLiam Girdwood 100883ac08c0SLiam Girdwood #define WM9713_PCM_FORMATS \ 100983ac08c0SLiam Girdwood (SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \ 101083ac08c0SLiam Girdwood SNDRV_PCM_FORMAT_S24_LE) 101183ac08c0SLiam Girdwood 101283ac08c0SLiam Girdwood struct snd_soc_codec_dai wm9713_dai[] = { 101383ac08c0SLiam Girdwood { 101483ac08c0SLiam Girdwood .name = "AC97 HiFi", 101583ac08c0SLiam Girdwood .type = SND_SOC_DAI_AC97_BUS, 101683ac08c0SLiam Girdwood .playback = { 101783ac08c0SLiam Girdwood .stream_name = "HiFi Playback", 101883ac08c0SLiam Girdwood .channels_min = 1, 101983ac08c0SLiam Girdwood .channels_max = 2, 102083ac08c0SLiam Girdwood .rates = WM9713_RATES, 102183ac08c0SLiam Girdwood .formats = SNDRV_PCM_FMTBIT_S16_LE,}, 102283ac08c0SLiam Girdwood .capture = { 102383ac08c0SLiam Girdwood .stream_name = "HiFi Capture", 102483ac08c0SLiam Girdwood .channels_min = 1, 102583ac08c0SLiam Girdwood .channels_max = 2, 102683ac08c0SLiam Girdwood .rates = WM9713_RATES, 102783ac08c0SLiam Girdwood .formats = SNDRV_PCM_FMTBIT_S16_LE,}, 102883ac08c0SLiam Girdwood .ops = { 102983ac08c0SLiam Girdwood .prepare = ac97_hifi_prepare,}, 103083ac08c0SLiam Girdwood .dai_ops = { 103183ac08c0SLiam Girdwood .set_clkdiv = wm9713_set_dai_clkdiv, 103283ac08c0SLiam Girdwood .set_pll = wm9713_set_dai_pll,}, 103383ac08c0SLiam Girdwood }, 103483ac08c0SLiam Girdwood { 103583ac08c0SLiam Girdwood .name = "AC97 Aux", 103683ac08c0SLiam Girdwood .playback = { 103783ac08c0SLiam Girdwood .stream_name = "Aux Playback", 103883ac08c0SLiam Girdwood .channels_min = 1, 103983ac08c0SLiam Girdwood .channels_max = 1, 104083ac08c0SLiam Girdwood .rates = WM9713_RATES, 104183ac08c0SLiam Girdwood .formats = SNDRV_PCM_FMTBIT_S16_LE,}, 104283ac08c0SLiam Girdwood .ops = { 104383ac08c0SLiam Girdwood .prepare = ac97_aux_prepare,}, 104483ac08c0SLiam Girdwood .dai_ops = { 104583ac08c0SLiam Girdwood .set_clkdiv = wm9713_set_dai_clkdiv, 104683ac08c0SLiam Girdwood .set_pll = wm9713_set_dai_pll,}, 104783ac08c0SLiam Girdwood }, 104883ac08c0SLiam Girdwood { 104983ac08c0SLiam Girdwood .name = "WM9713 Voice", 105083ac08c0SLiam Girdwood .playback = { 105183ac08c0SLiam Girdwood .stream_name = "Voice Playback", 105283ac08c0SLiam Girdwood .channels_min = 1, 105383ac08c0SLiam Girdwood .channels_max = 1, 105483ac08c0SLiam Girdwood .rates = WM9713_RATES, 105583ac08c0SLiam Girdwood .formats = WM9713_PCM_FORMATS,}, 105683ac08c0SLiam Girdwood .capture = { 105783ac08c0SLiam Girdwood .stream_name = "Voice Capture", 105883ac08c0SLiam Girdwood .channels_min = 1, 105983ac08c0SLiam Girdwood .channels_max = 2, 106083ac08c0SLiam Girdwood .rates = WM9713_RATES, 106183ac08c0SLiam Girdwood .formats = WM9713_PCM_FORMATS,}, 106283ac08c0SLiam Girdwood .ops = { 106383ac08c0SLiam Girdwood .hw_params = wm9713_pcm_hw_params, 106483ac08c0SLiam Girdwood .shutdown = wm9713_voiceshutdown,}, 106583ac08c0SLiam Girdwood .dai_ops = { 106683ac08c0SLiam Girdwood .set_clkdiv = wm9713_set_dai_clkdiv, 106783ac08c0SLiam Girdwood .set_pll = wm9713_set_dai_pll, 106883ac08c0SLiam Girdwood .set_fmt = wm9713_set_dai_fmt, 106983ac08c0SLiam Girdwood .set_tristate = wm9713_set_dai_tristate, 107083ac08c0SLiam Girdwood }, 107183ac08c0SLiam Girdwood }, 107283ac08c0SLiam Girdwood }; 107383ac08c0SLiam Girdwood EXPORT_SYMBOL_GPL(wm9713_dai); 107483ac08c0SLiam Girdwood 107583ac08c0SLiam Girdwood int wm9713_reset(struct snd_soc_codec *codec, int try_warm) 107683ac08c0SLiam Girdwood { 107783ac08c0SLiam Girdwood if (try_warm && soc_ac97_ops.warm_reset) { 107883ac08c0SLiam Girdwood soc_ac97_ops.warm_reset(codec->ac97); 107983ac08c0SLiam Girdwood if (!(ac97_read(codec, 0) & 0x8000)) 108083ac08c0SLiam Girdwood return 1; 108183ac08c0SLiam Girdwood } 108283ac08c0SLiam Girdwood 108383ac08c0SLiam Girdwood soc_ac97_ops.reset(codec->ac97); 108483ac08c0SLiam Girdwood if (ac97_read(codec, 0) & 0x8000) 108583ac08c0SLiam Girdwood return -EIO; 108683ac08c0SLiam Girdwood return 0; 108783ac08c0SLiam Girdwood } 108883ac08c0SLiam Girdwood EXPORT_SYMBOL_GPL(wm9713_reset); 108983ac08c0SLiam Girdwood 10900be9898aSMark Brown static int wm9713_set_bias_level(struct snd_soc_codec *codec, 10910be9898aSMark Brown enum snd_soc_bias_level level) 109283ac08c0SLiam Girdwood { 109383ac08c0SLiam Girdwood u16 reg; 109483ac08c0SLiam Girdwood 10950be9898aSMark Brown switch (level) { 10960be9898aSMark Brown case SND_SOC_BIAS_ON: 109783ac08c0SLiam Girdwood /* enable thermal shutdown */ 109883ac08c0SLiam Girdwood reg = ac97_read(codec, AC97_EXTENDED_MID) & 0x1bff; 109983ac08c0SLiam Girdwood ac97_write(codec, AC97_EXTENDED_MID, reg); 110083ac08c0SLiam Girdwood break; 11010be9898aSMark Brown case SND_SOC_BIAS_PREPARE: 110283ac08c0SLiam Girdwood break; 11030be9898aSMark Brown case SND_SOC_BIAS_STANDBY: 110483ac08c0SLiam Girdwood /* enable master bias and vmid */ 110583ac08c0SLiam Girdwood reg = ac97_read(codec, AC97_EXTENDED_MID) & 0x3bff; 110683ac08c0SLiam Girdwood ac97_write(codec, AC97_EXTENDED_MID, reg); 110783ac08c0SLiam Girdwood ac97_write(codec, AC97_POWERDOWN, 0x0000); 110883ac08c0SLiam Girdwood break; 11090be9898aSMark Brown case SND_SOC_BIAS_OFF: 111083ac08c0SLiam Girdwood /* disable everything including AC link */ 111183ac08c0SLiam Girdwood ac97_write(codec, AC97_EXTENDED_MID, 0xffff); 111283ac08c0SLiam Girdwood ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff); 111383ac08c0SLiam Girdwood ac97_write(codec, AC97_POWERDOWN, 0xffff); 111483ac08c0SLiam Girdwood break; 111583ac08c0SLiam Girdwood } 11160be9898aSMark Brown codec->bias_level = level; 111783ac08c0SLiam Girdwood return 0; 111883ac08c0SLiam Girdwood } 111983ac08c0SLiam Girdwood 112083ac08c0SLiam Girdwood static int wm9713_soc_suspend(struct platform_device *pdev, 112183ac08c0SLiam Girdwood pm_message_t state) 112283ac08c0SLiam Girdwood { 112383ac08c0SLiam Girdwood struct snd_soc_device *socdev = platform_get_drvdata(pdev); 112483ac08c0SLiam Girdwood struct snd_soc_codec *codec = socdev->codec; 112587b57fe2SMark Brown u16 reg; 112683ac08c0SLiam Girdwood 112787b57fe2SMark Brown /* Disable everything except touchpanel - that will be handled 112887b57fe2SMark Brown * by the touch driver and left disabled if touch is not in 112987b57fe2SMark Brown * use. */ 113087b57fe2SMark Brown reg = ac97_read(codec, AC97_EXTENDED_MID); 113187b57fe2SMark Brown ac97_write(codec, AC97_EXTENDED_MID, reg | 0x7fff); 113287b57fe2SMark Brown ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff); 113387b57fe2SMark Brown ac97_write(codec, AC97_POWERDOWN, 0x6f00); 113487b57fe2SMark Brown ac97_write(codec, AC97_POWERDOWN, 0xffff); 113587b57fe2SMark Brown 113683ac08c0SLiam Girdwood return 0; 113783ac08c0SLiam Girdwood } 113883ac08c0SLiam Girdwood 113983ac08c0SLiam Girdwood static int wm9713_soc_resume(struct platform_device *pdev) 114083ac08c0SLiam Girdwood { 114183ac08c0SLiam Girdwood struct snd_soc_device *socdev = platform_get_drvdata(pdev); 114283ac08c0SLiam Girdwood struct snd_soc_codec *codec = socdev->codec; 114383ac08c0SLiam Girdwood struct wm9713_priv *wm9713 = codec->private_data; 114483ac08c0SLiam Girdwood int i, ret; 114583ac08c0SLiam Girdwood u16 *cache = codec->reg_cache; 114683ac08c0SLiam Girdwood 114783ac08c0SLiam Girdwood ret = wm9713_reset(codec, 1); 114883ac08c0SLiam Girdwood if (ret < 0) { 114983ac08c0SLiam Girdwood printk(KERN_ERR "could not reset AC97 codec\n"); 115083ac08c0SLiam Girdwood return ret; 115183ac08c0SLiam Girdwood } 115283ac08c0SLiam Girdwood 11530be9898aSMark Brown wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 115483ac08c0SLiam Girdwood 115583ac08c0SLiam Girdwood /* do we need to re-start the PLL ? */ 115683ac08c0SLiam Girdwood if (wm9713->pll_out) 115783ac08c0SLiam Girdwood wm9713_set_pll(codec, 0, wm9713->pll_in, wm9713->pll_out); 115883ac08c0SLiam Girdwood 115983ac08c0SLiam Girdwood /* only synchronise the codec if warm reset failed */ 116083ac08c0SLiam Girdwood if (ret == 0) { 116183ac08c0SLiam Girdwood for (i = 2; i < ARRAY_SIZE(wm9713_reg) << 1; i += 2) { 116283ac08c0SLiam Girdwood if (i == AC97_POWERDOWN || i == AC97_EXTENDED_MID || 116383ac08c0SLiam Girdwood i == AC97_EXTENDED_MSTATUS || i > 0x66) 116483ac08c0SLiam Girdwood continue; 116583ac08c0SLiam Girdwood soc_ac97_ops.write(codec->ac97, i, cache[i>>1]); 116683ac08c0SLiam Girdwood } 116783ac08c0SLiam Girdwood } 116883ac08c0SLiam Girdwood 11690be9898aSMark Brown if (codec->suspend_bias_level == SND_SOC_BIAS_ON) 11700be9898aSMark Brown wm9713_set_bias_level(codec, SND_SOC_BIAS_ON); 117183ac08c0SLiam Girdwood 117283ac08c0SLiam Girdwood return ret; 117383ac08c0SLiam Girdwood } 117483ac08c0SLiam Girdwood 117583ac08c0SLiam Girdwood static int wm9713_soc_probe(struct platform_device *pdev) 117683ac08c0SLiam Girdwood { 117783ac08c0SLiam Girdwood struct snd_soc_device *socdev = platform_get_drvdata(pdev); 117883ac08c0SLiam Girdwood struct snd_soc_codec *codec; 117983ac08c0SLiam Girdwood int ret = 0, reg; 118083ac08c0SLiam Girdwood 118183ac08c0SLiam Girdwood printk(KERN_INFO "WM9713/WM9714 SoC Audio Codec %s\n", WM9713_VERSION); 118283ac08c0SLiam Girdwood 118383ac08c0SLiam Girdwood socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); 118483ac08c0SLiam Girdwood if (socdev->codec == NULL) 118583ac08c0SLiam Girdwood return -ENOMEM; 118683ac08c0SLiam Girdwood codec = socdev->codec; 118783ac08c0SLiam Girdwood mutex_init(&codec->mutex); 118883ac08c0SLiam Girdwood 118983ac08c0SLiam Girdwood codec->reg_cache = kmemdup(wm9713_reg, sizeof(wm9713_reg), GFP_KERNEL); 119083ac08c0SLiam Girdwood if (codec->reg_cache == NULL) { 119183ac08c0SLiam Girdwood ret = -ENOMEM; 119283ac08c0SLiam Girdwood goto cache_err; 119383ac08c0SLiam Girdwood } 119483ac08c0SLiam Girdwood codec->reg_cache_size = sizeof(wm9713_reg); 119583ac08c0SLiam Girdwood codec->reg_cache_step = 2; 119683ac08c0SLiam Girdwood 119783ac08c0SLiam Girdwood codec->private_data = kzalloc(sizeof(struct wm9713_priv), GFP_KERNEL); 119883ac08c0SLiam Girdwood if (codec->private_data == NULL) { 119983ac08c0SLiam Girdwood ret = -ENOMEM; 120083ac08c0SLiam Girdwood goto priv_err; 120183ac08c0SLiam Girdwood } 120283ac08c0SLiam Girdwood 120383ac08c0SLiam Girdwood codec->name = "WM9713"; 120483ac08c0SLiam Girdwood codec->owner = THIS_MODULE; 120583ac08c0SLiam Girdwood codec->dai = wm9713_dai; 120683ac08c0SLiam Girdwood codec->num_dai = ARRAY_SIZE(wm9713_dai); 120783ac08c0SLiam Girdwood codec->write = ac97_write; 120883ac08c0SLiam Girdwood codec->read = ac97_read; 12090be9898aSMark Brown codec->set_bias_level = wm9713_set_bias_level; 121083ac08c0SLiam Girdwood INIT_LIST_HEAD(&codec->dapm_widgets); 121183ac08c0SLiam Girdwood INIT_LIST_HEAD(&codec->dapm_paths); 121283ac08c0SLiam Girdwood 121383ac08c0SLiam Girdwood ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0); 121483ac08c0SLiam Girdwood if (ret < 0) 121583ac08c0SLiam Girdwood goto codec_err; 121683ac08c0SLiam Girdwood 121783ac08c0SLiam Girdwood /* register pcms */ 121883ac08c0SLiam Girdwood ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); 121983ac08c0SLiam Girdwood if (ret < 0) 122083ac08c0SLiam Girdwood goto pcm_err; 122183ac08c0SLiam Girdwood 122283ac08c0SLiam Girdwood /* do a cold reset for the controller and then try 122383ac08c0SLiam Girdwood * a warm reset followed by an optional cold reset for codec */ 122483ac08c0SLiam Girdwood wm9713_reset(codec, 0); 122583ac08c0SLiam Girdwood ret = wm9713_reset(codec, 1); 122683ac08c0SLiam Girdwood if (ret < 0) { 122783ac08c0SLiam Girdwood printk(KERN_ERR "AC97 link error\n"); 122883ac08c0SLiam Girdwood goto reset_err; 122983ac08c0SLiam Girdwood } 123083ac08c0SLiam Girdwood 12310be9898aSMark Brown wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 123283ac08c0SLiam Girdwood 123383ac08c0SLiam Girdwood /* unmute the adc - move to kcontrol */ 123483ac08c0SLiam Girdwood reg = ac97_read(codec, AC97_CD) & 0x7fff; 123583ac08c0SLiam Girdwood ac97_write(codec, AC97_CD, reg); 123683ac08c0SLiam Girdwood 123783ac08c0SLiam Girdwood wm9713_add_controls(codec); 123883ac08c0SLiam Girdwood wm9713_add_widgets(codec); 123983ac08c0SLiam Girdwood ret = snd_soc_register_card(socdev); 124083ac08c0SLiam Girdwood if (ret < 0) 124183ac08c0SLiam Girdwood goto reset_err; 124283ac08c0SLiam Girdwood return 0; 124383ac08c0SLiam Girdwood 124483ac08c0SLiam Girdwood reset_err: 124583ac08c0SLiam Girdwood snd_soc_free_pcms(socdev); 124683ac08c0SLiam Girdwood 124783ac08c0SLiam Girdwood pcm_err: 124883ac08c0SLiam Girdwood snd_soc_free_ac97_codec(codec); 124983ac08c0SLiam Girdwood 125083ac08c0SLiam Girdwood codec_err: 125183ac08c0SLiam Girdwood kfree(codec->private_data); 125283ac08c0SLiam Girdwood 125383ac08c0SLiam Girdwood priv_err: 125483ac08c0SLiam Girdwood kfree(codec->reg_cache); 125583ac08c0SLiam Girdwood 125683ac08c0SLiam Girdwood cache_err: 125783ac08c0SLiam Girdwood kfree(socdev->codec); 125883ac08c0SLiam Girdwood socdev->codec = NULL; 125983ac08c0SLiam Girdwood return ret; 126083ac08c0SLiam Girdwood } 126183ac08c0SLiam Girdwood 126283ac08c0SLiam Girdwood static int wm9713_soc_remove(struct platform_device *pdev) 126383ac08c0SLiam Girdwood { 126483ac08c0SLiam Girdwood struct snd_soc_device *socdev = platform_get_drvdata(pdev); 126583ac08c0SLiam Girdwood struct snd_soc_codec *codec = socdev->codec; 126683ac08c0SLiam Girdwood 126783ac08c0SLiam Girdwood if (codec == NULL) 126883ac08c0SLiam Girdwood return 0; 126983ac08c0SLiam Girdwood 127083ac08c0SLiam Girdwood snd_soc_dapm_free(socdev); 127183ac08c0SLiam Girdwood snd_soc_free_pcms(socdev); 127283ac08c0SLiam Girdwood snd_soc_free_ac97_codec(codec); 127383ac08c0SLiam Girdwood kfree(codec->private_data); 127483ac08c0SLiam Girdwood kfree(codec->reg_cache); 127583ac08c0SLiam Girdwood kfree(codec->dai); 127683ac08c0SLiam Girdwood kfree(codec); 127783ac08c0SLiam Girdwood return 0; 127883ac08c0SLiam Girdwood } 127983ac08c0SLiam Girdwood 128083ac08c0SLiam Girdwood struct snd_soc_codec_device soc_codec_dev_wm9713 = { 128183ac08c0SLiam Girdwood .probe = wm9713_soc_probe, 128283ac08c0SLiam Girdwood .remove = wm9713_soc_remove, 128383ac08c0SLiam Girdwood .suspend = wm9713_soc_suspend, 128483ac08c0SLiam Girdwood .resume = wm9713_soc_resume, 128583ac08c0SLiam Girdwood }; 128683ac08c0SLiam Girdwood EXPORT_SYMBOL_GPL(soc_codec_dev_wm9713); 128783ac08c0SLiam Girdwood 128883ac08c0SLiam Girdwood MODULE_DESCRIPTION("ASoC WM9713/WM9714 driver"); 128983ac08c0SLiam Girdwood MODULE_AUTHOR("Liam Girdwood"); 129083ac08c0SLiam Girdwood MODULE_LICENSE("GPL"); 1291