diff mbox

[2/2] ASoC: wm9712/wm9713: Replace virtual registers with custom put/get callbacks

Message ID 1415039583-4858-3-git-send-email-lars@metafoo.de (mailing list archive)
State Accepted
Commit cf1f2ebe8d6176de80ef9d9c979f998ec38fb265
Headers show

Commit Message

Lars-Peter Clausen Nov. 3, 2014, 6:33 p.m. UTC
The wm9712/wm9713 has separate mixers for the left and the right channel,
but the inputs to the mixers are enabled/disabled by the same control.
Currently this is implemented by the driver by registering two virtual
controls for each physical control, one for the left mixer and one for the
right mixer.

Using virtual registers will no longer work when the driver has
been converted to regmap. This patch converts the driver to use controls
with custom put/get callbacks instead which implement the logic making sure
that the physical control is unmuted when either the left or the right
control is unmuted.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
---
 sound/soc/codecs/wm9712.c | 163 +++++++++++++++++++++++++++++-----------------
 sound/soc/codecs/wm9713.c | 153 +++++++++++++++++++++++++------------------
 2 files changed, 194 insertions(+), 122 deletions(-)

Comments

Lars-Peter Clausen Nov. 7, 2014, 9:52 a.m. UTC | #1
On 11/03/2014 07:33 PM, Lars-Peter Clausen wrote:
> The wm9712/wm9713 has separate mixers for the left and the right channel,
> but the inputs to the mixers are enabled/disabled by the same control.
> Currently this is implemented by the driver by registering two virtual
> controls for each physical control, one for the left mixer and one for the
> right mixer.
>
> Using virtual registers will no longer work when the driver has
> been converted to regmap. This patch converts the driver to use controls
> with custom put/get callbacks instead which implement the logic making sure
> that the physical control is unmuted when either the left or the right
> control is unmuted.
>
> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>

Hi Charles,

Given your usual fast responses on patches I'm suspecting that this one may 
have been lost. If not I apologize for the noise.

Thanks,
- Lars


