HDMI 2.1 PCON devices (e.g. Parade PS196) that do not have an internal
DSC encoder can still forward a DSC-compressed stream from the DP source
to the downstream HDMI 2.1 sink unchanged (passthrough mode). The HDMI
sink (e.g. Lenovo ThinkVision Pro 27UD-10) then decompresses the stream
itself. This mode is signalled by DP_DSC_PASSTHROUGH_IS_SUPPORTED (DPCD
0x060 bit1) on the PCON.

Previously the driver had no awareness of this path: the PCON would fall
back to its autonomous passthrough mode and write 0x160=0x02 by itself,
leaving the source with no control over DSC parameters or DPCD 0x160.

This patch adds explicit driver control of PCON DSC passthrough:

- Detect passthrough capability at connect time from DPCD 0x060 bit1
  (DP_DSC_PASSTHROUGH_IS_SUPPORTED) combined with the downstream HDMI
  2.1 sink's DSC 1.2 support from EDID HF-VSDB. Cache the result in
  connector->dp.pcon_dsc_passthrough (same lifecycle as fec_capability
  and dsc_dpcd) to avoid repeated lookups on every modeset.

- Treat the PCON as DSC-capable in intel_dp_has_dsc() when passthrough
  is supported, allowing the normal DSC compute_config path to proceed.

- Derive DSC parameters (slice count, bpc limits) from the downstream
  HDMI sink's EDID DSC capabilities (dsc_cap.max_slices,
  dsc_cap.clk_per_slice, dsc_cap.bpc_supported) rather than from the
  PCON's DSC encoder DPCD (which is all-zero for passthrough-only PCONs).
  This ensures the source VDSC engine produces a stream the HDMI sink
  can actually decompress.

- Write DP_DSC_PASSTHROUGH_EN (0x160 bit1) to the PCON via
  intel_dp_sink_set_dsc_passthrough() on enable, and clear it on
  disable. The existing MST hub passthrough path in that function is
  preserved; the PCON case is handled by redirecting aux to
  intel_dp->aux when pcon_dsc_passthrough is set.

- Short-circuit intel_dp_sink_set_dsc_decompression() for PCON
  passthrough since the PCON itself does not decompress (the HDMI sink
  does); writing DP_DECOMPRESSION_EN to the PCON would be incorrect.

Tested with Parade PS196G (HW 0.2, SW 241.62) connected to a Lenovo
ThinkVision Pro 27UD-10 (HDMI 2.1, DSC 1.2, max 16 slices, 400 MHz/
slice). At 4K@240Hz DSC compression is required (10.5625 bpp, 8 slices);
driver now writes 0x160=0x02 explicitly and slice count is correctly
derived from the HDMI sink EDID rather than from PCON DPCD.

Signed-off-by: Xiao Lu <[email protected]>
---
 .../drm/i915/display/intel_display_types.h    |   1 +
 drivers/gpu/drm/i915/display/intel_dp.c       | 147 +++++++++++++++++-
 2 files changed, 140 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h 
b/drivers/gpu/drm/i915/display/intel_display_types.h
index b7cc361fd955..3e7a362fd4dd 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 pcon_dsc_passthrough:1; /* PCON DSC PT supported and HDMI 
sink DSC 1.2 */
 
                struct {
                        struct {
diff --git a/drivers/gpu/drm/i915/display/intel_dp.c 
b/drivers/gpu/drm/i915/display/intel_dp.c
index 113d767e62e9..ad55b08ecdea 100644
--- a/drivers/gpu/drm/i915/display/intel_dp.c
+++ b/drivers/gpu/drm/i915/display/intel_dp.c
@@ -1259,8 +1259,14 @@ bool intel_dp_has_dsc(const struct intel_connector 
*connector)
            connector->panel.vbt.edp.dsc_disable)
                return false;
 
-       if (!drm_dp_sink_supports_dsc(connector->dp.dsc_dpcd))
-               return false;
+       if (!drm_dp_sink_supports_dsc(connector->dp.dsc_dpcd)) {
+               /*
+                * PCON passthrough: PCON has no DSC decoder so dsc_dpcd is
+                * zero, but if pcon_dsc_passthrough is set (cached at connect
+                * time) the downstream HDMI 2.1 sink will decompress instead.
+                */
+               return connector->dp.pcon_dsc_passthrough;
+       }
 
        return true;
 }
@@ -2365,6 +2371,9 @@ void intel_dp_dsc_reset_config(struct intel_crtc_state 
*crtc_state)
        memset(&crtc_state->dsc.config, 0, sizeof(crtc_state->dsc.config));
 }
 
