[PATCH] ALSA: hda: Add a new CM9825 standard driver.

Leo Tsai posted 1 patch 2 months, 3 weeks ago
There is a newer version of this series
sound/pci/hda/patch_cmedia.c | 268 +++++++++++++++++++++++++++++++++++
1 file changed, 268 insertions(+)
mode change 100644 => 100755 sound/pci/hda/patch_cmedia.c
[PATCH] ALSA: hda: Add a new CM9825 standard driver.
Posted by Leo Tsai 2 months, 3 weeks ago
The CM9825 is a High Definition Audio Codec.
There are 2 independent stereo outputs, one of the stereo
outputs is cap-less with HP AMP, and the other is line out to
connect the active speaker. The inputs can be Line-in and MIC-in.

Signed-off-by: Leo Tsai <antivirus621@gmail.com>
---
 sound/pci/hda/patch_cmedia.c | 268 +++++++++++++++++++++++++++++++++++
 1 file changed, 268 insertions(+)
 mode change 100644 => 100755 sound/pci/hda/patch_cmedia.c

diff --git a/sound/pci/hda/patch_cmedia.c b/sound/pci/hda/patch_cmedia.c
old mode 100644
new mode 100755
index 2ddd33f8dd6c..3195261a7d2c
--- a/sound/pci/hda/patch_cmedia.c
+++ b/sound/pci/hda/patch_cmedia.c
@@ -17,10 +17,230 @@
 #include "hda_jack.h"
 #include "hda_generic.h"
 
