ALSA: hda/cirrus: Add jack detect interrupt support from CS42L42 companion codec.
authorVitaly Rodionov <vitalyr@opensource.cirrus.com>
Sat, 6 Mar 2021 11:19:33 +0000 (11:19 +0000)
committerTakashi Iwai <tiwai@suse.de>
Sun, 7 Mar 2021 08:18:28 +0000 (09:18 +0100)
In the case of CS8409 we do not have unsol events from NID's 0x24 and 0x34
where hs mic and hp are connected. Companion codec CS42L42 will generate
interrupt via gpio 4 to notify jack events. We have to overwrite standard
snd_hda_jack_unsol_event(), read CS42L42 jack detect status registers and
then notify status via generic snd_hda_jack_unsol_event() call.

Tested on DELL Inspiron-3500, DELL Inspiron-3501, DELL Inspiron-3505.

Signed-off-by: Vitaly Rodionov <vitalyr@opensource.cirrus.com>
Link: https://lore.kernel.org/r/20210306111934.4832-4-vitalyr@opensource.cirrus.com
Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/pci/hda/patch_cirrus.c

index d664eed..1d2f6a1 100644 (file)
@@ -9,6 +9,7 @@
 #include <linux/slab.h>
 #include <linux/module.h>
 #include <sound/core.h>
+#include <linux/mutex.h>
 #include <linux/pci.h>
 #include <sound/tlv.h>
 #include <sound/hda_codec.h>
@@ -38,6 +39,15 @@ struct cs_spec {
        /* for MBP SPDIF control */
        int (*spdif_sw_put)(struct snd_kcontrol *kcontrol,
                            struct snd_ctl_elem_value *ucontrol);
+
+       unsigned int cs42l42_hp_jack_in:1;
+       unsigned int cs42l42_mic_jack_in:1;
+
+       struct mutex cs8409_i2c_mux;
+
+       /* verb exec op override */
+       int (*exec_verb)(struct hdac_device *dev, unsigned int cmd,
+                                unsigned int flags, unsigned int *res);
 };
 
 /* available models with CS420x */
@@ -1229,6 +1239,13 @@ static int patch_cs4213(struct hda_codec *codec)
 #define CS8409_CS42L42_SPK_PIN_NID     0x2c
 #define CS8409_CS42L42_AMIC_PIN_NID    0x34
 #define CS8409_CS42L42_DMIC_PIN_NID    0x44
+#define CS8409_CS42L42_DMIC_ADC_PIN_NID        0x22
+
+#define CS42L42_HSDET_AUTO_DONE        0x02
+#define CS42L42_HSTYPE_MASK            0x03
+
+#define CS42L42_JACK_INSERTED  0x0C
+#define CS42L42_JACK_REMOVED   0x00
 
 #define GPIO3_INT (1 << 3)
 #define GPIO4_INT (1 << 4)
@@ -1429,6 +1446,7 @@ static const struct cs8409_i2c_param cs42l42_init_reg_seq[] = {
        { 0x1C03, 0xC0 },
        { 0x1105, 0x00 },
        { 0x1112, 0xC0 },
+       { 0x1101, 0x02 },
        {} /* Terminator */
 };
 
@@ -1565,6 +1583,8 @@ static unsigned int cs8409_i2c_write(struct hda_codec *codec,
 /* Assert/release RTS# line to CS42L42 */
 static void cs8409_cs42l42_reset(struct hda_codec *codec)
 {
+       struct cs_spec *spec = codec->spec;
+
        /* Assert RTS# line */
        snd_hda_codec_write(codec,
                        codec->core.afg, 0, AC_VERB_SET_GPIO_DATA, 0);
@@ -1576,21 +1596,190 @@ static void cs8409_cs42l42_reset(struct hda_codec *codec)
        /* wait ~10ms */
        usleep_range(10000, 15000);
 
-       /* Clear interrupts status */
+       mutex_lock(&spec->cs8409_i2c_mux);
+
+       /* Clear interrupts, by reading interrupt status registers */
        cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1308, 1);
        cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1309, 1);
        cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x130A, 1);
        cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x130F, 1);
 
