mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] ALSA: hda/generic: Add indep_dig_outs hint
@ 2026-11-02 23:55 Joseph Telaak
  0 siblings, 0 replies; only message in thread
From: Joseph Telaak @ 2026-11-02 23:55 UTC (permalink / raw)
  To: Jaroslav Kysela, Takashi Iwai, Jonathan Corbet, Shuah Khan, Randy Dunlap
  Cc: linux-sound, linux-doc, linux-kernel, Joseph Telaak

The generic parser gives a codec one digital playback PCM. Any further
digital output pin is set up as a follower of the first: it gets the
same stream tag, so every digital jack plays the same stream. That is
the right default for a codec with, say, a coax and an optical jack.

Some boards wire two digital converters to different places instead.
On the Control4 HC-800, an ALC888 has converter 0x10 driving pin 0x11,
which feeds an HDMI transmitter, and converter 0x06 driving pin 0x1e,
the S/PDIF coax jack. These are two outputs that should play different
streams at the same time. The follower setup cannot express that, and
userspace cannot split one stream tag in two.

Add an "indep_dig_outs" hint. Like indep_hp does for the headphone DAC,
it gives the second digital output pin its own stream: its own
hda_multi_out and its own PCM, "Alt Digital", typed from its pin and
with its own IEC958 controls. The digital playback ops pick the
multiout by the stream's NID. The analog "IEC958 Default PCM"
duplication stays on the first digital output only. Unlike indep_hp,
the split is fixed at parse time; there is no mixer switch. A codec
driver can also set spec->indep_dig_outs itself. Without the hint or
the flag nothing changes.

The new PCM takes the next free device number of its type. With an
HDMI-typed first output and an S/PDIF-typed second one, as on the
HC-800, the HDMI output stays the "Digital" PCM (device 3) and the
coax output becomes "Alt Digital" (device 1), with its IEC958 controls
at index 16.

Assisted-by: LLM
Signed-off-by: Joseph Telaak <joseph@telaak.dev>
---
Tested on a Control4 HC-800 (Atom D525, ALC888-VD) with this patch on 7.1.8,
with [hint] indep_dig_outs = yes in an hda patch file: HDMI and coax run at
the same time on separate stream tags, also at different rates (48 kHz and
44.1 kHz) and alongside the analog output. Each IEC958 switch affects only
its own converter, and closing or reopening one output leaves the other
running. The HDMI transmitter fed from pin 0x11 (an ADV7513) decodes the
sample rate of the Digital PCM in every combination, and never the Alt
Digital one. With the coax output connected to an external S/PDIF receiver,
the coax carries the Alt Digital PCM bit-exact and never the Digital one, at
equal and at different rates. Built on sound.git for-next with W=1, with no
new warnings.

The HC-800 turns this on with a patch file, and the patch adds no quirk for
it. It is meant as a generic option for any board whose digital outputs go
to different places.
---
 Documentation/sound/hd-audio/notes.rst |  5 +++
 sound/hda/codecs/generic.c             | 63 +++++++++++++++++++++++++++++-----
 sound/hda/codecs/generic.h             |  7 +++-
 3 files changed, 65 insertions(+), 10 deletions(-)

diff --git a/Documentation/sound/hd-audio/notes.rst b/Documentation/sound/hd-audio/notes.rst
index c16f83875..c618bb2d7 100644
--- a/Documentation/sound/hd-audio/notes.rst
+++ b/Documentation/sound/hd-audio/notes.rst
@@ -486,6 +486,11 @@ inv_dmic_split (bool)
 indep_hp (bool)
     provide the independent headphone PCM stream and the corresponding
     mixer control, if available
+indep_dig_outs (bool)
+    give the second digital output pin (in parsing order) its own PCM
+    stream, "Alt Digital", instead of making it follow the first one's
+    stream. The PCM takes the next free device number of its type, so
+    two S/PDIF outputs need dynamic minors. Default false
 add_stereo_mix_input (bool)
     add the stereo mix (analog-loopback mix) to the input mux if
     available
