From: Dave Airlie <airlied@gmail.com>
To: dri-devel@lists.freedesktop.org
Cc: nouveau@lists.freedesktop.org
Subject: [PATCH 4/4] nouveau: wire up HDMI FRL in the display frontend
Date: Thu, 23 Apr 2026 10:42:16 +1000 [thread overview]
Message-ID: <20260423004552.3289884-5-airlied@gmail.com> (raw)
In-Reply-To: <20260423004552.3289884-1-airlied@gmail.com>
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
prev parent reply other threads:[~2026-04-23 0:52 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 ` [PATCH 3/4] nouveau: add HDMI FRL training API and retrain event handling Dave Airlie
2026-04-23 0:42 ` Dave Airlie [this message]
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-5-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 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.