ALSA: hda/conexant: Fix headset auto detect fail in cx8070 and SN6140
[platform/kernel/linux-rpi.git] / sound / pci / hda / patch_conexant.c
index a889ccc..e8819e8 100644 (file)
 #include "hda_jack.h"
 #include "hda_generic.h"
 
+enum {
+       CX_HEADSET_NOPRESENT = 0,
+       CX_HEADSET_PARTPRESENT,
+       CX_HEADSET_ALLPRESENT,
+};
+
 struct conexant_spec {
        struct hda_gen_spec gen;
 
@@ -42,7 +48,8 @@ struct conexant_spec {
        unsigned int gpio_led;
        unsigned int gpio_mute_led_mask;
        unsigned int gpio_mic_led_mask;
-
+       unsigned int headset_present_flag;
+       bool is_cx8070_sn6140;
 };
 
 
@@ -164,6 +171,27 @@ static void cxt_init_gpio_led(struct hda_codec *codec)
        }
 }
 
+static void cx_fixup_headset_recog(struct hda_codec *codec)
+{
+       unsigned int mic_persent;
+
+       /* fix some headset type recognize fail issue, such as EDIFIER headset */
+       /* set micbiasd output current comparator threshold from 66% to 55%. */
+       snd_hda_codec_write(codec, 0x1c, 0, 0x320, 0x010);
+       /* set OFF voltage for DFET from -1.2V to -0.8V, set headset micbias registor
+        * value adjustment trim from 2.2K ohms to 2.0K ohms.
+        */
+       snd_hda_codec_write(codec, 0x1c, 0, 0x3b0, 0xe10);
+       /* fix reboot headset type recognize fail issue */
+       mic_persent = snd_hda_codec_read(codec, 0x19, 0, AC_VERB_GET_PIN_SENSE, 0x0);
+       if (mic_persent & AC_PINSENSE_PRESENCE)
+               /* enable headset mic VREF */
+               snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24);
+       else
+               /* disable headset mic VREF */
+               snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20);
+}
+
 static int cx_auto_init(struct hda_codec *codec)
 {
        struct conexant_spec *spec = codec->spec;
@@ -174,6 +202,9 @@ static int cx_auto_init(struct hda_codec *codec)
        cxt_init_gpio_led(codec);
        snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_INIT);
 
+       if (spec->is_cx8070_sn6140)
+               cx_fixup_headset_recog(codec);
+
        return 0;
 }
 
@@ -192,6 +223,77 @@ static void cx_auto_free(struct hda_codec *codec)
        snd_hda_gen_free(codec);
 }
 
+static void cx_process_headset_plugin(struct hda_codec *codec)
+{
+       unsigned int val;
+       unsigned int count = 0;
+
+       /* Wait headset detect done. */
+       do {
+               val = snd_hda_codec_read(codec, 0x1c, 0, 0xca0, 0x0);
+               if (val & 0x080) {
+                       codec_dbg(codec, "headset type detect done!\n");
+                       break;
+               }
+               msleep(20);
+               count++;
+       } while (count < 3);
+       val = snd_hda_codec_read(codec, 0x1c, 0, 0xcb0, 0x0);
+       if (val & 0x800) {
+               codec_dbg(codec, "headset plugin, type is CTIA\n");
+               snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24);
+       } else if (val & 0x400) {
+               codec_dbg(codec, "headset plugin, type is OMTP\n");
+               snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24);
+       } else {
+               codec_dbg(codec, "headphone plugin\n");
+       }
+}
+
+static void cx_update_headset_mic_vref(struct hda_codec *codec, unsigned int res)
+{
+       unsigned int phone_present, mic_persent, phone_tag, mic_tag;
+       struct conexant_spec *spec = codec->spec;
+
+       /* In cx8070 and sn6140, the node 16 can only be config to headphone or disabled,
+        * the node 19 can only be config to microphone or disabled.
+        * Check hp&mic tag to process headset pulgin&plugout.
+        */
+       phone_tag = snd_hda_codec_read(codec, 0x16, 0, AC_VERB_GET_UNSOLICITED_RESPONSE, 0x0);
+       mic_tag = snd_hda_codec_read(codec, 0x19, 0, AC_VERB_GET_UNSOLICITED_RESPONSE, 0x0);
+       if ((phone_tag & (res >> AC_UNSOL_RES_TAG_SHIFT)) ||
+           (mic_tag & (res >> AC_UNSOL_RES_TAG_SHIFT))) {
+               phone_present = snd_hda_codec_read(codec, 0x16, 0, AC_VERB_GET_PIN_SENSE, 0x0);
+               if (!(phone_present & AC_PINSENSE_PRESENCE)) {/* headphone plugout */
+                       spec->headset_present_flag = CX_HEADSET_NOPRESENT;
+                       snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20);
+                       return;
+               }
+               if (spec->headset_present_flag == CX_HEADSET_NOPRESENT) {
+                       spec->headset_present_flag = CX_HEADSET_PARTPRESENT;
+               } else if (spec->headset_present_flag == CX_HEADSET_PARTPRESENT) {
+                       mic_persent = snd_hda_codec_read(codec, 0x19, 0,
+                                                        AC_VERB_GET_PIN_SENSE, 0x0);
+                       /* headset is present */
+                       if ((phone_present & AC_PINSENSE_PRESENCE) &&
+                           (mic_persent & AC_PINSENSE_PRESENCE)) {
+                               cx_process_headset_plugin(codec);
+                               spec->headset_present_flag = CX_HEADSET_ALLPRESENT;
+                       }
+               }
+       }
+}
+
+static void cx_jack_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+       struct conexant_spec *spec = codec->spec;
+
+       if (spec->is_cx8070_sn6140)
+               cx_update_headset_mic_vref(codec, res);
+
+       snd_hda_jack_unsol_event(codec, res);
+}
+
 #ifdef CONFIG_PM
 static int cx_auto_suspend(struct hda_codec *codec)
 {
@@ -205,7 +307,7 @@ static const struct hda_codec_ops cx_auto_patch_ops = {
        .build_pcms = snd_hda_gen_build_pcms,
        .init = cx_auto_init,
        .free = cx_auto_free,
-       .unsol_event = snd_hda_jack_unsol_event,
+       .unsol_event = cx_jack_unsol_event,
 #ifdef CONFIG_PM
        .suspend = cx_auto_suspend,
        .check_power_status = snd_hda_gen_check_power_status,
@@ -1042,6 +1144,15 @@ static int patch_conexant_auto(struct hda_codec *codec)
        codec->spec = spec;
        codec->patch_ops = cx_auto_patch_ops;
 
+       /* init cx8070/sn6140 flag and reset headset_present_flag */
+       switch (codec->core.vendor_id) {
+       case 0x14f11f86:
+       case 0x14f11f87:
+               spec->is_cx8070_sn6140 = true;
+               spec->headset_present_flag = CX_HEADSET_NOPRESENT;
+               break;
+       }
+
        cx_auto_parse_eapd(codec);
        spec->gen.own_eapd_ctl = 1;