All of lore.kernel.org
 help / color / mirror / Atom feed
* nouveau: add HDMI FRL support for GSP enabled GPUs
@ 2026-04-23  0:42 Dave Airlie
  2026-04-23  0:42 ` [PATCH 1/4] nouveau: move HDMI sink caps to outp level for GSP-RM Dave Airlie
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Dave Airlie @ 2026-04-23  0:42 UTC (permalink / raw)
  To: dri-devel; +Cc: nouveau

This adds higher refresh and resolution modes that need FRL support
on HDMI.

With GSP the firmware handles most of the hard work, just need to
send things in the correct order and handle the link training at the
right points.

The first commit reorders current SCDC support on GSP to do things
in the same order as NVIDIA open driver. Then it builds on that to add
FRL pieces in the backend, and the final patch adds all the frontend
pieces.

I've tested this on Ampere GA106 + a HDMI 2.1 capture card (Elgato X).

I used claude code to iterate on this a bunch to solve the ordering
issues and align with NVIDIA programming sequences.

Dave.


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

* [PATCH 1/4] nouveau: move HDMI sink caps to outp level for GSP-RM
  2026-04-23  0:42 nouveau: add HDMI FRL support for GSP enabled GPUs Dave Airlie
@ 2026-04-23  0:42 ` Dave Airlie
  2026-04-23  0:42 ` [PATCH 2/4] nouveau: add FRL and DSC parameters to HDMI sink caps interface Dave Airlie
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Dave Airlie @ 2026-04-23  0:42 UTC (permalink / raw)
  To: dri-devel; +Cc: nouveau

From: Dave Airlie <airlied@redhat.com>

NVIDIA's driver sends SET_HDMI_SINK_CAPS during connector detection
(ApplyNewEdid), not during modeset. Move this RM call from the IOR-level
scdc callback to a new outp-level hdmi_sink_caps method that doesn't
require an acquired SOR.

Add nvif_outp_hdmi_sink_caps() routed through the noacquire dispatch
table and call it from nouveau_connector_detect() for TMDS outputs on
Turing+. Remove r535_sor_hdmi_scdc and its .scdc entry from r535_sor_hdmi
since GSP now gets sink caps at detect time. The gm200 hardware scdc
callback is preserved for direct register writes on pre-GSP chips.

This was claude code assisted.

Signed-off-by: Dave Airlie <airlied@redhat.com>
---
 drivers/gpu/drm/nouveau/include/nvif/if0012.h | 11 ++++
 drivers/gpu/drm/nouveau/include/nvif/outp.h   |  2 +
 drivers/gpu/drm/nouveau/nouveau_connector.c   | 11 ++++
 drivers/gpu/drm/nouveau/nvif/outp.c           | 19 +++++++
 .../gpu/drm/nouveau/nvkm/engine/disp/outp.h   |  2 +
 .../gpu/drm/nouveau/nvkm/engine/disp/uoutp.c  | 23 +++++++--
 .../nouveau/nvkm/subdev/gsp/rm/r535/disp.c    | 51 +++++++++----------
 7 files changed, 89 insertions(+), 30 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/include/nvif/if0012.h b/drivers/gpu/drm/nouveau/include/nvif/if0012.h
index bde9bfae8d11f..89cfac7ef3a54 100644
--- a/drivers/gpu/drm/nouveau/include/nvif/if0012.h
+++ b/drivers/gpu/drm/nouveau/include/nvif/if0012.h
@@ -43,6 +43,7 @@ union nvif_outp_args {
 
 #define NVIF_OUTP_V0_DETECT        0x00
 #define NVIF_OUTP_V0_EDID_GET      0x01
+#define NVIF_OUTP_V0_HDMI_SINK_CAPS 0x02
 
 #define NVIF_OUTP_V0_INHERIT       0x10
 #define NVIF_OUTP_V0_ACQUIRE       0x11
@@ -165,6 +166,16 @@ union nvif_outp_lvds_args {
 	} v0;
 };
 
+union nvif_outp_hdmi_sink_caps_args {
+	struct nvif_outp_hdmi_sink_caps_v0 {
+		__u8 version;
+		__u8 scdc;
+		__u8 scdc_scrambling;
+		__u8 scdc_low_rates;
+		__u8 pad04[4];
+	} v0;
+};
+
 union nvif_outp_hdmi_args {
 	struct nvif_outp_hdmi_v0 {
 		__u8 version;
diff --git a/drivers/gpu/drm/nouveau/include/nvif/outp.h b/drivers/gpu/drm/nouveau/include/nvif/outp.h
index bc122a5ba7df7..240e1f80ed539 100644
--- a/drivers/gpu/drm/nouveau/include/nvif/outp.h
+++ b/drivers/gpu/drm/nouveau/include/nvif/outp.h
@@ -66,6 +66,8 @@ enum nvif_outp_detect_status {
 
 enum nvif_outp_detect_status nvif_outp_detect(struct nvif_outp *);
 int nvif_outp_edid_get(struct nvif_outp *, u8 **pedid);
+int nvif_outp_hdmi_sink_caps(struct nvif_outp *, bool scdc, bool scdc_scrambling,
+			     bool scdc_low_rates);
 
 int nvif_outp_load_detect(struct nvif_outp *, u32 loadval);
 int nvif_outp_acquire_dac(struct nvif_outp *);
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c
index cc239492c7f0b..eae37d938f3bc 100644
--- a/drivers/gpu/drm/nouveau/nouveau_connector.c
+++ b/drivers/gpu/drm/nouveau/nouveau_connector.c
@@ -48,6 +48,7 @@
 
 #include <nvif/class.h>
 #include <nvif/if0011.h>
+#include <nvif/outp.h>
 
 struct drm_display_mode *
 nouveau_conn_native_mode(struct drm_connector *connector)
@@ -637,6 +638,16 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
 		nouveau_connector_set_encoder(connector, nv_encoder);
 		conn_status = connector_status_connected;
 
+		if (nv_encoder->dcb->type == DCB_OUTPUT_TMDS &&
+		    drm->client.device.info.family >= NV_DEVICE_INFO_V0_TURING) {
+			struct drm_hdmi_info *hdmi = &connector->display_info.hdmi;
+
+			nvif_outp_hdmi_sink_caps(&nv_encoder->outp,
+						hdmi->scdc.supported,
+						hdmi->scdc.scrambling.supported,
+						hdmi->scdc.scrambling.low_rates);
+		}
+
 		if (nv_encoder->dcb->type == DCB_OUTPUT_DP)
 			drm_dp_cec_set_edid(&nv_connector->aux, nv_connector->edid);
 
diff --git a/drivers/gpu/drm/nouveau/nvif/outp.c b/drivers/gpu/drm/nouveau/nvif/outp.c
index 8cf4775a0a1eb..d66f437587ff3 100644
--- a/drivers/gpu/drm/nouveau/nvif/outp.c
+++ b/drivers/gpu/drm/nouveau/nvif/outp.c
@@ -461,6 +461,25 @@ nvif_outp_edid_get(struct nvif_outp *outp, u8 **pedid)
 	return ret;
 }
 
+int
+nvif_outp_hdmi_sink_caps(struct nvif_outp *outp, bool scdc, bool scdc_scrambling,
+			 bool scdc_low_rates)
+{
+	struct nvif_outp_hdmi_sink_caps_v0 args;
+	int ret;
+
+	args.version = 0;
+	args.scdc = scdc;
+	args.scdc_scrambling = scdc_scrambling;
+	args.scdc_low_rates = scdc_low_rates;
+
+	ret = nvif_mthd(&outp->object, NVIF_OUTP_V0_HDMI_SINK_CAPS, &args, sizeof(args));
+	NVIF_ERRON(ret, &outp->object,
+		   "[HDMI_SINK_CAPS scdc:%d scrambling:%d low_rates:%d]",
+		   args.scdc, args.scdc_scrambling, args.scdc_low_rates);
+	return ret;
+}
+
 enum nvif_outp_detect_status
 nvif_outp_detect(struct nvif_outp *outp)
 {
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.h
index ebd2f499b4b1d..fd54c17ba4d6d 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.h
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.h
@@ -95,6 +95,8 @@ struct nvkm_outp_func {
 
 	int (*detect)(struct nvkm_outp *);
 	int (*edid_get)(struct nvkm_outp *, u8 *data, u16 *size);
+	void (*hdmi_sink_caps)(struct nvkm_outp *, bool scdc, bool scrambling,
+			       bool low_rates);
 
 	struct nvkm_ior *(*inherit)(struct nvkm_outp *);
 	int (*acquire)(struct nvkm_outp *, bool hda);
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c
index 377d0e0cef848..84381ba71ebaf 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c
@@ -253,8 +253,7 @@ nvkm_uoutp_mthd_hdmi(struct nvkm_outp *outp, void *argv, u32 argc)
 
 	if (!ior->func->hdmi ||
 	    args->v0.max_ac_packet > 0x1f ||
-	    args->v0.rekey > 0x7f ||
-	    (args->v0.scdc && !ior->func->hdmi->scdc))
+	    args->v0.rekey > 0x7f)
 		return -EINVAL;
 
 	if (!args->v0.enable) {
@@ -473,6 +472,21 @@ nvkm_uoutp_mthd_edid_get(struct nvkm_outp *outp, void *argv, u32 argc)
 	return outp->func->edid_get(outp, args->v0.data, &args->v0.size);
 }
 
+static int
+nvkm_uoutp_mthd_hdmi_sink_caps(struct nvkm_outp *outp, void *argv, u32 argc)
+{
+	union nvif_outp_hdmi_sink_caps_args *args = argv;
+
+	if (argc != sizeof(args->v0) || args->v0.version != 0)
+		return -ENOSYS;
+	if (!outp->func->hdmi_sink_caps)
+		return -EINVAL;
+
+	outp->func->hdmi_sink_caps(outp, args->v0.scdc, args->v0.scdc_scrambling,
+				   args->v0.scdc_low_rates);
+	return 0;
+}
+
 static int
 nvkm_uoutp_mthd_detect(struct nvkm_outp *outp, void *argv, u32 argc)
 {
@@ -522,8 +536,9 @@ static int
 nvkm_uoutp_mthd_noacquire(struct nvkm_outp *outp, u32 mthd, void *argv, u32 argc, bool *invalid)
 {
 	switch (mthd) {
-	case NVIF_OUTP_V0_DETECT     : return nvkm_uoutp_mthd_detect     (outp, argv, argc);
-	case NVIF_OUTP_V0_EDID_GET   : return nvkm_uoutp_mthd_edid_get   (outp, argv, argc);
+	case NVIF_OUTP_V0_DETECT         : return nvkm_uoutp_mthd_detect         (outp, argv, argc);
+	case NVIF_OUTP_V0_EDID_GET       : return nvkm_uoutp_mthd_edid_get       (outp, argv, argc);
+	case NVIF_OUTP_V0_HDMI_SINK_CAPS : return nvkm_uoutp_mthd_hdmi_sink_caps (outp, argv, argc);
 	case NVIF_OUTP_V0_INHERIT    : return nvkm_uoutp_mthd_inherit    (outp, argv, argc);
 	case NVIF_OUTP_V0_ACQUIRE    : return nvkm_uoutp_mthd_acquire    (outp, argv, argc);
 	case NVIF_OUTP_V0_LOAD_DETECT: return nvkm_uoutp_mthd_load_detect(outp, argv, argc);
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
index 6e63df816d855..fa20a0c633653 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
@@ -468,31 +468,6 @@ r535_sor_dp = {
 	.audio = r535_sor_dp_audio,
 };
 
-static void
-r535_sor_hdmi_scdc(struct nvkm_ior *sor, u32 khz, bool support, bool scrambling,
-		   bool scrambling_low_rates)
-{
-	struct nvkm_outp *outp = sor->asy.outp;
-	struct nvkm_disp *disp = outp->disp;
-	NV0073_CTRL_SPECIFIC_SET_HDMI_SINK_CAPS_PARAMS *ctrl;
-
-	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
-				    NV0073_CTRL_CMD_SPECIFIC_SET_HDMI_SINK_CAPS, sizeof(*ctrl));
-	if (WARN_ON(IS_ERR(ctrl)))
-		return;
-
-	ctrl->displayId = BIT(outp->index);
-	ctrl->caps = 0;
-	if (support)
-		ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, SCDC_SUPPORTED, TRUE);
-	if (scrambling)
-		ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, GT_340MHZ_CLOCK_SUPPORTED, TRUE);
-	if (scrambling_low_rates)
-		ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, LTE_340MHZ_SCRAMBLING_SUPPORTED, TRUE);
-
-	WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl));
-}
-
 static void
 r535_sor_hdmi_ctrl_audio_mute(struct nvkm_outp *outp, bool mute)
 {
@@ -580,7 +555,6 @@ r535_sor_hdmi_ctrl(struct nvkm_ior *sor, int head, bool enable, u8 max_ac_packet
 static const struct nvkm_ior_func_hdmi
 r535_sor_hdmi = {
 	.ctrl = r535_sor_hdmi_ctrl,
-	.scdc = r535_sor_hdmi_scdc,
 	/*TODO: SF_USER -> KMS. */
 	.infoframe_avi = gv100_sor_hdmi_infoframe_avi,
 	.infoframe_vsi = gv100_sor_hdmi_infoframe_vsi,
@@ -1242,6 +1216,30 @@ r535_tmds_edid_get(struct nvkm_outp *outp, u8 *data, u16 *psize)
 	return ret;
 }
 
+static void
+r535_outp_hdmi_sink_caps(struct nvkm_outp *outp, bool scdc, bool scrambling,
+			 bool low_rates)
+{
+	struct nvkm_disp *disp = outp->disp;
+	NV0073_CTRL_SPECIFIC_SET_HDMI_SINK_CAPS_PARAMS *ctrl;
+
+	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
+				    NV0073_CTRL_CMD_SPECIFIC_SET_HDMI_SINK_CAPS, sizeof(*ctrl));
+	if (WARN_ON(IS_ERR(ctrl)))
+		return;
+
+	ctrl->displayId = BIT(outp->index);
+	ctrl->caps = 0;
+	if (scdc)
+		ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, SCDC_SUPPORTED, TRUE);
+	if (scrambling)
+		ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, GT_340MHZ_CLOCK_SUPPORTED, TRUE);
+	if (low_rates)
+		ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, LTE_340MHZ_SCRAMBLING_SUPPORTED, TRUE);
+
+	WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl));
+}
+
 static const struct nvkm_outp_func
 r535_tmds = {
 	.detect = r535_outp_detect,
@@ -1249,6 +1247,7 @@ r535_tmds = {
 	.acquire = r535_outp_acquire,
 	.release = r535_outp_release,
 	.edid_get = r535_tmds_edid_get,
+	.hdmi_sink_caps = r535_outp_hdmi_sink_caps,
 };
 
 static int
-- 
2.53.0


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

* [PATCH 2/4] nouveau: add FRL and DSC parameters to HDMI sink caps interface
  2026-04-23  0:42 nouveau: add HDMI FRL support for GSP enabled GPUs Dave Airlie
  2026-04-23  0:42 ` [PATCH 1/4] nouveau: move HDMI sink caps to outp level for GSP-RM Dave Airlie