diff --git a/sound/hda/codecs/generic.c b/sound/hda/codecs/generic.c
index ca780b7f6..12e40c382 100644
--- a/sound/hda/codecs/generic.c
+++ b/sound/hda/codecs/generic.c
@@ -168,6 +168,9 @@ static void parse_user_hints(struct hda_codec *codec)
 	val = snd_hda_get_bool_hint(codec, "indep_hp");
 	if (val >= 0)
 		spec->indep_hp = !!val;
+	val = snd_hda_get_bool_hint(codec, "indep_dig_outs");
+	if (val >= 0)
+		spec->indep_dig_outs = !!val;
 	val = snd_hda_get_bool_hint(codec, "add_stereo_mix_input");
 	if (val >= 0)
 		spec->add_stereo_mix_input = !!val;
@@ -4000,7 +4003,9 @@ static void parse_digital(struct hda_codec *codec)
 	int i, nums;
 	hda_nid_t dig_nid, pin;
 
-	/* support multiple SPDIFs; the secondary is set up as a follower */
+	/* support multiple SPDIFs; the secondary is set up as a follower,
+	 * or as an independent PCM with the indep_dig_outs hint
+	 */
 	nums = 0;
 	for (i = 0; i < spec->autocfg.dig_outs; i++) {
 		pin = spec->autocfg.dig_out_pins[i];
@@ -4018,6 +4023,10 @@ static void parse_digital(struct hda_codec *codec)
 		if (!nums) {
 			spec->multiout.dig_out_nid = dig_nid;
 			spec->dig_out_type = spec->autocfg.dig_out_type[0];
+		} else if (spec->indep_dig_outs) {
+			/* autocfg gives at most two digital outs */
+			spec->alt_dig_multiout.dig_out_nid = dig_nid;
+			spec->alt_dig_out_type = spec->autocfg.dig_out_type[i];
 		} else {
 			spec->multiout.follower_dig_outs = spec->follower_dig_outs;
 			if (nums >= ARRAY_SIZE(spec->follower_dig_outs) - 1)
@@ -5228,6 +5237,14 @@ int snd_hda_gen_build_controls(struct hda_codec *codec)
 			spec->multiout.share_spdif = 1;
 		}
 	}
+	if (spec->alt_dig_multiout.dig_out_nid) {
+		err = snd_hda_create_dig_out_ctls(codec,
+						  spec->alt_dig_multiout.dig_out_nid,
+						  spec->alt_dig_multiout.dig_out_nid,
+						  spec->pcm_rec[3]->pcm_type);
+		if (err < 0)
+			return err;
+	}
 	if (spec->dig_in_nid) {
 		err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in_nid);
 		if (err < 0)
@@ -5450,12 +5467,24 @@ static int alt_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
 /*
  * Digital out
  */
+
+/* the independent second digital out has its own multiout */
+static struct hda_multi_out *dig_multiout(struct hda_codec *codec,
+					  struct hda_pcm_stream *hinfo)
+{
+	struct hda_gen_spec *spec = codec->spec;
+
+	if (spec->alt_dig_multiout.dig_out_nid &&
+	    hinfo->nid == spec->alt_dig_multiout.dig_out_nid)
+		return &spec->alt_dig_multiout;
+	return &spec->multiout;
+}
+
 static int dig_playback_pcm_open(struct hda_pcm_stream *hinfo,
 				 struct hda_codec *codec,
 				 struct snd_pcm_substream *substream)
 {
-	struct hda_gen_spec *spec = codec->spec;
-	return snd_hda_multi_out_dig_open(codec, &spec->multiout);
+	return snd_hda_multi_out_dig_open(codec, dig_multiout(codec, hinfo));
 }
 
 static int dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
