From: Dave Airlie <airlied@gmail.com>
To: dri-devel@lists.freedesktop.org
Cc: nouveau@lists.freedesktop.org
Subject: [PATCH 3/4] nouveau: add HDMI FRL training API and retrain event handling
Date: Thu, 23 Apr 2026 10:42:15 +1000 [thread overview]
Message-ID: <20260423004552.3289884-4-airlied@gmail.com> (raw)
In-Reply-To: <20260423004552.3289884-1-airlied@gmail.com>
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
next prev parent reply other threads:[~2026-04-23 2:55 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
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 [this message]
2026-04-23 0:42 ` [PATCH 4/4] nouveau: wire up HDMI FRL in the display frontend Dave Airlie
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260423004552.3289884-4-airlied@gmail.com \
--to=airlied@gmail.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=nouveau@lists.freedesktop.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox