ALSA: hda/conexant: Fix headset auto detect fail in cx8070 and SN6140

Message ID 20240102060457.4917-1-bo.liu@senarytech.com
State New
Headers
Series ALSA: hda/conexant: Fix headset auto detect fail in cx8070 and SN6140 |

Commit Message

bo liu Jan. 2, 2024, 6:04 a.m. UTC
  When OMTP headset plugin the headset jack of CX8070 and SN6160 sound cards,
the headset type detection circuit will recognize the headset type as CTIA.
At this point, plugout and plugin the headset will get the correct headset
type as OMTP.
The reason for the failure of headset type recognition is that the sound
card creation will enable the VREF voltage of the headset mic, which
interferes with the headset type automatic detection circuit. Plugout and
plugin the headset will restart the headset detection and get the correct
headset type.
The patch is disable the VREF voltage when the headset is not present, and
will enable the VREF voltage when the headset is present.

Signed-off-by: bo liu <bo.liu@senarytech.com>
---
 sound/pci/hda/patch_conexant.c | 72 +++++++++++++++++++++++++++++++++-
 1 file changed, 71 insertions(+), 1 deletion(-)
  

Comments

Takashi Iwai Jan. 2, 2024, 3:11 p.m. UTC | #1
On Tue, 02 Jan 2024 07:04:57 +0100,
bo liu wrote:
> 
> When OMTP headset plugin the headset jack of CX8070 and SN6160 sound cards,
> the headset type detection circuit will recognize the headset type as CTIA.
> At this point, plugout and plugin the headset will get the correct headset
> type as OMTP.
> The reason for the failure of headset type recognition is that the sound
> card creation will enable the VREF voltage of the headset mic, which
> interferes with the headset type automatic detection circuit. Plugout and
> plugin the headset will restart the headset detection and get the correct
> headset type.
> The patch is disable the VREF voltage when the headset is not present, and
> will enable the VREF voltage when the headset is present.
> 
> Signed-off-by: bo liu <bo.liu@senarytech.com>

Thanks, this is *much* better than the previous version!

However, something still need to be fixed in the content:

> ---
>  sound/pci/hda/patch_conexant.c | 72 +++++++++++++++++++++++++++++++++-
>  1 file changed, 71 insertions(+), 1 deletion(-)
> 
> diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c
> index a889cccdd607..e24befa1fad9 100644
> --- a/sound/pci/hda/patch_conexant.c
> +++ b/sound/pci/hda/patch_conexant.c
> @@ -166,6 +166,7 @@ static void cxt_init_gpio_led(struct hda_codec *codec)
>  
>  static int cx_auto_init(struct hda_codec *codec)
>  {
> +	unsigned int mic_persent;
>  	struct conexant_spec *spec = codec->spec;
>  	snd_hda_gen_init(codec);
>  	if (!spec->dynamic_eapd)
> @@ -174,6 +175,22 @@ static int cx_auto_init(struct hda_codec *codec)
>  	cxt_init_gpio_led(codec);
>  	snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_INIT);
>  
> +	switch (codec->core.vendor_id) {
> +	case 0x14f11f86:
> +	case 0x14f11f87:

If those ID checks appear multiple times, it's better to make it as a
flag in conexant_spec, and set it at the probe time.

> +		/* fix some headset type recognize fail issue, such as EDIFIER headset */
> +		snd_hda_codec_write(codec, 0x1c, 0, 0x320, 0x010);
> +		snd_hda_codec_write(codec, 0x1c, 0, 0x3b0, 0xe10);
> +		snd_hda_codec_write(codec, 0x1c, 0, 0x4f0, 0x0eb);
(snip)

Those code can be better factored out to a function.
It'll lead to less indentation, hence it makes easier to read, too.

> @@ -192,6 +209,58 @@ static void cx_auto_free(struct hda_codec *codec)
>  	snd_hda_gen_free(codec);
>  }
>  
> +static int headset_present_flag;

It's bad to use a static variable here.  In theory, there can be
multiple same codecs used on the bus.

If any, put this into conexant_spec and use locally.

