mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2] ALSA: hda/hdmi: Don't share an assigned converter on NVIDIA codecs
@ 2026-11-02 19:04 Razvan Dinculescu
  2026-11-03 14:49 ` Takashi Iwai
  0 siblings, 1 reply; 2+ messages in thread
From: Razvan Dinculescu @ 2026-11-02 19:04 UTC (permalink / raw)
  To: Takashi Iwai, Jaroslav Kysela; +Cc: linux-sound, linux-kernel

All pins of the NVIDIA HDMI/DP codecs share one connection list, so a
converter can be selected by several pins at once. Unlike the Intel
codecs (intel_not_share_assigned_cvt()), the NVIDIA ones never move an
idle pin off the converter that another pin streams on, and the display
that should play stays silent.

Seen on an RTX 5090 (codec 0x10de00aa) with a DP monitor on pin 0x04
and an HDMI TV on pin 0x05. PipeWire probes every PCM at start-up: the
monitor's PCM got converter 0x08 and kept it selected after closing,
then the TV's PCM got 0x08 as well when it was opened. With both pins
on 0x08 the TV played nothing, although its PCM was running and its
ELD was valid. Opening the monitor's PCM while the TV's was running
moved the monitor to 0x09 and the TV played at once. Restarting
PipeWire put both pins back on 0x08.

Implement pin_cvt_fixup for the generic NVIDIA model as Intel does:
restore the pin's own converter selection, which
snd_hda_hdmi_generic_pcm_prepare() relies on after the hardware drops
it, then move every other connected pin that selects the assigned
converter to an unassigned one. Look the converter up in each pin's
own connection list instead of assuming the same order on every pin.

Tested on that system with the TV's PCM on 0x08 and the monitor's on
0x09, both pins then forced onto 0x08 with hda-verb: a prepare on the
TV's PCM moved the monitor off 0x08, and a prepare on the monitor's
PCM put it back on 0x09.

Signed-off-by: Razvan Dinculescu <razvandinculescu@gmail.com>
---
v2:
- Restore the pin's own converter selection before moving the other
  pins, as i915_pin_cvt_fixup() does; without it a pin moved to another
  converter while its own PCM was open stayed there at its next prepare
  (reported by the Sashiko review of v1). The PipeWire restart test of
  v1 was not repeated; the code that moves the other pins is unchanged.

v1: https://lore.kernel.org/linux-sound/20261102182213.2154560-1-razvandinculescu@gmail.com/

 sound/hda/codecs/hdmi/nvhdmi.c | 97 ++++++++++++++++++++++++++++++++++
 1 file changed, 97 insertions(+)

diff --git a/sound/hda/codecs/hdmi/nvhdmi.c b/sound/hda/codecs/hdmi/nvhdmi.c
index 94671ad24b5e..e61ad1624dfb 100644
--- a/sound/hda/codecs/hdmi/nvhdmi.c
+++ b/sound/hda/codecs/hdmi/nvhdmi.c
@@ -66,6 +66,102 @@ static const struct drm_audio_component_audio_ops nvhdmi_audio_ops = {
 	.master_unbind = snd_hda_hdmi_acomp_master_unbind,
 };
 
+/*
+ * All pins of these codecs share one connection list, as on Intel.  An idle
+ * pin left selecting the converter that another pin streams on takes that
+ * stream: the display that plays stays silent and the audio goes to the
+ * other display instead.  Seen on GB202 (0x10de00aa) with an HDMI TV and a
+ * DP monitor: whenever the monitor's PCM was opened first (PipeWire probes
+ * every PCM at start-up), its pin kept the converter the TV got next.
+ *
+ * Move every other connected pin off the converter just assigned, to a
+ * converter nobody uses.
+ */
+static void nvhdmi_not_share_assigned_cvt(struct hda_codec *codec,
+					  hda_nid_t pin_nid, int dev_id,
+					  hda_nid_t cvt_nid)
+{
+	struct hdmi_spec *spec = codec->spec;
+	struct hdmi_spec_per_pin *per_pin;
+	struct hdmi_spec_per_cvt *per_cvt;
+	int pin_idx, cvt_idx, mux_idx, curr, dev_id_saved;
+
+	if (!cvt_nid)
+		return;
+
+	for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
+		per_pin = get_pin(spec, pin_idx);
+		/* no display on this pin */
+		if (!per_pin->pcm)
+			continue;
+		if (per_pin->pin_nid == pin_nid && per_pin->dev_id == dev_id)
+			continue;
+		if (per_pin->dev_id >=
+		    snd_hda_get_num_devices(codec, per_pin->pin_nid) + 1)
+			continue;
+
+		dev_id_saved = snd_hda_get_dev_select(codec, per_pin->pin_nid);
+		snd_hda_set_dev_select(codec, per_pin->pin_nid, per_pin->dev_id);
+		curr = snd_hda_codec_read(codec, per_pin->pin_nid, 0,
+					  AC_VERB_GET_CONNECT_SEL, 0);
+		if (curr < 0 || curr >= per_pin->num_mux_nids ||
+		    per_pin->mux_nids[curr] != cvt_nid)
+			goto restore;
+
+		for (cvt_idx = 0; cvt_idx < spec->num_cvts; cvt_idx++) {
+			per_cvt = get_cvt(spec, cvt_idx);
+			if (per_cvt->assigned || per_cvt->cvt_nid == cvt_nid)
+				continue;
+			for (mux_idx = 0; mux_idx < per_pin->num_mux_nids; mux_idx++)
+				if (per_pin->mux_nids[mux_idx] == per_cvt->cvt_nid)
+					break;
+			if (mux_idx == per_pin->num_mux_nids)
+				continue;
+			codec_dbg(codec,
+				  "hdmi: move pin 0x%x off cvt 0x%x to cvt 0x%x\n",
+				  per_pin->pin_nid, cvt_nid, per_cvt->cvt_nid);
+			snd_hda_codec_write_cache(codec, per_pin->pin_nid, 0,
+						  AC_VERB_SET_CONNECT_SEL,
+						  mux_idx);
+			break;
+		}
+restore:
+		snd_hda_set_dev_select(codec, per_pin->pin_nid, dev_id_saved);
+	}
+}
+
+/*
+ * The hardware can drop a pin's converter selection, e.g. on a display
+ * reset.  snd_hda_hdmi_generic_pcm_prepare() relies on pin_cvt_fixup() to
+ * restore it, as i915_pin_cvt_fixup() does on Intel.
+ */
+static void nvhdmi_verify_pin_cvt_connect(struct hda_codec *codec,
+					  struct hdmi_spec_per_pin *per_pin)
+{
+	int curr;
+
+	curr = snd_hda_codec_read(codec, per_pin->pin_nid, 0,
+				  AC_VERB_GET_CONNECT_SEL, 0);
+	if (curr != per_pin->mux_idx)
+		snd_hda_codec_write_cache(codec, per_pin->pin_nid, 0,
+					  AC_VERB_SET_CONNECT_SEL,
+					  per_pin->mux_idx);
+}
+
+static void nvhdmi_pin_cvt_fixup(struct hda_codec *codec,
+				 struct hdmi_spec_per_pin *per_pin,
+				 hda_nid_t cvt_nid)
+{
+	if (per_pin) {
+		snd_hda_set_dev_select(codec, per_pin->pin_nid, per_pin->dev_id);
+		nvhdmi_verify_pin_cvt_connect(codec, per_pin);
+		nvhdmi_not_share_assigned_cvt(codec, per_pin->pin_nid,
+					      per_pin->dev_id, per_pin->cvt_nid);
+	} else {
+		nvhdmi_not_share_assigned_cvt(codec, 0, 0, cvt_nid);
+	}
+}
+
 static int probe_generic(struct hda_codec *codec)
 {
 	struct hdmi_spec *spec;
@@ -77,6 +173,7 @@ static int probe_generic(struct hda_codec *codec)
 	codec->dp_mst = true;
 
 	spec = codec->spec;
+	spec->ops.pin_cvt_fixup = nvhdmi_pin_cvt_fixup;
 
 	err = snd_hda_hdmi_parse_codec(codec);
 	if (err < 0) {
-- 
2.56.0


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

end of thread, other threads:[~2026-11-03 14:49 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-02 19:04 [PATCH v2] ALSA: hda/hdmi: Don't share an assigned converter on NVIDIA codecs Razvan Dinculescu
2026-11-03 14:49 ` Takashi Iwai

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®