> ---
>   sound/soc/codecs/wm9712.c | 163 +++++++++++++++++++++++++++++-----------------
>   sound/soc/codecs/wm9713.c | 153 +++++++++++++++++++++++++------------------
>   2 files changed, 194 insertions(+), 122 deletions(-)
>
> diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c
> index f3aab6e..3fad37e 100644
> --- a/sound/soc/codecs/wm9712.c
> +++ b/sound/soc/codecs/wm9712.c
> @@ -23,6 +23,11 @@
>   #include <sound/tlv.h>
>   #include "wm9712.h"
>
> +struct wm9712_priv {
> +	unsigned int hp_mixer[2];
> +	struct mutex lock;
> +};
> +
>   static unsigned int ac97_read(struct snd_soc_codec *codec,
>   	unsigned int reg);
>   static int ac97_write(struct snd_soc_codec *codec,
> @@ -48,12 +53,10 @@ static const u16 wm9712_reg[] = {
>   	0x0000, 0x0000, 0x0000, 0x0000, /* 6e */
>   	0x0000, 0x0000, 0x0000, 0x0006, /* 76 */
>   	0x0001, 0x0000, 0x574d, 0x4c12, /* 7e */
> -	0x0000, 0x0000 /* virtual hp mixers */
>   };
>
> -/* virtual HP mixers regs */
> -#define HPL_MIXER	0x80
> -#define HPR_MIXER	0x82
> +#define HPL_MIXER	0x0
> +#define HPR_MIXER	0x1
>
>   static const char *wm9712_alc_select[] = {"None", "Left", "Right", "Stereo"};
>   static const char *wm9712_alc_mux[] = {"Stereo", "Left", "Right", "None"};
> @@ -157,75 +160,108 @@ SOC_SINGLE_TLV("Mic 2 Volume", AC97_MIC, 0, 31, 1, main_tlv),
>   SOC_SINGLE_TLV("Mic Boost Volume", AC97_MIC, 7, 1, 0, boost_tlv),
>   };
>
> +static const unsigned int wm9712_mixer_mute_regs[] = {
> +	AC97_VIDEO,
> +	AC97_PCM,
> +	AC97_LINE,
> +	AC97_PHONE,
> +	AC97_CD,
> +	AC97_PC_BEEP,
> +};
> +
>   /* We have to create a fake left and right HP mixers because
>    * the codec only has a single control that is shared by both channels.
>    * This makes it impossible to determine the audio path.
>    */
> -static int mixer_event(struct snd_soc_dapm_widget *w,
> -	struct snd_kcontrol *k, int event)
> +static int wm9712_hp_mixer_put(struct snd_kcontrol *kcontrol,
> +	struct snd_ctl_elem_value *ucontrol)
>   {
> -	u16 l, r, beep, line, phone, mic, pcm, aux;
> -
> -	l = ac97_read(w->codec, HPL_MIXER);
> -	r = ac97_read(w->codec, HPR_MIXER);
> -	beep = ac97_read(w->codec, AC97_PC_BEEP);
> -	mic = ac97_read(w->codec, AC97_VIDEO);
> -	phone = ac97_read(w->codec, AC97_PHONE);
> -	line = ac97_read(w->codec, AC97_LINE);
> -	pcm = ac97_read(w->codec, AC97_PCM);
> -	aux = ac97_read(w->codec, AC97_CD);
> -
> -	if (l & 0x1 || r & 0x1)
> -		ac97_write(w->codec, AC97_VIDEO, mic & 0x7fff);
> +	struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);
> +	struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm);
> +	struct wm9712_priv *wm9712 = snd_soc_codec_get_drvdata(codec);
> +	unsigned int val = ucontrol->value.enumerated.item[0];
> +	struct soc_mixer_control *mc =
> +		(struct soc_mixer_control *)kcontrol->private_value;
> +	unsigned int mixer, mask, shift, old;
> +	struct snd_soc_dapm_update update;
> +	bool change;
> +
> +	mixer = mc->shift >> 8;
> +	shift = mc->shift & 0xff;
> +	mask = 1 << shift;
> +
> +	mutex_lock(&wm9712->lock);
> +	old = wm9712->hp_mixer[mixer];
> +	if (ucontrol->value.enumerated.item[0])
> +		wm9712->hp_mixer[mixer] |= mask;
>   	else
> -		ac97_write(w->codec, AC97_VIDEO, mic | 0x8000);
> +		wm9712->hp_mixer[mixer] &= ~mask;
> +
> +	change = old != wm9712->hp_mixer[mixer];
> +	if (change) {
> +		update.kcontrol = kcontrol;
> +		update.reg = wm9712_mixer_mute_regs[shift];
> +		update.mask = 0x8000;
> +		if ((wm9712->hp_mixer[0] & mask) ||
> +		    (wm9712->hp_mixer[1] & mask))
> +			update.val = 0x0;
> +		else
> +			update.val = 0x8000;
> +
> +		snd_soc_dapm_mixer_update_power(dapm, kcontrol, val,
> +			&update);
> +	}
>
> -	if (l & 0x2 || r & 0x2)
> -		ac97_write(w->codec, AC97_PCM, pcm & 0x7fff);
> -	else
> -		ac97_write(w->codec, AC97_PCM, pcm | 0x8000);
> +	mutex_unlock(&wm9712->lock);
>
> -	if (l & 0x4 || r & 0x4)
> -		ac97_write(w->codec, AC97_LINE, line & 0x7fff);
> -	else
> -		ac97_write(w->codec, AC97_LINE, line | 0x8000);
> +	return change;
> +}
>
> -	if (l & 0x8 || r & 0x8)
> -		ac97_write(w->codec, AC97_PHONE, phone & 0x7fff);
> -	else
> -		ac97_write(w->codec, AC97_PHONE, phone | 0x8000);
> +static int wm9712_hp_mixer_get(struct snd_kcontrol *kcontrol,
> +	struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);
> +	struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm);
> +	struct wm9712_priv *wm9712 = snd_soc_codec_get_drvdata(codec);
> +	struct soc_mixer_control *mc =
> +		(struct soc_mixer_control *)kcontrol->private_value;
> +	unsigned int shift, mixer;
>
> -	if (l & 0x10 || r & 0x10)
> -		ac97_write(w->codec, AC97_CD, aux & 0x7fff);
> -	else
> -		ac97_write(w->codec, AC97_CD, aux | 0x8000);
> +	mixer = mc->shift >> 8;
> +	shift = mc->shift & 0xff;
>
> -	if (l & 0x20 || r & 0x20)
> -		ac97_write(w->codec, AC97_PC_BEEP, beep & 0x7fff);
> -	else
> -		ac97_write(w->codec, AC97_PC_BEEP, beep | 0x8000);
> +	ucontrol->value.enumerated.item[0] =
> +		(wm9712->hp_mixer[mixer] >> shift) & 1;
>
>   	return 0;
>   }
>
> +#define WM9712_HP_MIXER_CTRL(xname, xmixer, xshift) { \
> +	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
> +	.info = snd_soc_info_volsw, \
> +	.get = wm9712_hp_mixer_get, .put = wm9712_hp_mixer_put, \
> +	.private_value = SOC_SINGLE_VALUE(SND_SOC_NOPM, \
> +		(xmixer << 8) | xshift, 1, 0, 0) \
> +}
> +
>   /* Left Headphone Mixers */
>   static const struct snd_kcontrol_new wm9712_hpl_mixer_controls[] = {
> -	SOC_DAPM_SINGLE("PCBeep Bypass Switch", HPL_MIXER, 5, 1, 0),
> -	SOC_DAPM_SINGLE("Aux Playback Switch", HPL_MIXER, 4, 1, 0),
> -	SOC_DAPM_SINGLE("Phone Bypass Switch", HPL_MIXER, 3, 1, 0),
> -	SOC_DAPM_SINGLE("Line Bypass Switch", HPL_MIXER, 2, 1, 0),
> -	SOC_DAPM_SINGLE("PCM Playback Switch", HPL_MIXER, 1, 1, 0),
> -	SOC_DAPM_SINGLE("Mic Sidetone Switch", HPL_MIXER, 0, 1, 0),
> +	WM9712_HP_MIXER_CTRL("PCBeep Bypass Switch", HPL_MIXER, 5),
> +	WM9712_HP_MIXER_CTRL("Aux Playback Switch", HPL_MIXER, 4),
> +	WM9712_HP_MIXER_CTRL("Phone Bypass Switch", HPL_MIXER, 3),
> +	WM9712_HP_MIXER_CTRL("Line Bypass Switch", HPL_MIXER, 2),
> +	WM9712_HP_MIXER_CTRL("PCM Playback Switch", HPL_MIXER, 1),
> +	WM9712_HP_MIXER_CTRL("Mic Sidetone Switch", HPL_MIXER, 0),
>   };
>
>   /* Right Headphone Mixers */
>   static const struct snd_kcontrol_new wm9712_hpr_mixer_controls[] = {
> -	SOC_DAPM_SINGLE("PCBeep Bypass Switch", HPR_MIXER, 5, 1, 0),
> -	SOC_DAPM_SINGLE("Aux Playback Switch", HPR_MIXER, 4, 1, 0),
> -	SOC_DAPM_SINGLE("Phone Bypass Switch", HPR_MIXER, 3, 1, 0),
> -	SOC_DAPM_SINGLE("Line Bypass Switch", HPR_MIXER, 2, 1, 0),
> -	SOC_DAPM_SINGLE("PCM Playback Switch", HPR_MIXER, 1, 1, 0),
> -	SOC_DAPM_SINGLE("Mic Sidetone Switch", HPR_MIXER, 0, 1, 0),
> +	WM9712_HP_MIXER_CTRL("PCBeep Bypass Switch", HPR_MIXER, 5),
> +	WM9712_HP_MIXER_CTRL("Aux Playback Switch", HPR_MIXER, 4),
> +	WM9712_HP_MIXER_CTRL("Phone Bypass Switch", HPR_MIXER, 3),
> +	WM9712_HP_MIXER_CTRL("Line Bypass Switch", HPR_MIXER, 2),
> +	WM9712_HP_MIXER_CTRL("PCM Playback Switch", HPR_MIXER, 1),
> +	WM9712_HP_MIXER_CTRL("Mic Sidetone Switch", HPR_MIXER, 0),
>   };
>
>   /* Speaker Mixer */
> @@ -299,12 +335,10 @@ SND_SOC_DAPM_MUX("Right Mic Select Source", SND_SOC_NOPM, 0, 0,
>   SND_SOC_DAPM_MUX("Differential Source", SND_SOC_NOPM, 0, 0,
>   	&wm9712_diff_sel_controls),
>   SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
> -SND_SOC_DAPM_MIXER_E("Left HP Mixer", AC97_INT_PAGING, 9, 1,
> -	&wm9712_hpl_mixer_controls[0], ARRAY_SIZE(wm9712_hpl_mixer_controls),
> -	mixer_event, SND_SOC_DAPM_POST_REG),
> -SND_SOC_DAPM_MIXER_E("Right HP Mixer", AC97_INT_PAGING, 8, 1,
> -	&wm9712_hpr_mixer_controls[0], ARRAY_SIZE(wm9712_hpr_mixer_controls),
> -	 mixer_event, SND_SOC_DAPM_POST_REG),
> +SND_SOC_DAPM_MIXER("Left HP Mixer", AC97_INT_PAGING, 9, 1,
> +	&wm9712_hpl_mixer_controls[0], ARRAY_SIZE(wm9712_hpl_mixer_controls)),
> +SND_SOC_DAPM_MIXER("Right HP Mixer", AC97_INT_PAGING, 8, 1,
> +	&wm9712_hpr_mixer_controls[0], ARRAY_SIZE(wm9712_hpr_mixer_controls)),
>   SND_SOC_DAPM_MIXER("Phone Mixer", AC97_INT_PAGING, 6, 1,
>   	&wm9712_phone_mixer_controls[0], ARRAY_SIZE(wm9712_phone_mixer_controls)),
>   SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_INT_PAGING, 7, 1,
> @@ -471,8 +505,7 @@ static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
>   {
>   	u16 *cache = codec->reg_cache;
>
> -	if (reg < 0x7c)
> -		soc_ac97_ops->write(codec->ac97, reg, val);
> +	soc_ac97_ops->write(codec->ac97, reg, val);
>   	reg = reg >> 1;
>   	if (reg < (ARRAY_SIZE(wm9712_reg)))
>   		cache[reg] = val;
> @@ -684,6 +717,16 @@ static struct snd_soc_codec_driver soc_codec_dev_wm9712 = {
>
>   static int wm9712_probe(struct platform_device *pdev)
>   {
> +	struct wm9712_priv *wm9712;
> +
> +	wm9712 = devm_kzalloc(&pdev->dev, sizeof(*wm9712), GFP_KERNEL);
> +	if (wm9712 == NULL)
> +		return -ENOMEM;
> +
> +	mutex_init(&wm9712->lock);
> +
> +	platform_set_drvdata(pdev, wm9712);
> +
>   	return snd_soc_register_codec(&pdev->dev,
>   			&soc_codec_dev_wm9712, wm9712_dai, ARRAY_SIZE(wm9712_dai));
>   }
> diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c
> index ac13fc8..998e4c7 100644
> --- a/sound/soc/codecs/wm9713.c
> +++ b/sound/soc/codecs/wm9713.c
> @@ -31,6 +31,8 @@
>
>   struct wm9713_priv {
>   	u32 pll_in; /* PLL input frequency */
> +	unsigned int hp_mixer[2];
> +	struct mutex lock;
>   };
>
>   static unsigned int ac97_read(struct snd_soc_codec *codec,
> @@ -59,12 +61,10 @@ static const u16 wm9713_reg[] = {
>   	0x0000, 0x0000, 0x0000, 0x0000,
>   	0x0000, 0x0000, 0x0000, 0x0006,
>   	0x0001, 0x0000, 0x574d, 0x4c13,
> -	0x0000, 0x0000
>   };
>
> -/* virtual HP mixers regs */
> -#define HPL_MIXER	0x80
> -#define HPR_MIXER	0x82
> +#define HPL_MIXER 0
> +#define HPR_MIXER 1
>
>   static const char *wm9713_mic_mixer[] = {"Stereo", "Mic 1", "Mic 2", "Mute"};
>   static const char *wm9713_rec_mux[] = {"Stereo", "Left", "Right", "Mute"};
> @@ -233,6 +233,14 @@ static int wm9713_voice_shutdown(struct snd_soc_dapm_widget *w,
>   	return 0;
>   }
>
> +static const unsigned int wm9713_mixer_mute_regs[] = {
> +	AC97_PC_BEEP,
> +	AC97_MASTER_TONE,
> +	AC97_PHONE,
> +	AC97_REC_SEL,
> +	AC97_PCM,
> +	AC97_AUX,
> +};
>
>   /* We have to create a fake left and right HP mixers because
>    * the codec only has a single control that is shared by both channels.
> @@ -240,73 +248,95 @@ static int wm9713_voice_shutdown(struct snd_soc_dapm_widget *w,
>    * register map, thus we add a new (virtual) register to help determine the
>    * audio route within the device.
>    */
> -static int mixer_event(struct snd_soc_dapm_widget *w,
> -	struct snd_kcontrol *kcontrol, int event)
> +static int wm9713_hp_mixer_put(struct snd_kcontrol *kcontrol,
> +	struct snd_ctl_elem_value *ucontrol)
>   {
> -	u16 l, r, beep, tone, phone, rec, pcm, aux;
> -
> -	l = ac97_read(w->codec, HPL_MIXER);
> -	r = ac97_read(w->codec, HPR_MIXER);
> -	beep = ac97_read(w->codec, AC97_PC_BEEP);
> -	tone = ac97_read(w->codec, AC97_MASTER_TONE);
> -	phone = ac97_read(w->codec, AC97_PHONE);
> -	rec = ac97_read(w->codec, AC97_REC_SEL);
> -	pcm = ac97_read(w->codec, AC97_PCM);
> -	aux = ac97_read(w->codec, AC97_AUX);
> -
> -	if (event & SND_SOC_DAPM_PRE_REG)
> -		return 0;
> -	if ((l & 0x1) || (r & 0x1))
> -		ac97_write(w->codec, AC97_PC_BEEP, beep & 0x7fff);
> +	struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);
> +	struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm);
> +	struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec);
> +	unsigned int val = ucontrol->value.enumerated.item[0];
> +	struct soc_mixer_control *mc =
> +		(struct soc_mixer_control *)kcontrol->private_value;
> +	unsigned int mixer, mask, shift, old;
> +	struct snd_soc_dapm_update update;
> +	bool change;
> +
> +	mixer = mc->shift >> 8;
> +	shift = mc->shift & 0xff;
> +	mask = (1 << shift);
> +
> +	mutex_lock(&wm9713->lock);
> +	old = wm9713->hp_mixer[mixer];
> +	if (ucontrol->value.enumerated.item[0])
> +		wm9713->hp_mixer[mixer] |= mask;
>   	else
> -		ac97_write(w->codec, AC97_PC_BEEP, beep | 0x8000);
> +		wm9713->hp_mixer[mixer] &= ~mask;
> +
> +	change = old != wm9713->hp_mixer[mixer];
> +	if (change) {
> +		update.kcontrol = kcontrol;
> +		update.reg = wm9713_mixer_mute_regs[shift];
> +		update.mask = 0x8000;
> +		if ((wm9713->hp_mixer[0] & mask) ||
> +		    (wm9713->hp_mixer[1] & mask))
> +			update.val = 0x0;
> +		else
> +			update.val = 0x8000;
> +
> +		snd_soc_dapm_mixer_update_power(dapm, kcontrol, val,
> +			&update);
> +	}
>
> -	if ((l & 0x2) || (r & 0x2))
> -		ac97_write(w->codec, AC97_MASTER_TONE, tone & 0x7fff);
> -	else
> -		ac97_write(w->codec, AC97_MASTER_TONE, tone | 0x8000);
> +	mutex_unlock(&wm9713->lock);
>
> -	if ((l & 0x4) || (r & 0x4))
> -		ac97_write(w->codec, AC97_PHONE, phone & 0x7fff);
> -	else
> -		ac97_write(w->codec, AC97_PHONE, phone | 0x8000);
> +	return change;
> +}
>
> -	if ((l & 0x8) || (r & 0x8))
> -		ac97_write(w->codec, AC97_REC_SEL, rec & 0x7fff);
> -	else
> -		ac97_write(w->codec, AC97_REC_SEL, rec | 0x8000);
> +static int wm9713_hp_mixer_get(struct snd_kcontrol *kcontrol,
> +	struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);
> +	struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm);
> +	struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec);
> +	struct soc_mixer_control *mc =
> +		(struct soc_mixer_control *)kcontrol->private_value;
> +	unsigned int mixer, shift;
>
> -	if ((l & 0x10) || (r & 0x10))
> -		ac97_write(w->codec, AC97_PCM, pcm & 0x7fff);
> -	else
> -		ac97_write(w->codec, AC97_PCM, pcm | 0x8000);
> +	mixer = mc->shift >> 8;
> +	shift = mc->shift & 0xff;
>
> -	if ((l & 0x20) || (r & 0x20))
> -		ac97_write(w->codec, AC97_AUX, aux & 0x7fff);
> -	else
> -		ac97_write(w->codec, AC97_AUX, aux | 0x8000);
> +	ucontrol->value.enumerated.item[0] =
> +		(wm9713->hp_mixer[mixer] >> shift) & 1;
>
>   	return 0;
>   }
>
> +#define WM9713_HP_MIXER_CTRL(xname, xmixer, xshift) { \
> +	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
> +	.info = snd_soc_info_volsw, \
> +	.get = wm9713_hp_mixer_get, .put = wm9713_hp_mixer_put, \
> +	.private_value = SOC_DOUBLE_VALUE(SND_SOC_NOPM, \
> +		xshift, xmixer, 1, 0, 0) \
> +}
> +
>   /* Left Headphone Mixers */
>   static const struct snd_kcontrol_new wm9713_hpl_mixer_controls[] = {
> -SOC_DAPM_SINGLE("Beep Playback Switch", HPL_MIXER, 5, 1, 0),
> -SOC_DAPM_SINGLE("Voice Playback Switch", HPL_MIXER, 4, 1, 0),
> -SOC_DAPM_SINGLE("Aux Playback Switch", HPL_MIXER, 3, 1, 0),
> -SOC_DAPM_SINGLE("PCM Playback Switch", HPL_MIXER, 2, 1, 0),
> -SOC_DAPM_SINGLE("MonoIn Playback Switch", HPL_MIXER, 1, 1, 0),
> -SOC_DAPM_SINGLE("Bypass Playback Switch", HPL_MIXER, 0, 1, 0),
> +WM9713_HP_MIXER_CTRL("Beep Playback Switch", HPL_MIXER, 5),
> +WM9713_HP_MIXER_CTRL("Voice Playback Switch", HPL_MIXER, 4),
> +WM9713_HP_MIXER_CTRL("Aux Playback Switch", HPL_MIXER, 3),
> +WM9713_HP_MIXER_CTRL("PCM Playback Switch", HPL_MIXER, 2),
> +WM9713_HP_MIXER_CTRL("MonoIn Playback Switch", HPL_MIXER, 1),
> +WM9713_HP_MIXER_CTRL("Bypass Playback Switch", HPL_MIXER, 0),
>   };
>
>   /* Right Headphone Mixers */
>   static const struct snd_kcontrol_new wm9713_hpr_mixer_controls[] = {
> -SOC_DAPM_SINGLE("Beep Playback Switch", HPR_MIXER, 5, 1, 0),
> -SOC_DAPM_SINGLE("Voice Playback Switch", HPR_MIXER, 4, 1, 0),
> -SOC_DAPM_SINGLE("Aux Playback Switch", HPR_MIXER, 3, 1, 0),
> -SOC_DAPM_SINGLE("PCM Playback Switch", HPR_MIXER, 2, 1, 0),
> -SOC_DAPM_SINGLE("MonoIn Playback Switch", HPR_MIXER, 1, 1, 0),
> -SOC_DAPM_SINGLE("Bypass Playback Switch", HPR_MIXER, 0, 1, 0),
> +WM9713_HP_MIXER_CTRL("Beep Playback Switch", HPR_MIXER, 5),
> +WM9713_HP_MIXER_CTRL("Voice Playback Switch", HPR_MIXER, 4),
> +WM9713_HP_MIXER_CTRL("Aux Playback Switch", HPR_MIXER, 3),
> +WM9713_HP_MIXER_CTRL("PCM Playback Switch", HPR_MIXER, 2),
> +WM9713_HP_MIXER_CTRL("MonoIn Playback Switch", HPR_MIXER, 1),
> +WM9713_HP_MIXER_CTRL("Bypass Playback Switch", HPR_MIXER, 0),
>   };
>
>   /* headphone capture mux */
> @@ -428,12 +458,10 @@ SND_SOC_DAPM_MUX("Mic A Source", SND_SOC_NOPM, 0, 0,
>   	&wm9713_mic_sel_mux_controls),
>   SND_SOC_DAPM_MUX("Mic B Source", SND_SOC_NOPM, 0, 0,
>   	&wm9713_micb_sel_mux_controls),
> -SND_SOC_DAPM_MIXER_E("Left HP Mixer", AC97_EXTENDED_MID, 3, 1,
> -	&wm9713_hpl_mixer_controls[0], ARRAY_SIZE(wm9713_hpl_mixer_controls),
> -	mixer_event, SND_SOC_DAPM_POST_REG),
> -SND_SOC_DAPM_MIXER_E("Right HP Mixer", AC97_EXTENDED_MID, 2, 1,
> -	&wm9713_hpr_mixer_controls[0], ARRAY_SIZE(wm9713_hpr_mixer_controls),
> -	mixer_event, SND_SOC_DAPM_POST_REG),
> +SND_SOC_DAPM_MIXER("Left HP Mixer", AC97_EXTENDED_MID, 3, 1,
> +	&wm9713_hpl_mixer_controls[0], ARRAY_SIZE(wm9713_hpl_mixer_controls)),
> +SND_SOC_DAPM_MIXER("Right HP Mixer", AC97_EXTENDED_MID, 2, 1,
> +	&wm9713_hpr_mixer_controls[0], ARRAY_SIZE(wm9713_hpr_mixer_controls)),
>   SND_SOC_DAPM_MIXER("Mono Mixer", AC97_EXTENDED_MID, 0, 1,
>   	&wm9713_mono_mixer_controls[0], ARRAY_SIZE(wm9713_mono_mixer_controls)),
>   SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_EXTENDED_MID, 1, 1,
> @@ -666,8 +694,7 @@ static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
>   	unsigned int val)
>   {
>   	u16 *cache = codec->reg_cache;
> -	if (reg < 0x7c)
> -		soc_ac97_ops->write(codec->ac97, reg, val);
> +	soc_ac97_ops->write(codec->ac97, reg, val);
>   	reg = reg >> 1;
>   	if (reg < (ARRAY_SIZE(wm9713_reg)))
>   		cache[reg] = val;
> @@ -1251,6 +1278,8 @@ static int wm9713_probe(struct platform_device *pdev)
>   	if (wm9713 == NULL)
>   		return -ENOMEM;
>
> +	mutex_init(&wm9713->lock);
> +
>   	platform_set_drvdata(pdev, wm9713);
>
>   	return snd_soc_register_codec(&pdev->dev,
>
Mark Brown Nov. 8, 2014, 9:45 a.m. UTC | #2
On Mon, Nov 03, 2014 at 07:33:03PM +0100, Lars-Peter Clausen wrote:
> The wm9712/wm9713 has separate mixers for the left and the right channel,
> but the inputs to the mixers are enabled/disabled by the same control.
> Currently this is implemented by the driver by registering two virtual
> controls for each physical control, one for the left mixer and one for the
> right mixer.

Applied, thanks.  One patch per driver please unless there's a reason -
I usually have a branch per driver, there was no branch this applied to.
Charles Keepax Nov. 8, 2014, 12:43 p.m. UTC | #3
On Fri, Nov 07, 2014 at 10:52:56AM +0100, Lars-Peter Clausen wrote:
> On 11/03/2014 07:33 PM, Lars-Peter Clausen wrote:
>> The wm9712/wm9713 has separate mixers for the left and the right channel,
>> but the inputs to the mixers are enabled/disabled by the same control.
>> Currently this is implemented by the driver by registering two virtual
>> controls for each physical control, one for the left mixer and one for the
>> right mixer.
>>
>> Using virtual registers will no longer work when the driver has
>> been converted to regmap. This patch converts the driver to use controls
>> with custom put/get callbacks instead which implement the logic making sure
>> that the physical control is unmuted when either the left or the right
>> control is unmuted.
>>
>> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
>
> Hi Charles,
>
> Given your usual fast responses on patches I'm suspecting that this one 
> may have been lost. If not I apologize for the noise.
>
> Thanks,
> - Lars

Apologies for that, it must have come in on a busy morning and
then I forgot to come back to it.

All looks good to me and seems Mark has applied it now.

Thanks,
Charles
diff mbox

Patch

diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c
index f3aab6e..3fad37e 100644
--- a/sound/soc/codecs/wm9712.c
+++ b/sound/soc/codecs/wm9712.c
@@ -23,6 +23,11 @@ 
 #include <sound/tlv.h>
 #include "wm9712.h"
 
+struct wm9712_priv {
+	unsigned int hp_mixer[2];
+	struct mutex lock;
+};
+
 static unsigned int ac97_read(struct snd_soc_codec *codec,
 	unsigned int reg);
 static int ac97_write(struct snd_soc_codec *codec,
@@ -48,12 +53,10 @@  static const u16 wm9712_reg[] = {
 	0x0000, 0x0000, 0x0000, 0x0000, /* 6e */
 	0x0000, 0x0000, 0x0000, 0x0006, /* 76 */
 	0x0001, 0x0000, 0x574d, 0x4c12, /* 7e */
-	0x0000, 0x0000 /* virtual hp mixers */
 };
 
-/* virtual HP mixers regs */
-#define HPL_MIXER	0x80
-#define HPR_MIXER	0x82
+#define HPL_MIXER	0x0
+#define HPR_MIXER	0x1
 
 static const char *wm9712_alc_select[] = {"None", "Left", "Right", "Stereo"};
 static const char *wm9712_alc_mux[] = {"Stereo", "Left", "Right", "None"};
@@ -157,75 +160,108 @@  SOC_SINGLE_TLV("Mic 2 Volume", AC97_MIC, 0, 31, 1, main_tlv),
 SOC_SINGLE_TLV("Mic Boost Volume", AC97_MIC, 7, 1, 0, boost_tlv),
 };
 
+static const unsigned int wm9712_mixer_mute_regs[] = {
+	AC97_VIDEO,
+	AC97_PCM,
+	AC97_LINE,
+	AC97_PHONE,
+	AC97_CD,
+	AC97_PC_BEEP,
+};
+
 /* We have to create a fake left and right HP mixers because
  * the codec only has a single control that is shared by both channels.
  * This makes it impossible to determine the audio path.
  */
-static int mixer_event(struct snd_soc_dapm_widget *w,
-	struct snd_kcontrol *k, int event)
+static int wm9712_hp_mixer_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
 {
-	u16 l, r, beep, line, phone, mic, pcm, aux;
-
-	l = ac97_read(w->codec, HPL_MIXER);
-	r = ac97_read(w->codec, HPR_MIXER);
-	beep = ac97_read(w->codec, AC97_PC_BEEP);
-	mic = ac97_read(w->codec, AC97_VIDEO);
-	phone = ac97_read(w->codec, AC97_PHONE);
-	line = ac97_read(w->codec, AC97_LINE);
-	pcm = ac97_read(w->codec, AC97_PCM);
-	aux = ac97_read(w->codec, AC97_CD);
-
-	if (l & 0x1 || r & 0x1)
-		ac97_write(w->codec, AC97_VIDEO, mic & 0x7fff);
+	struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);
+	struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm);
+	struct wm9712_priv *wm9712 = snd_soc_codec_get_drvdata(codec);
+	unsigned int val = ucontrol->value.enumerated.item[0];
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	unsigned int mixer, mask, shift, old;
+	struct snd_soc_dapm_update update;
+	bool change;
+
+	mixer = mc->shift >> 8;
+	shift = mc->shift & 0xff;
+	mask = 1 << shift;
+
+	mutex_lock(&wm9712->lock);
+	old = wm9712->hp_mixer[mixer];
+	if (ucontrol->value.enumerated.item[0])
+		wm9712->hp_mixer[mixer] |= mask;
 	else
-		ac97_write(w->codec, AC97_VIDEO, mic | 0x8000);
+		wm9712->hp_mixer[mixer] &= ~mask;
+
+	change = old != wm9712->hp_mixer[mixer];
+	if (change) {
+		update.kcontrol = kcontrol;
+		update.reg = wm9712_mixer_mute_regs[shift];
+		update.mask = 0x8000;
+		if ((wm9712->hp_mixer[0] & mask) ||
+		    (wm9712->hp_mixer[1] & mask))
+			update.val = 0x0;
+		else
+			update.val = 0x8000;
+
+		snd_soc_dapm_mixer_update_power(dapm, kcontrol, val,
+			&update);
+	}
 
-	if (l & 0x2 || r & 0x2)
-		ac97_write(w->codec, AC97_PCM, pcm & 0x7fff);
-	else
-		ac97_write(w->codec, AC97_PCM, pcm | 0x8000);
+	mutex_unlock(&wm9712->lock);
 
-	if (l & 0x4 || r & 0x4)
-		ac97_write(w->codec, AC97_LINE, line & 0x7fff);
-	else
-		ac97_write(w->codec, AC97_LINE, line | 0x8000);
+	return change;
+}
 