+       mutex_unlock(&spec->cs8409_i2c_mux);
+
+}
+
+/* Configure CS42L42 slave codec for jack autodetect */
+static int cs8409_cs42l42_enable_jack_detect(struct hda_codec *codec)
+{
+       struct cs_spec *spec = codec->spec;
+
+       mutex_lock(&spec->cs8409_i2c_mux);
+
+       /* Set TIP_SENSE_EN for analog front-end of tip sense. */
+       cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b70, 0x0020, 1);
+       /* Clear WAKE# */
+       cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b71, 0x0001, 1);
+       /* Wait ~2.5ms */
+       usleep_range(2500, 3000);
+       /* Set mode WAKE# output follows the combination logic directly */
+       cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b71, 0x0020, 1);
+       /* Clear interrupts status */
+       cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x130f, 1);
+       cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1b7b, 1);
+       /* Enable interrupt */
+       cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1320, 0x03, 1);
+       cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b79, 0x00, 1);
+
+       mutex_unlock(&spec->cs8409_i2c_mux);
+
+       return 0;
+}
+
+/* Enable and run CS42L42 slave codec jack auto detect */
+static void cs8409_cs42l42_run_jack_detect(struct hda_codec *codec)
+{
+       struct cs_spec *spec = codec->spec;
+
+       mutex_lock(&spec->cs8409_i2c_mux);
+
+       /* Clear interrupts */
+       cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1308, 1);
+       cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1b77, 1);
+
+       cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1102, 0x87, 1);
+       cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1f06, 0x86, 1);
+       cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b74, 0x07, 1);
+       cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x131b, 0x01, 1);
+       cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1120, 0x80, 1);
+       /* Wait ~110ms*/
+       usleep_range(110000, 200000);
+       cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x111f, 0x77, 1);
+       cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1120, 0xc0, 1);
+       /* Wait ~10ms */
+       usleep_range(10000, 25000);
+
+       mutex_unlock(&spec->cs8409_i2c_mux);
+
 }
 
 static void cs8409_cs42l42_reg_setup(struct hda_codec *codec)
 {
        const struct cs8409_i2c_param *seq = cs42l42_init_reg_seq;
+       struct cs_spec *spec = codec->spec;
+
+       mutex_lock(&spec->cs8409_i2c_mux);
 
        for (; seq->addr; seq++)
                cs8409_i2c_write(codec, CS42L42_I2C_ADDR, seq->addr, seq->reg, 1);
 
+       mutex_unlock(&spec->cs8409_i2c_mux);
+
+}
+
+/*
+ * In the case of CS8409 we do not have unsolicited events from NID's 0x24
+ * and 0x34 where hs mic and hp are connected. Companion codec CS42L42 will
+ * generate interrupt via gpio 4 to notify jack events. We have to overwrite
+ * generic snd_hda_jack_unsol_event(), read CS42L42 jack detect status registers
+ * and then notify status via generic snd_hda_jack_unsol_event() call.
+ */
+static void cs8409_jack_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+       struct cs_spec *spec = codec->spec;
+       int status_changed = 0;
+       unsigned int reg_cdc_status = 0;
+       unsigned int reg_hs_status = 0;
+       unsigned int reg_ts_status = 0;
+       int type = 0;
+       struct hda_jack_tbl *jk;
+
+       /* jack_unsol_event() will be called every time gpio line changing state.
+        * In this case gpio4 line goes up as a result of reading interrupt status
+        * registers in previous cs8409_jack_unsol_event() call.
+        * We don't need to handle this event, ignoring...
+        */
+       if ((res & (1 << 4)))
+               return;
+
+       mutex_lock(&spec->cs8409_i2c_mux);
+
+       /* Read jack detect status registers */
+       reg_cdc_status = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1308, 1);
+       reg_hs_status = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1124, 1);
+       reg_ts_status = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x130f, 1);
+
+       /* Clear interrupts */
+       cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1b7b, 1);
+       cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1308, 1);
+       cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x130f, 1);
+
+       mutex_unlock(&spec->cs8409_i2c_mux);
+
+       /* HSDET_AUTO_DONE */
+       if (reg_cdc_status & CS42L42_HSDET_AUTO_DONE) {
+
+               type = ((reg_hs_status & CS42L42_HSTYPE_MASK) + 1);
+               /* CS42L42 reports optical jack as type 4
+                * We don't handle optical jack
+                */
+               if (type != 4) {
+                       if (!spec->cs42l42_hp_jack_in) {
+                               status_changed = 1;
+                               spec->cs42l42_hp_jack_in = 1;
+                       }
+                       /* type = 3 has no mic */
+                       if ((!spec->cs42l42_mic_jack_in) && (type != 3)) {
+                               status_changed = 1;
+                               spec->cs42l42_mic_jack_in = 1;
+                       }
+               } else {
+                       if (spec->cs42l42_hp_jack_in || spec->cs42l42_mic_jack_in) {
+                               status_changed = 1;
+                               spec->cs42l42_hp_jack_in = 0;
+                               spec->cs42l42_mic_jack_in = 0;
+                       }
+               }
+
+       } else {
+               /* TIP_SENSE INSERT/REMOVE */
+               switch (reg_ts_status) {
+               case CS42L42_JACK_INSERTED:
+                       cs8409_cs42l42_run_jack_detect(codec);
+                       break;
+
+               case CS42L42_JACK_REMOVED:
+                       if (spec->cs42l42_hp_jack_in || spec->cs42l42_mic_jack_in) {
+                               status_changed = 1;
+                               spec->cs42l42_hp_jack_in = 0;
+                               spec->cs42l42_mic_jack_in = 0;
+                       }
+                       break;
+
+               default:
+                       /* jack in transition */
+                       status_changed = 0;
+                       break;
+               }
+       }
+
+       if (status_changed) {
+
+               snd_hda_set_pin_ctl(codec, CS8409_CS42L42_SPK_PIN_NID,
+                               (spec->cs42l42_hp_jack_in)?0 : PIN_OUT);
+
+               /* Report jack*/
+               jk = snd_hda_jack_tbl_get_mst(codec, CS8409_CS42L42_HP_PIN_NID, 0);
+               if (jk) {
+                       snd_hda_jack_unsol_event(codec,
+                               (jk->tag << AC_UNSOL_RES_TAG_SHIFT) & AC_UNSOL_RES_TAG);
+               }
+               /* Report jack*/
+               jk = snd_hda_jack_tbl_get_mst(codec, CS8409_CS42L42_AMIC_PIN_NID, 0);
+               if (jk) {
+                       snd_hda_jack_unsol_event(codec,
+                               (jk->tag << AC_UNSOL_RES_TAG_SHIFT) & AC_UNSOL_RES_TAG);
+               }
+       }
 }
 
 static int cs8409_cs42l42_build_controls(struct hda_codec *codec)
