* [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