|
@@ -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,13 +61,10 @@ static const u16 wm9713_reg[] = {
|
|
|
0x0000, 0x0000, 0x0000, 0x0000,
|
|
|
0x0000, 0x0000, 0x0000, 0x0006,
|
|
|
0x0001, 0x0000, 0x574d, 0x4c13,
|
|
|
- 0x0000, 0x0000, 0x0000
|
|
|
};
|
|
|
|
|
|
-/* virtual HP mixers regs */
|
|
|
-#define HPL_MIXER 0x80
|
|
|
-#define HPR_MIXER 0x82
|
|
|
-#define MICB_MUX 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"};
|
|
@@ -110,7 +109,7 @@ SOC_ENUM_SINGLE(AC97_REC_GAIN_MIC, 10, 8, wm9713_dac_inv), /* dac invert 2 15 */
|
|
|
SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, wm9713_bass), /* bass control 16 */
|
|
|
SOC_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9713_ng_type), /* noise gate type 17 */
|
|
|
SOC_ENUM_SINGLE(AC97_3D_CONTROL, 12, 3, wm9713_mic_select), /* mic selection 18 */
|
|
|
-SOC_ENUM_SINGLE(MICB_MUX, 0, 2, wm9713_micb_select), /* mic selection 19 */
|
|
|
+SOC_ENUM_SINGLE_VIRT(2, wm9713_micb_select), /* mic selection 19 */
|
|
|
};
|
|
|
|
|
|
static const DECLARE_TLV_DB_SCALE(out_tlv, -4650, 150, 0);
|
|
@@ -234,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.
|
|
@@ -241,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 */
|
|
@@ -429,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,
|
|
@@ -667,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;
|
|
@@ -689,7 +715,8 @@ struct _pll_div {
|
|
|
* to allow rounding later */
|
|
|
#define FIXED_PLL_SIZE ((1 << 22) * 10)
|
|
|
|
|
|
-static void pll_factors(struct _pll_div *pll_div, unsigned int source)
|
|
|
+static void pll_factors(struct snd_soc_codec *codec,
|
|
|
+ struct _pll_div *pll_div, unsigned int source)
|
|
|
{
|
|
|
u64 Kpart;
|
|
|
unsigned int K, Ndiv, Nmod, target;
|
|
@@ -724,7 +751,7 @@ static void pll_factors(struct _pll_div *pll_div, unsigned int source)
|
|
|
|
|
|
Ndiv = target / source;
|
|
|
if ((Ndiv < 5) || (Ndiv > 12))
|
|
|
- printk(KERN_WARNING
|
|
|
+ dev_warn(codec->dev,
|
|
|
"WM9713 PLL N value %u out of recommended range!\n",
|
|
|
Ndiv);
|
|
|
|
|
@@ -768,7 +795,7 @@ static int wm9713_set_pll(struct snd_soc_codec *codec,
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
- pll_factors(&pll_div, freq_in);
|
|
|
+ pll_factors(codec, &pll_div, freq_in);
|
|
|
|
|
|
if (pll_div.k == 0) {
|
|
|
reg = (pll_div.n << 12) | (pll_div.lf << 11) |
|
|
@@ -1104,8 +1131,11 @@ int wm9713_reset(struct snd_soc_codec *codec, int try_warm)
|
|
|
soc_ac97_ops->reset(codec->ac97);
|
|
|
if (soc_ac97_ops->warm_reset)
|
|
|
soc_ac97_ops->warm_reset(codec->ac97);
|
|
|
- if (ac97_read(codec, 0) != wm9713_reg[0])
|
|
|
+ if (ac97_read(codec, 0) != wm9713_reg[0]) {
|
|
|
+ dev_err(codec->dev, "Failed to reset: AC97 link error\n");
|
|
|
return -EIO;
|
|
|
+ }
|
|
|
+
|
|
|
return 0;
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(wm9713_reset);
|
|
@@ -1163,10 +1193,8 @@ static int wm9713_soc_resume(struct snd_soc_codec *codec)
|
|
|
u16 *cache = codec->reg_cache;
|
|
|
|
|
|
ret = wm9713_reset(codec, 1);
|
|
|
- if (ret < 0) {
|
|
|
- printk(KERN_ERR "could not reset AC97 codec\n");
|
|
|
+ if (ret < 0)
|
|
|
return ret;
|
|
|
- }
|
|
|
|
|
|
wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
|
|
|
|
@@ -1189,26 +1217,18 @@ static int wm9713_soc_resume(struct snd_soc_codec *codec)
|
|
|
|
|
|
static int wm9713_soc_probe(struct snd_soc_codec *codec)
|
|
|
{
|
|
|
- struct wm9713_priv *wm9713;
|
|
|
int ret = 0, reg;
|
|
|
|
|
|
- wm9713 = kzalloc(sizeof(struct wm9713_priv), GFP_KERNEL);
|
|
|
- if (wm9713 == NULL)
|
|
|
- return -ENOMEM;
|
|
|
- snd_soc_codec_set_drvdata(codec, wm9713);
|
|
|
-
|
|
|
ret = snd_soc_new_ac97_codec(codec, soc_ac97_ops, 0);
|
|
|
if (ret < 0)
|
|
|
- goto codec_err;
|
|
|
+ return ret;
|
|
|
|
|
|
/* do a cold reset for the controller and then try
|
|
|
* a warm reset followed by an optional cold reset for codec */
|
|
|
wm9713_reset(codec, 0);
|
|
|
ret = wm9713_reset(codec, 1);
|
|
|
- if (ret < 0) {
|
|
|
- printk(KERN_ERR "Failed to reset WM9713: AC97 link error\n");
|
|
|
+ if (ret < 0)
|
|
|
goto reset_err;
|
|
|
- }
|
|
|
|
|
|
wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
|
|
|
|
@@ -1216,23 +1236,16 @@ static int wm9713_soc_probe(struct snd_soc_codec *codec)
|
|
|
reg = ac97_read(codec, AC97_CD) & 0x7fff;
|
|
|
ac97_write(codec, AC97_CD, reg);
|
|
|
|
|
|
- snd_soc_add_codec_controls(codec, wm9713_snd_ac97_controls,
|
|
|
- ARRAY_SIZE(wm9713_snd_ac97_controls));
|
|
|
-
|
|
|
return 0;
|
|
|
|
|
|
reset_err:
|
|
|
snd_soc_free_ac97_codec(codec);
|
|
|
-codec_err:
|
|
|
- kfree(wm9713);
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
static int wm9713_soc_remove(struct snd_soc_codec *codec)
|
|
|
{
|
|
|
- struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec);
|
|
|
snd_soc_free_ac97_codec(codec);
|
|
|
- kfree(wm9713);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -1248,6 +1261,9 @@ static struct snd_soc_codec_driver soc_codec_dev_wm9713 = {
|
|
|
.reg_word_size = sizeof(u16),
|
|
|
.reg_cache_step = 2,
|
|
|
.reg_cache_default = wm9713_reg,
|
|
|
+
|
|
|
+ .controls = wm9713_snd_ac97_controls,
|
|
|
+ .num_controls = ARRAY_SIZE(wm9713_snd_ac97_controls),
|
|
|
.dapm_widgets = wm9713_dapm_widgets,
|
|
|
.num_dapm_widgets = ARRAY_SIZE(wm9713_dapm_widgets),
|
|
|
.dapm_routes = wm9713_audio_map,
|
|
@@ -1256,6 +1272,16 @@ static struct snd_soc_codec_driver soc_codec_dev_wm9713 = {
|
|
|
|
|
|
static int wm9713_probe(struct platform_device *pdev)
|
|
|
{
|
|
|
+ struct wm9713_priv *wm9713;
|
|
|
+
|
|
|
+ wm9713 = devm_kzalloc(&pdev->dev, sizeof(*wm9713), GFP_KERNEL);
|
|
|
+ if (wm9713 == NULL)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ mutex_init(&wm9713->lock);
|
|
|
+
|
|
|
+ platform_set_drvdata(pdev, wm9713);
|
|
|
+
|
|
|
return snd_soc_register_codec(&pdev->dev,
|
|
|
&soc_codec_dev_wm9713, wm9713_dai, ARRAY_SIZE(wm9713_dai));
|
|
|
}
|