+/* CM9825 Offset Definitions */
+
+#define CM9825_VERB_SET_HPF_1 0x781
+#define CM9825_VERB_SET_HPF_2 0x785
+#define CM9825_VERB_SET_PLL 0x7a0
+#define CM9825_VERB_SET_NEG 0x7a1
+#define CM9825_VERB_SET_ADCL 0x7a2
+#define CM9825_VERB_SET_DACL 0x7a3
+#define CM9825_VERB_SET_MBIAS 0x7a4
+#define CM9825_VERB_SET_VNEG 0x7a8
+#define CM9825_VERB_SET_D2S 0x7a9
+#define CM9825_VERB_SET_DACTRL 0x7aa
+#define CM9825_VERB_SET_PDNEG 0x7ac
+#define CM9825_VERB_SET_VDO 0x7ad
+#define CM9825_VERB_SET_CDALR 0x7b0
+#define CM9825_VERB_SET_MTCBA 0x7b1
+#define CM9825_VERB_SET_OTP 0x7b2
+#define CM9825_VERB_SET_OCP 0x7b3
+#define CM9825_VERB_SET_GAD 0x7b4
+#define CM9825_VERB_SET_TMOD 0x7b5
+#define CM9825_VERB_SET_SNR 0x7b6
+
 struct cmi_spec {
 	struct hda_gen_spec gen;
+	const struct hda_verb *chip_d0_verbs;
+	const struct hda_verb *chip_d3_verbs;
+	const struct hda_verb *chip_hp_present_verbs;
+	const struct hda_verb *chip_hp_remove_verbs;
+	struct hda_codec *codec;
+	struct delayed_work unsol_hp_work;
+	int quirk;
+};
+
+static const struct hda_verb cm9825_std_d3_verbs[] = {
+	/* chip sleep verbs */
+	{0x43, CM9825_VERB_SET_D2S, 0x62},	/* depop */
+	{0x43, CM9825_VERB_SET_PLL, 0x01},	/* PLL set */
+	{0x43, CM9825_VERB_SET_NEG, 0xc2},	/* NEG set */
+	{0x43, CM9825_VERB_SET_ADCL, 0x00},	/* ADC */
+	{0x43, CM9825_VERB_SET_DACL, 0x02},	/* DACL */
+	{0x43, CM9825_VERB_SET_VNEG, 0x50},	/* VOL NEG */
+	{0x43, CM9825_VERB_SET_MBIAS, 0x00},	/* MBIAS */
+	{0x43, CM9825_VERB_SET_PDNEG, 0x04},	/* SEL OSC */
+	{0x43, CM9825_VERB_SET_CDALR, 0xf6},	/* Class D */
+	{0x43, CM9825_VERB_SET_OTP, 0xcd},	/* OTP set */
+	{}
+};
+
+static const struct hda_verb cm9825_std_d0_verbs[] = {
+	/* chip init verbs */
+	{0x34, AC_VERB_SET_EAPD_BTLENABLE, 0x02},	/* EAPD set */
+	{0x43, CM9825_VERB_SET_SNR, 0x30},	/* SNR set */
+	{0x43, CM9825_VERB_SET_PLL, 0x00},	/* PLL set */
+	{0x43, CM9825_VERB_SET_ADCL, 0x00},	/* ADC */
+	{0x43, CM9825_VERB_SET_DACL, 0x02},	/* DACL */
+	{0x43, CM9825_VERB_SET_MBIAS, 0x00},	/* MBIAS */
+	{0x43, CM9825_VERB_SET_VNEG, 0x56},	/* VOL NEG */
+	{0x43, CM9825_VERB_SET_D2S, 0x62},	/* depop */
+	{0x43, CM9825_VERB_SET_DACTRL, 0x00},	/* DACTRL set */
+	{0x43, CM9825_VERB_SET_PDNEG, 0x0c},	/* SEL OSC */
+	{0x43, CM9825_VERB_SET_VDO, 0x80},	/* VDO set */
+	{0x43, CM9825_VERB_SET_CDALR, 0xf4},	/* Class D */
+	{0x43, CM9825_VERB_SET_OTP, 0xcd},	/* OTP set */
+	{0x43, CM9825_VERB_SET_MTCBA, 0x61},	/* SR set */
+	{0x43, CM9825_VERB_SET_OCP, 0x33},	/* OTP set */
+	{0x43, CM9825_VERB_SET_GAD, 0x07},	/* ADC -3db */
+	{0x43, CM9825_VERB_SET_TMOD, 0x26},	/* Class D clk */
+	{0x3C, AC_VERB_SET_AMP_GAIN_MUTE | 0x0a0, 0x2d},	/* Gain set */
+	{0x3C, AC_VERB_SET_AMP_GAIN_MUTE | 0x090, 0x2d},	/* Gain set */
+	{0x43, CM9825_VERB_SET_HPF_1, 0x40},	/* HPF set */
+	{0x43, CM9825_VERB_SET_HPF_2, 0x40},	/* HPF set */
+	{}
 };
 
