Hi Jani, Apologies for the rapid succession of versions. The repeated revisions were primarily to address CI.Checkpatch formatting warnings and i915.CI.BAT WARN_ON failures. The core logic of the patch has not changed. v6 is the final version for this patch. Happy to wait for your review at your convenience. Thank you for the feedback.
Best regards ________________________________ From: Jani Nikula <[email protected]> Sent: Wednesday, September 2, 2026 4:37 PM To: Xie, Xiaolu <[email protected]>; [email protected] <[email protected]> Cc: [email protected] <[email protected]>; Deak, Imre <[email protected]>; Xie, Xiaolu <[email protected]> Subject: Re: [PATCH v6] drm/i915/display: Enable VRR/HDR over MST On Wed, 02 Sep 2026, Xiao Lu <[email protected]> wrote: > VRR over MST: > - Remove the hard MST block in intel_vrr_is_capable(); check the > downstream port's MSA timing par ignore capability via REMOTE_DPCD_READ > and cache it in connector->dp.mst_msa_timing_par_ignore to avoid hot > path sideband round-trips. > - Read mst_msa_timing_par_ignore unconditionally from the sink's virtual > DPCD before the DSC decompression check, so VRR works on MST sinks > without DSC support. > - Attach vrr_capable property by reusing the root SST connector's existing > property object (MST connectors are created post device-registration so > new properties cannot be created). > - Update vrr_capable after EDID read; gate AS-SDP and > DP_MSA_TIMING_PAR_IGNORE_EN writes on pipe_config->vrr.enable > (actual state) not intel_vrr_is_capable() (capability); allow AS-SDP > through MST hubs by excluding only non-MST branch devices (PCONs) > from the branch device check. > - Reuse colorspace property from root SST connector for MST connectors > to avoid WARN_ON from creating properties post device-registration. > > HDR metadata over MST: > - Add VSC SDP, HDR metadata infoframe SDP and AS-SDP computation to > mst_stream_compute_config() and enable their transmission in > mst_stream_enable() and intel_ddi_update_pipe(). > - Lift the hard 8bpc pipe_bpp cap and use the bpp capability from sink > EDID for MST streams to allow 10bpc, relying on > intel_dp_mst_atomic_check_link()'s BW retry loop to fall back if the > link cannot fit it. What changed between versions? Please don't send a bunch of versions of a patch in quick succession. Truth is, nobody's going to spend time looking at it, because they think it's in a state of flux and it'll be wasted effort. BR, Jani. > > Signed-off-by: Xiao Lu <[email protected]> > --- > drivers/gpu/drm/display/drm_dp_mst_topology.c | 2 + > drivers/gpu/drm/i915/display/intel_ddi.c | 3 + > .../drm/i915/display/intel_display_types.h | 1 + > drivers/gpu/drm/i915/display/intel_dp.c | 36 ++++--- > drivers/gpu/drm/i915/display/intel_dp.h | 8 ++ > drivers/gpu/drm/i915/display/intel_dp_mst.c | 95 +++++++++++++++++++ > drivers/gpu/drm/i915/display/intel_vrr.c | 15 ++- > 7 files changed, 143 insertions(+), 17 deletions(-) > > diff --git a/drivers/gpu/drm/display/drm_dp_mst_topology.c > b/drivers/gpu/drm/display/drm_dp_mst_topology.c > index 7ce9e212770a..174fa62cb0dc 100644 > --- a/drivers/gpu/drm/display/drm_dp_mst_topology.c > +++ b/drivers/gpu/drm/display/drm_dp_mst_topology.c > @@ -2164,6 +2164,7 @@ ssize_t drm_dp_mst_dpcd_read(struct drm_dp_aux *aux, > return drm_dp_send_dpcd_read(port->mgr, port, > offset, size, buffer); > } > +EXPORT_SYMBOL(drm_dp_mst_dpcd_read); > > /** > * drm_dp_mst_dpcd_write() - write a series of bytes to the DPCD via sideband > @@ -2187,6 +2188,7 @@ ssize_t drm_dp_mst_dpcd_write(struct drm_dp_aux *aux, > return drm_dp_send_dpcd_write(port->mgr, port, > offset, size, buffer); > } > +EXPORT_SYMBOL(drm_dp_mst_dpcd_write); > > static int drm_dp_check_mstb_guid(struct drm_dp_mst_branch *mstb, guid_t > *guid) > { > diff --git a/drivers/gpu/drm/i915/display/intel_ddi.c > b/drivers/gpu/drm/i915/display/intel_ddi.c > index b8985e1e0a81..f345d9a2154d 100644 > --- a/drivers/gpu/drm/i915/display/intel_ddi.c > +++ b/drivers/gpu/drm/i915/display/intel_ddi.c > @@ -3651,6 +3651,9 @@ void intel_ddi_update_pipe(struct intel_atomic_state > *state, > intel_ddi_update_pipe_dp(state, encoder, crtc_state, > conn_state); > > + if (intel_encoder_is_mst(encoder)) > + intel_dp_set_infoframes(encoder, true, crtc_state, conn_state); > + > if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) > intel_ddi_update_pipe_hdmi(encoder, crtc_state, > conn_state); > diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h > b/drivers/gpu/drm/i915/display/intel_display_types.h > index b7cc361fd955..d09cf18f5376 100644 > --- a/drivers/gpu/drm/i915/display/intel_display_types.h > +++ b/drivers/gpu/drm/i915/display/intel_display_types.h > @@ -564,6 +564,7 @@ struct intel_connector { > u8 dsc_hblank_expansion_quirk:1; > u8 dsc_throughput_quirk:1; > u8 dsc_decompression_enabled:1; > + u8 mst_msa_timing_par_ignore:1; /* cached from MST virtual DPCD > 0x007 */ > > struct { > struct { > diff --git a/drivers/gpu/drm/i915/display/intel_dp.c > b/drivers/gpu/drm/i915/display/intel_dp.c > index 113d767e62e9..4b196d26d27f 100644 > --- a/drivers/gpu/drm/i915/display/intel_dp.c > +++ b/drivers/gpu/drm/i915/display/intel_dp.c > @@ -2682,15 +2682,18 @@ intel_dp_compute_config_limits(struct intel_dp > *intel_dp, > > limits->pipe.min_bpp = intel_dp_min_bpp(crtc_state->output_format); > if (is_mst) { > + struct intel_connector *mst_connector = > + to_intel_connector(conn_state->connector); > + int sink_bpc = mst_connector->base.display_info.bpc ?: 8; > + > /* > - * FIXME: If all the streams can't fit into the link with their > - * current pipe_bpp we should reduce pipe_bpp across the board > - * until things start to fit. Until then we limit to <= 8bpc > - * since that's what was hardcoded for all MST streams > - * previously. This hack should be removed once we have the > - * proper retry logic in place. > + * For MST streams, use the sink's EDID-reported color depth > + * as the max bpp, rather than the pipe_bpp from a previous > + * modeset (which may be stale) or a hardcoded 8bpc cap. > + * The BW retry loop in intel_dp_mst_atomic_check_link() will > + * reduce bpp if the aggregate link bandwidth is insufficient. > */ > - limits->pipe.max_bpp = min(crtc_state->max_pipe_bpp, 24); > + limits->pipe.max_bpp = sink_bpc * 3; > } else { > limits->pipe.max_bpp = intel_dp_max_bpp(intel_dp, crtc_state, > > respect_downstream_limits); > @@ -3107,9 +3110,12 @@ static bool intel_dp_needs_as_sdp(struct intel_dp > *intel_dp, > return false; > > /* > - * #TODO: Add AS SDP v1 support for PCONs (DP branch devices). > + * AS-SDP is not yet supported for PCONs (DP-to-HDMI converters). > + * MST hubs are also branch devices but do support AS-SDP forwarding > + * to downstream sinks; allow it for MST streams. > */ > - if (drm_dp_is_branch(intel_dp->dpcd)) > + if (drm_dp_is_branch(intel_dp->dpcd) && > + !intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP_MST)) > return false; > > if (intel_psr_needs_alpm_aux_less(intel_dp, crtc_state) && > @@ -3119,8 +3125,8 @@ static bool intel_dp_needs_as_sdp(struct intel_dp > *intel_dp, > return intel_vrr_possible(crtc_state); > } > > -static void intel_dp_compute_as_sdp(struct intel_dp *intel_dp, > - struct intel_crtc_state *crtc_state) > +void intel_dp_compute_as_sdp(struct intel_dp *intel_dp, > + struct intel_crtc_state *crtc_state) > { > struct drm_dp_as_sdp *as_sdp = &crtc_state->infoframes.as_sdp; > const struct drm_display_mode *adjusted_mode = > @@ -3169,9 +3175,9 @@ static void intel_dp_compute_as_sdp(struct intel_dp > *intel_dp, > as_sdp->coasting_vtotal = crtc_state->vrr.vmax; > } > > -static void intel_dp_compute_vsc_sdp(struct intel_dp *intel_dp, > - struct intel_crtc_state *crtc_state, > - const struct drm_connector_state > *conn_state) > +void intel_dp_compute_vsc_sdp(struct intel_dp *intel_dp, > + struct intel_crtc_state *crtc_state, > + const struct drm_connector_state *conn_state) > { > struct drm_dp_vsc_sdp *vsc; > > @@ -3228,7 +3234,7 @@ intel_dp_in_hdr_mode(const struct drm_connector_state > *conn_state) > return hdr_metadata->hdmi_metadata_type1.eotf == > HDMI_EOTF_SMPTE_ST2084; > } > > -static void > +void > intel_dp_compute_hdr_metadata_infoframe_sdp(struct intel_dp *intel_dp, > struct intel_crtc_state > *crtc_state, > const struct drm_connector_state > *conn_state) > diff --git a/drivers/gpu/drm/i915/display/intel_dp.h > b/drivers/gpu/drm/i915/display/intel_dp.h > index 04fae203bb69..7887ba63c7a4 100644 > --- a/drivers/gpu/drm/i915/display/intel_dp.h > +++ b/drivers/gpu/drm/i915/display/intel_dp.h > @@ -77,6 +77,8 @@ int intel_dp_compute_config(struct intel_atomic_state > *state, > struct intel_encoder *encoder, > struct intel_crtc_state *pipe_config, > struct drm_connector_state *conn_state); > +void intel_dp_compute_as_sdp(struct intel_dp *intel_dp, > + struct intel_crtc_state *crtc_state); > bool intel_dp_needs_8b10b_fec(const struct intel_crtc_state *crtc_state, > bool dsc_enabled_on_crtc); > void intel_dp_dsc_reset_config(struct intel_crtc_state *crtc_state); > @@ -130,6 +132,12 @@ bool intel_dp_joiner_needs_dsc(struct intel_display > *display, > bool intel_dp_has_joiner(struct intel_dp *intel_dp); > bool intel_dp_needs_vsc_colorimetry(const struct intel_crtc_state > *crtc_state, > const struct drm_connector_state > *conn_state); > +void intel_dp_compute_vsc_sdp(struct intel_dp *intel_dp, > + struct intel_crtc_state *crtc_state, > + const struct drm_connector_state *conn_state); > +void intel_dp_compute_hdr_metadata_infoframe_sdp(struct intel_dp *intel_dp, > + struct intel_crtc_state > *crtc_state, > + const struct > drm_connector_state *conn_state); > void intel_dp_set_infoframes(struct intel_encoder *encoder, bool enable, > const struct intel_crtc_state *crtc_state, > const struct drm_connector_state *conn_state); > diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.c > b/drivers/gpu/drm/i915/display/intel_dp_mst.c > index 3be1643f8d03..cbef63e94e2c 100644 > --- a/drivers/gpu/drm/i915/display/intel_dp_mst.c > +++ b/drivers/gpu/drm/i915/display/intel_dp_mst.c > @@ -793,6 +793,9 @@ static int mst_stream_compute_config(struct > intel_atomic_state *state, > > bxt_dpio_phy_calc_lane_lat_optim_mask(pipe_config->lane_count); > > intel_vrr_compute_config(pipe_config, conn_state); > + intel_dp_compute_as_sdp(intel_dp, pipe_config); > + intel_dp_compute_vsc_sdp(intel_dp, pipe_config, conn_state); > + intel_dp_compute_hdr_metadata_infoframe_sdp(intel_dp, pipe_config, > conn_state); > > intel_dp_audio_compute_config(encoder, pipe_config, conn_state); > > @@ -1157,6 +1160,24 @@ static void mst_stream_post_disable(struct > intel_atomic_state *state, > * Power down mst path before disabling the port, otherwise we end > * up getting interrupts from the sink upon detecting link loss. > */ > + if (old_crtc_state->vrr.enable) { > + u8 val; > + ssize_t ret; > + > + ret = drm_dp_mst_dpcd_read(&connector->mst.port->aux, > + DP_DOWNSPREAD_CTRL, &val, 1); > + if (ret < 0) { > + drm_dbg_kms(display->drm, > + "[CONNECTOR:%d:%s] Failed to read > DP_DOWNSPREAD_CTRL: %zd\n", > + connector->base.base.id, > + connector->base.name, ret); > + } else { > + val &= ~DP_MSA_TIMING_PAR_IGNORE_EN; > + drm_dp_mst_dpcd_write(&connector->mst.port->aux, > + DP_DOWNSPREAD_CTRL, &val, 1); > + } > + } > + > drm_dp_send_power_updown_phy(&intel_dp->mst.mgr, connector->mst.port, > false); > > @@ -1274,6 +1295,24 @@ static void mst_stream_pre_enable(struct > intel_atomic_state *state, > > drm_dp_send_power_updown_phy(&intel_dp->mst.mgr, connector->mst.port, > true); > > + if (pipe_config->vrr.enable) { > + u8 val; > + ssize_t ret; > + > + ret = drm_dp_mst_dpcd_read(&connector->mst.port->aux, > + DP_DOWNSPREAD_CTRL, &val, 1); > + if (ret < 0) { > + drm_dbg_kms(display->drm, > + "[CONNECTOR:%d:%s] Failed to read > DP_DOWNSPREAD_CTRL: %zd\n", > + connector->base.base.id, > + connector->base.name, ret); > + } else { > + val |= DP_MSA_TIMING_PAR_IGNORE_EN; > + drm_dp_mst_dpcd_write(&connector->mst.port->aux, > + DP_DOWNSPREAD_CTRL, &val, 1); > + } > + } > + > intel_dp_sink_enable_decompression(state, connector, pipe_config); > > if (first_mst_stream) { > @@ -1397,6 +1436,8 @@ static void mst_stream_enable(struct intel_atomic_state > *state, > > intel_enable_transcoder(pipe_config); > > + intel_dp_set_infoframes(encoder, true, pipe_config, conn_state); > + > for_each_pipe_crtc_modeset_enable(display, pipe_crtc, pipe_config) { > const struct intel_crtc_state *pipe_crtc_state = > intel_atomic_get_new_crtc_state(state, pipe_crtc); > @@ -1451,6 +1492,15 @@ static int mst_connector_get_ddc_modes(struct > drm_connector *_connector) > > ret = intel_connector_update_modes(&connector->base, drm_edid); > > + if (HAS_VRR(display)) { > + bool vrr_capable = intel_vrr_is_capable(connector); > + > + drm_dbg_kms(display->drm, "[CONNECTOR:%d:%s] VRR capable: %s\n", > + connector->base.base.id, connector->base.name, > + str_yes_no(vrr_capable)); > + drm_connector_set_vrr_capable_property(&connector->base, > vrr_capable); > + } > + > drm_edid_free(drm_edid); > > if (intel_dp_tunnel_uhbr_lanes_wa_setup(intel_dp)) { > @@ -1735,6 +1785,22 @@ static int > mst_topology_add_connector_properties(struct intel_dp *intel_dp, > > intel_attach_force_audio_property(&connector->base); > intel_attach_broadcast_rgb_property(&connector->base); > + /* > + * Reuse the colorspace property from the root SST connector. > + * MST connectors are created post device-registration so new > + * properties cannot be created here. > + */ > + if (intel_dp->attached_connector && > + intel_dp->attached_connector->base.colorspace_property) { > + connector->base.colorspace_property = > + intel_dp->attached_connector->base.colorspace_property; > + drm_object_attach_property(&connector->base.base, > + connector->base.colorspace_property, > + DRM_MODE_COLORIMETRY_DEFAULT); > + } > + > + if (intel_dp_has_gamut_metadata_dip(&dp_to_dig_port(intel_dp)->base)) > + > drm_connector_attach_hdr_output_metadata_property(&connector->base); > > /* > * Reuse the prop from the SST connector because we're > @@ -1745,6 +1811,23 @@ static int > mst_topology_add_connector_properties(struct intel_dp *intel_dp, > if (connector->base.max_bpc_property) > drm_connector_attach_max_bpc_property(&connector->base, 6, 12); > > + /* > + * Reuse the vrr_capable property from the root intel_dp SST connector > + * which was created during driver load (before device registration). > + * MST connectors are created after device registration, so we cannot > + * create new properties here - we must attach the existing one. > + * Also assign connector->vrr_capable_property so that > + * drm_connector_set_vrr_capable_property() can update the value. > + */ > + if (HAS_VRR(display) && intel_dp->attached_connector && > + intel_dp->attached_connector->base.vrr_capable_property) { > + connector->base.vrr_capable_property = > + intel_dp->attached_connector->base.vrr_capable_property; > + drm_object_attach_property(&connector->base.base, > + connector->base.vrr_capable_property, > + 0); > + } > + > return drm_connector_set_path_property(&connector->base, pathprop); > } > > @@ -1755,6 +1838,18 @@ intel_dp_mst_read_decompression_port_dsc_caps(struct > intel_dp *intel_dp, > u8 dpcd_caps[DP_RECEIVER_CAP_SIZE]; > struct drm_dp_desc desc; > > + /* > + * Read MSA timing par ignore capability from the sink's virtual DPCD. > + * This is independent of DSC support so read it before the DSC check. > + */ > + { > + u8 mst_dpcd[DP_RECEIVER_CAP_SIZE]; > + > + if (drm_dp_read_dpcd_caps(&connector->mst.port->aux, mst_dpcd) > == 0) > + connector->dp.mst_msa_timing_par_ignore = > + > drm_dp_sink_can_do_video_without_timing_msa(mst_dpcd); > + } > + > if (!connector->dp.dsc_decompression_aux) > return; > > diff --git a/drivers/gpu/drm/i915/display/intel_vrr.c > b/drivers/gpu/drm/i915/display/intel_vrr.c > index 51e4f3309b8b..9737a19c1899 100644 > --- a/drivers/gpu/drm/i915/display/intel_vrr.c > +++ b/drivers/gpu/drm/i915/display/intel_vrr.c > @@ -55,8 +55,19 @@ bool intel_vrr_is_capable(struct intel_connector > *connector) > return false; > fallthrough; > case DRM_MODE_CONNECTOR_DisplayPort: > - if (connector->mst.dp) > - return false; > + if (connector->mst.dp) { > + /* > + * Use cached MSA timing ignore capability from the DFP > + * sink's virtual DPCD, set during connector init in > + * intel_dp_mst_read_decompression_port_dsc_caps(). > + * Avoid live sideband reads here as this function is > + * called from the atomic check path. > + */ > + if (!connector->dp.mst_msa_timing_par_ignore) > + return false; > + > + break; > + } > intel_dp = intel_attached_dp(connector); > /* > * Among non-MST DP branch devices, only an HDMI 2.1 sink > connected -- Jani Nikula, Intel
