ALSA: hda/cs8409: Support manual mode detection for CS42L42
authorStefan Binding <sbinding@opensource.cirrus.com>
Wed, 4 May 2022 16:12:36 +0000 (17:12 +0100)
committerTakashi Iwai <tiwai@suse.de>
Mon, 9 May 2022 08:46:14 +0000 (10:46 +0200)
For Jack detection on CS42L42, detection is normally done using
"auto" mode, which automatically detects what type of jack is
connected to the device. However, some headsets are not
automatically detected, and as such and alternative detection
method "manual mode" can be used to detect these headsets.

Signed-off-by: Stefan Binding <sbinding@opensource.cirrus.com>
Tested-by: Chris Chiu <chris.chiu@canonical.com>
Link: https://lore.kernel.org/r/20220504161236.2490532-4-sbinding@opensource.cirrus.com
Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/pci/hda/patch_cs8409-tables.c
sound/pci/hda/patch_cs8409.c
sound/pci/hda/patch_cs8409.h

index a7ee489e6aec40f174be1d88614ee00631af2b73..0d11b24a1317270533c59e55a371e0cce269bab8 100644 (file)
@@ -252,7 +252,6 @@ struct sub_codec cs8409_cs42l42_codec = {
        .init_seq_num = ARRAY_SIZE(cs42l42_init_reg_seq),
        .hp_jack_in = 0,
        .mic_jack_in = 0,
-       .force_status_change = 1,
        .paged = 1,
        .suspended = 1,
        .no_type_dect = 0,
@@ -444,7 +443,6 @@ struct sub_codec dolphin_cs42l42_0 = {
        .init_seq_num = ARRAY_SIZE(dolphin_c0_init_reg_seq),
        .hp_jack_in = 0,
        .mic_jack_in = 0,
-       .force_status_change = 1,
        .paged = 1,
        .suspended = 1,
        .no_type_dect = 0,
@@ -458,7 +456,6 @@ struct sub_codec dolphin_cs42l42_1 = {
        .init_seq_num = ARRAY_SIZE(dolphin_c1_init_reg_seq),
        .hp_jack_in = 0,
        .mic_jack_in = 0,
-       .force_status_change = 1,
        .paged = 1,
        .suspended = 1,
        .no_type_dect = 1,
index d35d124bf3dc175ab4ab02458668f6249fa15f2b..c3a8b04c71d8b021d022e2c50a3fd59da7da295c 100644 (file)
@@ -634,38 +634,128 @@ static void cs42l42_run_jack_detect(struct sub_codec *cs42l42)
        cs8409_i2c_write(cs42l42, CS42L42_HSDET_CTL2, 0xc0);
 }
 
-static int cs42l42_handle_tip_sense(struct sub_codec *cs42l42, unsigned int reg_ts_status)
+static int cs42l42_manual_hs_det(struct sub_codec *cs42l42)
 {
-       int status_changed = cs42l42->force_status_change;
+       unsigned int hs_det_status;
+       unsigned int hs_det_comp1;
+       unsigned int hs_det_comp2;
+       unsigned int hs_det_sw;
+       unsigned int hs_type;
+
+       /* Set hs detect to manual, active mode */
+       cs8409_i2c_write(cs42l42, CS42L42_HSDET_CTL2,
+                        (1 << CS42L42_HSDET_CTRL_SHIFT) |
+                        (0 << CS42L42_HSDET_SET_SHIFT) |
+                        (0 << CS42L42_HSBIAS_REF_SHIFT) |
+                        (0 << CS42L42_HSDET_AUTO_TIME_SHIFT));
+
+       /* Configure HS DET comparator reference levels. */
+       cs8409_i2c_write(cs42l42, CS42L42_HSDET_CTL1,
+                        (CS42L42_HSDET_COMP1_LVL_VAL << CS42L42_HSDET_COMP1_LVL_SHIFT) |
+                        (CS42L42_HSDET_COMP2_LVL_VAL << CS42L42_HSDET_COMP2_LVL_SHIFT));
+
+       /* Open the SW_HSB_HS3 switch and close SW_HSB_HS4 for a Type 1 headset. */
+       cs8409_i2c_write(cs42l42, CS42L42_HS_SWITCH_CTL, CS42L42_HSDET_SW_COMP1);
+
+       msleep(100);
+
+       hs_det_status = cs8409_i2c_read(cs42l42, CS42L42_HS_DET_STATUS);
+
+       hs_det_comp1 = (hs_det_status & CS42L42_HSDET_COMP1_OUT_MASK) >>
+                       CS42L42_HSDET_COMP1_OUT_SHIFT;
+       hs_det_comp2 = (hs_det_status & CS42L42_HSDET_COMP2_OUT_MASK) >>
+                       CS42L42_HSDET_COMP2_OUT_SHIFT;
+
+       /* Close the SW_HSB_HS3 switch for a Type 2 headset. */
+       cs8409_i2c_write(cs42l42, CS42L42_HS_SWITCH_CTL, CS42L42_HSDET_SW_COMP2);
 
-       cs42l42->force_status_change = 0;
+       msleep(100);
+
+       hs_det_status = cs8409_i2c_read(cs42l42, CS42L42_HS_DET_STATUS);
+
+       hs_det_comp1 |= ((hs_det_status & CS42L42_HSDET_COMP1_OUT_MASK) >>
+                       CS42L42_HSDET_COMP1_OUT_SHIFT) << 1;
+       hs_det_comp2 |= ((hs_det_status & CS42L42_HSDET_COMP2_OUT_MASK) >>
+                       CS42L42_HSDET_COMP2_OUT_SHIFT) << 1;
+
+       /* Use Comparator 1 with 1.25V Threshold. */
+       switch (hs_det_comp1) {
+       case CS42L42_HSDET_COMP_TYPE1:
+               hs_type = CS42L42_PLUG_CTIA;
+               hs_det_sw = CS42L42_HSDET_SW_TYPE1;
+               break;
+       case CS42L42_HSDET_COMP_TYPE2:
+               hs_type = CS42L42_PLUG_OMTP;
+               hs_det_sw = CS42L42_HSDET_SW_TYPE2;
+               break;
+       default:
+               /* Fallback to Comparator 2 with 1.75V Threshold. */
+               switch (hs_det_comp2) {
+               case CS42L42_HSDET_COMP_TYPE1:
+                       hs_type = CS42L42_PLUG_CTIA;
+                       hs_det_sw = CS42L42_HSDET_SW_TYPE1;
+                       break;
+               case CS42L42_HSDET_COMP_TYPE2:
+                       hs_type = CS42L42_PLUG_OMTP;
+                       hs_det_sw = CS42L42_HSDET_SW_TYPE2;
+                       break;
+               case CS42L42_HSDET_COMP_TYPE3:
+                       hs_type = CS42L42_PLUG_HEADPHONE;
+                       hs_det_sw = CS42L42_HSDET_SW_TYPE3;
+                       break;
+               default:
+                       hs_type = CS42L42_PLUG_INVALID;
+                       hs_det_sw = CS42L42_HSDET_SW_TYPE4;
+                       break;
+               }
+       }
+
+       /* Set Switches */
+       cs8409_i2c_write(cs42l42, CS42L42_HS_SWITCH_CTL, hs_det_sw);
+
+       /* Set HSDET mode to Manual—Disabled */
+       cs8409_i2c_write(cs42l42, CS42L42_HSDET_CTL2,
+                        (0 << CS42L42_HSDET_CTRL_SHIFT) |
+                        (0 << CS42L42_HSDET_SET_SHIFT) |
+                        (0 << CS42L42_HSBIAS_REF_SHIFT) |
+                        (0 << CS42L42_HSDET_AUTO_TIME_SHIFT));
+
+       /* Configure HS DET comparator reference levels. */
+       cs8409_i2c_write(cs42l42, CS42L42_HSDET_CTL1,
+                        (CS42L42_HSDET_COMP1_LVL_DEFAULT << CS42L42_HSDET_COMP1_LVL_SHIFT) |
+                        (CS42L42_HSDET_COMP2_LVL_DEFAULT << CS42L42_HSDET_COMP2_LVL_SHIFT));
+
+       return hs_type;
+}
+
+static int cs42l42_handle_tip_sense(struct sub_codec *cs42l42, unsigned int reg_ts_status)
+{
+       int status_changed = 0;
 
        /* TIP_SENSE INSERT/REMOVE */
        switch (reg_ts_status) {
        case CS42L42_TS_PLUG:
-               if (!cs42l42->hp_jack_in) {
-                       if (cs42l42->no_type_dect) {
-                               status_changed = 1;
-                               cs42l42->hp_jack_in = 1;
-                               cs42l42->mic_jack_in = 0;
-                       } else {
-                               cs42l42_run_jack_detect(cs42l42);
-                       }
+               if (cs42l42->no_type_dect) {
+                       status_changed = 1;
+                       cs42l42->hp_jack_in = 1;
+                       cs42l42->mic_jack_in = 0;
+               } else {
+                       cs42l42_run_jack_detect(cs42l42);
                }
                break;
 
        case CS42L42_TS_UNPLUG:
-               if (cs42l42->hp_jack_in || cs42l42->mic_jack_in) {
-                       status_changed = 1;
-                       cs42l42->hp_jack_in = 0;
-                       cs42l42->mic_jack_in = 0;
-               }
+               status_changed = 1;
+               cs42l42->hp_jack_in = 0;
+               cs42l42->mic_jack_in = 0;
                break;
        default:
                /* jack in transition */
                break;
        }
 
+       codec_dbg(cs42l42->codec, "Tip Sense Detection: (%d)\n", reg_ts_status);
+
        return status_changed;
 }
 
@@ -698,24 +788,40 @@ static int cs42l42_jack_unsol_event(struct sub_codec *cs42l42)
 
                type = (reg_hs_status & CS42L42_HSDET_TYPE_MASK) >> CS42L42_HSDET_TYPE_SHIFT;
 
+               /* Configure the HSDET mode. */
+               cs8409_i2c_write(cs42l42, CS42L42_HSDET_CTL2, 0x80);
+
                if (cs42l42->no_type_dect) {
                        status_changed = cs42l42_handle_tip_sense(cs42l42, current_plug_status);
-               } else if (type == CS42L42_PLUG_INVALID) {
-                       /* Type CS42L42_PLUG_INVALID not supported      */
-                       status_changed = cs42l42_handle_tip_sense(cs42l42, CS42L42_TS_UNPLUG);
                } else {
-                       if (!cs42l42->hp_jack_in) {
-                               status_changed = 1;
-                               cs42l42->hp_jack_in = 1;
+                       if (type == CS42L42_PLUG_INVALID || type == CS42L42_PLUG_HEADPHONE) {
+                               codec_dbg(cs42l42->codec,
+                                         "Auto detect value not valid (%d), running manual det\n",
+                                         type);
+                               type = cs42l42_manual_hs_det(cs42l42);
                        }
-                       /* type = CS42L42_PLUG_HEADPHONE has no mic */
-                       if ((!cs42l42->mic_jack_in) && (type != CS42L42_PLUG_HEADPHONE)) {
+
+                       switch (type) {
+                       case CS42L42_PLUG_CTIA:
+                       case CS42L42_PLUG_OMTP:
                                status_changed = 1;
+                               cs42l42->hp_jack_in = 1;
                                cs42l42->mic_jack_in = 1;
+                               break;
+                       case CS42L42_PLUG_HEADPHONE:
+                               status_changed = 1;
+                               cs42l42->hp_jack_in = 1;
+                               cs42l42->mic_jack_in = 0;
+                               break;
+                       default:
+                               status_changed = 1;
+                               cs42l42->hp_jack_in = 0;
+                               cs42l42->mic_jack_in = 0;
+                               break;
                        }
+                       codec_dbg(cs42l42->codec, "Detection done (%d)\n", type);
                }
-               /* Configure the HSDET mode. */
-               cs8409_i2c_write(cs42l42, CS42L42_HSDET_CTL2, 0x80);
+
                /* Enable the HPOUT ground clamp and configure the HP pull-down */
                cs8409_i2c_write(cs42l42, CS42L42_DAC_CTL2, 0x02);
                /* Re-Enable Tip Sense Interrupt */
@@ -803,7 +909,6 @@ static void cs42l42_suspend(struct sub_codec *cs42l42)
        cs42l42->last_page = 0;
        cs42l42->hp_jack_in = 0;
        cs42l42->mic_jack_in = 0;
-       cs42l42->force_status_change = 1;
 
        /* Put CS42L42 into Reset */
        gpio_data = snd_hda_codec_read(codec, CS8409_PIN_AFG, 0, AC_VERB_GET_GPIO_DATA, 0);
index 988259f8a9402475903594a9ec41a6fbd2c1be25..ebf473a3f109ce54061fbe9e03d954261877ea7f 100644 (file)
@@ -304,7 +304,6 @@ struct sub_codec {
 
        unsigned int hp_jack_in:1;
        unsigned int mic_jack_in:1;
-       unsigned int force_status_change:1;
        unsigned int suspended:1;
        unsigned int paged:1;
        unsigned int last_page;