+static const struct hda_verb cm9825_hp_present_verbs[] = {
+	{0x42, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00},	/* PIN off */
+	{0x43, CM9825_VERB_SET_ADCL, 0x88},	/* ADC */
+	{0x43, CM9825_VERB_SET_DACL, 0xaa},	/* DACL */
+	{0x43, CM9825_VERB_SET_MBIAS, 0x10},	/* MBIAS */
+	{0x43, CM9825_VERB_SET_D2S, 0xf2},	/* depop */
+	{0x43, CM9825_VERB_SET_DACTRL, 0x00},	/* DACTRL set */
+	{0x43, CM9825_VERB_SET_VDO, 0xc4},	/* VDO set */
+	{}
+};
+
+static const struct hda_verb cm9825_hp_remove_verbs[] = {
+	{0x43, CM9825_VERB_SET_ADCL, 0x00},	/* ADC */
+	{0x43, CM9825_VERB_SET_DACL, 0x56},	/* DACL */
+	{0x43, CM9825_VERB_SET_MBIAS, 0x00},	/* MBIAS */
+	{0x43, CM9825_VERB_SET_D2S, 0x62},	/* depop */
+	{0x43, CM9825_VERB_SET_DACTRL, 0xe0},	/* DACTRL set */
+	{0x43, CM9825_VERB_SET_VDO, 0x80},	/* VDO set */
+	{0x42, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},	/* PIN on */
+	{}
+};
+
+static void cm9825_unsol_hp_delayed(struct work_struct *work)
+{
+	struct cmi_spec *spec =
+	    container_of(to_delayed_work(work), struct cmi_spec, unsol_hp_work);
+	struct hda_jack_tbl *jack;
+	hda_nid_t hp_pin = spec->gen.autocfg.hp_pins[0];
+	bool hp_jack_plugin = false;
+	int err = 0;
+
+	hp_jack_plugin = snd_hda_jack_detect(spec->codec, hp_pin);
+
+	codec_dbg(spec->codec, "hp_jack_plugin %d, hp_pin 0x%X\n",
+		  (int)hp_jack_plugin, hp_pin);
+
+	if (!hp_jack_plugin) {
+		err =
+		    snd_hda_codec_write(spec->codec, 0x42, 0,
+					AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40);
+		if (err)
+			codec_dbg(spec->codec, "codec_write err %d\n", err);
+
+		snd_hda_sequence_write(spec->codec, spec->chip_hp_remove_verbs);
+	} else {
+		snd_hda_sequence_write(spec->codec,
+				       spec->chip_hp_present_verbs);
+	}
+
+	jack = snd_hda_jack_tbl_get(spec->codec, hp_pin);
+	if (jack) {
+		jack->block_report = 0;
+		snd_hda_jack_report_sync(spec->codec);
+	}
+}
+
+static void hp_callback(struct hda_codec *codec, struct hda_jack_callback *cb)
+{
+	struct cmi_spec *spec = codec->spec;
+	struct hda_jack_tbl *tbl;
+
+	/* Delay enabling the HP amp, to let the mic-detection
+	 * state machine run.
+	 */
+
+	codec_dbg(spec->codec, "cb->nid 0x%X\n", cb->nid);
+
+	tbl = snd_hda_jack_tbl_get(codec, cb->nid);
+	if (tbl)
+		tbl->block_report = 1;
+	schedule_delayed_work(&spec->unsol_hp_work, msecs_to_jiffies(200));
+}
+
+static void cm9825_setup_unsol(struct hda_codec *codec)
+{
+	struct cmi_spec *spec = codec->spec;
+
+	hda_nid_t hp_pin = spec->gen.autocfg.hp_pins[0];
+
+	snd_hda_jack_detect_enable_callback(codec, hp_pin, hp_callback);
+}
+
+#define cmi_is_s3_resume(codec) \
+	((codec)->core.dev.power.power_state.event == PM_EVENT_RESUME)
+#define cmi_is_s4_resume(codec) \
+	((codec)->core.dev.power.power_state.event == PM_EVENT_RESTORE)
+
+static int cm9825_init(struct hda_codec *codec)
+{
+	snd_hda_gen_init(codec);
+	snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_INIT);
+
+	return 0;
+}
+
+static int cm9825_suspend(struct hda_codec *codec)
+{
+	struct cmi_spec *spec = codec->spec;
+
+	cancel_delayed_work_sync(&spec->unsol_hp_work);
+
+	snd_hda_sequence_write(codec, spec->chip_d3_verbs);
+
+	return 0;
+}
+
+static int cm9825_resume(struct hda_codec *codec)
+{
+	struct cmi_spec *spec = codec->spec;
+	hda_nid_t hp_pin = 0;
+	bool hp_jack_plugin = false;
+	int err;
+
+	err =
+	    snd_hda_codec_write(spec->codec, 0x42, 0,
+				AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00);
+	if (err)
+		codec_dbg(codec, "codec_write err %d\n", err);
+
+	/* hibernation resume needs the full chip initialization */
+	if (cmi_is_s4_resume(codec))
+		codec_dbg(spec->codec, "resume from S4\n");
+
+	msleep(150);		/* for depop noise */
+
+	codec->patch_ops.init(codec);
+
+	hp_pin = spec->gen.autocfg.hp_pins[0];
+	hp_jack_plugin = snd_hda_jack_detect(spec->codec, hp_pin);
+
+	codec_dbg(spec->codec, "hp_jack_plugin %d, hp_pin 0x%X\n",
+		  (int)hp_jack_plugin, hp_pin);
+
+	if (!hp_jack_plugin) {
+		err =
+		    snd_hda_codec_write(spec->codec, 0x42, 0,
+					AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40);
+
+		if (err)
+			codec_dbg(codec, "codec_write err %d\n", err);
+
+		snd_hda_sequence_write(codec, cm9825_hp_remove_verbs);
+	}
+
+	snd_hda_regmap_sync(codec);
+	hda_call_check_power_status(codec, 0x01);
+
+	return 0;
+}
+
 /*
  * stuff for auto-parser
  */