> +static void cx_jack_unsol_event(struct hda_codec *codec, unsigned int res)
> +{
> +	unsigned int val, phone_present, mic_persent, phone_tag, mic_tag;
> +	unsigned int count = 0;
> +
> +	switch (codec->core.vendor_id) {
> +	case 0x14f11f86:
> +	case 0x14f11f87:

Again, use a different flag.  Also factor out the specific code into a
function.

> +		/* check hp&mic tag to process headset pulgin&plugout */
> +		phone_tag = snd_hda_codec_read(codec, 0x16, 0, 0xf08, 0x0);
> +		mic_tag = snd_hda_codec_read(codec, 0x19, 0, 0xf08, 0x0);

Are those pins *always* fixed to 0x16 and 0x19?  Or they might be
assigned to different pins...?   In the latter case, the pin nid
should be taken from the parsed configuration instead of fixed
numbers.

> +		if ((phone_tag&(res>>26)) || (mic_tag&(res>>26))) {

Some coding style issues here.  Consult scripts/checkpatch.pl.
Also avoid a magic number.  0xf08 is AC_VERB_GET_UNSOLICITED_RESPONSE,
and 26 is AC_UNSOL_RES_TAG_SHIFT, for example.

> +			phone_present = snd_hda_codec_read(codec, 0x16, 0, 0xf09, 0x0);
> +			if (!(phone_present&0x80000000)) {/* headphone plugout */

Ditto.  0x80000000 is AC_PINSENSE_PRESENCE.

> +				headset_present_flag = 0;

Better to use an enum to hold the state instead of the raw 0, 1, 2.


> +				break;
> +			}
> +			if (headset_present_flag == 0) {
> +				headset_present_flag = 1;
> +			} else if (headset_present_flag == 1) {
> +				mic_persent = snd_hda_codec_read(codec, 0x19, 0, 0xf09, 0x0);
> +				/* headset is present */
> +				if ((phone_present&0x80000000) && (mic_persent&0x80000000)) {
> +					/* wait headset detect done */
> +					do {
> +						msleep(20);
> +						val = snd_hda_codec_read(codec, 0x1c,
> +									0, 0xca0, 0x0);
> +						count += 1;

Usually we use "++" for increment.


thanks,

Takashi
  

Patch

diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c
index a889cccdd607..e24befa1fad9 100644
--- a/sound/pci/hda/patch_conexant.c
+++ b/sound/pci/hda/patch_conexant.c
@@ -166,6 +166,7 @@  static void cxt_init_gpio_led(struct hda_codec *codec)
 
 static int cx_auto_init(struct hda_codec *codec)
 {
+	unsigned int mic_persent;
 	struct conexant_spec *spec = codec->spec;
 	snd_hda_gen_init(codec);
 	if (!spec->dynamic_eapd)
@@ -174,6 +175,22 @@  static int cx_auto_init(struct hda_codec *codec)
 	cxt_init_gpio_led(codec);
 	snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_INIT);
 
+	switch (codec->core.vendor_id) {
+	case 0x14f11f86:
+	case 0x14f11f87:
+		/* fix some headset type recognize fail issue, such as EDIFIER headset */
+		snd_hda_codec_write(codec, 0x1c, 0, 0x320, 0x010);
+		snd_hda_codec_write(codec, 0x1c, 0, 0x3b0, 0xe10);
+		snd_hda_codec_write(codec, 0x1c, 0, 0x4f0, 0x0eb);
+		/* fix reboot headset type recognize fail issue */
+		mic_persent = snd_hda_codec_read(codec, 0x19, 0, 0xf09, 0x0);
+		if (mic_persent&0x80000000)
+			snd_hda_codec_write(codec, 0x19, 0, 0x707, 0x24);
+		else
+			snd_hda_codec_write(codec, 0x19, 0, 0x707, 0x20);
+		break;
+	}
+
 	return 0;
 }
 
@@ -192,6 +209,58 @@  static void cx_auto_free(struct hda_codec *codec)
 	snd_hda_gen_free(codec);
 }
 
+static int headset_present_flag;
+static void cx_jack_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+	unsigned int val, phone_present, mic_persent, phone_tag, mic_tag;
+	unsigned int count = 0;
+
+	switch (codec->core.vendor_id) {
+	case 0x14f11f86:
+	case 0x14f11f87:
+		/* check hp&mic tag to process headset pulgin&plugout */
+		phone_tag = snd_hda_codec_read(codec, 0x16, 0, 0xf08, 0x0);
+		mic_tag = snd_hda_codec_read(codec, 0x19, 0, 0xf08, 0x0);
+		if ((phone_tag&(res>>26)) || (mic_tag&(res>>26))) {
+			phone_present = snd_hda_codec_read(codec, 0x16, 0, 0xf09, 0x0);
+			if (!(phone_present&0x80000000)) {/* headphone plugout */
+				headset_present_flag = 0;
+				snd_hda_codec_write(codec, 0x19, 0, 0x707, 0x20);
+				break;
+			}
+			if (headset_present_flag == 0) {
+				headset_present_flag = 1;
+			} else if (headset_present_flag == 1) {
+				mic_persent = snd_hda_codec_read(codec, 0x19, 0, 0xf09, 0x0);
+				/* headset is present */
+				if ((phone_present&0x80000000) && (mic_persent&0x80000000)) {
+					/* wait headset detect done */
+					do {
+						msleep(20);
+						val = snd_hda_codec_read(codec, 0x1c,
+									0, 0xca0, 0x0);
+						count += 1;
+					} while ((count > 3) || (val&0x080));
+					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, 0x707, 0x24);
+					} else if (val&0x400) {
+						codec_dbg(codec, "headset plugin, type is OMTP\n");
+						snd_hda_codec_write(codec, 0x19, 0, 0x707, 0x24);
+					} else {
+						codec_dbg(codec, "headphone plugin\n");
+					}
+					headset_present_flag = 2;
+				}
+			}
+		}
+		break;
+	}
+
+	snd_hda_jack_unsol_event(codec, res);
+}
+
 #ifdef CONFIG_PM
 static int cx_auto_suspend(struct hda_codec *codec)
 {
@@ -205,7 +274,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 +1111,7 @@  static int patch_conexant_auto(struct hda_codec *codec)
 	codec->spec = spec;
 	codec->patch_ops = cx_auto_patch_ops;
 
+	headset_present_flag = 0;
 	cx_auto_parse_eapd(codec);
 	spec->gen.own_eapd_ctl = 1;