@@ -5464,8 +5493,7 @@ static int dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
 				    unsigned int format,
 				    struct snd_pcm_substream *substream)
 {
-	struct hda_gen_spec *spec = codec->spec;
-	return snd_hda_multi_out_dig_prepare(codec, &spec->multiout,
+	return snd_hda_multi_out_dig_prepare(codec, dig_multiout(codec, hinfo),
 					     stream_tag, format, substream);
 }
 
@@ -5473,16 +5501,14 @@ static int dig_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
 				    struct hda_codec *codec,
 				    struct snd_pcm_substream *substream)
 {
-	struct hda_gen_spec *spec = codec->spec;
-	return snd_hda_multi_out_dig_cleanup(codec, &spec->multiout);
+	return snd_hda_multi_out_dig_cleanup(codec, dig_multiout(codec, hinfo));
 }
 
 static int dig_playback_pcm_close(struct hda_pcm_stream *hinfo,
 				  struct hda_codec *codec,
 				  struct snd_pcm_substream *substream)
 {
-	struct hda_gen_spec *spec = codec->spec;
-	return snd_hda_multi_out_dig_close(codec, &spec->multiout);
+	return snd_hda_multi_out_dig_close(codec, dig_multiout(codec, hinfo));
 }
 
 /*
@@ -5775,6 +5801,25 @@ int snd_hda_gen_build_pcms(struct hda_codec *codec)
 					 spec->dig_in_nid);
 	}
 
+	/* independent second digital out, stream index #3 */
+	if (spec->alt_dig_multiout.dig_out_nid) {
+		fill_pcm_stream_name(spec->stream_name_alt_digital,
+				     sizeof(spec->stream_name_alt_digital),
+				     " Alt Digital", codec->core.chip_name);
+		info = snd_hda_codec_pcm_new(codec, "%s",
+					     spec->stream_name_alt_digital);
+		if (!info)
+			return -ENOMEM;
+		spec->pcm_rec[3] = info;
+		if (spec->alt_dig_out_type)
+			info->pcm_type = spec->alt_dig_out_type;
+		else
+			info->pcm_type = HDA_PCM_TYPE_SPDIF;
+		setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK],
+				 &pcm_digital_playback, NULL,
+				 spec->alt_dig_multiout.dig_out_nid);
+	}
+
 	if (spec->no_analog)
 		return 0;
 
diff --git a/sound/hda/codecs/generic.h b/sound/hda/codecs/generic.h
index 524591821..e6971ff66 100644
--- a/sound/hda/codecs/generic.h
+++ b/sound/hda/codecs/generic.h
@@ -93,6 +93,7 @@ struct hda_gen_spec {
 	const struct hda_pcm_stream *stream_analog_capture;
 
 	char stream_name_alt_analog[32]; /* alternative analog PCM stream */
+	char stream_name_alt_digital[32]; /* independent 2nd digital PCM */
 	const struct hda_pcm_stream *stream_analog_alt_playback;
 	const struct hda_pcm_stream *stream_analog_alt_capture;
 
@@ -112,6 +113,9 @@ struct hda_gen_spec {
 	hda_nid_t alt_dac_nid;
 	hda_nid_t follower_dig_outs[3];	/* optional - for auto-parsing */
 	int dig_out_type;
+	/* the 2nd digital out with the indep_dig_outs hint */
+	struct hda_multi_out alt_dig_multiout;
+	int alt_dig_out_type;
 
 	/* capture */
 	unsigned int num_adc_nids;
@@ -148,7 +152,7 @@ struct hda_gen_spec {
 	int const_channel_count;	/* channel count for all */
 
 	/* PCM information */
-	struct hda_pcm *pcm_rec[3];	/* used in build_pcms() */
+	struct hda_pcm *pcm_rec[4];	/* used in build_pcms() */
 
 	/* dynamic controls, init_verbs and input_mux */
 	struct auto_pin_cfg autocfg;
@@ -226,6 +230,7 @@ struct hda_gen_spec {
 	unsigned int vmaster_mute_led:1; /* add SPK-LED flag to vmaster mute switch */
 	unsigned int mic_mute_led:1; /* add MIC-LED flag to capture mute switch */
 	unsigned int indep_hp:1; /* independent HP supported */
+	unsigned int indep_dig_outs:1; /* 2nd digital out has its own PCM */
 	unsigned int prefer_hp_amp:1; /* enable HP amp for speaker if any */
 	unsigned int add_stereo_mix_input:2; /* add aamix as a capture src */
 	unsigned int add_jack_modes:1; /* add i/o jack mode enum ctls */

---
base-commit: 3f2882212c558336d0bf3b1c03b8e1a6b348a6e0
change-id: 20261102-indep-dig-outs-abdb157f3425

Best regards,
--  
Joseph Telaak <joseph@telaak.dev>


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-11-02 23:55 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-02 23:55 [PATCH] ALSA: hda/generic: Add indep_dig_outs hint Joseph Telaak

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®