@@ -32,6 +252,53 @@ static const struct hda_codec_ops cmi_auto_patch_ops = {
 	.unsol_event = snd_hda_jack_unsol_event,
 };
 
+static int patch_cm9825(struct hda_codec *codec)
+{
+	struct cmi_spec *spec;
+	struct auto_pin_cfg *cfg;
+	int err;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->spec = spec;
+	spec->codec = codec;
+	codec->patch_ops = cmi_auto_patch_ops;
+	codec->patch_ops.init = cm9825_init;
+	codec->patch_ops.suspend = cm9825_suspend;
+	codec->patch_ops.resume = cm9825_resume;
+	codec->patch_ops.check_power_status = snd_hda_gen_check_power_status;
+	cfg = &spec->gen.autocfg;
+	snd_hda_gen_spec_init(&spec->gen);
+	spec->chip_d0_verbs = cm9825_std_d0_verbs;
+	spec->chip_d3_verbs = cm9825_std_d3_verbs;
+	spec->chip_hp_present_verbs = cm9825_hp_present_verbs;
+	spec->chip_hp_remove_verbs = cm9825_hp_remove_verbs;
+
+	snd_hda_sequence_write(codec, spec->chip_d0_verbs);
+
+	err = snd_hda_parse_pin_defcfg(codec, cfg, NULL, 0);
+	if (err < 0)
+		goto error;
+	err = snd_hda_gen_parse_auto_config(codec, cfg);
+	if (err < 0)
+		goto error;
+
+	INIT_DELAYED_WORK(&spec->unsol_hp_work, cm9825_unsol_hp_delayed);
+
+	cm9825_setup_unsol(codec);
+
+	return 0;
+
+ error:
+	snd_hda_gen_free(codec);
+
+	codec_info(codec, "Enter err %d\n", err);
+
+	return err;
+}
+
 static int patch_cmi9880(struct hda_codec *codec)
 {
 	struct cmi_spec *spec;
@@ -113,6 +380,7 @@ static const struct hda_device_id snd_hda_id_cmedia[] = {
 	HDA_CODEC_ENTRY(0x13f68888, "CMI8888", patch_cmi8888),
 	HDA_CODEC_ENTRY(0x13f69880, "CMI9880", patch_cmi9880),
 	HDA_CODEC_ENTRY(0x434d4980, "CMI9880", patch_cmi9880),
+	HDA_CODEC_ENTRY(0x13f69825, "CM9825", patch_cm9825),
 	{} /* terminator */
 };
 MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_cmedia);
