|
@@ -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));
|
|
|
}
|