@@ -1603,6 +1792,13 @@ static int cs8409_cs42l42_build_controls(struct hda_codec *codec)
 
        snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_BUILD);
 
+       /* Run jack auto detect first time on boot
+        * after controls have been added, to check if jack has
+        * been already plugged in
+        */
+       cs8409_cs42l42_run_jack_detect(codec);
+       usleep_range(100000, 150000);
+
        return 0;
 }
 
@@ -1610,14 +1806,61 @@ static int cs8409_cs42l42_build_controls(struct hda_codec *codec)
 /* Manage PDREF, when transition to D3hot */
 static int cs8409_suspend(struct hda_codec *codec)
 {
+       struct cs_spec *spec = codec->spec;
+
+       mutex_lock(&spec->cs8409_i2c_mux);
+       /* Power down CS42L42 ASP/EQ/MIX/HP */
+       cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1101, 0xfe, 1);
+       mutex_unlock(&spec->cs8409_i2c_mux);
        /* Assert CS42L42 RTS# line */
        snd_hda_codec_write(codec,
                        codec->core.afg, 0, AC_VERB_SET_GPIO_DATA, 0);
+
        snd_hda_shutup_pins(codec);
+
        return 0;
 }
 #endif
 
+static void cs8409_cs42l42_cap_sync_hook(struct hda_codec *codec,
+                                        struct snd_kcontrol *kcontrol,
+                                        struct snd_ctl_elem_value *ucontrol)
+{
+       struct cs_spec *spec = codec->spec;
+       unsigned int curval, expval;
+       /* CS8409 DMIC Pin only allows the setting of the Stream Parameters in
+        * Power State D0. When a headset is unplugged, and the path is switched to
+        * the DMIC, the Stream is restarted with the new ADC, but this is done in
+        * Power State D3. Restart the Stream now DMIC is in D0.
+        */
+       if (spec->gen.cur_adc == CS8409_CS42L42_DMIC_ADC_PIN_NID) {
+               curval = snd_hda_codec_read(codec, spec->gen.cur_adc,
+                       0, AC_VERB_GET_CONV, 0);
+               expval = (spec->gen.cur_adc_stream_tag << 4) | 0;
+               if (curval != expval) {
+                       codec_dbg(codec, "%s Restarting Stream after DMIC switch\n", __func__);
+                       __snd_hda_codec_cleanup_stream(codec, spec->gen.cur_adc, 1);
+                       snd_hda_codec_setup_stream(codec, spec->gen.cur_adc,
+                                          spec->gen.cur_adc_stream_tag, 0,
+                                          spec->gen.cur_adc_format);
+               }
+       }
+}
+
+/* Enable/Disable Unsolicited Response for gpio(s) 3,4 */
+static void cs8409_enable_ur(struct hda_codec *codec, int flag)
+{
+       /* GPIO4 INT# and GPIO3 WAKE# */
+       snd_hda_codec_write(codec, codec->core.afg,
+                       0, AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK,
+                       flag?(GPIO3_INT | GPIO4_INT) : 0);
+
+       snd_hda_codec_write(codec, codec->core.afg,
+                       0, AC_VERB_SET_UNSOLICITED_ENABLE,
+                       flag?AC_UNSOL_ENABLED : 0);
+
+}
+
 /* Vendor specific HW configuration
  * PLL, ASP, I2C, SPI, GPIOs, DMIC etc...
  */