@ 2026-04-23  0:42 ` Dave Airlie
  2026-04-23  0:42 ` [PATCH 3/4] nouveau: add HDMI FRL training API and retrain event handling Dave Airlie
  2026-04-23  0:42 ` [PATCH 4/4] nouveau: wire up HDMI FRL in the display frontend Dave Airlie
  3 siblings, 0 replies; 5+ messages in thread
From: Dave Airlie @ 2026-04-23  0:42 UTC (permalink / raw)
  To: dri-devel; +Cc: nouveau

From: Dave Airlie <airlied@redhat.com>

Extend the outp-level hdmi_sink_caps method with max_frl_rate and
dsc_1p2 parameters so GSP firmware can be informed about the sink's
HDMI 2.1 FRL and DSC capabilities at detect time.

Add nouveau_hdmi_get_frl_rate() helper to convert drm_hdmi_info
lane count and per-lane rate into the FRL rate index (1-6) expected
by the RM SET_HDMI_SINK_CAPS command.

This was claude code assisted.

Signed-off-by: Dave Airlie <airlied@redhat.com>
---
 drivers/gpu/drm/nouveau/include/nvif/if0012.h |  4 +++-
 drivers/gpu/drm/nouveau/include/nvif/outp.h   |  2 +-
 drivers/gpu/drm/nouveau/nouveau_connector.c   | 24 ++++++++++++++++++-
 drivers/gpu/drm/nouveau/nvif/outp.c           |  9 ++++---
 .../gpu/drm/nouveau/nvkm/engine/disp/outp.h   |  2 +-
 .../gpu/drm/nouveau/nvkm/engine/disp/uoutp.c  |  3 ++-
 .../nouveau/nvkm/subdev/gsp/rm/r535/disp.c    | 22 ++++++++++++++++-
 7 files changed, 57 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/include/nvif/if0012.h b/drivers/gpu/drm/nouveau/include/nvif/if0012.h
index 89cfac7ef3a54..dc0a5c372f511 100644
--- a/drivers/gpu/drm/nouveau/include/nvif/if0012.h
+++ b/drivers/gpu/drm/nouveau/include/nvif/if0012.h
@@ -172,7 +172,9 @@ union nvif_outp_hdmi_sink_caps_args {
 		__u8 scdc;
 		__u8 scdc_scrambling;
 		__u8 scdc_low_rates;
-		__u8 pad04[4];
+		__u8 max_frl_rate;
+		__u8 dsc_1p2;
+		__u8 pad06[2];
 	} v0;
 };
 
diff --git a/drivers/gpu/drm/nouveau/include/nvif/outp.h b/drivers/gpu/drm/nouveau/include/nvif/outp.h
index 240e1f80ed539..55c02a34f381e 100644
--- a/drivers/gpu/drm/nouveau/include/nvif/outp.h
+++ b/drivers/gpu/drm/nouveau/include/nvif/outp.h
@@ -67,7 +67,7 @@ enum nvif_outp_detect_status {
 enum nvif_outp_detect_status nvif_outp_detect(struct nvif_outp *);
 int nvif_outp_edid_get(struct nvif_outp *, u8 **pedid);
 int nvif_outp_hdmi_sink_caps(struct nvif_outp *, bool scdc, bool scdc_scrambling,
-			     bool scdc_low_rates);
+			     bool scdc_low_rates, int max_frl_rate, int dsc_1p2);
 
 int nvif_outp_load_detect(struct nvif_outp *, u32 loadval);
 int nvif_outp_acquire_dac(struct nvif_outp *);
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c
index eae37d938f3bc..870b467d35e17 100644
--- a/drivers/gpu/drm/nouveau/nouveau_connector.c
+++ b/drivers/gpu/drm/nouveau/nouveau_connector.c
@@ -560,6 +560,23 @@ nouveau_connector_set_edid(struct nouveau_connector *nv_connector,
 	}
 }
 
+static int
+nouveau_hdmi_get_frl_rate(u8 max_frl_rate_per_lane, u8 max_lanes)
+{
+	switch (max_lanes) {
+	case 3:
+		switch (max_frl_rate_per_lane) {
+		case 3: return 1;
+		case 6: return 2;
+		}
+		return 0;
+	case 4:
+		return max_frl_rate_per_lane / 2;
+	default:
+		return 0;
+	}
+}
+
 static enum drm_connector_status
 nouveau_connector_detect(struct drm_connector *connector, bool force)
 {
@@ -645,7 +662,12 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
 			nvif_outp_hdmi_sink_caps(&nv_encoder->outp,
 						hdmi->scdc.supported,
 						hdmi->scdc.scrambling.supported,
-						hdmi->scdc.scrambling.low_rates);
+						hdmi->scdc.scrambling.low_rates,
+						nouveau_hdmi_get_frl_rate(hdmi->max_frl_rate_per_lane,
+									 hdmi->max_lanes),
+						hdmi->dsc_cap.v_1p2 ?
+							nouveau_hdmi_get_frl_rate(hdmi->dsc_cap.max_frl_rate_per_lane,
+										  hdmi->dsc_cap.max_lanes) : 0);
 		}
 
 		if (nv_encoder->dcb->type == DCB_OUTPUT_DP)
diff --git a/drivers/gpu/drm/nouveau/nvif/outp.c b/drivers/gpu/drm/nouveau/nvif/outp.c
index d66f437587ff3..60479c884b2dc 100644
--- a/drivers/gpu/drm/nouveau/nvif/outp.c
+++ b/drivers/gpu/drm/nouveau/nvif/outp.c
@@ -463,7 +463,7 @@ nvif_outp_edid_get(struct nvif_outp *outp, u8 **pedid)
 
 int
 nvif_outp_hdmi_sink_caps(struct nvif_outp *outp, bool scdc, bool scdc_scrambling,
-			 bool scdc_low_rates)
+			 bool scdc_low_rates, int max_frl_rate, int dsc_1p2)
 {
 	struct nvif_outp_hdmi_sink_caps_v0 args;
 	int ret;
@@ -472,11 +472,14 @@ nvif_outp_hdmi_sink_caps(struct nvif_outp *outp, bool scdc, bool scdc_scrambling
 	args.scdc = scdc;
 	args.scdc_scrambling = scdc_scrambling;
 	args.scdc_low_rates = scdc_low_rates;
+	args.max_frl_rate = max_frl_rate;
+	args.dsc_1p2 = dsc_1p2;
 
 	ret = nvif_mthd(&outp->object, NVIF_OUTP_V0_HDMI_SINK_CAPS, &args, sizeof(args));
 	NVIF_ERRON(ret, &outp->object,
-		   "[HDMI_SINK_CAPS scdc:%d scrambling:%d low_rates:%d]",
-		   args.scdc, args.scdc_scrambling, args.scdc_low_rates);
+		   "[HDMI_SINK_CAPS scdc:%d scrambling:%d low_rates:%d max_frl_rate:%d dsc_1p2:%d]",
+		   args.scdc, args.scdc_scrambling, args.scdc_low_rates,
+		   args.max_frl_rate, args.dsc_1p2);
 	return ret;
 }
 
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.h
index fd54c17ba4d6d..17698b9ea7186 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.h
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.h
@@ -96,7 +96,7 @@ struct nvkm_outp_func {
 	int (*detect)(struct nvkm_outp *);
 	int (*edid_get)(struct nvkm_outp *, u8 *data, u16 *size);
 	void (*hdmi_sink_caps)(struct nvkm_outp *, bool scdc, bool scrambling,
-			       bool low_rates);
+			       bool low_rates, int max_frl_rate, int dsc_1p2);
 
 	struct nvkm_ior *(*inherit)(struct nvkm_outp *);
 	int (*acquire)(struct nvkm_outp *, bool hda);
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c
index 84381ba71ebaf..202fdc73f7cab 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c
@@ -483,7 +483,8 @@ nvkm_uoutp_mthd_hdmi_sink_caps(struct nvkm_outp *outp, void *argv, u32 argc)
 		return -EINVAL;
 
 	outp->func->hdmi_sink_caps(outp, args->v0.scdc, args->v0.scdc_scrambling,
-				   args->v0.scdc_low_rates);
+				   args->v0.scdc_low_rates, args->v0.max_frl_rate,
+				   args->v0.dsc_1p2);
 	return 0;
 }
 
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
index fa20a0c633653..2fb7cc83852f9 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
@@ -1218,7 +1218,7 @@ r535_tmds_edid_get(struct nvkm_outp *outp, u8 *data, u16 *psize)
 
 static void
 r535_outp_hdmi_sink_caps(struct nvkm_outp *outp, bool scdc, bool scrambling,
-			 bool low_rates)
+			 bool low_rates, int max_frl_rate, int dsc_1p2)
 {
 	struct nvkm_disp *disp = outp->disp;
 	NV0073_CTRL_SPECIFIC_SET_HDMI_SINK_CAPS_PARAMS *ctrl;
@@ -1236,7 +1236,27 @@ r535_outp_hdmi_sink_caps(struct nvkm_outp *outp, bool scdc, bool scrambling,
 		ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, GT_340MHZ_CLOCK_SUPPORTED, TRUE);
 	if (low_rates)
 		ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, LTE_340MHZ_SCRAMBLING_SUPPORTED, TRUE);
+	switch (max_frl_rate) {
+	case 1: ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, MAX_FRL_RATE_SUPPORTED, 3LANES_3G); break;
+	case 2: ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, MAX_FRL_RATE_SUPPORTED, 3LANES_6G); break;
+	case 3: ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, MAX_FRL_RATE_SUPPORTED, 4LANES_6G); break;
+	case 4: ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, MAX_FRL_RATE_SUPPORTED, 4LANES_8G); break;
+	case 5: ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, MAX_FRL_RATE_SUPPORTED, 4LANES_10G); break;
+	case 6: ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, MAX_FRL_RATE_SUPPORTED, 4LANES_12G); break;
+	default: break;
+	}
 
+	if (dsc_1p2) {
+		ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, DSC_12_SUPPORTED, TRUE);
+		switch (dsc_1p2) {
+		case 1: ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, DSC_MAX_FRL_RATE_SUPPORTED, 3LANES_3G); break;
+		case 2: ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, DSC_MAX_FRL_RATE_SUPPORTED, 3LANES_6G); break;
+		case 3: ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, DSC_MAX_FRL_RATE_SUPPORTED, 4LANES_6G); break;
+		case 4: ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, DSC_MAX_FRL_RATE_SUPPORTED, 4LANES_8G); break;
+		case 5: ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, DSC_MAX_FRL_RATE_SUPPORTED, 4LANES_10G); break;
+		case 6: ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, DSC_MAX_FRL_RATE_SUPPORTED, 4LANES_12G); break;
+		}
+	}
 	WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl));
 }
 
-- 
2.53.0


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

* [PATCH 3/4] nouveau: add HDMI FRL training API and retrain event handling
  2026-04-23  0:42 nouveau: add HDMI FRL support for GSP enabled GPUs Dave Airlie
  2026-04-23  0:42 ` [PATCH 1/4] nouveau: move HDMI sink caps to outp level for GSP-RM Dave Airlie
  2026-04-23  0:42 ` [PATCH 2/4] nouveau: add FRL and DSC parameters to HDMI sink caps interface Dave Airlie
@ 2026-04-23  0:42 ` Dave Airlie
  2026-04-23  0:42 ` [PATCH 4/4] nouveau: wire up HDMI FRL in the display frontend Dave Airlie
  3 siblings, 0 replies; 5+ messages in thread
From: Dave Airlie @ 2026-04-23  0:42 UTC (permalink / raw)
  To: dri-devel; +Cc: nouveau

From: Dave Airlie <airlied@redhat.com>

Add the nvif/nvkm plumbing for HDMI 2.1 Fixed Rate Link training and
FRL retrain event notification from GSP-RM firmware.

Wire format: NVIF_OUTP_V0_HDMI_FRL (0x51) method with head and frl_rate
parameters, dispatched through the acquired IOR table in uoutp.c.

Backend: r535 GSP-RM implementation using SET_HDMI_FRL_CONFIG (0x73029a)
RM control with NVVAL-encoded FRL rate. Training attempts real link
training first, falling back to fake LT on failure.

Events: Register for NV2080_NOTIFIERS_HDMI_FRL_RETRAINING_REQUEST (18)
from GSP, propagated as NVKM_DPYID_FRL_RETRAIN / NVIF_CONN_EVENT_V0_FRL
through the existing connector event infrastructure.

FRL is automatically cleared when HDMI output is disabled.

This was claude code assisted.

Signed-off-by: Dave Airlie <airlied@redhat.com>
---
 drivers/gpu/drm/nouveau/include/nvif/if0011.h |  1 +
 drivers/gpu/drm/nouveau/include/nvif/if0012.h | 10 +++
 drivers/gpu/drm/nouveau/include/nvif/outp.h   |  1 +
 .../drm/nouveau/include/nvkm/engine/disp.h    |  8 ++-
 drivers/gpu/drm/nouveau/nvif/outp.c           | 16 +++++
 .../gpu/drm/nouveau/nvkm/engine/disp/ior.h    |  1 +
 .../gpu/drm/nouveau/nvkm/engine/disp/uconn.c  |  3 +
 .../gpu/drm/nouveau/nvkm/engine/disp/uoutp.c  | 20 ++++++
 .../nouveau/nvkm/subdev/gsp/rm/r535/disp.c    | 65 ++++++++++++++++++-
 .../nvkm/subdev/gsp/rm/r535/nvrm/disp.h       | 26 ++++++++
 10 files changed, 147 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/include/nvif/if0011.h b/drivers/gpu/drm/nouveau/include/nvif/if0011.h
index 3ed0ddd75bd8f..3dfbcd1238fc0 100644
--- a/drivers/gpu/drm/nouveau/include/nvif/if0011.h
+++ b/drivers/gpu/drm/nouveau/include/nvif/if0011.h
@@ -26,6 +26,7 @@ union nvif_conn_event_args {
 #define NVIF_CONN_EVENT_V0_PLUG   0x01
 #define NVIF_CONN_EVENT_V0_UNPLUG 0x02
 #define NVIF_CONN_EVENT_V0_IRQ    0x04
+#define NVIF_CONN_EVENT_V0_FRL    0x08
 		__u8 types;
 		__u8 pad02[6];
 	} v0;
diff --git a/drivers/gpu/drm/nouveau/include/nvif/if0012.h b/drivers/gpu/drm/nouveau/include/nvif/if0012.h
index dc0a5c372f511..84145d22c2593 100644
--- a/drivers/gpu/drm/nouveau/include/nvif/if0012.h
+++ b/drivers/gpu/drm/nouveau/include/nvif/if0012.h
@@ -57,6 +57,7 @@ union nvif_outp_args {
 #define NVIF_OUTP_V0_LVDS          0x40
 
 #define NVIF_OUTP_V0_HDMI          0x50
+#define NVIF_OUTP_V0_HDMI_FRL      0x51
 
 #define NVIF_OUTP_V0_INFOFRAME     0x60
 #define NVIF_OUTP_V0_HDA_ELD       0x61
@@ -192,6 +193,15 @@ union nvif_outp_hdmi_args {
 	} v0;
 };
 
+union nvif_outp_hdmi_frl_args {
+	struct nvif_outp_hdmi_frl_v0 {
+		__u8 version;
+		__u8 head;
+		__u8 frl_rate;
+		__u8 pad03[5];
+	} v0;
+};
+
 union nvif_outp_infoframe_args {
 	struct nvif_outp_infoframe_v0 {
 		__u8 version;
diff --git a/drivers/gpu/drm/nouveau/include/nvif/outp.h b/drivers/gpu/drm/nouveau/include/nvif/outp.h
index 55c02a34f381e..22681b14add4f 100644
--- a/drivers/gpu/drm/nouveau/include/nvif/outp.h
+++ b/drivers/gpu/drm/nouveau/include/nvif/outp.h
@@ -93,6 +93,7 @@ int nvif_outp_lvds(struct nvif_outp *, bool dual, bool bpc8);
 
 int nvif_outp_hdmi(struct nvif_outp *, int head, bool enable, u8 max_ac_packet, u8 rekey, u32 khz,
 		   bool scdc, bool scdc_scrambling, bool scdc_low_rates);
+int nvif_outp_hdmi_frl(struct nvif_outp *, int head, int frl_rate);
 
 int nvif_outp_infoframe(struct nvif_outp *, u8 type, struct nvif_outp_infoframe_v0 *, u32 size);
 int nvif_outp_hda_eld(struct nvif_outp *, int head, void *data, u32 size);
diff --git a/drivers/gpu/drm/nouveau/include/nvkm/engine/disp.h b/drivers/gpu/drm/nouveau/include/nvkm/engine/disp.h
index 7903d7470d194..4736f07a266b9 100644
--- a/drivers/gpu/drm/nouveau/include/nvkm/engine/disp.h
+++ b/drivers/gpu/drm/nouveau/include/nvkm/engine/disp.h
@@ -18,12 +18,14 @@ struct nvkm_disp {
 		struct nvkm_gsp_object objcom;
 		struct nvkm_gsp_object object;
 
-#define NVKM_DPYID_PLUG   BIT(0)
-#define NVKM_DPYID_UNPLUG BIT(1)
-#define NVKM_DPYID_IRQ    BIT(2)
+#define NVKM_DPYID_PLUG       BIT(0)
+#define NVKM_DPYID_UNPLUG     BIT(1)
+#define NVKM_DPYID_IRQ        BIT(2)
+#define NVKM_DPYID_FRL_RETRAIN BIT(3)
 		struct nvkm_event event;
 		struct nvkm_gsp_event hpd;
 		struct nvkm_gsp_event irq;
+		struct nvkm_gsp_event frl_retrain;
 
 		u32 assigned_sors;
 	} rm;
diff --git a/drivers/gpu/drm/nouveau/nvif/outp.c b/drivers/gpu/drm/nouveau/nvif/outp.c
index 60479c884b2dc..828c312536d41 100644
--- a/drivers/gpu/drm/nouveau/nvif/outp.c
+++ b/drivers/gpu/drm/nouveau/nvif/outp.c
@@ -222,6 +222,22 @@ nvif_outp_infoframe(struct nvif_outp *outp, u8 type, struct nvif_outp_infoframe_
 	return ret;
 }
 
+int
+nvif_outp_hdmi_frl(struct nvif_outp *outp, int head, int frl_rate)
+{
+	struct nvif_outp_hdmi_frl_v0 args;
+	int ret;
+
+	args.version = 0;
+	args.head = head;
+	args.frl_rate = frl_rate;
+
+	ret = nvif_mthd(&outp->object, NVIF_OUTP_V0_HDMI_FRL, &args, sizeof(args));
+	NVIF_ERRON(ret, &outp->object, "[HDMI_FRL head:%d frl_rate:%d]",
+		   args.head, args.frl_rate);
+	return ret;
+}
+
 int
 nvif_outp_hdmi(struct nvif_outp *outp, int head, bool enable, u8 max_ac_packet, u8 rekey,
 	       u32 khz, bool scdc, bool scdc_scrambling, bool scdc_low_rates)
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h
index 3ba04bead2f9c..fba24625b6882 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h
@@ -75,6 +75,7 @@ struct nvkm_ior_func {
 		void (*infoframe_avi)(struct nvkm_ior *, int head, void *data, u32 size);
 		void (*infoframe_vsi)(struct nvkm_ior *, int head, void *data, u32 size);
 		void (*audio)(struct nvkm_ior *, int head, bool enable);
+		void (*frl_train)(struct nvkm_ior *, int head, int frl_rate);
 	} *hdmi;
 
 	const struct nvkm_ior_func_dp {
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/uconn.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/uconn.c
index 23d1e5c27bb1e..e1e2760833ad5 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/uconn.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/uconn.c
@@ -43,6 +43,8 @@ nvkm_uconn_uevent_gsp(struct nvkm_object *object, u64 token, u32 bits)
 		args.v0.types |= NVIF_CONN_EVENT_V0_UNPLUG;
 	if (bits & NVKM_DPYID_IRQ)
 		args.v0.types |= NVIF_CONN_EVENT_V0_IRQ;
+	if (bits & NVKM_DPYID_FRL_RETRAIN)
+		args.v0.types |= NVIF_CONN_EVENT_V0_FRL;
 
 	return object->client->event(token, &args, sizeof(args.v0));
 }
@@ -122,6 +124,7 @@ nvkm_uconn_uevent(struct nvkm_object *object, void *argv, u32 argc, struct nvkm_
 		if (args->v0.types & NVIF_CONN_EVENT_V0_PLUG  ) bits |= NVKM_DPYID_PLUG;
 		if (args->v0.types & NVIF_CONN_EVENT_V0_UNPLUG) bits |= NVKM_DPYID_UNPLUG;
 		if (args->v0.types & NVIF_CONN_EVENT_V0_IRQ   ) bits |= NVKM_DPYID_IRQ;
+		if (args->v0.types & NVIF_CONN_EVENT_V0_FRL   ) bits |= NVKM_DPYID_FRL_RETRAIN;
 
 		return nvkm_uevent_add(uevent, &disp->rm.event, outp->index, bits,
 				       nvkm_uconn_uevent_gsp);
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c
index 202fdc73f7cab..2245fbd2d1e94 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c
@@ -239,6 +239,23 @@ nvkm_uoutp_mthd_infoframe(struct nvkm_outp *outp, void *argv, u32 argc)
 	return -EINVAL;
 }
 
+static int
+nvkm_uoutp_mthd_hdmi_frl(struct nvkm_outp *outp, void *argv, u32 argc)
+{
+	union nvif_outp_hdmi_frl_args *args = argv;
+	struct nvkm_ior *ior = outp->ior;
+
+	if (argc != sizeof(args->v0) || args->v0.version != 0)
+		return -ENOSYS;
+	if (!ior->func->hdmi || !ior->func->hdmi->frl_train)
+		return -EINVAL;
+	if (!nvkm_head_find(outp->disp, args->v0.head))
+		return -EINVAL;
+
+	ior->func->hdmi->frl_train(ior, args->v0.head, args->v0.frl_rate);
+	return 0;
+}
+
 static int
 nvkm_uoutp_mthd_hdmi(struct nvkm_outp *outp, void *argv, u32 argc)
 {
@@ -257,6 +274,8 @@ nvkm_uoutp_mthd_hdmi(struct nvkm_outp *outp, void *argv, u32 argc)
 		return -EINVAL;
 
 	if (!args->v0.enable) {
+		if (ior->func->hdmi->frl_train)
+			ior->func->hdmi->frl_train(ior, args->v0.head, 0);
 		ior->func->hdmi->infoframe_avi(ior, args->v0.head, NULL, 0);
 		ior->func->hdmi->infoframe_vsi(ior, args->v0.head, NULL, 0);
 		ior->func->hdmi->ctrl(ior, args->v0.head, false, 0, 0);
@@ -518,6 +537,7 @@ nvkm_uoutp_mthd_acquired(struct nvkm_outp *outp, u32 mthd, void *argv, u32 argc)
 	case NVIF_OUTP_V0_RELEASE      : return nvkm_uoutp_mthd_release      (outp, argv, argc);
 	case NVIF_OUTP_V0_LVDS         : return nvkm_uoutp_mthd_lvds         (outp, argv, argc);
 	case NVIF_OUTP_V0_HDMI         : return nvkm_uoutp_mthd_hdmi         (outp, argv, argc);
+	case NVIF_OUTP_V0_HDMI_FRL     : return nvkm_uoutp_mthd_hdmi_frl     (outp, argv, argc);
 	case NVIF_OUTP_V0_INFOFRAME    : return nvkm_uoutp_mthd_infoframe    (outp, argv, argc);
 	case NVIF_OUTP_V0_HDA_ELD      : return nvkm_uoutp_mthd_hda_eld      (outp, argv, argc);
 	case NVIF_OUTP_V0_DP_TRAIN     : return nvkm_uoutp_mthd_dp_train     (outp, argv, argc);
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
index 2fb7cc83852f9..a4a9462d59027 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
@@ -552,6 +552,46 @@ r535_sor_hdmi_ctrl(struct nvkm_ior *sor, int head, bool enable, u8 max_ac_packet
 	WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl));
 }
 
+static int
+r535_sor_frl_train_one(struct nvkm_ior *sor, int frl_rate, bool fake_lt)
+{
+	struct nvkm_disp *disp = sor->disp;
+	NV0073_CTRL_SPECIFIC_SET_HDMI_FRL_LINK_CONFIG_PARAMS *ctrl;
+	int ret;
+
+	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
+				    NV0073_CTRL_CMD_SPECIFIC_SET_HDMI_FRL_CONFIG,
+				    sizeof(*ctrl));
+	if (IS_ERR(ctrl))
+		return PTR_ERR(ctrl);
+
+	ctrl->displayId = BIT(sor->asy.outp->index);
+	ctrl->data = NVVAL(NV0073_CTRL, HDMI_FRL_DATA, SET_FRL_RATE, frl_rate);
+	ctrl->bFakeLt = fake_lt;
+	ctrl->bDoNotSkipLt = true;
+
+	ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl));
+	if (ret) {
+		nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
+		return ret;
+	}
+
+	ret = ctrl->bLtSkipped ? -EIO : 0;
+
+	nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
+	return ret;
+}
+
+static void
+r535_sor_frl_train(struct nvkm_ior *sor, int head, int frl_rate)
+{
+	if (!frl_rate)
+		return;
+
+	if (r535_sor_frl_train_one(sor, frl_rate, false))
+		r535_sor_frl_train_one(sor, frl_rate, true);
+}
+
 static const struct nvkm_ior_func_hdmi
 r535_sor_hdmi = {
 	.ctrl = r535_sor_hdmi_ctrl,
@@ -559,6 +599,7 @@ r535_sor_hdmi = {
 	.infoframe_avi = gv100_sor_hdmi_infoframe_avi,
 	.infoframe_vsi = gv100_sor_hdmi_infoframe_vsi,
 	.audio = r535_sor_hdmi_audio,
+	.frl_train = r535_sor_frl_train,
 };
 
 static const struct nvkm_ior_func
@@ -1383,6 +1424,21 @@ r535_outp_new(struct nvkm_disp *disp, u32 id)
 	return 0;
 }
 
+static void
+r535_disp_frl_retrain(struct nvkm_gsp_event *event, void *repv, u32 repc)
+{
+	struct nvkm_disp *disp = container_of(event, typeof(*disp), rm.frl_retrain);
+	Nv2080HdmiFrlRetrainingRequestNotification *frl = repv;
+
+	if (WARN_ON(repc < sizeof(*frl)))
+		return;
+
+	nvkm_debug(&disp->engine.subdev, "event: frl retrain displayId %08x\n", frl->displayId);
+
+	if (frl->displayId)
+		nvkm_event_ntfy(&disp->rm.event, fls(frl->displayId) - 1, NVKM_DPYID_FRL_RETRAIN);
+}
+
 static void
 r535_disp_irq(struct nvkm_gsp_event *event, void *repv, u32 repc)
 {
@@ -1465,6 +1521,7 @@ r535_disp_fini(struct nvkm_disp *disp, bool suspend)
 	nvkm_gsp_rm_free(&disp->rm.object);
 
 	if (!suspend) {
+		nvkm_gsp_event_dtor(&disp->rm.frl_retrain);
 		nvkm_gsp_event_dtor(&disp->rm.irq);
 		nvkm_gsp_event_dtor(&disp->rm.hpd);
 		nvkm_event_fini(&disp->rm.event);
@@ -1706,7 +1763,7 @@ r535_disp_oneinit(struct nvkm_disp *disp)
 			return ret;
 	}
 
-	ret = nvkm_event_init(&r535_disp_event, &gsp->subdev, 3, 32, &disp->rm.event);
+	ret = nvkm_event_init(&r535_disp_event, &gsp->subdev, 4, 32, &disp->rm.event);
 	if (WARN_ON(ret))
 		return ret;
 
@@ -1720,6 +1777,12 @@ r535_disp_oneinit(struct nvkm_disp *disp)
 	if (ret)
 		return ret;
 
+	ret = nvkm_gsp_device_event_ctor(&disp->rm.device, 0x007e0002,
+					 NV2080_NOTIFIERS_HDMI_FRL_RETRAINING_REQUEST,
+					 r535_disp_frl_retrain, &disp->rm.frl_retrain);
+	if (ret)
+		return ret;
+
 	/* RAMHT. */
 	ret = nvkm_ramht_new(device, disp->func->ramht_size ? disp->func->ramht_size :
 			     0x1000, 0, disp->inst, &disp->ramht);
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/nvrm/disp.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/nvrm/disp.h
index 7b7539639540a..f6c7f0b96a48b 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/nvrm/disp.h
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/nvrm/disp.h
@@ -184,6 +184,12 @@ typedef struct Nv2080DpIrqNotificationRec {
     NvU32 displayId;
 } Nv2080DpIrqNotification;
 
+#define NV2080_NOTIFIERS_HDMI_FRL_RETRAINING_REQUEST               (18)
+
+typedef struct {
+    NvU32 displayId;
+} Nv2080HdmiFrlRetrainingRequestNotification;
+
 #define NV0073_CTRL_CMD_SYSTEM_GET_CONNECT_STATE (0x730122U) /* finn: Evaluated from "(FINN_NV04_DISPLAY_COMMON_SYSTEM_INTERFACE_ID << 8) | NV0073_CTRL_SYSTEM_GET_CONNECT_STATE_PARAMS_MESSAGE_ID" */
 typedef struct NV0073_CTRL_SYSTEM_GET_CONNECT_STATE_PARAMS {
     NvU32 subDeviceInstance;
@@ -393,6 +399,26 @@ typedef struct NV0073_CTRL_SPECIFIC_SET_HDMI_SINK_CAPS_PARAMS {
 #define NV0073_CTRL_CMD_SPECIFIC_SET_HDMI_SINK_CAPS_DSC_MAX_FRL_RATE_SUPPORTED_4LANES_10G (0x00000005U)
 #define NV0073_CTRL_CMD_SPECIFIC_SET_HDMI_SINK_CAPS_DSC_MAX_FRL_RATE_SUPPORTED_4LANES_12G (0x00000006U)
 
+#define NV0073_CTRL_CMD_SPECIFIC_SET_HDMI_FRL_CONFIG (0x73029aU)
+#define NV0073_CTRL_SPECIFIC_SET_HDMI_FRL_LINK_CONFIG_PARAMS_MESSAGE_ID (0x9AU)
+typedef struct NV0073_CTRL_SPECIFIC_SET_HDMI_FRL_LINK_CONFIG_PARAMS {
+    NvU32  subDeviceInstance;
+    NvU32  displayId;
+    NvU32  data;
+    NvBool bFakeLt;
+    NvBool bDoNotSkipLt;
+    NvBool bLtSkipped;
+    NvBool bLinkAssessmentOnly;
+} NV0073_CTRL_SPECIFIC_SET_HDMI_FRL_LINK_CONFIG_PARAMS;
+#define NV0073_CTRL_HDMI_FRL_DATA_SET_FRL_RATE                                           2:0
+#define NV0073_CTRL_HDMI_FRL_DATA_SET_FRL_RATE_NONE       (0x00000000U)
+#define NV0073_CTRL_HDMI_FRL_DATA_SET_FRL_RATE_3LANES_3G  (0x00000001U)
+#define NV0073_CTRL_HDMI_FRL_DATA_SET_FRL_RATE_3LANES_6G  (0x00000002U)
+#define NV0073_CTRL_HDMI_FRL_DATA_SET_FRL_RATE_4LANES_6G  (0x00000003U)
+#define NV0073_CTRL_HDMI_FRL_DATA_SET_FRL_RATE_4LANES_8G  (0x00000004U)
+#define NV0073_CTRL_HDMI_FRL_DATA_SET_FRL_RATE_4LANES_10G (0x00000005U)
+#define NV0073_CTRL_HDMI_FRL_DATA_SET_FRL_RATE_4LANES_12G (0x00000006U)
+
 #define NV0073_CTRL_SET_OD_MAX_PACKET_SIZE     36U
 
 #define NV0073_CTRL_CMD_SPECIFIC_SET_OD_PACKET (0x730288U) /* finn: Evaluated from "(FINN_NV04_DISPLAY_COMMON_SPECIFIC_INTERFACE_ID << 8) | NV0073_CTRL_SPECIFIC_SET_OD_PACKET_PARAMS_MESSAGE_ID" */
-- 
2.53.0


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

* [PATCH 4/4] nouveau: wire up HDMI FRL in the display frontend
  2026-04-23  0:42 nouveau: add HDMI FRL support for GSP enabled GPUs Dave Airlie
                   ` (2 preceding siblings ...)
  2026-04-23  0:42 ` [PATCH 3/4] nouveau: add HDMI FRL training API and retrain event handling Dave Airlie
@ 2026-04-23  0:42 ` Dave Airlie
  3 siblings, 0 replies; 5+ messages in thread