-	if (l & 0x8 || r & 0x8)
-		ac97_write(w->codec, AC97_PHONE, phone & 0x7fff);
-	else
-		ac97_write(w->codec, AC97_PHONE, phone | 0x8000);
+static int wm9712_hp_mixer_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);
+	struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm);
+	struct wm9712_priv *wm9712 = snd_soc_codec_get_drvdata(codec);
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	unsigned int shift, mixer;
 
-	if (l & 0x10 || r & 0x10)
-		ac97_write(w->codec, AC97_CD, aux & 0x7fff);
-	else
-		ac97_write(w->codec, AC97_CD, aux | 0x8000);
+	mixer = mc->shift >> 8;
+	shift = mc->shift & 0xff;
 
-	if (l & 0x20 || r & 0x20)
-		ac97_write(w->codec, AC97_PC_BEEP, beep & 0x7fff);
-	else
-		ac97_write(w->codec, AC97_PC_BEEP, beep | 0x8000);
+	ucontrol->value.enumerated.item[0] =
+		(wm9712->hp_mixer[mixer] >> shift) & 1;
 
 	return 0;
 }
 
+#define WM9712_HP_MIXER_CTRL(xname, xmixer, xshift) { \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+	.info = snd_soc_info_volsw, \
+	.get = wm9712_hp_mixer_get, .put = wm9712_hp_mixer_put, \
+	.private_value = SOC_SINGLE_VALUE(SND_SOC_NOPM, \
+		(xmixer << 8) | xshift, 1, 0, 0) \
+}
+
 /* Left Headphone Mixers */
 static const struct snd_kcontrol_new wm9712_hpl_mixer_controls[] = {
-	SOC_DAPM_SINGLE("PCBeep Bypass Switch", HPL_MIXER, 5, 1, 0),
-	SOC_DAPM_SINGLE("Aux Playback Switch", HPL_MIXER, 4, 1, 0),
-	SOC_DAPM_SINGLE("Phone Bypass Switch", HPL_MIXER, 3, 1, 0),
-	SOC_DAPM_SINGLE("Line Bypass Switch", HPL_MIXER, 2, 1, 0),
-	SOC_DAPM_SINGLE("PCM Playback Switch", HPL_MIXER, 1, 1, 0),
-	SOC_DAPM_SINGLE("Mic Sidetone Switch", HPL_MIXER, 0, 1, 0),
+	WM9712_HP_MIXER_CTRL("PCBeep Bypass Switch", HPL_MIXER, 5),
+	WM9712_HP_MIXER_CTRL("Aux Playback Switch", HPL_MIXER, 4),
+	WM9712_HP_MIXER_CTRL("Phone Bypass Switch", HPL_MIXER, 3),
+	WM9712_HP_MIXER_CTRL("Line Bypass Switch", HPL_MIXER, 2),
+	WM9712_HP_MIXER_CTRL("PCM Playback Switch", HPL_MIXER, 1),
+	WM9712_HP_MIXER_CTRL("Mic Sidetone Switch", HPL_MIXER, 0),
 };
 
 /* Right Headphone Mixers */
 static const struct snd_kcontrol_new wm9712_hpr_mixer_controls[] = {
-	SOC_DAPM_SINGLE("PCBeep Bypass Switch", HPR_MIXER, 5, 1, 0),
-	SOC_DAPM_SINGLE("Aux Playback Switch", HPR_MIXER, 4, 1, 0),
-	SOC_DAPM_SINGLE("Phone Bypass Switch", HPR_MIXER, 3, 1, 0),
-	SOC_DAPM_SINGLE("Line Bypass Switch", HPR_MIXER, 2, 1, 0),
-	SOC_DAPM_SINGLE("PCM Playback Switch", HPR_MIXER, 1, 1, 0),
-	SOC_DAPM_SINGLE("Mic Sidetone Switch", HPR_MIXER, 0, 1, 0),
+	WM9712_HP_MIXER_CTRL("PCBeep Bypass Switch", HPR_MIXER, 5),
+	WM9712_HP_MIXER_CTRL("Aux Playback Switch", HPR_MIXER, 4),
+	WM9712_HP_MIXER_CTRL("Phone Bypass Switch", HPR_MIXER, 3),
+	WM9712_HP_MIXER_CTRL("Line Bypass Switch", HPR_MIXER, 2),
+	WM9712_HP_MIXER_CTRL("PCM Playback Switch", HPR_MIXER, 1),
+	WM9712_HP_MIXER_CTRL("Mic Sidetone Switch", HPR_MIXER, 0),
 };
 
 /* Speaker Mixer */
@@ -299,12 +335,10 @@  SND_SOC_DAPM_MUX("Right Mic Select Source", SND_SOC_NOPM, 0, 0,
 SND_SOC_DAPM_MUX("Differential Source", SND_SOC_NOPM, 0, 0,
 	&wm9712_diff_sel_controls),
 SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
-SND_SOC_DAPM_MIXER_E("Left HP Mixer", AC97_INT_PAGING, 9, 1,
-	&wm9712_hpl_mixer_controls[0], ARRAY_SIZE(wm9712_hpl_mixer_controls),
-	mixer_event, SND_SOC_DAPM_POST_REG),
-SND_SOC_DAPM_MIXER_E("Right HP Mixer", AC97_INT_PAGING, 8, 1,
-	&wm9712_hpr_mixer_controls[0], ARRAY_SIZE(wm9712_hpr_mixer_controls),
-	 mixer_event, SND_SOC_DAPM_POST_REG),
+SND_SOC_DAPM_MIXER("Left HP Mixer", AC97_INT_PAGING, 9, 1,
+	&wm9712_hpl_mixer_controls[0], ARRAY_SIZE(wm9712_hpl_mixer_controls)),
+SND_SOC_DAPM_MIXER("Right HP Mixer", AC97_INT_PAGING, 8, 1,
+	&wm9712_hpr_mixer_controls[0], ARRAY_SIZE(wm9712_hpr_mixer_controls)),
 SND_SOC_DAPM_MIXER("Phone Mixer", AC97_INT_PAGING, 6, 1,
 	&wm9712_phone_mixer_controls[0], ARRAY_SIZE(wm9712_phone_mixer_controls)),
 SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_INT_PAGING, 7, 1,
@@ -471,8 +505,7 @@  static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
 {
 	u16 *cache = codec->reg_cache;
 
-	if (reg < 0x7c)
-		soc_ac97_ops->write(codec->ac97, reg, val);
+	soc_ac97_ops->write(codec->ac97, reg, val);
 	reg = reg >> 1;
 	if (reg < (ARRAY_SIZE(wm9712_reg)))
 		cache[reg] = val;
@@ -684,6 +717,16 @@  static struct snd_soc_codec_driver soc_codec_dev_wm9712 = {
 
 static int wm9712_probe(struct platform_device *pdev)
 {
+	struct wm9712_priv *wm9712;
+
+	wm9712 = devm_kzalloc(&pdev->dev, sizeof(*wm9712), GFP_KERNEL);
+	if (wm9712 == NULL)
+		return -ENOMEM;
+
+	mutex_init(&wm9712->lock);
+
+	platform_set_drvdata(pdev, wm9712);
+
 	return snd_soc_register_codec(&pdev->dev,
 			&soc_codec_dev_wm9712, wm9712_dai, ARRAY_SIZE(wm9712_dai));
 }
diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c
index ac13fc8..998e4c7 100644
--- a/sound/soc/codecs/wm9713.c
+++ b/sound/soc/codecs/wm9713.c
@@ -31,6 +31,8 @@ 
 
 struct wm9713_priv {
 	u32 pll_in; /* PLL input frequency */
+	unsigned int hp_mixer[2];
+	struct mutex lock;
 };
 
 static unsigned int ac97_read(struct snd_soc_codec *codec,
@@ -59,12 +61,10 @@  static const u16 wm9713_reg[] = {
 	0x0000, 0x0000, 0x0000, 0x0000,
 	0x0000, 0x0000, 0x0000, 0x0006,
 	0x0001, 0x0000, 0x574d, 0x4c13,
-	0x0000, 0x0000
 };
 
-/* virtual HP mixers regs */
-#define HPL_MIXER	0x80
-#define HPR_MIXER	0x82
+#define HPL_MIXER 0
+#define HPR_MIXER 1
 
 static const char *wm9713_mic_mixer[] = {"Stereo", "Mic 1", "Mic 2", "Mute"};
 static const char *wm9713_rec_mux[] = {"Stereo", "Left", "Right", "Mute"};
@@ -233,6 +233,14 @@  static int wm9713_voice_shutdown(struct snd_soc_dapm_widget *w,
 	return 0;
 }
 
+static const unsigned int wm9713_mixer_mute_regs[] = {
+	AC97_PC_BEEP,
+	AC97_MASTER_TONE,
+	AC97_PHONE,
+	AC97_REC_SEL,
+	AC97_PCM,
+	AC97_AUX,
+};
 
 /* We have to create a fake left and right HP mixers because
  * the codec only has a single control that is shared by both channels.
@@ -240,73 +248,95 @@  static int wm9713_voice_shutdown(struct snd_soc_dapm_widget *w,
  * register map, thus we add a new (virtual) register to help determine the
  * audio route within the device.
  */
-static int mixer_event(struct snd_soc_dapm_widget *w,
-	struct snd_kcontrol *kcontrol, int event)
+static int wm9713_hp_mixer_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
 {
-	u16 l, r, beep, tone, phone, rec, pcm, aux;
-
-	l = ac97_read(w->codec, HPL_MIXER);
-	r = ac97_read(w->codec, HPR_MIXER);
-	beep = ac97_read(w->codec, AC97_PC_BEEP);
-	tone = ac97_read(w->codec, AC97_MASTER_TONE);
-	phone = ac97_read(w->codec, AC97_PHONE);
-	rec = ac97_read(w->codec, AC97_REC_SEL);
-	pcm = ac97_read(w->codec, AC97_PCM);
-	aux = ac97_read(w->codec, AC97_AUX);
-
-	if (event & SND_SOC_DAPM_PRE_REG)
-		return 0;
-	if ((l & 0x1) || (r & 0x1))
-		ac97_write(w->codec, AC97_PC_BEEP, beep & 0x7fff);
+	struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);
+	struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm);
+	struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec);
+	unsigned int val = ucontrol->value.enumerated.item[0];
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	unsigned int mixer, mask, shift, old;
+	struct snd_soc_dapm_update update;
+	bool change;
+
+	mixer = mc->shift >> 8;
+	shift = mc->shift & 0xff;
+	mask = (1 << shift);
+
+	mutex_lock(&wm9713->lock);
+	old = wm9713->hp_mixer[mixer];
+	if (ucontrol->value.enumerated.item[0])
+		wm9713->hp_mixer[mixer] |= mask;
 	else
-		ac97_write(w->codec, AC97_PC_BEEP, beep | 0x8000);
+		wm9713->hp_mixer[mixer] &= ~mask;
+
+	change = old != wm9713->hp_mixer[mixer];
+	if (change) {
+		update.kcontrol = kcontrol;
+		update.reg = wm9713_mixer_mute_regs[shift];
+		update.mask = 0x8000;
+		if ((wm9713->hp_mixer[0] & mask) ||
+		    (wm9713->hp_mixer[1] & mask))
+			update.val = 0x0;
+		else
+			update.val = 0x8000;
+
+		snd_soc_dapm_mixer_update_power(dapm, kcontrol, val,
+			&update);
+	}
 
-	if ((l & 0x2) || (r & 0x2))
-		ac97_write(w->codec, AC97_MASTER_TONE, tone & 0x7fff);
-	else
-		ac97_write(w->codec, AC97_MASTER_TONE, tone | 0x8000);
+	mutex_unlock(&wm9713->lock);
 
-	if ((l & 0x4) || (r & 0x4))
-		ac97_write(w->codec, AC97_PHONE, phone & 0x7fff);
-	else
-		ac97_write(w->codec, AC97_PHONE, phone | 0x8000);
+	return change;
+}
 
-	if ((l & 0x8) || (r & 0x8))
-		ac97_write(w->codec, AC97_REC_SEL, rec & 0x7fff);
-	else
-		ac97_write(w->codec, AC97_REC_SEL, rec | 0x8000);
+static int wm9713_hp_mixer_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);
+	struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm);
+	struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec);
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	unsigned int mixer, shift;
 