@@ -1638,6 +1881,9 @@ static int cs8409_cs42l42_hw_init(struct hda_codec *codec)
        for (; seq->nid; seq++)
                cs_vendor_coef_set(codec, seq->cir, seq->coeff);
 
+       /* Disable Unsolicited Response during boot */
+       cs8409_enable_ur(codec, 0);
+
        /* Reset CS42L42 */
        cs8409_cs42l42_reset(codec);
 
@@ -1647,11 +1893,18 @@ static int cs8409_cs42l42_hw_init(struct hda_codec *codec)
        if (codec->fixup_id == CS8409_WARLOCK ||
                        codec->fixup_id == CS8409_CYBORG) {
                /* FULL_SCALE_VOL = 0 for Warlock / Cyborg */
+               mutex_lock(&spec->cs8409_i2c_mux);
                cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x2001, 0x01, 1);
+               mutex_unlock(&spec->cs8409_i2c_mux);
                /* DMIC1_MO=00b, DMIC1/2_SR=1 */
                cs_vendor_coef_set(codec, 0x09, 0x0003);
        }
 
+       cs8409_cs42l42_enable_jack_detect(codec);
+
+       /* Enable Unsolicited Response */
+       cs8409_enable_ur(codec, 1);
+
        return 1;
 }
 
