Check more strictly about the validity of pinctl values in snd_hda_set_pin_ctl() and correct the wrong bits automatically. Also provide the helper function to correct pinctl bits to codec drivers.
This automatically fixes the invalid pinctl writes that are found in a few Realtek fixups for NID 0x0f amp like ASUS A6Rp.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/hda_codec.c | 66 +++++++++++++++++++++++++++++++++++++---------- sound/pci/hda/hda_local.h | 2 ++ 2 files changed, 54 insertions(+), 14 deletions(-)
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 505cb72..0a531f2 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -5275,23 +5275,61 @@ unsigned int snd_hda_get_default_vref(struct hda_codec *codec, hda_nid_t pin) } EXPORT_SYMBOL_HDA(snd_hda_get_default_vref);
+/* correct the pin ctl value for matching with the pin cap */ +unsigned int snd_hda_correct_pin_ctl(struct hda_codec *codec, + hda_nid_t pin, unsigned int val) +{ + static unsigned int cap_lists[][2] = { + { AC_PINCTL_VREF_100, AC_PINCAP_VREF_100 }, + { AC_PINCTL_VREF_80, AC_PINCAP_VREF_80 }, + { AC_PINCTL_VREF_50, AC_PINCAP_VREF_50 }, + { AC_PINCTL_VREF_GRD, AC_PINCAP_VREF_GRD }, + }; + unsigned int cap; + + if (!val) + return 0; + cap = snd_hda_query_pin_caps(codec, pin); + if (!cap) + return val; /* don't know what to do... */ + + if (val & AC_PINCTL_OUT_EN) { + if (!(cap & AC_PINCAP_OUT)) + val &= ~(AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN); + else if ((val & AC_PINCTL_HP_EN) && !(cap & AC_PINCAP_HP_DRV)) + val &= ~AC_PINCTL_HP_EN; + } + + if (val & AC_PINCTL_IN_EN) { + if (!(cap & AC_PINCAP_IN)) + val &= ~(AC_PINCTL_IN_EN | AC_PINCTL_VREFEN); + else { + unsigned int vcap, vref; + int i; + vcap = (cap & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT; + vref = val & AC_PINCTL_VREFEN; + for (i = 0; i < ARRAY_SIZE(cap_lists); i++) { + if (vref == cap_lists[i][0] && + !(vcap & cap_lists[i][1])) { + if (i == ARRAY_SIZE(cap_lists) - 1) + vref = AC_PINCTL_VREF_HIZ; + else + vref = cap_lists[i + 1][0]; + } + } + val &= ~AC_PINCTL_VREFEN; + val |= vref; + } + } + + return val; +} +EXPORT_SYMBOL_HDA(snd_hda_correct_pin_ctl); + int _snd_hda_set_pin_ctl(struct hda_codec *codec, hda_nid_t pin, unsigned int val, bool cached) { - if (val) { - unsigned int cap = snd_hda_query_pin_caps(codec, pin); - if (cap && (val & AC_PINCTL_OUT_EN)) { - if (!(cap & AC_PINCAP_OUT)) - val &= ~(AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN); - else if ((val & AC_PINCTL_HP_EN) && - !(cap & AC_PINCAP_HP_DRV)) - val &= ~AC_PINCTL_HP_EN; - } - if (cap && (val & AC_PINCTL_IN_EN)) { - if (!(cap & AC_PINCAP_IN)) - val &= ~(AC_PINCTL_IN_EN | AC_PINCTL_VREFEN); - } - } + val = snd_hda_correct_pin_ctl(codec, pin, val); snd_hda_codec_set_pin_target(codec, pin, val); if (cached) return snd_hda_codec_update_cache(codec, pin, 0, diff --git a/sound/pci/hda/hda_local.h b/sound/pci/hda/hda_local.h index 655a40f..aa721aa 100644 --- a/sound/pci/hda/hda_local.h +++ b/sound/pci/hda/hda_local.h @@ -490,6 +490,8 @@ struct hda_bus_unsolicited { #define PIN_HP_AMP (AC_PINCTL_HP_EN)
unsigned int snd_hda_get_default_vref(struct hda_codec *codec, hda_nid_t pin); +unsigned int snd_hda_correct_pin_ctl(struct hda_codec *codec, + hda_nid_t pin, unsigned int val); int _snd_hda_set_pin_ctl(struct hda_codec *codec, hda_nid_t pin, unsigned int val, bool cached);