-	if ((l & 0x10) || (r & 0x10))
-		ac97_write(w->codec, AC97_PCM, pcm & 0x7fff);
-	else
-		ac97_write(w->codec, AC97_PCM, pcm | 0x8000);
+	mixer = mc->shift >> 8;
+	shift = mc->shift & 0xff;
 
-	if ((l & 0x20) || (r & 0x20))
-		ac97_write(w->codec, AC97_AUX, aux & 0x7fff);
-	else
-		ac97_write(w->codec, AC97_AUX, aux | 0x8000);
+	ucontrol->value.enumerated.item[0] =
+		(wm9713->hp_mixer[mixer] >> shift) & 1;
 
 	return 0;
 }
 
+#define WM9713_HP_MIXER_CTRL(xname, xmixer, xshift) { \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+	.info = snd_soc_info_volsw, \
+	.get = wm9713_hp_mixer_get, .put = wm9713_hp_mixer_put, \
+	.private_value = SOC_DOUBLE_VALUE(SND_SOC_NOPM, \
+		xshift, xmixer, 1, 0, 0) \
+}
+
 /* Left Headphone Mixers */
 static const struct snd_kcontrol_new wm9713_hpl_mixer_controls[] = {
-SOC_DAPM_SINGLE("Beep Playback Switch", HPL_MIXER, 5, 1, 0),
-SOC_DAPM_SINGLE("Voice Playback Switch", HPL_MIXER, 4, 1, 0),
-SOC_DAPM_SINGLE("Aux Playback Switch", HPL_MIXER, 3, 1, 0),
-SOC_DAPM_SINGLE("PCM Playback Switch", HPL_MIXER, 2, 1, 0),
-SOC_DAPM_SINGLE("MonoIn Playback Switch", HPL_MIXER, 1, 1, 0),
-SOC_DAPM_SINGLE("Bypass Playback Switch", HPL_MIXER, 0, 1, 0),
+WM9713_HP_MIXER_CTRL("Beep Playback Switch", HPL_MIXER, 5),
+WM9713_HP_MIXER_CTRL("Voice Playback Switch", HPL_MIXER, 4),
+WM9713_HP_MIXER_CTRL("Aux Playback Switch", HPL_MIXER, 3),
+WM9713_HP_MIXER_CTRL("PCM Playback Switch", HPL_MIXER, 2),
+WM9713_HP_MIXER_CTRL("MonoIn Playback Switch", HPL_MIXER, 1),
+WM9713_HP_MIXER_CTRL("Bypass Playback Switch", HPL_MIXER, 0),
 };
 
 /* Right Headphone Mixers */
 static const struct snd_kcontrol_new wm9713_hpr_mixer_controls[] = {
-SOC_DAPM_SINGLE("Beep Playback Switch", HPR_MIXER, 5, 1, 0),
-SOC_DAPM_SINGLE("Voice Playback Switch", HPR_MIXER, 4, 1, 0),
-SOC_DAPM_SINGLE("Aux Playback Switch", HPR_MIXER, 3, 1, 0),
-SOC_DAPM_SINGLE("PCM Playback Switch", HPR_MIXER, 2, 1, 0),
-SOC_DAPM_SINGLE("MonoIn Playback Switch", HPR_MIXER, 1, 1, 0),
-SOC_DAPM_SINGLE("Bypass Playback Switch", HPR_MIXER, 0, 1, 0),
+WM9713_HP_MIXER_CTRL("Beep Playback Switch", HPR_MIXER, 5),
+WM9713_HP_MIXER_CTRL("Voice Playback Switch", HPR_MIXER, 4),
+WM9713_HP_MIXER_CTRL("Aux Playback Switch", HPR_MIXER, 3),
+WM9713_HP_MIXER_CTRL("PCM Playback Switch", HPR_MIXER, 2),
+WM9713_HP_MIXER_CTRL("MonoIn Playback Switch", HPR_MIXER, 1),
+WM9713_HP_MIXER_CTRL("Bypass Playback Switch", HPR_MIXER, 0),
 };
 
 /* headphone capture mux */