+static u8 intel_dp_pcon_passthrough_dsc_slice_count(struct intel_dp *intel_dp,
+                                                   const struct 
intel_crtc_state *crtc_state);
+
 int intel_dp_dsc_compute_config(struct intel_dp *intel_dp,
                                struct intel_crtc_state *pipe_config,
                                struct drm_connector_state *conn_state,
@@ -2403,7 +2412,21 @@ int intel_dp_dsc_compute_config(struct intel_dp 
*intel_dp,
                }
        }
 
-       if (!intel_dp_dsc_get_slice_config(connector, adjusted_mode->crtc_clock,
+       if (intel_dp->attached_connector->dp.pcon_dsc_passthrough) {
+               /*
+                * PCON passthrough: no PCON encoder DPCD, derive slice count
+                * from the downstream HDMI 2.1 sink DSC capabilities.
+                */
+               u8 pt_slice_count =
+                       intel_dp_pcon_passthrough_dsc_slice_count(intel_dp,
+                                                                 pipe_config);
+               if (!pt_slice_count) {
+                       drm_dbg_kms(display->drm,
+                                   "PCON DSC passthrough: no valid slice 
count\n");
+                       return -EINVAL;
+               }
+               pipe_config->dsc.slice_count = pt_slice_count;
+       } else if (!intel_dp_dsc_get_slice_config(connector, 
adjusted_mode->crtc_clock,
                                           adjusted_mode->crtc_hdisplay, 
num_joined_pipes,
                                           &pipe_config->dsc.slice_config))
                return -EINVAL;
@@ -2732,8 +2755,33 @@ intel_dp_compute_config_limits(struct intel_dp *intel_dp,
                return false;
        }
 
-       if (dsc && !intel_dp_dsc_compute_pipe_bpp_limits(connector, limits))
-               return false;
+       if (dsc) {
+               if (connector->dp.pcon_dsc_passthrough) {
+                       /*
+                        * PCON passthrough: PCON has no DSC encoder DPCD so
+                        * intel_dp_dsc_compute_pipe_bpp_limits() would see
+                        * all-zero dsc_dpcd and fail.  Use the downstream HDMI
+                        * sink DSC bpc cap to constrain the pipe bpp instead.
+                        */
+                       const struct drm_display_info *info =
+                               &connector->base.display_info;
+                       u8 hdmi_max_bpc = info->hdmi.dsc_cap.bpc_supported ?: 8;
+                       int dsc_min_bpc = intel_dp_dsc_min_src_input_bpc();
+                       int dsc_max_bpc = min_t(int,
+                                               
intel_dp_dsc_max_src_input_bpc(display),
+                                               hdmi_max_bpc);
+
+                       limits->pipe.min_bpp = max(limits->pipe.min_bpp,
+                                                  dsc_min_bpc * 3);
+                       limits->pipe.max_bpp = min(limits->pipe.max_bpp,
+                                                  dsc_max_bpc * 3);
+
+                       if (limits->pipe.min_bpp > limits->pipe.max_bpp)
+                               return false;
+               } else if (!intel_dp_dsc_compute_pipe_bpp_limits(connector, 
limits)) {
+                       return false;
+               }
+       }
 
        /*
         * crtc_state->pipe_bpp is the non-DP specific baseline (platform /
@@ -3745,6 +3793,10 @@ intel_dp_sink_set_dsc_decompression(struct 
intel_connector *connector,
 {
        struct intel_display *display = to_intel_display(connector);
 
+       /* PCON passthrough: PCON forwards the stream, sink decompresses. */
+       if (!connector->mst.dp && connector->dp.pcon_dsc_passthrough)
+               return;
+
        if (write_dsc_decompression_flag(connector->dp.dsc_decompression_aux,
                                         DP_DECOMPRESSION_EN, enable) < 0)
                drm_dbg_kms(display->drm,
@@ -3757,8 +3809,19 @@ intel_dp_sink_set_dsc_passthrough(const struct 
intel_connector *connector,
                                  bool enable)
 {
        struct intel_display *display = to_intel_display(connector);
-       struct drm_dp_aux *aux = connector->mst.port ?
-                                connector->mst.port->passthrough_aux : NULL;
+       struct drm_dp_aux *aux = NULL;
+
+       if (!connector->mst.port) {
+               /*
+                * HDMI 2.1 PCON passthrough: write DP_DSC_PASSTHROUGH_EN on
+                * the PCON's own aux channel so it forwards the compressed
+                * stream to the HDMI sink instead of decoding it.
+                */
+               if (connector->dp.pcon_dsc_passthrough)
+                       aux = &intel_attached_dp((struct intel_connector 
*)connector)->aux;
+       } else {
+               aux = connector->mst.port->passthrough_aux;
+       }
 
        if (!aux)
                return;
@@ -3766,7 +3829,7 @@ intel_dp_sink_set_dsc_passthrough(const struct 
intel_connector *connector,
        if (write_dsc_decompression_flag(aux,
                                         DP_DSC_PASSTHROUGH_EN, enable) < 0)
                drm_dbg_kms(display->drm,
-                           "Failed to %s sink compression passthrough state\n",
+                           "Failed to %s DSC passthrough\n",
                            str_enable_disable(enable));
 }
 
@@ -4297,6 +4360,71 @@ void intel_dp_check_frl_training(struct intel_dp 
*intel_dp)
        }
 }
 
+/*
+ * intel_dp_pcon_set_dsc_passthrough_cap - cache PCON DSC passthrough 
capability
+ *
+ * Called once at connect time (from intel_dp_get_dsc_sink_cap()) after
+ * dsc_dpcd and the HDMI sink EDID have been read. Caches the result in
+ * connector->dp.pcon_dsc_passthrough so modeset paths can read it cheaply
+ * without repeating the DPCD and EDID lookups.
+ *
+ * A PCON supports DSC passthrough when it explicitly advertises
+ * DP_DSC_PASSTHROUGH_IS_SUPPORTED (DPCD 0x060 bit1) and the downstream
+ * HDMI 2.1 sink supports DSC 1.2. The compressed stream produced by the
+ * source VDSC engine is forwarded unchanged through the PCON to the HDMI
+ * sink for decompression (DP_DSC_ENABLE bit1 on the PCON).
+ */
+static void
+intel_dp_pcon_set_dsc_passthrough_cap(struct intel_dp *intel_dp)
+{
+       struct intel_connector *connector = intel_dp->attached_connector;
+       const struct drm_display_info *info;
+
+       if (!connector)
+               return;
+
+       connector->dp.pcon_dsc_passthrough = false;
+
+       if (!intel_dp_is_hdmi_2_1_sink(intel_dp))
+               return;
+
+       if (!(connector->dp.dsc_dpcd[0] & DP_DSC_PASSTHROUGH_IS_SUPPORTED))
+               return;
+
+       info = &connector->base.display_info;
+       connector->dp.pcon_dsc_passthrough = info->hdmi.dsc_cap.v_1p2;
+}
+
+static u8
+intel_dp_pcon_passthrough_dsc_slice_count(struct intel_dp *intel_dp,
+                                         const struct intel_crtc_state 
*crtc_state)
+{
+       struct intel_display *display = to_intel_display(intel_dp);
+       struct intel_connector *connector = intel_dp->attached_connector;
+       const struct drm_display_info *info = &connector->base.display_info;
+       int hdmi_throughput = info->hdmi.dsc_cap.clk_per_slice;
+       int hdmi_max_slices = info->hdmi.dsc_cap.max_slices;
+       int num_slices;
+
+       /*
+        * Passthrough: no PCON encoder constraints, use only the HDMI sink
+        * DSC caps from EDID. Pass hdisplay for pcon_max_slice_width so that
+        * constraint is effectively unconstrained.
+        */
+       num_slices = intel_hdmi_dsc_get_num_slices(crtc_state,
+                                                  hdmi_max_slices,
+                                                  
crtc_state->hw.adjusted_mode.hdisplay,
+                                                  hdmi_max_slices,
+                                                  hdmi_throughput);
+
+       drm_dbg_kms(display->drm,
+                    "PCON DSC passthrough: slice count %d from HDMI sink EDID 
cap"
+                    " (max_slices=%d clk_per_slice=%d MHz)\n",
+                    num_slices, hdmi_max_slices, hdmi_throughput);
+
+       return (u8)num_slices;
+}
+
 static int
 intel_dp_pcon_dsc_enc_slice_height(const struct intel_crtc_state *crtc_state)
 {
@@ -4536,6 +4664,7 @@ void intel_dp_get_dsc_sink_cap(u8 dpcd_rev,
 
        memset(&connector->dp.dsc_branch_caps, 0, 
sizeof(connector->dp.dsc_branch_caps));
        connector->dp.dsc_throughput_quirk = false;
+       connector->dp.pcon_dsc_passthrough = false;
 
        if (dpcd_rev < DP_DPCD_REV_14)
                return;
@@ -4565,6 +4694,8 @@ void intel_dp_get_dsc_sink_cap(u8 dpcd_rev,
        if (drm_dp_has_quirk(desc, DP_DPCD_QUIRK_DSC_THROUGHPUT_BPP_LIMIT) &&
            desc->ident.hw_rev == 0x10)
                connector->dp.dsc_throughput_quirk = true;
+
+       intel_dp_pcon_set_dsc_passthrough_cap(intel_attached_dp(connector));
 }
 
 static void intel_edp_get_dsc_sink_cap(u8 edp_dpcd_rev, struct intel_connector 
*connector)
-- 
2.43.0

Reply via email to