-- 
2.34.1
Re: [PATCH] ALSA: hda: Add a new CM9825 standard driver.
Posted by Takashi Iwai 2 months, 3 weeks ago
On Mon, 09 Sep 2024 04:23:40 +0200,
Leo Tsai wrote:
> 
> The CM9825 is a High Definition Audio Codec.
> There are 2 independent stereo outputs, one of the stereo
> outputs is cap-less with HP AMP, and the other is line out to
> connect the active speaker. The inputs can be Line-in and MIC-in.
> 
> Signed-off-by: Leo Tsai <antivirus621@gmail.com>
> ---
>  sound/pci/hda/patch_cmedia.c | 268 +++++++++++++++++++++++++++++++++++
>  1 file changed, 268 insertions(+)
>  mode change 100644 => 100755 sound/pci/hda/patch_cmedia.c
> 
> diff --git a/sound/pci/hda/patch_cmedia.c b/sound/pci/hda/patch_cmedia.c
> old mode 100644
> new mode 100755

You changed the file permission mistakenly here?

> index 2ddd33f8dd6c..3195261a7d2c
> --- a/sound/pci/hda/patch_cmedia.c
> +++ b/sound/pci/hda/patch_cmedia.c
(snip)
> +	{0x3C, AC_VERB_SET_AMP_GAIN_MUTE | 0x0a0, 0x2d},	/* Gain set */
> +	{0x3C, AC_VERB_SET_AMP_GAIN_MUTE | 0x090, 0x2d},	/* Gain set */

FWIW, those are equivalent with:
	{0x3C, AC_VERB_SET_AMP_GAIN_MUTE, AC_AMP_SET_OUTPUT | AC_AMP_SET_RIGHT | 0x2d},	/* Gain set */
	{0x3C, AC_VERB_SET_AMP_GAIN_MUTE, AC_AMP_SET_OUTPUT | AC_AMP_SET_LEFT | 0x2d},	/* Gain set */

