alsa-devel.alsa-project.org archive mirror
 help / color / mirror / Atom feed
* Re: [PATCH 2/4] ALSA: hda/cirrus: Add support for CS8409 HDA bridge and CS42L42 companion codec.
       [not found] ` <20210303182959.5322-3-vitalyr@opensource.cirrus.com>
@ 2021-03-04 13:39   ` Takashi Iwai
  2021-03-04 18:36     ` Vitaly Rodionov
  0 siblings, 1 reply; 7+ messages in thread
From: Takashi Iwai @ 2021-03-04 13:39 UTC (permalink / raw)
  To: Vitaly Rodionov; +Cc: patches, alsa-devel, Takashi Iwai, linux-kernel

On Wed, 03 Mar 2021 19:29:57 +0100,
Vitaly Rodionov wrote:
> 
> +static const struct hda_verb cs8409_cs42l42_init_verbs[] = {
> +	{ 0x01, AC_VERB_SET_POWER_STATE, 0x0000 },    /* AFG: D0 */

I guess this power state change is superfluous.  The AFG node is
already powered up when the codec probe or init is called.

> +	{ 0x01, AC_VERB_SET_GPIO_DIRECTION, 0x0020 }, /* GPIO 5 out, 3,4 in */
> +	{ 0x01, AC_VERB_SET_GPIO_DATA, 0x0000 },      /* GPIO  data 0 */
> +	{ 0x01, AC_VERB_SET_GPIO_MASK, 0x003f },      /* Enable GPIO */

Those are handled in spec->gpio_dir, gpio->data and gpio->mask
fields.

> +	{ 0x01, AC_VERB_SET_GPIO_WAKE_MASK, 0x0018 }, /* WAKE from GPIO 3,4 */
> +	{ 0x47, AC_VERB_SET_PROC_STATE, 0x0001 },     /* Enable VPW processing  */
> +	{ 0x47, AC_VERB_SET_COEF_INDEX, 0x0002 },     /* Configure GPIO 6,7 */
> +	{ 0x47, AC_VERB_SET_PROC_COEF,  0x0080 },     /* I2C mode */
> +	{ 0x47, AC_VERB_SET_COEF_INDEX, 0x005b },     /* Set I2C bus speed */
> +	{ 0x47, AC_VERB_SET_PROC_COEF,  0x0200 },     /* 100kHz I2C_STO = 2 */

Those remaining verbs are good in the init verbs.  But I suppose they
have to be applied at the resume as well?  But...

> +static int cs8409_cs42l42_fixup(struct hda_codec *codec)
> +{
> +	int err = 0;
> +	struct cs_spec *spec = codec->spec;
> +	unsigned int pincap = 0;
> +
> +	/* Basic initial sequence for specific hw configuration */
> +	snd_hda_sequence_write(codec, cs8409_cs42l42_init_verbs);

... it seems applied only at the fixup call at parsing?

Ditto about cs8409_cs42l42_hw_init(codec).


thanks,

Takashi

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 3/4] ALSA: hda/cirrus: Add jack detect interrupt support from CS42L42 companion codec.
       [not found] ` <20210303182959.5322-4-vitalyr@opensource.cirrus.com>
@ 2021-03-04 13:45   ` Takashi Iwai
  2021-03-04 18:44     ` Vitaly Rodionov
  0 siblings, 1 reply; 7+ messages in thread
From: Takashi Iwai @ 2021-03-04 13:45 UTC (permalink / raw)
  To: Vitaly Rodionov; +Cc: patches, alsa-devel, Takashi Iwai, linux-kernel

On Wed, 03 Mar 2021 19:29:58 +0100,
Vitaly Rodionov wrote:
> @@ -1243,6 +1258,8 @@ static int patch_cs4213(struct hda_codec *codec)
>  #define CIR_I2C_QWRITE	0x005D
>  #define CIR_I2C_QREAD	0x005E
>  
> +static struct mutex cs8409_i2c_mux;

Any reason that this must be the global mutex?  I suppose it can fit
in own spec->i2c_mutex instead?


> +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);

Hrm, this looks a big scary.  We may need to reconsider how to handle
this better later, but it's OK as long as you've tested with this
code...