@@ -1671,6 +1924,8 @@ static int cs8409_cs42l42_init(struct hda_codec *codec)
 
                cs8409_cs42l42_hw_init(codec);
 
+               cs8409_cs42l42_run_jack_detect(codec);
+               usleep_range(100000, 150000);
        }
 
        return ret;
@@ -1681,7 +1936,7 @@ static const struct hda_codec_ops cs8409_cs42l42_patch_ops = {
        .build_pcms = snd_hda_gen_build_pcms,
        .init = cs8409_cs42l42_init,
        .free = cs_free,
-       .unsol_event = snd_hda_jack_unsol_event,
+       .unsol_event = cs8409_jack_unsol_event,
 #ifdef CONFIG_PM
        .suspend = cs8409_suspend,
 #endif
@@ -1741,6 +1996,45 @@ static int cs8409_cs42l42_fixup(struct hda_codec *codec)
        return err;
 }
 
+static int cs8409_cs42l42_exec_verb(struct hdac_device *dev,
+               unsigned int cmd, unsigned int flags, unsigned int *res)
+{
+       struct hda_codec *codec = container_of(dev, struct hda_codec, core);
+       struct cs_spec *spec = codec->spec;
+
+       unsigned int nid = 0;
+       unsigned int verb = 0;
+
+       nid = ((cmd >> 20) & 0x07f);
+       verb = ((cmd >> 8) & 0x0fff);
+
+       /* CS8409 pins have no AC_PINSENSE_PRESENCE
+        * capabilities. We have to intercept 2 calls for pins 0x24 and 0x34
+        * and return correct pin sense values for read_pin_sense() call from
+        * hda_jack based on CS42L42 jack detect status.
+        */
+       switch (nid) {
+       case CS8409_CS42L42_HP_PIN_NID:
+               if (verb == AC_VERB_GET_PIN_SENSE) {
+                       *res = (spec->cs42l42_hp_jack_in) ? AC_PINSENSE_PRESENCE : 0;
+                       return 0;
+               }
+               break;
+
+       case CS8409_CS42L42_AMIC_PIN_NID:
+               if (verb == AC_VERB_GET_PIN_SENSE) {
+                       *res = (spec->cs42l42_mic_jack_in) ? AC_PINSENSE_PRESENCE : 0;
+                       return 0;
+               }
+               break;
+
+       default:
+               break;
+       }
+
+       return spec->exec_verb(dev, cmd, flags, res);
+}
+
 static int patch_cs8409(struct hda_codec *codec)
 {
        struct cs_spec *spec;
@@ -1766,8 +2060,16 @@ static int patch_cs8409(struct hda_codec *codec)
 
                snd_hda_add_verbs(codec, cs8409_cs42l42_add_verbs);
 
+               /* verb exec op override */
+               spec->exec_verb = codec->core.exec_verb;
+               codec->core.exec_verb = cs8409_cs42l42_exec_verb;
+
+               mutex_init(&spec->cs8409_i2c_mux);
+
                codec->patch_ops = cs8409_cs42l42_patch_ops;
 
+               spec->gen.cap_sync_hook = cs8409_cs42l42_cap_sync_hook;
+
                spec->gen.suppress_auto_mute = 1;
                spec->gen.no_primary_hp = 1;
                /* GPIO 5 out, 3,4 in */
@@ -1775,6 +2077,9 @@ static int patch_cs8409(struct hda_codec *codec)
                spec->gpio_data = 0;
                spec->gpio_mask = 0x03f;
 
+               spec->cs42l42_hp_jack_in = 0;
+               spec->cs42l42_mic_jack_in = 0;
+
                err = cs8409_cs42l42_fixup(codec);
 
                if (err > 0)