From: Dave Airlie @ 2026-04-23  0:42 UTC (permalink / raw)
  To: dri-devel; +Cc: nouveau

From: Dave Airlie <airlied@redhat.com>

Integrate HDMI FRL (Fixed Rate Link) support into the nouveau display
frontend for Turing+ GPUs with GSP-RM firmware:

- Select FRL protocol and trigger link training during SOR enable for
  modes at >= 594 MHz on FRL-capable sinks
- Pick minimum sufficient FRL rate rather than always using maximum
- Report FRL bandwidth caps in TMDS link bandwidth for mode validation
- Set flush_disable for FRL outputs (link training happens post-flush)
- Skip SCDC TMDS config when using FRL signaling
- Register FRL retrain events on HDMI connectors and handle retrains
- Clear FRL state on encoder disable
- Move shared FRL rate table and max rate helper to nouveau_encoder.h

This was claude code assisted.

Signed-off-by: Dave Airlie <airlied@redhat.com>
---
 drivers/gpu/drm/nouveau/dispnv50/disp.c     | 36 +++++++++++--
 drivers/gpu/drm/nouveau/nouveau_connector.c | 60 +++++++++++++++------
 drivers/gpu/drm/nouveau/nouveau_connector.h |  1 +
 drivers/gpu/drm/nouveau/nouveau_display.c   | 10 +++-
 drivers/gpu/drm/nouveau/nouveau_encoder.h   | 31 +++++++++++
 5 files changed, 117 insertions(+), 21 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c b/drivers/gpu/drm/nouveau/dispnv50/disp.c