> +static int cs8409_cs42l42_init(struct hda_codec *codec)
> +{
> +	int ret = 0;
> +
> +	ret = snd_hda_gen_init(codec);
> +
> +	if (!ret) {
> +		/* On Dell platforms with suspend D3 mode support we
> +		 * have to re-initialise cs8409 bridge and companion
> +		 * cs42l42 codec
> +		 */
> +		snd_hda_sequence_write(codec, cs8409_cs42l42_init_verbs);
> +		snd_hda_sequence_write(codec, cs8409_cs42l42_add_verbs);
> +
> +		cs8409_cs42l42_hw_init(codec);

Ah... the init stuff at resume appears finally here.  This part should
be in the second patch instead.

> +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;

The blank line above should be removed.

> +	case CS8409_CS42L42_HP_PIN_NID:
> +		if (verb == AC_VERB_GET_PIN_SENSE) {
> +			*res = (spec->cs42l42_hp_jack_in)?AC_PINSENSE_PRESENCE:0;

The spaces are needed around operators.
Similar coding-style issues are seen other places.  Please try to run
scripts/checkpatch.pl.


thanks,

Takashi

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 4/4] ALSA: hda/cirrus: Add Headphone and Headset MIC Volume Control
       [not found] ` <20210303182959.5322-5-vitalyr@opensource.cirrus.com>
@ 2021-03-04 13:49   ` Takashi Iwai
  2021-03-04 18:45     ` Vitaly Rodionov
  0 siblings, 1 reply; 7+ messages in thread
From: Takashi Iwai @ 2021-03-04 13:49 UTC (permalink / raw)
  To: Vitaly Rodionov
  Cc: alsa-devel, patches, Stefan Binding, Takashi Iwai, linux-kernel

On Wed, 03 Mar 2021 19:29:59 +0100,
Vitaly Rodionov wrote:
> 
> +static int cs8409_cs42l42_volume_get(struct snd_kcontrol *kcontrol,
> +				 struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
> +	hda_nid_t nid = get_amp_nid(kcontrol);
> +	int chs = get_amp_channels(kcontrol);
> +	long *valp = ucontrol->value.integer.value;
> +	char vol = 0;
> +
> +	codec_dbg(codec, "%s() nid: %d\n", __func__, nid);
> +	snd_hda_power_up(codec);
> +	switch (nid) {
> +	case CS8409_CS42L42_HP_PIN_NID:
> +		mutex_lock(&cs8409_i2c_mux);
> +		if (chs & 1) {
> +			vol = -(cs8409_i2c_read(codec, CS42L42_I2C_ADDR,
> +				CS8409_CS42L42_REG_HS_VOLUME_CHA, 1));

Better to cache the values instead of i2c read at each time?
Then the unnecessary power up/down sequence can be avoided, too.


thanks,

Takashi

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 2/4] ALSA: hda/cirrus: Add support for CS8409 HDA bridge and CS42L42 companion codec.
  2021-03-04 13:39   ` [PATCH 2/4] ALSA: hda/cirrus: Add support for CS8409 HDA bridge and CS42L42 companion codec Takashi Iwai
@ 2021-03-04 18:36     ` Vitaly Rodionov
  0 siblings, 0 replies; 7+ messages in thread
From: Vitaly Rodionov @ 2021-03-04 18:36 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: patches, alsa-devel, Takashi Iwai, linux-kernel

On 04/03/2021 1:39 pm, Takashi Iwai wrote:
Thank you very much for quick response!
> On Wed, 03 Mar 2021 19:29:57 +0100,
> Vitaly Rodionov wrote:
>> +static const struct hda_verb cs8409_cs42l42_init_verbs[] = {
>> +	{ 0x01, AC_VERB_SET_POWER_STATE, 0x0000 },    /* AFG: D0 */
> I guess this power state change is superfluous.  The AFG node is
> already powered up when the codec probe or init is called.
Yes, This should be removed
>
>> +	{ 0x01, AC_VERB_SET_GPIO_DIRECTION, 0x0020 }, /* GPIO 5 out, 3,4 in */
>> +	{ 0x01, AC_VERB_SET_GPIO_DATA, 0x0000 },      /* GPIO  data 0 */
>> +	{ 0x01, AC_VERB_SET_GPIO_MASK, 0x003f },      /* Enable GPIO */
> Those are handled in spec->gpio_dir, gpio->data and gpio->mask
> fields.
Will handle via spec fields.
>
>> +	{ 0x01, AC_VERB_SET_GPIO_WAKE_MASK, 0x0018 }, /* WAKE from GPIO 3,4 */
>> +	{ 0x47, AC_VERB_SET_PROC_STATE, 0x0001 },     /* Enable VPW processing  */
>> +	{ 0x47, AC_VERB_SET_COEF_INDEX, 0x0002 },     /* Configure GPIO 6,7 */
>> +	{ 0x47, AC_VERB_SET_PROC_COEF,  0x0080 },     /* I2C mode */
>> +	{ 0x47, AC_VERB_SET_COEF_INDEX, 0x005b },     /* Set I2C bus speed */
>> +	{ 0x47, AC_VERB_SET_PROC_COEF,  0x0200 },     /* 100kHz I2C_STO = 2 */
> Those remaining verbs are good in the init verbs.  But I suppose they
> have to be applied at the resume as well?  But...
This was added in the next patch, but you are right we should handle it 
here.
>
>> +static int cs8409_cs42l42_fixup(struct hda_codec *codec)
>> +{
>> +	int err = 0;
>> +	struct cs_spec *spec = codec->spec;
>> +	unsigned int pincap = 0;
>> +
>> +	/* Basic initial sequence for specific hw configuration */
>> +	snd_hda_sequence_write(codec, cs8409_cs42l42_init_verbs);
> ... it seems applied only at the fixup call at parsing?
>
> Ditto about cs8409_cs42l42_hw_init(codec).
Yes, needs to be fixed. I will make v2 patch set.
>
>
> thanks,
>
> Takashi



^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 3/4] ALSA: hda/cirrus: Add jack detect interrupt support from CS42L42 companion codec.
  2021-03-04 13:45   ` [PATCH 3/4] ALSA: hda/cirrus: Add jack detect interrupt support from " Takashi Iwai
@ 2021-03-04 18:44     ` Vitaly Rodionov
  0 siblings, 0 replies; 7+ messages in thread
From: Vitaly Rodionov @ 2021-03-04 18:44 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: patches, alsa-devel, Takashi Iwai, linux-kernel

On 04/03/2021 1:45 pm, Takashi Iwai wrote:
> On Wed, 03 Mar 2021 19:29:58 +0100,
> Vitaly Rodionov wrote:
>> @@ -1243,6 +1258,8 @@ static int patch_cs4213(struct hda_codec *codec)
>>   #define CIR_I2C_QWRITE	0x005D
>>   #define CIR_I2C_QREAD	0x005E
>>   
>> +static struct mutex cs8409_i2c_mux;
> Any reason that this must be the global mutex?  I suppose it can fit
> in own spec->i2c_mutex instead?
No,  there is no reason to have global mutex, will move it out to spec.
>
>
>> +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);
> Hrm, this looks a big scary.  We may need to reconsider how to handle
> this better later, but it's OK as long as you've tested with this
> code...

We have been thinking about this code, and we have some ideas , it was 
tested by us, DELL and Canonical and works.

But we would like to change it a bit later, and handle it in a more 
generic way.

>
>> +static int cs8409_cs42l42_init(struct hda_codec *codec)
>> +{
>> +	int ret = 0;
>> +
>> +	ret = snd_hda_gen_init(codec);
>> +
>> +	if (!ret) {
>> +		/* On Dell platforms with suspend D3 mode support we
>> +		 * have to re-initialise cs8409 bridge and companion
>> +		 * cs42l42 codec
>> +		 */
>> +		snd_hda_sequence_write(codec, cs8409_cs42l42_init_verbs);
>> +		snd_hda_sequence_write(codec, cs8409_cs42l42_add_verbs);
>> +
>> +		cs8409_cs42l42_hw_init(codec);
> Ah... the init stuff at resume appears finally here.  This part should
> be in the second patch instead.
Yes, moving this into second patch.
>
>> +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;
> The blank line above should be removed.
>
>> +	case CS8409_CS42L42_HP_PIN_NID:
>> +		if (verb == AC_VERB_GET_PIN_SENSE) {
>> +			*res = (spec->cs42l42_hp_jack_in)?AC_PINSENSE_PRESENCE:0;
> The spaces are needed around operators.
> Similar coding-style issues are seen other places.  Please try to run
> scripts/checkpatch.pl.

Fixed, and checked with scripts/checkpatch.pl. Base has 19 warnings. 
This patch will not introduce any new.

>
>
> thanks,
>
> Takashi

Thanks,

Vitaly



^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 4/4] ALSA: hda/cirrus: Add Headphone and Headset MIC Volume Control
  2021-03-04 13:49   ` [PATCH 4/4] ALSA: hda/cirrus: Add Headphone and Headset MIC Volume Control Takashi Iwai
@ 2021-03-04 18:45     ` Vitaly Rodionov
  0 siblings, 0 replies; 7+ messages in thread
From: Vitaly Rodionov @ 2021-03-04 18:45 UTC (permalink / raw)
  To: Takashi Iwai
  Cc: alsa-devel, patches, Stefan Binding, Takashi Iwai, linux-kernel

On 04/03/2021 1:49 pm, Takashi Iwai wrote:
> On Wed, 03 Mar 2021 19:29:59 +0100,
> Vitaly Rodionov wrote:
>> +static int cs8409_cs42l42_volume_get(struct snd_kcontrol *kcontrol,
>> +				 struct snd_ctl_elem_value *ucontrol)
>> +{
>> +	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
>> +	hda_nid_t nid = get_amp_nid(kcontrol);
>> +	int chs = get_amp_channels(kcontrol);
>> +	long *valp = ucontrol->value.integer.value;
>> +	char vol = 0;
>> +
>> +	codec_dbg(codec, "%s() nid: %d\n", __func__, nid);
>> +	snd_hda_power_up(codec);
>> +	switch (nid) {
>> +	case CS8409_CS42L42_HP_PIN_NID:
>> +		mutex_lock(&cs8409_i2c_mux);
>> +		if (chs & 1) {
>> +			vol = -(cs8409_i2c_read(codec, CS42L42_I2C_ADDR,
>> +				CS8409_CS42L42_REG_HS_VOLUME_CHA, 1));
> Better to cache the values instead of i2c read at each time?
> Then the unnecessary power up/down sequence can be avoided, too.
Yes, agree. Will be fixed in next version of patch.
>
>
> thanks,
>
> Takashi

Thank you,

Vitaly


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH 3/4] ALSA: hda/cirrus: Add jack detect interrupt support from CS42L42 companion codec.
  2021-03-03 18:53 [PATCH 0/4] ALSA: hda/cirrus: Add support for CS8409 HDA bridge and CS42L42 companion codec Vitaly Rodionov
@ 2021-03-03 18:53 ` Vitaly Rodionov
  0 siblings, 0 replies; 7+ messages in thread
From: Vitaly Rodionov @ 2021-03-03 18:53 UTC (permalink / raw)
  To: Jaroslav Kysela, Takashi Iwai; +Cc: patches, alsa-devel, linux-kernel

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>
---
 sound/pci/hda/patch_cirrus.c | 335 +++++++++++++++++++++++++++++++++--
 1 file changed, 324 insertions(+), 11 deletions(-)

diff --git a/sound/pci/hda/patch_cirrus.c b/sound/pci/hda/patch_cirrus.c
index 9d664982544f..fa417f7adc7d 100644
--- a/sound/pci/hda/patch_cirrus.c
+++ b/sound/pci/hda/patch_cirrus.c
@@ -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,13 @@ 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;
+
+	/* 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 +1237,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)
@@ -1243,6 +1258,8 @@ static int patch_cs4213(struct hda_codec *codec)
 #define CIR_I2C_QWRITE	0x005D
 #define CIR_I2C_QREAD	0x005E
 
+static struct mutex cs8409_i2c_mux;
+
 struct cs8409_i2c_param {
 	unsigned int addr;
 	unsigned int reg;
@@ -1433,6 +1450,7 @@ static const struct cs8409_i2c_param cs42l42_init_reg_seq[] = {
 	{ 0x1C03, 0xC0 },
 	{ 0x1105, 0x00 },
 	{ 0x1112, 0xC0 },
+	{ 0x1101, 0x02 },
 	{} /* Terminator */
 };
 
@@ -1580,21 +1598,180 @@ static void cs8409_cs42l42_reset(struct hda_codec *codec)
 	/* wait ~10ms */
 	usleep_range(10000, 15000);
 
-	/* Clear interrupts status */
+	mutex_lock(&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(&cs8409_i2c_mux);
+
+}
+
+/* Configure CS42L42 slave codec for jack autodetect */
+static int cs8409_cs42l42_enable_jack_detect(struct hda_codec *codec)
+{
+	mutex_lock(&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(&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)
+{
+	mutex_lock(&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(&cs8409_i2c_mux);
+
 }
 
 static void cs8409_cs42l42_reg_setup(struct hda_codec *codec)
 {
 	const struct cs8409_i2c_param *seq = cs42l42_init_reg_seq;
 
+	mutex_lock(&cs8409_i2c_mux);
+
 	for (; seq->addr; seq++)
 		cs8409_i2c_write(codec, CS42L42_I2C_ADDR, seq->addr, seq->reg, 1);
 
+	mutex_unlock(&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.
+ */
+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(&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(&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 {
+		/* 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)
@@ -1607,6 +1784,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;
 }
 
@@ -1614,21 +1798,55 @@ static int cs8409_cs42l42_build_controls(struct hda_codec *codec)
 /* Manage PDREF, when transition to D3hot */
 static int cs8409_suspend(struct hda_codec *codec)
 {
+	mutex_lock(&cs8409_i2c_mux);
+	/* Power down CS42L42 ASP/EQ/MIX/HP */
+	cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1101, 0xfe, 1);
+	mutex_unlock(&cs8409_i2c_mux);
+
 	snd_hda_shutup_pins(codec);
+
 	return 0;
 }
 #endif
 
-static const struct hda_codec_ops cs8409_cs42l42_patch_ops = {
-	.build_controls = cs8409_cs42l42_build_controls,
-	.build_pcms = snd_hda_gen_build_pcms,
-	.init = snd_hda_gen_init,
-	.free = cs_free,
-	.unsol_event = snd_hda_jack_unsol_event,
-#ifdef CONFIG_PM
-	.suspend = cs8409_suspend,
-#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...
@@ -1640,6 +1858,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);
 
@@ -1650,14 +1871,55 @@ static int cs8409_cs42l42_hw_init(struct hda_codec *codec)
 			codec->fixup_id == CS8409_CYBORG) {
 
 		/* FULL_SCALE_VOL = 0 for Warlock / Cyborg */
+		mutex_lock(&cs8409_i2c_mux);
 		cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x2001, 0x01, 1);
+		mutex_unlock(&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;
 }
 
+static int cs8409_cs42l42_init(struct hda_codec *codec)
+{
+	int ret = 0;
+
+	ret = snd_hda_gen_init(codec);
+
+	if (!ret) {
+		/* On Dell platforms with suspend D3 mode support we
+		 * have to re-initialise cs8409 bridge and companion
+		 * cs42l42 codec
+		 */
+		snd_hda_sequence_write(codec, cs8409_cs42l42_init_verbs);
+		snd_hda_sequence_write(codec, cs8409_cs42l42_add_verbs);
+
+		cs8409_cs42l42_hw_init(codec);
+
+		cs8409_cs42l42_run_jack_detect(codec);
+		usleep_range(100000, 150000);
+	}
+
+	return ret;
+}
+
+static const struct hda_codec_ops cs8409_cs42l42_patch_ops = {
+	.build_controls = cs8409_cs42l42_build_controls,
+	.build_pcms = snd_hda_gen_build_pcms,
+	.init = cs8409_cs42l42_init,
+	.free = cs_free,
+	.unsol_event = cs8409_jack_unsol_event,
+#ifdef CONFIG_PM
+	.suspend = cs8409_suspend,
+#endif
+};
+
 static int cs8409_cs42l42_fixup(struct hda_codec *codec)
 {
 	int err = 0;
@@ -1712,6 +1974,46 @@ 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;
@@ -1737,11 +2039,22 @@ 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(&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;
 
+		spec->cs42l42_hp_jack_in = 0;
+		spec->cs42l42_mic_jack_in = 0;
+
 		err = cs8409_cs42l42_fixup(codec);
 
 		if (err > 0)
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2021-03-04 19:00 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <20210303182959.5322-1-vitalyr@opensource.cirrus.com>
     [not found] ` <20210303182959.5322-3-vitalyr@opensource.cirrus.com>
2021-03-04 13:39   ` [PATCH 2/4] ALSA: hda/cirrus: Add support for CS8409 HDA bridge and CS42L42 companion codec Takashi Iwai
2021-03-04 18:36     ` Vitaly Rodionov
     [not found] ` <20210303182959.5322-4-vitalyr@opensource.cirrus.com>
2021-03-04 13:45   ` [PATCH 3/4] ALSA: hda/cirrus: Add jack detect interrupt support from " Takashi Iwai
2021-03-04 18:44     ` Vitaly Rodionov
     [not found] ` <20210303182959.5322-5-vitalyr@opensource.cirrus.com>
2021-03-04 13:49   ` [PATCH 4/4] ALSA: hda/cirrus: Add Headphone and Headset MIC Volume Control Takashi Iwai
2021-03-04 18:45     ` Vitaly Rodionov
2021-03-03 18:53 [PATCH 0/4] ALSA: hda/cirrus: Add support for CS8409 HDA bridge and CS42L42 companion codec Vitaly Rodionov
2021-03-03 18:53 ` [PATCH 3/4] ALSA: hda/cirrus: Add jack detect interrupt support from " Vitaly Rodionov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).