> +static void cm9825_unsol_hp_delayed(struct work_struct *work)
> +{
> +	struct cmi_spec *spec =
> +	    container_of(to_delayed_work(work), struct cmi_spec, unsol_hp_work);
> +	struct hda_jack_tbl *jack;
> +	hda_nid_t hp_pin = spec->gen.autocfg.hp_pins[0];
> +	bool hp_jack_plugin = false;
> +	int err = 0;

Both are unnecessary initializations.

> +static int cm9825_resume(struct hda_codec *codec)
> +{
> +	struct cmi_spec *spec = codec->spec;
> +	hda_nid_t hp_pin = 0;
> +	bool hp_jack_plugin = false;
> +	int err;
> +
> +	err =
> +	    snd_hda_codec_write(spec->codec, 0x42, 0,
> +				AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00);
> +	if (err)
> +		codec_dbg(codec, "codec_write err %d\n", err);
> +
> +	/* hibernation resume needs the full chip initialization */
> +	if (cmi_is_s4_resume(codec))
> +		codec_dbg(spec->codec, "resume from S4\n");

Is the comment above correct?
It only shows a debug print and does nothing else, no?

> @@ -32,6 +252,53 @@ static const struct hda_codec_ops cmi_auto_patch_ops = {
>  	.unsol_event = snd_hda_jack_unsol_event,
>  };
>  
> +static int patch_cm9825(struct hda_codec *codec)
> +{
(snip)
> +	INIT_DELAYED_WORK(&spec->unsol_hp_work, cm9825_unsol_hp_delayed);

If you use the delayed work, it has to be cleared at the destructor
via cancel_delayed_work().  That is, you'd need the own ops.free
callback implementation.

And if doing so, it's better to put this initialization earlier (right
after the alloc of spec and its field initializations), so that you
can call cancel_delayed_work() unconditionally.


thanks,

Takashi
Re: [PATCH] ALSA: hda: Add a new CM9825 standard driver.
Posted by Takashi Iwai 2 months, 3 weeks ago
On Mon, 09 Sep 2024 12:20:12 +0200,
Takashi Iwai wrote:
> 
> On Mon, 09 Sep 2024 04:23:40 +0200,
> Leo Tsai wrote:
> > 
> > The CM9825 is a High Definition Audio Codec.
> > There are 2 independent stereo outputs, one of the stereo
> > outputs is cap-less with HP AMP, and the other is line out to
> > connect the active speaker. The inputs can be Line-in and MIC-in.
> > 
> > Signed-off-by: Leo Tsai <antivirus621@gmail.com>
> > ---
> >  sound/pci/hda/patch_cmedia.c | 268 +++++++++++++++++++++++++++++++++++
> >  1 file changed, 268 insertions(+)
> >  mode change 100644 => 100755 sound/pci/hda/patch_cmedia.c
> > 
> > diff --git a/sound/pci/hda/patch_cmedia.c b/sound/pci/hda/patch_cmedia.c
> > old mode 100644
> > new mode 100755
> 
> You changed the file permission mistakenly here?
> 
> > index 2ddd33f8dd6c..3195261a7d2c
> > --- a/sound/pci/hda/patch_cmedia.c
> > +++ b/sound/pci/hda/patch_cmedia.c
> (snip)
> > +	{0x3C, AC_VERB_SET_AMP_GAIN_MUTE | 0x0a0, 0x2d},	/* Gain set */
> > +	{0x3C, AC_VERB_SET_AMP_GAIN_MUTE | 0x090, 0x2d},	/* Gain set */
> 
> FWIW, those are equivalent with:
> 	{0x3C, AC_VERB_SET_AMP_GAIN_MUTE, AC_AMP_SET_OUTPUT | AC_AMP_SET_RIGHT | 0x2d},	/* Gain set */
> 	{0x3C, AC_VERB_SET_AMP_GAIN_MUTE, AC_AMP_SET_OUTPUT | AC_AMP_SET_LEFT | 0x2d},	/* Gain set */
> 
> > +static void cm9825_unsol_hp_delayed(struct work_struct *work)
> > +{
> > +	struct cmi_spec *spec =
> > +	    container_of(to_delayed_work(work), struct cmi_spec, unsol_hp_work);
> > +	struct hda_jack_tbl *jack;
> > +	hda_nid_t hp_pin = spec->gen.autocfg.hp_pins[0];
> > +	bool hp_jack_plugin = false;
> > +	int err = 0;
> 
> Both are unnecessary initializations.
> 
> > +static int cm9825_resume(struct hda_codec *codec)
> > +{
> > +	struct cmi_spec *spec = codec->spec;
> > +	hda_nid_t hp_pin = 0;
> > +	bool hp_jack_plugin = false;
> > +	int err;
> > +
> > +	err =
> > +	    snd_hda_codec_write(spec->codec, 0x42, 0,
> > +				AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00);
> > +	if (err)
> > +		codec_dbg(codec, "codec_write err %d\n", err);
> > +
> > +	/* hibernation resume needs the full chip initialization */
> > +	if (cmi_is_s4_resume(codec))
> > +		codec_dbg(spec->codec, "resume from S4\n");
> 
> Is the comment above correct?
> It only shows a debug print and does nothing else, no?
> 
> > @@ -32,6 +252,53 @@ static const struct hda_codec_ops cmi_auto_patch_ops = {
> >  	.unsol_event = snd_hda_jack_unsol_event,
> >  };
> >  
> > +static int patch_cm9825(struct hda_codec *codec)
> > +{
> (snip)
> > +	INIT_DELAYED_WORK(&spec->unsol_hp_work, cm9825_unsol_hp_delayed);
> 
> If you use the delayed work, it has to be cleared at the destructor
> via cancel_delayed_work().  That is, you'd need the own ops.free
> callback implementation.
> 
> And if doing so, it's better to put this initialization earlier (right
> after the alloc of spec and its field initializations), so that you
> can call cancel_delayed_work() unconditionally.

And last but not least: don't forget to put v2 prefix to the subject
at the next submission, e.g.
  Subject: [PATCH v2] ALSA: hda: ....


Takashi