index 6c3a8712d38ab..ed71ad6d38c27 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/disp.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c
@@ -60,6 +60,7 @@
 #include <nvhw/class/cl887d.h>
 #include <nvhw/class/cl907d.h>
 #include <nvhw/class/cl917d.h>
+#include <nvhw/class/clca7d.h>
 
 #include "nouveau_drv.h"
 #include "nouveau_dma.h"
@@ -774,6 +775,20 @@ nv50_audio_enable(struct drm_encoder *encoder, struct nouveau_crtc *nv_crtc,
 /******************************************************************************
  * HDMI
  *****************************************************************************/
+static int
+nv50_hdmi_pick_frl_rate(struct drm_hdmi_info *hdmi, unsigned clock)
+{
+	int max_rate = nouveau_hdmi_get_max_frl_rate(hdmi->max_frl_rate_per_lane,
+						     hdmi->max_lanes);
+	int rate;
+
+	for (rate = 1; rate <= max_rate; rate++) {
+		if (nouveau_hdmi_frl_max_clock[rate] >= clock)
+			return rate;
+	}
+	return max_rate;
+}
+
 static void
 nv50_hdmi_enable(struct drm_encoder *encoder, struct nouveau_crtc *nv_crtc,
 		 struct nouveau_connector *nv_connector, struct drm_atomic_state *state,
@@ -794,7 +809,8 @@ nv50_hdmi_enable(struct drm_encoder *encoder, struct nouveau_crtc *nv_crtc,
 	max_ac_packet -= 18; /* constant from tegra */
 	max_ac_packet /= 32;
 
-	if (nv_encoder->i2c && hdmi->scdc.scrambling.supported) {
+	if (nv_encoder->i2c && hdmi->scdc.scrambling.supported &&
+	    !(mode->clock >= 594000 && hdmi->max_frl_rate_per_lane)) {
 		const bool high_tmds_clock_ratio = mode->clock > 340000;
 		u8 scdc;
 
@@ -848,6 +864,11 @@ nv50_hdmi_enable(struct drm_encoder *encoder, struct nouveau_crtc *nv_crtc,
 	nvif_outp_infoframe(&nv_encoder->outp, NVIF_OUTP_INFOFRAME_V0_VSI, args, size);
 
 	nv_encoder->hdmi.enabled = true;
+
+	if (mode->clock >= 594000 && hdmi->max_frl_rate_per_lane)
+		nv_encoder->hdmi.frl_rate = nv50_hdmi_pick_frl_rate(hdmi, mode->clock);
+	else
+		nv_encoder->hdmi.frl_rate = 0;
 }
 
 /******************************************************************************
@@ -1585,6 +1606,7 @@ nv50_sor_atomic_disable(struct drm_encoder *encoder, struct drm_atomic_state *st
 		nvif_outp_hdmi(&nv_encoder->outp, head->base.index,
 			       false, 0, 0, 0, false, false, false);
 		nv_encoder->hdmi.enabled = false;
+		nv_encoder->hdmi.frl_rate = 0;
 	}
 
 	if (nv_encoder->dcb->type == DCB_OUTPUT_DP)
@@ -1780,7 +1802,9 @@ nv50_sor_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_state *sta
 		    nv_connector->base.display_info.is_hdmi)
 			nv50_hdmi_enable(encoder, nv_crtc, nv_connector, state, mode, hda);
 
-		if (nv_encoder->outp.or.link & 1) {
+		if (nv_encoder->hdmi.frl_rate) {
+			proto = NVCA7D_SOR_SET_CONTROL_PROTOCOL_HDMI_FRL;
+		} else if (nv_encoder->outp.or.link & 1) {
 			proto = NV507D_SOR_SET_CONTROL_PROTOCOL_SINGLE_TMDS_A;
 			/* Only enable dual-link if:
 			 *  - Need to (i.e. rate > 165MHz)
@@ -1852,6 +1876,10 @@ nv50_sor_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_state *sta
 		head->func->display_id(head, BIT(nv_encoder->outp.id));
 
 	nv_encoder->update(nv_encoder, nv_crtc->index, asyh, proto, depth);
+
+	if (nv_encoder->hdmi.frl_rate)
+		nvif_outp_hdmi_frl(&nv_encoder->outp, nv_crtc->index,
+				   nv_encoder->hdmi.frl_rate);
 }
 
 static const struct drm_encoder_helper_funcs
@@ -2525,7 +2553,9 @@ nv50_disp_outp_atomic_check_clr(struct nv50_atom *atom,
 			return PTR_ERR(outp);
 
 		if (outp->encoder->encoder_type == DRM_MODE_ENCODER_DPMST ||
-		    nouveau_encoder(outp->encoder)->dcb->type == DCB_OUTPUT_DP)
+		    nouveau_encoder(outp->encoder)->dcb->type == DCB_OUTPUT_DP ||
+		    nouveau_encoder(outp->encoder)->hdmi.frl_rate ||
+		    old_connector_state->connector->display_info.hdmi.max_frl_rate_per_lane)
 			atom->flush_disable = true;
 		outp->clr.ctrl = true;
 		atom->lock_core = true;
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c
index 870b467d35e17..8a9d20fd041ab 100644
--- a/drivers/gpu/drm/nouveau/nouveau_connector.c
+++ b/drivers/gpu/drm/nouveau/nouveau_connector.c
@@ -396,6 +396,7 @@ static void
 nouveau_connector_destroy(struct drm_connector *connector)
 {
 	struct nouveau_connector *nv_connector = nouveau_connector(connector);
+	nvif_event_dtor(&nv_connector->frl);
 	nvif_event_dtor(&nv_connector->irq);
 	nvif_event_dtor(&nv_connector->hpd);
 	kfree(nv_connector->edid);
@@ -560,21 +561,23 @@ nouveau_connector_set_edid(struct nouveau_connector *nv_connector,
 	}
 }
 
-static int
-nouveau_hdmi_get_frl_rate(u8 max_frl_rate_per_lane, u8 max_lanes)
+bool
+nouveau_hdmi_frl_retrain(struct nouveau_connector *nv_connector)
 {
-	switch (max_lanes) {
-	case 3:
-		switch (max_frl_rate_per_lane) {
-		case 3: return 1;
-		case 6: return 2;
-		}
-		return 0;
-	case 4:
-		return max_frl_rate_per_lane / 2;
-	default:
-		return 0;
-	}
+	struct nouveau_encoder *nv_encoder;
+	struct nouveau_crtc *nv_crtc;
+
+	nv_encoder = find_encoder(&nv_connector->base, DCB_OUTPUT_TMDS);
+	if (!nv_encoder || !nv_encoder->hdmi.frl_rate)
+		return true;
+
+	if (!nv_encoder->crtc)
+		return true;
+
+	nv_crtc = nouveau_crtc(nv_encoder->crtc);
+	nvif_outp_hdmi_frl(&nv_encoder->outp, nv_crtc->index,
+			   nv_encoder->hdmi.frl_rate);
+	return true;
 }
 
 static enum drm_connector_status
@@ -663,10 +666,10 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
 						hdmi->scdc.supported,
 						hdmi->scdc.scrambling.supported,
 						hdmi->scdc.scrambling.low_rates,
-						nouveau_hdmi_get_frl_rate(hdmi->max_frl_rate_per_lane,
+						nouveau_hdmi_get_max_frl_rate(hdmi->max_frl_rate_per_lane,
 									 hdmi->max_lanes),
 						hdmi->dsc_cap.v_1p2 ?
-							nouveau_hdmi_get_frl_rate(hdmi->dsc_cap.max_frl_rate_per_lane,
+							nouveau_hdmi_get_max_frl_rate(hdmi->dsc_cap.max_frl_rate_per_lane,
 										  hdmi->dsc_cap.max_lanes) : 0);
 		}
 
@@ -1085,6 +1088,12 @@ get_tmds_link_bandwidth(struct drm_connector *connector)
 			const int max_tmds_clock =
 				info->hdmi.scdc.scrambling.supported ?
 				594000 : 340000;
+			int frl_rate = nouveau_hdmi_get_max_frl_rate(
+				info->hdmi.max_frl_rate_per_lane,
+				info->hdmi.max_lanes);
+			if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_TURING &&
+			    frl_rate > 0)
+				return nouveau_hdmi_frl_max_clock[frl_rate];
 			return info->max_tmds_clock ?
 				min(info->max_tmds_clock, max_tmds_clock) :
 				max_tmds_clock;
@@ -1244,6 +1253,16 @@ nouveau_connector_irq(struct nvif_event *event, void *repv, u32 repc)
 	return NVIF_EVENT_KEEP;
 }
 
+static int
+nouveau_connector_frl(struct nvif_event *event, void *repv, u32 repc)
+{
+	struct nouveau_connector *nv_connector = container_of(event, typeof(*nv_connector), frl);
+	struct nvif_conn_event_v0 *rep = repv;
+
+	nouveau_connector_hpd(nv_connector, rep->types);
+	return NVIF_EVENT_KEEP;
+}
+
 static int
 nouveau_connector_hotplug(struct nvif_event *event, void *repv, u32 repc)
 {
@@ -1476,6 +1495,15 @@ nouveau_connector_create(struct drm_device *dev, int index)
 				nvif_conn_dtor(&nv_connector->conn);
 				goto drm_conn_err;
 			}
+		} else if (type == DRM_MODE_CONNECTOR_HDMIA) {
+			ret = nvif_conn_event_ctor(&nv_connector->conn, "kmsFrlRetrain",
+						   nouveau_connector_frl, NVIF_CONN_EVENT_V0_FRL,
+						   &nv_connector->frl);
+			if (ret) {
+				nvif_event_dtor(&nv_connector->hpd);
+				nvif_conn_dtor(&nv_connector->conn);
+				goto drm_conn_err;
+			}
 		}
 	}
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.h b/drivers/gpu/drm/nouveau/nouveau_connector.h
index 0608cabed058e..d522dbc5678ef 100644
--- a/drivers/gpu/drm/nouveau/nouveau_connector.h
+++ b/drivers/gpu/drm/nouveau/nouveau_connector.h
@@ -126,6 +126,7 @@ struct nouveau_connector {
 	u64 hpd_pending;
 	struct nvif_event hpd;
 	struct nvif_event irq;
+	struct nvif_event frl;
 	struct work_struct irq_work;
 
 	struct drm_dp_aux aux;
diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c
index d71dcfc6ee66b..96e66d04f75e2 100644
--- a/drivers/gpu/drm/nouveau/nouveau_display.c
+++ b/drivers/gpu/drm/nouveau/nouveau_display.c
@@ -456,17 +456,23 @@ nouveau_display_hpd_work(struct work_struct *work)
 		nv_connector->hpd_pending = 0;
 		spin_unlock_irq(&drm->hpd_lock);
 
-		drm_dbg_kms(dev, "[CONNECTOR:%d:%s] plug:%d unplug:%d irq:%d\n",
+		drm_dbg_kms(dev, "[CONNECTOR:%d:%s] plug:%d unplug:%d irq:%d frl:%d\n",
 			    connector->base.id, connector->name,
 			    !!(bits & NVIF_CONN_EVENT_V0_PLUG),
 			    !!(bits & NVIF_CONN_EVENT_V0_UNPLUG),
-			    !!(bits & NVIF_CONN_EVENT_V0_IRQ));
+			    !!(bits & NVIF_CONN_EVENT_V0_IRQ),
+			    !!(bits & NVIF_CONN_EVENT_V0_FRL));
 
 		if (bits & NVIF_CONN_EVENT_V0_IRQ) {
 			if (nouveau_dp_link_check(nv_connector))
 				continue;
 		}
 
+		if (bits & NVIF_CONN_EVENT_V0_FRL) {
+			if (nouveau_hdmi_frl_retrain(nv_connector))
+				continue;
+		}
+
 		connector->status = drm_helper_probe_detect(connector, NULL, false);
 		if (old_epoch_counter == connector->epoch_counter)
 			continue;
diff --git a/drivers/gpu/drm/nouveau/nouveau_encoder.h b/drivers/gpu/drm/nouveau/nouveau_encoder.h
index dce8e5d9d4964..7eefbf4a09205 100644
--- a/drivers/gpu/drm/nouveau/nouveau_encoder.h
+++ b/drivers/gpu/drm/nouveau/nouveau_encoder.h
@@ -72,6 +72,7 @@ struct nouveau_encoder {
 	struct {
 		struct {
 			bool enabled;
+			int frl_rate;
 		} hdmi;
 
 		struct {
@@ -164,6 +165,36 @@ enum drm_mode_status nv50_dp_mode_valid(struct nouveau_encoder *,
 					const struct drm_display_mode *,
 					unsigned *clock);
 
+/* nouveau_connector.c */
+bool nouveau_hdmi_frl_retrain(struct nouveau_connector *);
+
+/* Max pixel clock (kHz) at 8bpc for each FRL rate, after 16b/18b encoding. */
+static const unsigned nouveau_hdmi_frl_max_clock[] __maybe_unused = {
+	[1] = 333333,   /* 3L×3G */
+	[2] = 666666,   /* 3L×6G */
+	[3] = 888888,   /* 4L×6G */
+	[4] = 1185185,  /* 4L×8G */
+	[5] = 1481481,  /* 4L×10G */
+	[6] = 1777777,  /* 4L×12G */
+};
+
+static inline int
+nouveau_hdmi_get_max_frl_rate(u8 max_frl_rate_per_lane, u8 max_lanes)
+{
+	switch (max_lanes) {
+	case 3:
+		switch (max_frl_rate_per_lane) {
+		case 3: return 1;
+		case 6: return 2;
+		}
+		return 0;
+	case 4:
+		return max_frl_rate_per_lane / 2;
+	default:
+		return 0;
+	}
+}
+
 struct nouveau_connector *
 nv50_outp_get_new_connector(struct drm_atomic_state *state, struct nouveau_encoder *outp);
 struct nouveau_connector *
-- 
2.53.0


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

end of thread, other threads:[~2026-04-23  0:53 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-23  0:42 nouveau: add HDMI FRL support for GSP enabled GPUs Dave Airlie
2026-04-23  0:42 ` [PATCH 1/4] nouveau: move HDMI sink caps to outp level for GSP-RM Dave Airlie
2026-04-23  0:42 ` [PATCH 2/4] nouveau: add FRL and DSC parameters to HDMI sink caps interface Dave Airlie
2026-04-23  0:42 ` [PATCH 3/4] nouveau: add HDMI FRL training API and retrain event handling Dave Airlie
2026-04-23  0:42 ` [PATCH 4/4] nouveau: wire up HDMI FRL in the display frontend Dave Airlie

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.