diff options
Diffstat (limited to 'sound/soc/codecs/wm9713.c')
-rw-r--r-- | sound/soc/codecs/wm9713.c | 230 |
1 files changed, 131 insertions, 99 deletions
diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c index bddee30a4bc7..5df7f6d12bef 100644 --- a/sound/soc/codecs/wm9713.c +++ b/sound/soc/codecs/wm9713.c @@ -30,7 +30,10 @@ #include "wm9713.h" struct wm9713_priv { + struct snd_ac97 *ac97; 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 +62,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 +110,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 +234,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 +249,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 +459,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, @@ -647,12 +675,13 @@ static const struct snd_soc_dapm_route wm9713_audio_map[] = { static unsigned int ac97_read(struct snd_soc_codec *codec, unsigned int reg) { + struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec); u16 *cache = codec->reg_cache; if (reg == AC97_RESET || reg == AC97_GPIO_STATUS || reg == AC97_VENDOR_ID1 || reg == AC97_VENDOR_ID2 || reg == AC97_CD) - return soc_ac97_ops->read(codec->ac97, reg); + return soc_ac97_ops->read(wm9713->ac97, reg); else { reg = reg >> 1; @@ -666,9 +695,10 @@ static unsigned int ac97_read(struct snd_soc_codec *codec, static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int val) { + struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec); + u16 *cache = codec->reg_cache; - if (reg < 0x7c) - soc_ac97_ops->write(codec->ac97, reg, val); + soc_ac97_ops->write(wm9713->ac97, reg, val); reg = reg >> 1; if (reg < (ARRAY_SIZE(wm9713_reg))) cache[reg] = val; @@ -689,7 +719,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 +755,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 +799,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) | @@ -1049,7 +1080,6 @@ static const struct snd_soc_dai_ops wm9713_dai_ops_voice = { static struct snd_soc_dai_driver wm9713_dai[] = { { .name = "wm9713-hifi", - .ac97_control = 1, .playback = { .stream_name = "HiFi Playback", .channels_min = 1, @@ -1095,17 +1125,22 @@ static struct snd_soc_dai_driver wm9713_dai[] = { int wm9713_reset(struct snd_soc_codec *codec, int try_warm) { + struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec); + if (try_warm && soc_ac97_ops->warm_reset) { - soc_ac97_ops->warm_reset(codec->ac97); + soc_ac97_ops->warm_reset(wm9713->ac97); if (ac97_read(codec, 0) == wm9713_reg[0]) return 1; } - soc_ac97_ops->reset(codec->ac97); + soc_ac97_ops->reset(wm9713->ac97); if (soc_ac97_ops->warm_reset) - soc_ac97_ops->warm_reset(codec->ac97); - if (ac97_read(codec, 0) != wm9713_reg[0]) + soc_ac97_ops->warm_reset(wm9713->ac97); + 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 +1198,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); @@ -1180,7 +1213,7 @@ static int wm9713_soc_resume(struct snd_soc_codec *codec) if (i == AC97_POWERDOWN || i == AC97_EXTENDED_MID || i == AC97_EXTENDED_MSTATUS || i > 0x66) continue; - soc_ac97_ops->write(codec->ac97, i, cache[i>>1]); + soc_ac97_ops->write(wm9713->ac97, i, cache[i>>1]); } } @@ -1189,50 +1222,36 @@ static int wm9713_soc_resume(struct snd_soc_codec *codec) static int wm9713_soc_probe(struct snd_soc_codec *codec) { - struct wm9713_priv *wm9713; + struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec); 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; + wm9713->ac97 = snd_soc_new_ac97_codec(codec); + if (IS_ERR(wm9713->ac97)) + return PTR_ERR(wm9713->ac97); /* 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); /* unmute the adc - move to kcontrol */ 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); + snd_soc_free_ac97_codec(wm9713->ac97); 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); + + snd_soc_free_ac97_codec(wm9713->ac97); return 0; } @@ -1248,6 +1267,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 +1278,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)); } |