@@ -428,12 +458,10 @@  SND_SOC_DAPM_MUX("Mic A Source", SND_SOC_NOPM, 0, 0,
 	&wm9713_mic_sel_mux_controls),
 SND_SOC_DAPM_MUX("Mic B Source", SND_SOC_NOPM, 0, 0,
 	&wm9713_micb_sel_mux_controls),
-SND_SOC_DAPM_MIXER_E("Left HP Mixer", AC97_EXTENDED_MID, 3, 1,
-	&wm9713_hpl_mixer_controls[0], ARRAY_SIZE(wm9713_hpl_mixer_controls),
-	mixer_event, SND_SOC_DAPM_POST_REG),
-SND_SOC_DAPM_MIXER_E("Right HP Mixer", AC97_EXTENDED_MID, 2, 1,
-	&wm9713_hpr_mixer_controls[0], ARRAY_SIZE(wm9713_hpr_mixer_controls),
-	mixer_event, SND_SOC_DAPM_POST_REG),
+SND_SOC_DAPM_MIXER("Left HP Mixer", AC97_EXTENDED_MID, 3, 1,
+	&wm9713_hpl_mixer_controls[0], ARRAY_SIZE(wm9713_hpl_mixer_controls)),
+SND_SOC_DAPM_MIXER("Right HP Mixer", AC97_EXTENDED_MID, 2, 1,
+	&wm9713_hpr_mixer_controls[0], ARRAY_SIZE(wm9713_hpr_mixer_controls)),
 SND_SOC_DAPM_MIXER("Mono Mixer", AC97_EXTENDED_MID, 0, 1,
 	&wm9713_mono_mixer_controls[0], ARRAY_SIZE(wm9713_mono_mixer_controls)),
 SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_EXTENDED_MID, 1, 1,
@@ -666,8 +694,7 @@  static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
 	unsigned int val)
 {
 	u16 *cache = codec->reg_cache;
-	if (reg < 0x7c)
-		soc_ac97_ops->write(codec->ac97, reg, val);
+	soc_ac97_ops->write(codec->ac97, reg, val);
 	reg = reg >> 1;
 	if (reg < (ARRAY_SIZE(wm9713_reg)))
 		cache[reg] = val;
@@ -1251,6 +1278,8 @@  static int wm9713_probe(struct platform_device *pdev)
 	if (wm9713 == NULL)
 		return -ENOMEM;
 
+	mutex_init(&wm9713->lock);
+
 	platform_set_drvdata(pdev, wm9713);
 
 	return snd_soc_register_codec(&pdev->dev,