From 61c45e4c0db8f477737597571d6b2aa4f7d493ef Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Wed, 1 Jul 2026 16:52:59 +0300 Subject: [PATCH 01/93] drm/i915: call intel_uncore_runtime_resume() for each gt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All uncore suspend/resume calls are done on each gt. Follow suit in vlv_suspend_complete() error path purely for completeness, even if VLV/CHV only have one. Cc: Rodrigo Vivi Cc: Ville Syrjälä Reviewed-by: Andi Shyti Reviewed-by: Ville Syrjälä Link: https://patch.msgid.link/e9dde426a8f84c75a5d61857c4259e562fa8ee41.1782913901.git.jani.nikula@intel.com Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/i915_driver.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/i915_driver.c b/drivers/gpu/drm/i915/i915_driver.c index 45f2dcc5130e..c9ce2b05b787 100644 --- a/drivers/gpu/drm/i915/i915_driver.c +++ b/drivers/gpu/drm/i915/i915_driver.c @@ -1522,7 +1522,9 @@ static int i915_pm_runtime_suspend(struct device *kdev) if (ret) { drm_err(&dev_priv->drm, "Runtime suspend failed, disabling it (%d)\n", ret); - intel_uncore_runtime_resume(&dev_priv->uncore); + + for_each_gt(gt, dev_priv, i) + intel_uncore_runtime_resume(gt->uncore); intel_irq_resume(dev_priv); From 25e9bc0a7ba740a3aa3c0bb9e324fb0e9334f2fb Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Wed, 1 Jul 2026 16:53:00 +0300 Subject: [PATCH 02/93] drm/i915: call intel_display_power_runtime_resume() on suspend error path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The vlv_suspend_complete() error path fails to call intel_display_power_runtime_resume(). Fix it. Cc: Rodrigo Vivi Cc: Ville Syrjälä Reviewed-by: Rodrigo Vivi Reviewed-by: Ville Syrjälä Link: https://patch.msgid.link/b0c131ed970d16d0147c87ba583abe3a4ea36ed5.1782913901.git.jani.nikula@intel.com Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/i915_driver.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/i915/i915_driver.c b/drivers/gpu/drm/i915/i915_driver.c index c9ce2b05b787..10427f8f5699 100644 --- a/drivers/gpu/drm/i915/i915_driver.c +++ b/drivers/gpu/drm/i915/i915_driver.c @@ -1523,6 +1523,8 @@ static int i915_pm_runtime_suspend(struct device *kdev) drm_err(&dev_priv->drm, "Runtime suspend failed, disabling it (%d)\n", ret); + intel_display_power_runtime_resume(display); + for_each_gt(gt, dev_priv, i) intel_uncore_runtime_resume(gt->uncore); From 8d880fe6f6d0b477e1c83460abd8dc09cb4270d3 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Wed, 1 Jul 2026 16:53:01 +0300 Subject: [PATCH 03/93] drm/i915: move some display runtime suspend operations earlier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In preparation for abstracting display runtime suspend/resume calls, relocate opregion adapter notification and hotplug poll enable slightly earlier. Even though this is intended to be a non-functional change, do it as a separate step to pinpoint any issues here. Cc: Rodrigo Vivi Cc: Ville Syrjälä Reviewed-by: Rodrigo Vivi Reviewed-by: Ville Syrjälä Link: https://patch.msgid.link/00d016457a78589b97972203a52a390f5f1a3541.1782913901.git.jani.nikula@intel.com Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/i915_driver.c | 54 ++++++++++++++++-------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/drivers/gpu/drm/i915/i915_driver.c b/drivers/gpu/drm/i915/i915_driver.c index 10427f8f5699..b3d87acc142c 100644 --- a/drivers/gpu/drm/i915/i915_driver.c +++ b/drivers/gpu/drm/i915/i915_driver.c @@ -1518,11 +1518,39 @@ static int i915_pm_runtime_suspend(struct device *kdev) intel_display_power_runtime_suspend(display); + /* + * FIXME: We really should find a document that references the arguments + * used below! + */ + if (IS_BROADWELL(dev_priv)) { + /* + * On Broadwell, if we use PCI_D1 the PCH DDI ports will stop + * being detected, and the call we do at i915_pm_runtime_resume() + * won't be able to restore them. Since PCI_D3hot matches the + * actual specification and appears to be working, use it. + */ + intel_opregion_notify_adapter(display, PCI_D3hot); + } else { + /* + * current versions of firmware which depend on this opregion + * notification have repurposed the D1 definition to mean + * "runtime suspended" vs. what you would normally expect (D3) + * to distinguish it from notifications that might be sent via + * the suspend path. + */ + intel_opregion_notify_adapter(display, PCI_D1); + } + + if (!IS_VALLEYVIEW(dev_priv) && !IS_CHERRYVIEW(dev_priv)) + intel_hpd_poll_enable(display); + ret = vlv_suspend_complete(dev_priv); if (ret) { drm_err(&dev_priv->drm, "Runtime suspend failed, disabling it (%d)\n", ret); + intel_opregion_notify_adapter(display, PCI_D0); + intel_display_power_runtime_resume(display); for_each_gt(gt, dev_priv, i) @@ -1554,34 +1582,8 @@ static int i915_pm_runtime_suspend(struct device *kdev) if (root_pdev) pci_d3cold_disable(root_pdev); - /* - * FIXME: We really should find a document that references the arguments - * used below! - */ - if (IS_BROADWELL(dev_priv)) { - /* - * On Broadwell, if we use PCI_D1 the PCH DDI ports will stop - * being detected, and the call we do at i915_pm_runtime_resume() - * won't be able to restore them. Since PCI_D3hot matches the - * actual specification and appears to be working, use it. - */ - intel_opregion_notify_adapter(display, PCI_D3hot); - } else { - /* - * current versions of firmware which depend on this opregion - * notification have repurposed the D1 definition to mean - * "runtime suspended" vs. what you would normally expect (D3) - * to distinguish it from notifications that might be sent via - * the suspend path. - */ - intel_opregion_notify_adapter(display, PCI_D1); - } - assert_forcewakes_inactive(&dev_priv->uncore); - if (!IS_VALLEYVIEW(dev_priv) && !IS_CHERRYVIEW(dev_priv)) - intel_hpd_poll_enable(display); - drm_dbg(&dev_priv->drm, "Device suspended\n"); return 0; } From 93094e9099e06c1d2b505cc5b9d7bd256bc107a2 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Wed, 1 Jul 2026 16:53:02 +0300 Subject: [PATCH 04/93] drm/i915: add intel_display_driver_pm_runtime*() functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add new functions intel_display_driver_pm_runtime_suspend(), intel_display_driver_pm_runtime_suspend_late(), intel_display_driver_pm_runtime_resume_early(), and intel_display_driver_pm_runtime_resume(). The IRQ suspend/resume is meant to happen between the corresponding calls. For now, intel_display_driver_pm_runtime_suspend() is empty, but is included for completeness as a placeholder. Initially, only migrate i915, as there are some differences with xe that will be addressed later. There's a functional change, which should be benign: i915_pm_runtime_resume() moves intel_opregion_notify_adapter() call slightly later. In the interest of not introducing more severe functional changes, the calls become slightly asymmetric. We might want to address this later. v2: - Rebase, move placement of suspend calls Cc: Rodrigo Vivi Cc: Ville Syrjälä Reviewed-by: Ville Syrjälä Link: https://patch.msgid.link/81e29bd9bee6c023d079ab076af7dc4b7e8985ca.1782913901.git.jani.nikula@intel.com Signed-off-by: Jani Nikula --- .../drm/i915/display/intel_display_driver.c | 61 +++++++++++++++++++ .../drm/i915/display/intel_display_driver.h | 5 ++ drivers/gpu/drm/i915/i915_driver.c | 52 +++------------- 3 files changed, 74 insertions(+), 44 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_display_driver.c b/drivers/gpu/drm/i915/display/intel_display_driver.c index bb5301b90231..5f6619b7584a 100644 --- a/drivers/gpu/drm/i915/display/intel_display_driver.c +++ b/drivers/gpu/drm/i915/display/intel_display_driver.c @@ -887,3 +887,64 @@ void intel_display_driver_pm_resume(struct intel_display *display) intel_display_power_enable(display); } + +/* before irq suspend */ +void intel_display_driver_pm_runtime_suspend(struct intel_display *display) +{ +} + +/* after irq suspend */ +void intel_display_driver_pm_runtime_suspend_late(struct intel_display *display) +{ + intel_display_power_runtime_suspend(display); + + /* + * FIXME: We really should find a document that references the arguments + * used below! + */ + if (display->platform.broadwell) { + /* + * On Broadwell, if we use PCI_D1 the PCH DDI ports will stop + * being detected, and the call we do at i915_pm_runtime_resume() + * won't be able to restore them. Since PCI_D3hot matches the + * actual specification and appears to be working, use it. + */ + intel_opregion_notify_adapter(display, PCI_D3hot); + } else { + /* + * current versions of firmware which depend on this opregion + * notification have repurposed the D1 definition to mean + * "runtime suspended" vs. what you would normally expect (D3) + * to distinguish it from notifications that might be sent via + * the suspend path. + */ + intel_opregion_notify_adapter(display, PCI_D1); + } + + if (!display->platform.valleyview && !display->platform.cherryview) + intel_hpd_poll_enable(display); +} + +/* before irq resume */ +void intel_display_driver_pm_runtime_resume_early(struct intel_display *display) +{ + intel_opregion_notify_adapter(display, PCI_D0); + + intel_display_power_runtime_resume(display); +} + +/* after irq resume */ +void intel_display_driver_pm_runtime_resume(struct intel_display *display) +{ + /* + * On VLV/CHV display interrupts are part of the display + * power well, so hpd is reinitialized from there. For + * everyone else do it here. + */ + if (!display->platform.valleyview && !display->platform.cherryview) { + intel_hpd_init(display); + intel_hpd_poll_disable(display); + } + + skl_watermark_ipc_update(display); +} diff --git a/drivers/gpu/drm/i915/display/intel_display_driver.h b/drivers/gpu/drm/i915/display/intel_display_driver.h index 7eca3d17dd82..1b494337d629 100644 --- a/drivers/gpu/drm/i915/display/intel_display_driver.h +++ b/drivers/gpu/drm/i915/display/intel_display_driver.h @@ -43,5 +43,10 @@ void intel_display_driver_suspend_access(struct intel_display *display); void intel_display_driver_resume_access(struct intel_display *display); bool intel_display_driver_check_access(struct intel_display *display); +void intel_display_driver_pm_runtime_suspend(struct intel_display *display); +void intel_display_driver_pm_runtime_suspend_late(struct intel_display *display); +void intel_display_driver_pm_runtime_resume_early(struct intel_display *display); +void intel_display_driver_pm_runtime_resume(struct intel_display *display); + #endif /* __INTEL_DISPLAY_DRIVER_H__ */ diff --git a/drivers/gpu/drm/i915/i915_driver.c b/drivers/gpu/drm/i915/i915_driver.c index b3d87acc142c..259b39e31547 100644 --- a/drivers/gpu/drm/i915/i915_driver.c +++ b/drivers/gpu/drm/i915/i915_driver.c @@ -1511,47 +1511,21 @@ static int i915_pm_runtime_suspend(struct device *kdev) for_each_gt(gt, dev_priv, i) intel_gt_runtime_suspend(gt); + intel_display_driver_pm_runtime_suspend(display); + intel_irq_suspend(dev_priv); for_each_gt(gt, dev_priv, i) intel_uncore_suspend(gt->uncore); - intel_display_power_runtime_suspend(display); - - /* - * FIXME: We really should find a document that references the arguments - * used below! - */ - if (IS_BROADWELL(dev_priv)) { - /* - * On Broadwell, if we use PCI_D1 the PCH DDI ports will stop - * being detected, and the call we do at i915_pm_runtime_resume() - * won't be able to restore them. Since PCI_D3hot matches the - * actual specification and appears to be working, use it. - */ - intel_opregion_notify_adapter(display, PCI_D3hot); - } else { - /* - * current versions of firmware which depend on this opregion - * notification have repurposed the D1 definition to mean - * "runtime suspended" vs. what you would normally expect (D3) - * to distinguish it from notifications that might be sent via - * the suspend path. - */ - intel_opregion_notify_adapter(display, PCI_D1); - } - - if (!IS_VALLEYVIEW(dev_priv) && !IS_CHERRYVIEW(dev_priv)) - intel_hpd_poll_enable(display); + intel_display_driver_pm_runtime_suspend_late(display); ret = vlv_suspend_complete(dev_priv); if (ret) { drm_err(&dev_priv->drm, "Runtime suspend failed, disabling it (%d)\n", ret); - intel_opregion_notify_adapter(display, PCI_D0); - - intel_display_power_runtime_resume(display); + intel_display_driver_pm_runtime_resume_early(display); for_each_gt(gt, dev_priv, i) intel_uncore_runtime_resume(gt->uncore); @@ -1561,6 +1535,8 @@ static int i915_pm_runtime_suspend(struct device *kdev) for_each_gt(gt, dev_priv, i) intel_gt_runtime_resume(gt); + intel_display_driver_pm_runtime_resume(display); + enable_rpm_wakeref_asserts(rpm); return ret; @@ -1606,8 +1582,6 @@ static int i915_pm_runtime_resume(struct device *kdev) drm_WARN_ON_ONCE(&dev_priv->drm, atomic_read(&rpm->wakeref_count)); disable_rpm_wakeref_asserts(rpm); - intel_opregion_notify_adapter(display, PCI_D0); - root_pdev = pcie_find_root_port(pdev); if (root_pdev) pci_d3cold_enable(root_pdev); @@ -1616,7 +1590,7 @@ static int i915_pm_runtime_resume(struct device *kdev) drm_dbg(&dev_priv->drm, "Unclaimed access during suspend, bios?\n"); - intel_display_power_runtime_resume(display); + intel_display_driver_pm_runtime_resume_early(display); ret = vlv_resume_prepare(dev_priv, true); @@ -1634,17 +1608,7 @@ static int i915_pm_runtime_resume(struct device *kdev) intel_pxp_runtime_resume(dev_priv->pxp); - /* - * On VLV/CHV display interrupts are part of the display - * power well, so hpd is reinitialized from there. For - * everyone else do it here. - */ - if (!IS_VALLEYVIEW(dev_priv) && !IS_CHERRYVIEW(dev_priv)) { - intel_hpd_init(display); - intel_hpd_poll_disable(display); - } - - skl_watermark_ipc_update(display); + intel_display_driver_pm_runtime_resume(display); enable_rpm_wakeref_asserts(rpm); From 3cad64a10e1a53aee4cad92d28e0f2f66be47d6e Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Wed, 1 Jul 2026 16:53:03 +0300 Subject: [PATCH 05/93] drm/{i915, xe}: add new intel_display_driver_runtime_pm_{enable, disable}() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add new high-level functions intel_display_driver_runtime_pm_enable() and intel_display_driver_runtime_pm_disable() to abstract the low-level functions intel_display_power_enable() and intel_display_power_disable(), respectively. The asymmetry in the "runtime pm enable/disable" and "pm runtime suspend/etc" naming is intentional, with the latter intended to be called from the PM hooks. Suggested-by: Ville Syrjala Reviewed-by: Rodrigo Vivi Reviewed-by: Ville Syrjälä Link: https://patch.msgid.link/c95cb644a35ede7d4d456fd1654ce0b42b6eea33.1782913901.git.jani.nikula@intel.com Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_display_driver.c | 10 ++++++++++ drivers/gpu/drm/i915/display/intel_display_driver.h | 3 +++ drivers/gpu/drm/i915/i915_driver.c | 4 ++-- drivers/gpu/drm/xe/display/xe_display.c | 8 ++++---- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_display_driver.c b/drivers/gpu/drm/i915/display/intel_display_driver.c index 5f6619b7584a..70d112fd4287 100644 --- a/drivers/gpu/drm/i915/display/intel_display_driver.c +++ b/drivers/gpu/drm/i915/display/intel_display_driver.c @@ -888,6 +888,16 @@ void intel_display_driver_pm_resume(struct intel_display *display) intel_display_power_enable(display); } +void intel_display_driver_runtime_pm_enable(struct intel_display *display) +{ + intel_display_power_enable(display); +} + +void intel_display_driver_runtime_pm_disable(struct intel_display *display) +{ + intel_display_power_disable(display); +} + /* before irq suspend */ void intel_display_driver_pm_runtime_suspend(struct intel_display *display) { diff --git a/drivers/gpu/drm/i915/display/intel_display_driver.h b/drivers/gpu/drm/i915/display/intel_display_driver.h index 1b494337d629..1ae2ad7e95f6 100644 --- a/drivers/gpu/drm/i915/display/intel_display_driver.h +++ b/drivers/gpu/drm/i915/display/intel_display_driver.h @@ -43,6 +43,9 @@ void intel_display_driver_suspend_access(struct intel_display *display); void intel_display_driver_resume_access(struct intel_display *display); bool intel_display_driver_check_access(struct intel_display *display); +void intel_display_driver_runtime_pm_enable(struct intel_display *display); +void intel_display_driver_runtime_pm_disable(struct intel_display *display); + void intel_display_driver_pm_runtime_suspend(struct intel_display *display); void intel_display_driver_pm_runtime_suspend_late(struct intel_display *display); void intel_display_driver_pm_runtime_resume_early(struct intel_display *display); diff --git a/drivers/gpu/drm/i915/i915_driver.c b/drivers/gpu/drm/i915/i915_driver.c index 259b39e31547..ce6d20958320 100644 --- a/drivers/gpu/drm/i915/i915_driver.c +++ b/drivers/gpu/drm/i915/i915_driver.c @@ -638,7 +638,7 @@ static int i915_driver_register(struct drm_i915_private *dev_priv) intel_display_driver_register(display); - intel_display_power_enable(display); + intel_display_driver_runtime_pm_enable(display); intel_runtime_pm_enable(&dev_priv->runtime_pm); if (i915_switcheroo_register(dev_priv)) @@ -660,7 +660,7 @@ static void i915_driver_unregister(struct drm_i915_private *dev_priv) i915_switcheroo_unregister(dev_priv); intel_runtime_pm_disable(&dev_priv->runtime_pm); - intel_display_power_disable(display); + intel_display_driver_runtime_pm_disable(display); intel_display_driver_unregister(display); diff --git a/drivers/gpu/drm/xe/display/xe_display.c b/drivers/gpu/drm/xe/display/xe_display.c index 92a4573db28a..e0376409d71c 100644 --- a/drivers/gpu/drm/xe/display/xe_display.c +++ b/drivers/gpu/drm/xe/display/xe_display.c @@ -158,7 +158,7 @@ void xe_display_register(struct xe_device *xe) return; intel_display_driver_register(display); - intel_display_power_enable(display); + intel_display_driver_runtime_pm_enable(display); } void xe_display_unregister(struct xe_device *xe) @@ -168,7 +168,7 @@ void xe_display_unregister(struct xe_device *xe) if (!xe->info.probe_display) return; - intel_display_power_disable(display); + intel_display_driver_runtime_pm_disable(display); intel_display_driver_unregister(display); } @@ -313,7 +313,7 @@ static void xe_display_enable_d3cold(struct xe_device *xe) * We do a lot of poking in a lot of registers, make sure they work * properly. */ - intel_display_power_disable(display); + intel_display_driver_runtime_pm_disable(display); intel_display_flush_cleanup_work(display); @@ -346,7 +346,7 @@ static void xe_display_disable_d3cold(struct xe_device *xe) intel_opregion_resume(display); - intel_display_power_enable(display); + intel_display_driver_runtime_pm_enable(display); } void xe_display_pm_runtime_suspend(struct xe_device *xe) From 402e84752c27e693b3ac40a438b2411c89a94454 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Wed, 1 Jul 2026 16:53:04 +0300 Subject: [PATCH 06/93] drm/xe/display: separate d3cold handling from xe_display_pm_runtime_suspend_late() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make the special d3cold paths completely separate from the rest of the runtime pm calls. The intel_dmc_wl_flush_release_work() call right after xe_display_pm_suspend_late() might be completely redundant, but this avoids any functional changes. Wiggle the comment while at it. It gets duplicated for now, but this will be addressed in the follow-up. v2: Update comments Acked-by: Ville Syrjälä Reviewed-by: Rodrigo Vivi Reviewed-by: Ville Syrjälä Link: https://patch.msgid.link/73693f137e9601e027ab7e7add2a7b08d5a2f675.1782913901.git.jani.nikula@intel.com Signed-off-by: Jani Nikula --- drivers/gpu/drm/xe/display/xe_display.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/xe/display/xe_display.c b/drivers/gpu/drm/xe/display/xe_display.c index e0376409d71c..01b6501a204a 100644 --- a/drivers/gpu/drm/xe/display/xe_display.c +++ b/drivers/gpu/drm/xe/display/xe_display.c @@ -371,14 +371,14 @@ void xe_display_pm_runtime_suspend_late(struct xe_device *xe) if (!xe->info.probe_display) return; - if (xe->d3cold.allowed) + if (xe->d3cold.allowed) { xe_display_pm_suspend_late(xe); + /* Ensure the wakelock release work gets flushed */ + intel_dmc_wl_flush_release_work(display); + return; + } - /* - * If xe_display_pm_suspend_late() is not called, it is likely - * that we will be on dynamic DC states with DMC wakelock enabled. We - * need to flush the release work in that case. - */ + /* Ensure the wakelock release work gets flushed */ intel_dmc_wl_flush_release_work(display); } From fdd48cf3fcc4cbe4d2dfad038aa082658269cff1 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Thu, 2 Jul 2026 10:14:57 +0300 Subject: [PATCH 07/93] drm/xe/display: add xe_display_pm_runtime_resume_early() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add new display runtime PM hook xe_display_pm_runtime_resume_early(), to be called before IRQ resume. This is initially a no-op placeholder. Add comments on the timing relative to irq suspend/resume to all the runtime PM hooks. v2: Add stub for CONFIG_DRM_XE_DISPLAY=n Cc: Rodrigo Vivi Cc: Ville Syrjälä Reviewed-by: Ville Syrjälä Link: https://patch.msgid.link/20260702071457.2985893-1-jani.nikula@intel.com Signed-off-by: Jani Nikula --- drivers/gpu/drm/xe/display/xe_display.c | 13 +++++++++++++ drivers/gpu/drm/xe/display/xe_display.h | 2 ++ drivers/gpu/drm/xe/xe_pm.c | 2 ++ 3 files changed, 17 insertions(+) diff --git a/drivers/gpu/drm/xe/display/xe_display.c b/drivers/gpu/drm/xe/display/xe_display.c index 01b6501a204a..306c853dd07a 100644 --- a/drivers/gpu/drm/xe/display/xe_display.c +++ b/drivers/gpu/drm/xe/display/xe_display.c @@ -349,6 +349,7 @@ static void xe_display_disable_d3cold(struct xe_device *xe) intel_display_driver_runtime_pm_enable(display); } +/* before irq suspend */ void xe_display_pm_runtime_suspend(struct xe_device *xe) { struct intel_display *display = xe->display; @@ -364,6 +365,7 @@ void xe_display_pm_runtime_suspend(struct xe_device *xe) intel_hpd_poll_enable(display); } +/* after irq suspend */ void xe_display_pm_runtime_suspend_late(struct xe_device *xe) { struct intel_display *display = xe->display; @@ -382,6 +384,17 @@ void xe_display_pm_runtime_suspend_late(struct xe_device *xe) intel_dmc_wl_flush_release_work(display); } +/* before irq resume */ +void xe_display_pm_runtime_resume_early(struct xe_device *xe) +{ + if (!xe->info.probe_display) + return; + + if (xe->d3cold.allowed) + return; +} + +/* after irq resume */ void xe_display_pm_runtime_resume(struct xe_device *xe) { struct intel_display *display = xe->display; diff --git a/drivers/gpu/drm/xe/display/xe_display.h b/drivers/gpu/drm/xe/display/xe_display.h index e5f9aed93206..0babb50bfc77 100644 --- a/drivers/gpu/drm/xe/display/xe_display.h +++ b/drivers/gpu/drm/xe/display/xe_display.h @@ -43,6 +43,7 @@ void xe_display_pm_resume_early(struct xe_device *xe); void xe_display_pm_resume(struct xe_device *xe); void xe_display_pm_runtime_suspend(struct xe_device *xe); void xe_display_pm_runtime_suspend_late(struct xe_device *xe); +void xe_display_pm_runtime_resume_early(struct xe_device *xe); void xe_display_pm_runtime_resume(struct xe_device *xe); #define XE_DISPLAY_DRIVER_FEATURES (DRIVER_MODESET | DRIVER_ATOMIC) @@ -80,6 +81,7 @@ static inline void xe_display_pm_resume_early(struct xe_device *xe) {} static inline void xe_display_pm_resume(struct xe_device *xe) {} static inline void xe_display_pm_runtime_suspend(struct xe_device *xe) {} static inline void xe_display_pm_runtime_suspend_late(struct xe_device *xe) {} +static inline void xe_display_pm_runtime_resume_early(struct xe_device *xe) {} static inline void xe_display_pm_runtime_resume(struct xe_device *xe) {} #endif /* CONFIG_DRM_XE_DISPLAY */ diff --git a/drivers/gpu/drm/xe/xe_pm.c b/drivers/gpu/drm/xe/xe_pm.c index 99562f691080..a5289a9df8d2 100644 --- a/drivers/gpu/drm/xe/xe_pm.c +++ b/drivers/gpu/drm/xe/xe_pm.c @@ -700,6 +700,8 @@ int xe_pm_runtime_resume(struct xe_device *xe) if (xe->d3cold.allowed) xe_sysctrl_pm_resume(xe); + xe_display_pm_runtime_resume_early(xe); + xe_irq_resume(xe); for_each_gt(gt, xe, id) { From fac54030c30665f2848bd049589903194b3233e9 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Wed, 1 Jul 2026 16:53:06 +0300 Subject: [PATCH 08/93] drm/xe/display: unify runtime suspend/resume with i915 for non-d3cold MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the special d3cold handling in xe runtime suspend/resume separated, unify the non-d3cold paths with i915. There are multiple changes here at once that probably wouldn't make much sense to split out further. Add the call to intel_display_driver_pm_runtime_suspend(), which, for the time being, is a no-op. The intel_hpd_poll_enable() is called later. Add the call to intel_display_driver_pm_runtime_suspend_late(), which starts calling intel_display_power_runtime_suspend() and intel_opregion_notify_adapter(). Deep down, the former should take care of intel_dmc_wl_flush_release_work(), allowing us to drop the extra call in xe_display_pm_runtime_suspend_late(). The intel_hpd_poll_enable() call is also done here, postponed from the previous location in xe_display_pm_runtime_suspend(). Add the call to intel_display_driver_pm_runtime_resume_early(), which contains the intel_display_power_runtime_resume() and intel_opregion_notify_adapter() counterparts to the calls in intel_display_power_runtime_suspend_late(). Finally, intel_display_driver_pm_runtime_resume() replaces the direct calls to intel_hpd_init(), intel_hpd_poll_disable(), and skl_watermark_ipc_update(). (The hotplug poll enable/disable calls are asymmetric.) v2: - Rebase, move placement of the calls Cc: Rodrigo Vivi Cc: Ville Syrjälä Reviewed-by: Ville Syrjälä Link: https://patch.msgid.link/16205a1ee08e83f6e2f4dd30ed98fc043684a73e.1782913901.git.jani.nikula@intel.com Signed-off-by: Jani Nikula --- drivers/gpu/drm/xe/display/xe_display.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/xe/display/xe_display.c b/drivers/gpu/drm/xe/display/xe_display.c index 306c853dd07a..8da4c457a4a0 100644 --- a/drivers/gpu/drm/xe/display/xe_display.c +++ b/drivers/gpu/drm/xe/display/xe_display.c @@ -362,7 +362,7 @@ void xe_display_pm_runtime_suspend(struct xe_device *xe) return; } - intel_hpd_poll_enable(display); + intel_display_driver_pm_runtime_suspend(display); } /* after irq suspend */ @@ -380,18 +380,21 @@ void xe_display_pm_runtime_suspend_late(struct xe_device *xe) return; } - /* Ensure the wakelock release work gets flushed */ - intel_dmc_wl_flush_release_work(display); + intel_display_driver_pm_runtime_suspend_late(display); } /* before irq resume */ void xe_display_pm_runtime_resume_early(struct xe_device *xe) { + struct intel_display *display = xe->display; + if (!xe->info.probe_display) return; if (xe->d3cold.allowed) return; + + intel_display_driver_pm_runtime_resume_early(display); } /* after irq resume */ @@ -407,9 +410,7 @@ void xe_display_pm_runtime_resume(struct xe_device *xe) return; } - intel_hpd_init(display); - intel_hpd_poll_disable(display); - skl_watermark_ipc_update(display); + intel_display_driver_pm_runtime_resume(display); } From b4ea5272133059acb493cc36599071a9e852ec2e Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Thu, 25 Jun 2026 17:22:04 +0300 Subject: [PATCH 09/93] drm/i915/mst: limit DP MST ESI service loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The loop in intel_dp_check_mst_status() keeps servicing interrupts originating from the sink without bound. Add an upper bound to the new interrupts occurring during interrupt processing to not get stuck on potentially stuck sink devices. Use arbitrary 32 tries to clear incoming interrupts in one go. Discovered using AI-assisted static analysis confirmed by Intel Product Security. Note: The condition likely pre-dates the commit in the Fixes: tag, but this is about as far back as a backport has any chance of succeeding. Before that, the retry had a goto. Reported-by: Martin Hodo Fixes: 3c0ec2c2d594 ("drm/i915: Flatten intel_dp_check_mst_status() a bit") Cc: stable@vger.kernel.org # v5.8+ Cc: Ville Syrjälä Cc: Imre Deak Reviewed-by: Imre Deak Link: https://patch.msgid.link/20260625142204.1078287-1-jani.nikula@intel.com Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_dp.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index 50819e633f24..64090a106847 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -5551,8 +5551,9 @@ intel_dp_check_mst_status(struct intel_dp *intel_dp) struct intel_display *display = to_intel_display(intel_dp); bool force_retrain = intel_dp_link_training_get_force_retrain(intel_dp->link.training); bool reprobe_needed = false; + int tries = 33; - for (;;) { + while (--tries) { u8 esi[4] = {}; u8 ack[4] = {}; bool new_irqs; @@ -5595,6 +5596,11 @@ intel_dp_check_mst_status(struct intel_dp *intel_dp) break; } + if (!tries) { + drm_dbg_kms(display->drm, "DPRX ESI not clearing, device may be stuck\n"); + reprobe_needed = true; + } + return !reprobe_needed; } From cd0121502ce61a2efec23fb61b90ffec300be281 Mon Sep 17 00:00:00 2001 From: Dibin Moolakadan Subrahmanian Date: Wed, 1 Jul 2026 15:36:50 +0530 Subject: [PATCH 10/93] drm/i915/psr: Block DC3CO entry during active frame On Xe3P, when PSR2 is enabled on a panel that does not support Early Transport, DC3CO can be entered in the middle of an active frame. This prevents the pipe from completing the frame and leaves it in a bad state that does not recover well, causing visible corruption on screen. Set CHICKEN_DCPR_4 bit 24 in the PSR2 enable path when Early Transport is not in use, to notify DMC to prevent DC3CO entry. v2: - Remove display from commit header (Suraj Kandpal). - Add HSD number to intel_display_wa framework (Suraj Kandpal). - Change register prefix from XE3LPD_ to XE3P_ (Suraj Kandpal). BSpec: 71483, 75253 Signed-off-by: Dibin Moolakadan Subrahmanian Reviewed-by: Suraj Kandpal Signed-off-by: Suraj Kandpal Link: https://patch.msgid.link/20260701100650.1689665-2-dibin.moolakadan.subrahmanian@intel.com --- .../gpu/drm/i915/display/intel_display_regs.h | 3 +++ drivers/gpu/drm/i915/display/intel_display_wa.c | 2 ++ drivers/gpu/drm/i915/display/intel_display_wa.h | 1 + drivers/gpu/drm/i915/display/intel_psr.c | 16 ++++++++++++++++ 4 files changed, 22 insertions(+) diff --git a/drivers/gpu/drm/i915/display/intel_display_regs.h b/drivers/gpu/drm/i915/display/intel_display_regs.h index 39e50423132f..0f5018482497 100644 --- a/drivers/gpu/drm/i915/display/intel_display_regs.h +++ b/drivers/gpu/drm/i915/display/intel_display_regs.h @@ -1747,6 +1747,9 @@ #define XELPD_CHICKEN_DCPR_3 _MMIO(0x46438) #define DMD_RSP_TIMEOUT_DISABLE REG_BIT(19) +#define XE3P_CHICKEN_DCPR_4 _MMIO(0x454a0) +#define DCPR4_BLOCK_DC3CO_ACTIVE_FRAME REG_BIT(24) + #define SKL_DFSM _MMIO(0x51000) #define SKL_DFSM_DISPLAY_PM_DISABLE (1 << 27) #define SKL_DFSM_DISPLAY_HDCP_DISABLE (1 << 25) diff --git a/drivers/gpu/drm/i915/display/intel_display_wa.c b/drivers/gpu/drm/i915/display/intel_display_wa.c index 2094eda09c91..b4c49816f7eb 100644 --- a/drivers/gpu/drm/i915/display/intel_display_wa.c +++ b/drivers/gpu/drm/i915/display/intel_display_wa.c @@ -112,6 +112,8 @@ bool __intel_display_wa(struct intel_display *display, enum intel_display_wa wa, DISPLAY_VERx100(display) == 1401; case INTEL_DISPLAY_WA_14025769978: return DISPLAY_VER(display) == 35; + case INTEL_DISPLAY_WA_14026643300: + return DISPLAY_VER(display) == 35; case INTEL_DISPLAY_WA_15013987218: return DISPLAY_VER(display) == 20; case INTEL_DISPLAY_WA_15018326506: diff --git a/drivers/gpu/drm/i915/display/intel_display_wa.h b/drivers/gpu/drm/i915/display/intel_display_wa.h index 9cdd148ea4fa..92b3980bea84 100644 --- a/drivers/gpu/drm/i915/display/intel_display_wa.h +++ b/drivers/gpu/drm/i915/display/intel_display_wa.h @@ -43,6 +43,7 @@ enum intel_display_wa { INTEL_DISPLAY_WA_14016740474, INTEL_DISPLAY_WA_14020863754, INTEL_DISPLAY_WA_14025769978, + INTEL_DISPLAY_WA_14026643300, INTEL_DISPLAY_WA_15013987218, INTEL_DISPLAY_WA_15018326506, INTEL_DISPLAY_WA_16011181250, diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c index 92af21d823a3..40e3d7095996 100644 --- a/drivers/gpu/drm/i915/display/intel_psr.c +++ b/drivers/gpu/drm/i915/display/intel_psr.c @@ -2100,6 +2100,18 @@ static void intel_psr_enable_source(struct intel_dp *intel_dp, else if (display->platform.alderlake_p) intel_de_rmw(display, CLKGATE_DIS_MISC, 0, CLKGATE_DIS_MISC_DMASC_GATING_DIS); + + /* + * Wa_14026643300 + * On Xe3P, restrict DC3CO entry during active frame when PSR2 is + * enabled without panel Early Transport; required to avoid pipe bad state. + * DMC honours CHICKEN_DCPR_4 bit 24 to block DC3CO entry during active frame. + */ + if (intel_display_wa(display, INTEL_DISPLAY_WA_14026643300) && + !intel_dp->psr.panel_replay_enabled && + !intel_dp->psr.su_region_et_enabled) + intel_de_rmw(display, XE3P_CHICKEN_DCPR_4, + 0, DCPR4_BLOCK_DC3CO_ACTIVE_FRAME); } /* Wa_16025596647 */ @@ -2341,6 +2353,10 @@ static void intel_psr_disable_locked(struct intel_dp *intel_dp) else if (display->platform.alderlake_p) intel_de_rmw(display, CLKGATE_DIS_MISC, CLKGATE_DIS_MISC_DMASC_GATING_DIS, 0); + + if (intel_display_wa(display, INTEL_DISPLAY_WA_14026643300)) + intel_de_rmw(display, XE3P_CHICKEN_DCPR_4, + DCPR4_BLOCK_DC3CO_ACTIVE_FRAME, 0); } if (intel_dp_is_edp(intel_dp)) From a78d11e7859b87a0058e9b22d93deff17edd1a22 Mon Sep 17 00:00:00 2001 From: Suraj Kandpal Date: Fri, 3 Jul 2026 08:56:54 +0530 Subject: [PATCH 11/93] drm/i915/display: Skip TRANS_VTOTAL.Vtotal write on NVL pre-C0 On Nova Lake A/B steppings the GOP breaks DP-Alt / native DP output when TRANS_VTOTAL.Vtotal is programmed with a non-zero value, and the initial hardware readout of that field on such systems ends up in adjusted_mode.crtc_vtotal = 1 -- tripping the DSB scanline WARN in assert_dsl_ok() during intel_initial_commit. Keep TRANS_VTOTAL.Vtotal at 0 on Nova Lake pre-C0 (A0..B3) in both transcoder timing paths, and restore adjusted_mode.crtc_vtotal from TRANS_VRR_VMIN in intel_vrr_get_config() so downstream state stays consistent. Fixes: f26a8df8dff9 ("drm/i915/display: Program TRANS_VTOTAL from mode vtotal") Signed-off-by: Suraj Kandpal Reviewed-by: Ankit Nautiyal Link: https://patch.msgid.link/20260703032653.2122784-1-suraj.kandpal@intel.com --- drivers/gpu/drm/i915/display/intel_display.c | 9 +++++++++ drivers/gpu/drm/i915/display/intel_vrr.c | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c index 90c05ad08f86..214454f419e9 100644 --- a/drivers/gpu/drm/i915/display/intel_display.c +++ b/drivers/gpu/drm/i915/display/intel_display.c @@ -45,6 +45,7 @@ #include #include #include +#include #include "g4x_dp.h" #include "g4x_hdmi.h" @@ -2737,6 +2738,10 @@ void intel_set_transcoder_timings(const struct intel_crtc_state *crtc_state, HSYNC_START(adjusted_mode->crtc_hsync_start - 1) | HSYNC_END(adjusted_mode->crtc_hsync_end - 1)); + if (display->platform.novalake && + IS_DISPLAY_STEP(display, STEP_A0, STEP_C0)) + crtc_vtotal = 1; + intel_de_write(display, TRANS_VTOTAL(display, transcoder), VACTIVE(crtc_vdisplay - 1) | VTOTAL(crtc_vtotal - 1)); @@ -2830,6 +2835,10 @@ void intel_set_transcoder_timings_lrr(const struct intel_crtc_state *crtc_state, * The double buffer latch point for TRANS_VTOTAL * is the transcoder's undelayed vblank. */ + if (display->platform.novalake && + IS_DISPLAY_STEP(display, STEP_A0, STEP_C0)) + crtc_vtotal = 1; + intel_de_write(display, TRANS_VTOTAL(display, transcoder), VACTIVE(crtc_vdisplay - 1) | VTOTAL(crtc_vtotal - 1)); diff --git a/drivers/gpu/drm/i915/display/intel_vrr.c b/drivers/gpu/drm/i915/display/intel_vrr.c index bffbdee76ee1..51e4f3309b8b 100644 --- a/drivers/gpu/drm/i915/display/intel_vrr.c +++ b/drivers/gpu/drm/i915/display/intel_vrr.c @@ -5,6 +5,7 @@ */ #include +#include #include "intel_alpm.h" #include "intel_cmtg.h" @@ -1106,6 +1107,11 @@ void intel_vrr_get_config(struct intel_crtc_state *crtc_state) crtc_state->vrr.vmin += intel_vrr_vmin_flipline_offset(display); } + if (display->platform.novalake && + IS_DISPLAY_STEP(display, STEP_A0, STEP_C0)) + crtc_state->hw.adjusted_mode.crtc_vtotal = + intel_vrr_vmin_vtotal(crtc_state); + if (HAS_AS_SDP(display)) { trans_vrr_vsync = intel_de_read(display, From e6d534ed112d95203ef28ab87007b7696cbc946c Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 8 Jul 2026 17:05:58 +0530 Subject: [PATCH 12/93] drm/i915/audio: treat UHBR SST like MST for cpu_transcoder signalling Both DP MST and UHBR SST require the audio component to use a real cpu_transcoder (>= 0) value to identify the audio stream on a port. For DP MST this was already handled; for UHBR SST it was not, causing pin_eld_notify() to signal cpu_transcoder = -1 (the legacy SST sentinel) and find_audio_state() to skip the per-transcoder entry for the UHBR stream. After suspend/resume or replug, the ELD lookup fails and audio goes silent. Fix by detecting UHBR SST alongside DP MST when deciding whether a real cpu_transcoder value must be used for audio state addressing, in both the pin_eld_notify() signalling and the find_audio_state() lookup paths. --v2: - Commit message and comment update. (Suraj) - Change function name has_mst_transcoder. (Suraj) - Avoid Live crtc access in find_audio_state. (Suraj) --v3: - Minor comment changes to avoid MST over 128b/132b confusion. (Suraj) Signed-off-by: Kai Vehmanen Signed-off-by: Mitul Golani Reviewed-by: Suraj Kandpal Signed-off-by: Suraj Kandpal Link: https://patch.msgid.link/20260708113558.1863316-1-mitulkumar.ajitkumar.golani@intel.com --- drivers/gpu/drm/i915/display/intel_audio.c | 55 ++++++++++++++----- .../gpu/drm/i915/display/intel_display_core.h | 2 + 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_audio.c b/drivers/gpu/drm/i915/display/intel_audio.c index 9729f1837d2c..eae76e961105 100644 --- a/drivers/gpu/drm/i915/display/intel_audio.c +++ b/drivers/gpu/drm/i915/display/intel_audio.c @@ -38,6 +38,7 @@ #include "intel_de.h" #include "intel_display_types.h" #include "intel_display_wa.h" +#include "intel_dp.h" #include "intel_lpe_audio.h" /** @@ -696,6 +697,13 @@ static void ibx_audio_codec_enable(struct intel_encoder *encoder, mutex_unlock(&display->audio.mutex); } +static +bool intel_audio_needs_cpu_transcoder_id(const struct intel_crtc_state *crtc_state) +{ + return intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP_MST) || + intel_dp_is_uhbr(crtc_state); +} + bool intel_audio_compute_config(struct intel_encoder *encoder, struct intel_crtc_state *crtc_state, struct drm_connector_state *conn_state) @@ -762,6 +770,8 @@ void intel_audio_codec_enable(struct intel_encoder *encoder, audio_state = &display->audio.state[cpu_transcoder]; audio_state->encoder = encoder; + audio_state->needs_cpu_transcoder_id = + intel_audio_needs_cpu_transcoder_id(crtc_state); BUILD_BUG_ON(sizeof(audio_state->eld) != sizeof(crtc_state->eld)); memcpy(audio_state->eld, crtc_state->eld, sizeof(audio_state->eld)); @@ -769,8 +779,12 @@ void intel_audio_codec_enable(struct intel_encoder *encoder, if (acomp && acomp->base.audio_ops && acomp->base.audio_ops->pin_eld_notify) { - /* audio drivers expect cpu_transcoder = -1 to indicate Non-MST cases */ - if (!intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP_MST)) + /* + * Audio drivers expect cpu_transcoder = -1 to indicate + * Non-MST/HBR cases. MST and UHBR SST are addressed by + * a real cpu_transcoder. + */ + if (!intel_audio_needs_cpu_transcoder_id(crtc_state)) cpu_transcoder = -1; acomp->base.audio_ops->pin_eld_notify(acomp->base.audio_ops->audio_ptr, (int)port, (int)cpu_transcoder); @@ -819,14 +833,19 @@ void intel_audio_codec_disable(struct intel_encoder *encoder, audio_state = &display->audio.state[cpu_transcoder]; audio_state->encoder = NULL; + audio_state->needs_cpu_transcoder_id = false; memset(audio_state->eld, 0, sizeof(audio_state->eld)); mutex_unlock(&display->audio.mutex); if (acomp && acomp->base.audio_ops && acomp->base.audio_ops->pin_eld_notify) { - /* audio drivers expect cpu_transcoder = -1 to indicate Non-MST cases */ - if (!intel_crtc_has_type(old_crtc_state, INTEL_OUTPUT_DP_MST)) + /* + * Audio drivers expect cpu_transcoder = -1 to indicate + * Non-MST/HBR cases. MST and UHBR SST are addressed by + * a real cpu_transcoder. + */ + if (!intel_audio_needs_cpu_transcoder_id(old_crtc_state)) cpu_transcoder = -1; acomp->base.audio_ops->pin_eld_notify(acomp->base.audio_ops->audio_ptr, (int)port, (int)cpu_transcoder); @@ -1118,18 +1137,24 @@ static int intel_audio_component_get_cdclk_freq(struct device *kdev) } /* - * get the intel audio state according to the parameter port and cpu_transcoder - * MST & (cpu_transcoder >= 0): return the audio.state[cpu_transcoder].encoder], - * when port is matched - * MST & (cpu_transcoder < 0): this is invalid - * Non-MST & (cpu_transcoder >= 0): only cpu_transcoder = 0 (the first device entry) - * will get the right intel_encoder with port matched - * Non-MST & (cpu_transcoder < 0): get the right intel_encoder with port matched + * Get the intel audio state for a given (port, cpu_transcoder). + * + * Streams are addressed either by a real cpu_transcoder (DP MST and UHBR SST, + * i.e. entries whose stored needs_cpu_transcoder_id is true) or by port alone + * (legacy SST). Both the signalling side (pin_eld_notify()) and the lookup + * side use the same predicate, so the two are symmetric. + * + * cpu_transcoder >= 0 & needs_cpu_transcoder_id: return audio.state[cpu_transcoder] + * when the port matches. + * cpu_transcoder < 0 & !needs_cpu_transcoder_id: return the first port-matching + * entry. + * cpu_transcoder = 0 & !needs_cpu_transcoder_id: falls to the port-only + * loop so the first device entry of a legacy SST port is still found. */ static struct intel_audio_state *find_audio_state(struct intel_display *display, int port, int cpu_transcoder) { - /* MST */ + /* MST, or UHBR SST. */ if (cpu_transcoder >= 0) { struct intel_audio_state *audio_state; struct intel_encoder *encoder; @@ -1142,11 +1167,11 @@ static struct intel_audio_state *find_audio_state(struct intel_display *display, encoder = audio_state->encoder; if (encoder && encoder->port == port && - encoder->type == INTEL_OUTPUT_DP_MST) + audio_state->needs_cpu_transcoder_id) return audio_state; } - /* Non-MST */ + /* Legacy SST. */ if (cpu_transcoder > 0) return NULL; @@ -1158,7 +1183,7 @@ static struct intel_audio_state *find_audio_state(struct intel_display *display, encoder = audio_state->encoder; if (encoder && encoder->port == port && - encoder->type != INTEL_OUTPUT_DP_MST) + !audio_state->needs_cpu_transcoder_id) return audio_state; } diff --git a/drivers/gpu/drm/i915/display/intel_display_core.h b/drivers/gpu/drm/i915/display/intel_display_core.h index 17f7d3abdb9c..7e988b7b1fe7 100644 --- a/drivers/gpu/drm/i915/display/intel_display_core.h +++ b/drivers/gpu/drm/i915/display/intel_display_core.h @@ -97,6 +97,8 @@ struct intel_wm_funcs { struct intel_audio_state { struct intel_encoder *encoder; u8 eld[MAX_ELD_BYTES]; + /* MST, or SST on UHBR link */ + bool needs_cpu_transcoder_id; }; struct intel_audio { From ee8f7484f99db9fb80ad15af412dbf03eeb9ff57 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:30 +0300 Subject: [PATCH 13/93] drm/i915/doc: Document DP link capabilities Add documentation for the DP link capabilities interface. Reviewed-by: Suraj Kandpal Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-2-imre.deak@intel.com --- .../intel-display/dp-link-capabilities.rst | 11 +++ Documentation/gpu/intel-display/index.rst | 1 + .../gpu/drm/i915/display/intel_dp_link_caps.c | 81 +++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 Documentation/gpu/intel-display/dp-link-capabilities.rst diff --git a/Documentation/gpu/intel-display/dp-link-capabilities.rst b/Documentation/gpu/intel-display/dp-link-capabilities.rst new file mode 100644 index 000000000000..331cc69d13a0 --- /dev/null +++ b/Documentation/gpu/intel-display/dp-link-capabilities.rst @@ -0,0 +1,11 @@ +.. SPDX-License-Identifier: MIT +.. Copyright © 2026 Intel Corporation + +DisplayPort Link Capabilities +============================= + +.. kernel-doc:: drivers/gpu/drm/i915/display/intel_dp_link_caps.c + :doc: DisplayPort link capabilities + +.. kernel-doc:: drivers/gpu/drm/i915/display/intel_dp_link_caps.h + :internal: diff --git a/Documentation/gpu/intel-display/index.rst b/Documentation/gpu/intel-display/index.rst index 6fa929d82c38..e81f49bf20df 100644 --- a/Documentation/gpu/intel-display/index.rst +++ b/Documentation/gpu/intel-display/index.rst @@ -39,6 +39,7 @@ driver. The display driver isn't an independent driver in that sense. frontbuffer hotplug dp-link-training + dp-link-capabilities plane psr snps-phy diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index 1c34ba6c49c3..2c656c2c036c 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -19,6 +19,87 @@ #include "intel_dp.h" #include "intel_dp_link_caps.h" +/** + * DOC: DisplayPort link capabilities + * + * The Intel DP link caps API tracks the supported and allowed + * DisplayPort link configurations for a DP encoder and its attached + * connectors, and provides helpers to iterate over the allowed + * configurations and constrain them by filtering, disabling, or + * limiting them to maximum link parameters. + * + * Locking + * ------- + * + * All accesses to this API must be serialized. The only exception + * is intel_dp_link_caps_get_max_limits(), which allow lockless + * lookup. Such lookups may observe an out-of-sync &struct + * intel_dp_link_config tuple, i.e. a rate from one state and a lane + * count from another. + * + * The Intel i915/xe drivers ensure the above serialization by holding + * &drm_mode_config.connection_mutex and, while holding the lock, + * waiting for any pending asynchronous atomic commits. This also allows + * use of the API from the tails of asynchronous atomic commits, which + * cannot hold the lock. + * + * Iterating and restricting link configurations + * --------------------------------------------- + * + * The link configuration iterators can iterate the ``allowed + * configurations`` during modeset configuration selection or link + * training fallback handling in a configurable order. + * + * The iteration order can depend on connector type (eDP, DP SST, + * DP MST) and modeset-specific conditions or driver policies, such + * as DSC vs. non-DSC modes, power saving vs. better user experience, + * or policy changes after a link training failure. + * + * The configurations exposed via the iterators can be additionally + * constrained in the following ways: + * + * - Filtered for a given modeset based on modeset-specific conditions. + * Examples for such conditions include driver policies preferring + * power saving or better user experience, post-link training failure + * preference changes, or sink automated test requests limiting the + * usable configurations. + * + * - Disabled permanently for the connected sink. Examples of reasons + * to disable a configuration include a link training failure for a + * given configuration or a driver workaround preventing the use of + * a particular configuration. + * + * - Limited via a maximum link rate and lane count. For example, after + * a link training failure, subsequent modesets may be limited to + * configurations at or below the failed parameters. + * + * This mechanism exists for backward compatibility only. Eventually, + * it will be removed in favor of relying solely on individually + * disabled configurations, as described above. + * + * Terminology + * ----------- + * + * ``Common link capabilities`` (or ``common caps``) refer to the link + * rates and maximum lane count supported by both the source and the + * sink, i.e. the intersection of their respective capabilities. + * + * ``Supported configurations`` are all configurations defined by the + * ``Common link capabilities``' link rates and maximum lane count. + * + * ``Disabled configurations`` are ``Supported configurations`` disabled + * via this API. + * + * ``Enabled configurations`` are ``Supported configurations`` that are + * not disabled. + * + * ``Forced configurations`` are ``Enabled configurations`` forced via + * forced link parameter debugfs entries. + * + * ``Allowed configurations`` are the ``Enabled configurations``, or if + * forcing is in effect the ``Forced configurations``, constrained by a + * maximum rate and lane count set via the API. + */ struct intel_dp_link_caps { struct intel_dp *dp; From 87b7964494847d12903d03e48c93b73bebcf54ef Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:31 +0300 Subject: [PATCH 14/93] drm/i915/dp_link_caps: Factor out helper to get link config by index Factor out a helper that looks up a link configuration by index. This provides the link configuration directly, avoiding the indirect conversion via the packed config entry. Reviewed-by: Suraj Kandpal Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-3-imre.deak@intel.com --- .../gpu/drm/i915/display/intel_dp_link_caps.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index 2c656c2c036c..d0a863b00b45 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -235,6 +235,16 @@ static int intel_dp_link_config_lane_count(const struct intel_dp_link_config_ent return 1 << lce->lane_count_exp; } +static void +to_intel_dp_link_config(struct intel_dp_link_caps *link_caps, + int config_idx, struct intel_dp_link_config *config) +{ + const struct intel_dp_link_config_entry *lce = &link_caps->configs[config_idx]; + + config->rate = intel_dp_link_config_rate(link_caps, lce); + config->lane_count = intel_dp_link_config_lane_count(lce); +} + static void set_max_link_limits_no_update(struct intel_dp_link_caps *link_caps, const struct intel_dp_link_config *max_link_limits) { @@ -401,15 +411,15 @@ void intel_dp_link_config_get(struct intel_dp_link_caps *link_caps, int idx, int *link_rate, int *lane_count) { struct intel_display *display = to_intel_display(link_caps->dp); - const struct intel_dp_link_config_entry *lce; + struct intel_dp_link_config config; if (drm_WARN_ON(display->drm, idx < 0 || idx >= link_caps->num_configs)) idx = 0; - lce = &link_caps->configs[idx]; + to_intel_dp_link_config(link_caps, idx, &config); - *link_rate = intel_dp_link_config_rate(link_caps, lce); - *lane_count = intel_dp_link_config_lane_count(lce); + *link_rate = config.rate; + *lane_count = config.lane_count; } int intel_dp_link_config_index(struct intel_dp_link_caps *link_caps, From 4074974b71e5aa9f91b80cd11623b99303c572a4 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:32 +0300 Subject: [PATCH 15/93] drm/i915/dp_link_caps: Add support for link rate, lane count iteration orders Add support for iterating configurations in either link rate/lane count or lane count/link rate order. Both orders are required for DP SST connector types: the former for computing the connector state during a modeset, and the latter for selecting a fallback configuration after a link training failure. Reviewed-by: Suraj Kandpal Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-4-imre.deak@intel.com --- .../gpu/drm/i915/display/intel_dp_link_caps.c | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index d0a863b00b45..fe5a11efbe67 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -122,6 +122,13 @@ struct intel_dp_link_caps { u8 lane_count_exp:INTEL_DP_LANE_COUNT_EXP_BITS; } configs[INTEL_DP_MAX_LINK_CONFIGS]; + /* + * Indices to intel_dp_link_caps::configs[] in rate/lane count, + * lane_count/rate order. + */ + u8 rate_lane_map[INTEL_DP_MAX_LINK_CONFIGS]; + u8 lane_rate_map[INTEL_DP_MAX_LINK_CONFIGS]; + /* * Forced parameters requested via debugfs. Remains set across sink * disconnects. @@ -350,6 +357,34 @@ static int link_config_cmp_by_bw(const void *a, const void *b, const void *p) intel_dp_link_config_rate(link_caps, lce_b); } +static int link_config_cmp_by_rate_lane(const void *a, const void *b, const void *p) +{ + const struct intel_dp_link_caps *link_caps = p; + u8 *lce_a_idx = (u8 *)a; + u8 *lce_b_idx = (u8 *)b; + const struct intel_dp_link_config_entry *lce_a = &link_caps->configs[*lce_a_idx]; + const struct intel_dp_link_config_entry *lce_b = &link_caps->configs[*lce_b_idx]; + + if (lce_a->link_rate_idx != lce_b->link_rate_idx) + return lce_a->link_rate_idx - lce_b->link_rate_idx; + + return lce_a->lane_count_exp - lce_b->lane_count_exp; +} + +static int link_config_cmp_by_lane_rate(const void *a, const void *b, const void *p) +{ + const struct intel_dp_link_caps *link_caps = p; + u8 *lce_a_idx = (u8 *)a; + u8 *lce_b_idx = (u8 *)b; + const struct intel_dp_link_config_entry *lce_a = &link_caps->configs[*lce_a_idx]; + const struct intel_dp_link_config_entry *lce_b = &link_caps->configs[*lce_b_idx]; + + if (lce_a->lane_count_exp != lce_b->lane_count_exp) + return lce_a->lane_count_exp - lce_b->lane_count_exp; + + return lce_a->link_rate_idx - lce_b->link_rate_idx; +} + /* Return %true if the supported link parameters have changed. */ bool intel_dp_link_caps_update(struct intel_dp_link_caps *link_caps, const int *rates, int num_rates, int max_lane_count) @@ -404,6 +439,21 @@ bool intel_dp_link_caps_update(struct intel_dp_link_caps *link_caps, link_config_cmp_by_bw, NULL, intel_dp); + for (i = 0; i < link_caps->num_configs; i++) { + link_caps->rate_lane_map[i] = i; + link_caps->lane_rate_map[i] = i; + } + + sort_r(link_caps->rate_lane_map, link_caps->num_configs, + sizeof(link_caps->rate_lane_map[0]), + link_config_cmp_by_rate_lane, NULL, + link_caps); + + sort_r(link_caps->lane_rate_map, link_caps->num_configs, + sizeof(link_caps->lane_rate_map[0]), + link_config_cmp_by_lane_rate, NULL, + link_caps); + return link_params_changed; } From 39e30bdf2f92d3de30bc4cdc4ea6c0da41298be1 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:33 +0300 Subject: [PATCH 16/93] drm/i915/dp_link_caps: Add link configuration iterator Add helpers to iterate over the allowed and optionally filtered set of link configurations in a given order. Taking into account disabled configurations will be added later when adding support for disabling configurations. Use the BW order in descending direction by default. v2: - Keep the iteration state in an iteration object. (Jani) - Use a filter object instead of exposing configuration indices. (Jani) - Move changes adding helpers required to setup the iteration object to this patch. Reviewed-by: Suraj Kandpal Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-5-imre.deak@intel.com --- .../gpu/drm/i915/display/intel_dp_link_caps.c | 213 ++++++++++++++++++ .../gpu/drm/i915/display/intel_dp_link_caps.h | 106 +++++++++ 2 files changed, 319 insertions(+) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index fe5a11efbe67..d46b9d505a84 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -16,6 +16,7 @@ #include "intel_display_core.h" #include "intel_display_types.h" +#include "intel_display_utils.h" #include "intel_dp.h" #include "intel_dp_link_caps.h" @@ -146,6 +147,18 @@ struct intel_dp_link_caps { */ struct intel_dp_link_config max_limits; }; +static_assert(BITS_PER_TYPE(((struct intel_dp_link_caps_filter *)NULL)->config_mask) >= + ARRAY_SIZE(((struct intel_dp_link_caps *)NULL)->configs)); + +static struct intel_dp_link_caps_order bw_desc_config_order(void) +{ + struct intel_dp_link_caps_order order = { + .key = INTEL_DP_LINK_CAPS_ORDER_KEY_BW, + .dir = INTEL_DP_LINK_CAPS_ORDER_DIR_DESC, + }; + + return order; +} /* Get length of common rates array potentially limited by max_rate. */ int intel_dp_common_len_rate_limit(struct intel_dp_link_caps *link_caps, @@ -252,6 +265,206 @@ to_intel_dp_link_config(struct intel_dp_link_caps *link_caps, config->lane_count = intel_dp_link_config_lane_count(lce); } +static int +iter_pos_to_idx(struct intel_dp_link_caps *link_caps, + struct intel_dp_link_caps_order config_order, + int iter_pos) +{ + int config_idx; + + if (!in_range(iter_pos, 0, link_caps->num_configs)) + return -1; + + switch (config_order.dir) { + case INTEL_DP_LINK_CAPS_ORDER_DIR_ASC: + break; + case INTEL_DP_LINK_CAPS_ORDER_DIR_DESC: + iter_pos = link_caps->num_configs - 1 - iter_pos; + + break; + default: + MISSING_CASE(config_order.dir); + + return -1; + } + + switch (config_order.key) { + case INTEL_DP_LINK_CAPS_ORDER_KEY_BW: + config_idx = iter_pos; + + break; + case INTEL_DP_LINK_CAPS_ORDER_KEY_RATE_LANE: + config_idx = link_caps->rate_lane_map[iter_pos]; + + break; + case INTEL_DP_LINK_CAPS_ORDER_KEY_LANE_RATE: + config_idx = link_caps->lane_rate_map[iter_pos]; + + break; + default: + MISSING_CASE(config_order.key); + + return -1; + } + + return config_idx; +} + +static bool iter_get_next_config(struct intel_dp_link_caps_iter *iter, + struct intel_dp_link_config *config) +{ + while (true) { + int config_idx; + + iter->pos++; + + config_idx = iter_pos_to_idx(iter->link_caps, iter->order, iter->pos); + if (config_idx < 0) { + iter->pos = -1; + *config = INTEL_DP_LINK_CONFIG_NULL; + + break; + } + + if (!(BIT(config_idx) & iter->filter.config_mask)) + continue; + + to_intel_dp_link_config(iter->link_caps, config_idx, config); + + break; + } + + return iter->pos >= 0; +} + +static void iter_start(struct intel_dp_link_caps_iter *iter, + struct intel_dp_link_caps *link_caps, + struct intel_dp_link_caps_order order, + struct intel_dp_link_caps_filter filter) +{ + iter->link_caps = link_caps; + iter->pos = -1; + iter->order = order; + iter->filter = filter; + + iter->get_next_config = iter_get_next_config; +} + +static struct intel_dp_link_caps_filter +calc_allowed_config_filter(struct intel_dp_link_caps *link_caps, + struct intel_dp_link_caps_filter enabled_configs, + const struct intel_dp_link_config *max_limits, + const struct intel_dp_link_config *forced_params) +{ + struct intel_dp_link_caps_filter allowed_configs = INTEL_DP_LINK_CAPS_FILTER_NONE; + struct intel_dp_link_caps_order order = bw_desc_config_order(); + struct intel_dp_link_caps_iter iter; + struct intel_dp_link_config config; + + iter_start(&iter, link_caps, order, enabled_configs); + for_each_dp_link_config(&iter, &config) { + if (forced_params->rate && + forced_params->rate != config.rate) + continue; + + if (forced_params->lane_count && + forced_params->lane_count != config.lane_count) + continue; + + if (config.rate > max_limits->rate) + continue; + + if (config.lane_count > max_limits->lane_count) + continue; + + allowed_configs.config_mask |= BIT(iter_pos_to_idx(link_caps, order, iter.pos)); + } + intel_dp_link_caps_iter_end(&iter); + + return allowed_configs; +} + +/* + * get_allowed_config_filter - get filter for the currently allowed configs + * @link_caps: link capabilities state + * + * Return: + * Filter of link configurations allowed after applying the current + * maximum link limits, and further narrowing them by removing any disabled + * configuration and limiting to forced link parameters. + * + * See also: + * - intel_dp_link_caps_get_max_limits() + * - intel_dp_link_caps_get_forced_params() + */ +static struct intel_dp_link_caps_filter +get_allowed_config_filter(struct intel_dp_link_caps *link_caps) +{ + struct intel_dp_link_config forced_params; + + intel_dp_link_caps_get_forced_params(link_caps, &forced_params); + + /* TODO: Get filter for enabled configs. */ + return calc_allowed_config_filter(link_caps, INTEL_DP_LINK_CAPS_FILTER_ALL, + &link_caps->max_limits, &forced_params); +} + +void intel_dp_link_caps_iter_start(struct intel_dp_link_caps_iter *iter, + struct intel_dp_link_caps *link_caps, + struct intel_dp_link_caps_order order, + struct intel_dp_link_caps_filter filter) +{ + filter.config_mask &= get_allowed_config_filter(link_caps).config_mask; + + iter_start(iter, link_caps, order, filter); +} + +void intel_dp_link_caps_iter_end(struct intel_dp_link_caps_iter *iter) +{ + memset(iter, 0, sizeof(*iter)); +} + +static int find_config_idx(struct intel_dp_link_caps *link_caps, + struct intel_dp_link_caps_filter filter, + const struct intel_dp_link_config *link_config) +{ + struct intel_dp_link_caps_order order = bw_desc_config_order(); + struct intel_dp_link_config iter_config; + struct intel_dp_link_caps_iter iter; + int pos = -1; + + intel_dp_link_caps_iter_start(&iter, link_caps, order, filter); + for_each_dp_link_config(&iter, &iter_config) { + if (iter_config.rate == link_config->rate && + iter_config.lane_count == link_config->lane_count) { + pos = iter.pos; + + break; + } + } + intel_dp_link_caps_iter_end(&iter); + + if (pos < 0) + return pos; + + return iter_pos_to_idx(link_caps, order, pos); +} + +bool intel_dp_link_caps_filter_add(struct intel_dp_link_caps *link_caps, + struct intel_dp_link_caps_filter *filter, + const struct intel_dp_link_config *config) +{ + int idx; + + idx = find_config_idx(link_caps, get_allowed_config_filter(link_caps), config); + if (idx < 0) + return false; + + filter->config_mask |= BIT(idx); + + return true; +} + static void set_max_link_limits_no_update(struct intel_dp_link_caps *link_caps, const struct intel_dp_link_config *max_link_limits) { diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.h b/drivers/gpu/drm/i915/display/intel_dp_link_caps.h index af9028e7cb98..5ea87f112c13 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.h +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.h @@ -4,6 +4,7 @@ #ifndef __INTEL_DP_LINK_CAPS_H__ #define __INTEL_DP_LINK_CAPS_H__ +#include #include struct intel_connector; @@ -11,6 +12,107 @@ struct intel_dp; struct intel_dp_link_caps; struct intel_dp_link_config; +/** + * enum intel_dp_link_caps_order_key - key used to order configurations + * @INTEL_DP_LINK_CAPS_ORDER_KEY_BW: + * Order configurations by bandwidth, then by link rate. + * @INTEL_DP_LINK_CAPS_ORDER_KEY_RATE_LANE: + * Order configurations by link rate, then by lane count. + * @INTEL_DP_LINK_CAPS_ORDER_KEY_LANE_RATE: + * Order configurations by lane count, then by link rate. + * @INTEL_DP_LINK_CAPS_ORDER_KEY_NUM: + * Number of ordering keys. + * + * Selects how a caller wants the configuration table to be ordered, + * together with an &enum intel_dp_link_caps_order_direction, for + * iteration queries. + * + * See also: + * - &struct intel_dp_link_caps_order + */ +enum intel_dp_link_caps_order_key { + INTEL_DP_LINK_CAPS_ORDER_KEY_BW, + INTEL_DP_LINK_CAPS_ORDER_KEY_RATE_LANE, + INTEL_DP_LINK_CAPS_ORDER_KEY_LANE_RATE, + + INTEL_DP_LINK_CAPS_ORDER_KEY_NUM +}; + +/** + * enum intel_dp_link_caps_order_direction - iteration direction + * @INTEL_DP_LINK_CAPS_ORDER_DIR_ASC: + * Iterate in ascending order according to the selected ordering key. + * @INTEL_DP_LINK_CAPS_ORDER_DIR_DESC: + * Iterate in descending order according to the selected ordering key. + * @INTEL_DP_LINK_CAPS_ORDER_DIR_NUM: + * Number of ordering directions. + * + * Selects the direction associated with an + * &enum intel_dp_link_caps_order_key for iteration queries. + * + * See also: + * - &struct intel_dp_link_caps_order + */ +enum intel_dp_link_caps_order_direction { + INTEL_DP_LINK_CAPS_ORDER_DIR_ASC, + INTEL_DP_LINK_CAPS_ORDER_DIR_DESC, + + INTEL_DP_LINK_CAPS_ORDER_DIR_NUM +}; + +/** + * struct intel_dp_link_caps_order - configuration ordering + * @key: + * Key used to order configurations. + * @dir: + * Direction of the selected ordering. + * + * Describes an iteration order for link configurations. + * + * See also: + * - for_each_dp_link_config() + */ +struct intel_dp_link_caps_order { + enum intel_dp_link_caps_order_key key; + enum intel_dp_link_caps_order_direction dir; +}; + +struct intel_dp_link_caps_filter { + u32 config_mask; +}; + +#define INTEL_DP_LINK_CAPS_FILTER_NONE \ + ((struct intel_dp_link_caps_filter){ .config_mask = 0 }) +#define INTEL_DP_LINK_CAPS_FILTER_ALL \ + ((struct intel_dp_link_caps_filter){ .config_mask = (u32)-1 }) + +struct intel_dp_link_caps_iter { + struct intel_dp_link_caps *link_caps; + int pos; + struct intel_dp_link_caps_order order; + struct intel_dp_link_caps_filter filter; + + bool (*get_next_config)(struct intel_dp_link_caps_iter *iter, + struct intel_dp_link_config *config); +}; + +/** + * for_each_dp_link_config - iterate allowed link configurations + * @__iter: + * &struct intel_dp_link_caps_iter being iterated + * @__config: + * pointer to &struct intel_dp_link_config filled for each match + */ +#define for_each_dp_link_config(__iter, __config) \ + while ((__iter)->get_next_config((__iter), (__config))) + +void intel_dp_link_caps_iter_start(struct intel_dp_link_caps_iter *iter, + struct intel_dp_link_caps *link_caps, + struct intel_dp_link_caps_order order, + struct intel_dp_link_caps_filter filter); + +void intel_dp_link_caps_iter_end(struct intel_dp_link_caps_iter *iter); + int intel_dp_common_len_rate_limit(struct intel_dp_link_caps *link_caps, int max_rate); int intel_dp_common_rate(struct intel_dp_link_caps *link_caps, int index); @@ -29,6 +131,10 @@ int intel_dp_link_config_index(struct intel_dp_link_caps *link_caps, void intel_dp_link_config_get(struct intel_dp_link_caps *link_caps, int idx, int *link_rate, int *lane_count); +bool intel_dp_link_caps_filter_add(struct intel_dp_link_caps *link_caps, + struct intel_dp_link_caps_filter *filter, + const struct intel_dp_link_config *config); + void intel_dp_link_caps_get_max_limits(struct intel_dp_link_caps *link_caps, struct intel_dp_link_config *max_link_limits); bool intel_dp_link_caps_set_max_limits(struct intel_dp_link_caps *link_caps, From 9d4bd6654ed5746108a9e0bcd8d1898556a0f21e Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:34 +0300 Subject: [PATCH 17/93] drm/i915/dp_link_caps: Add helper to get iteration order for a connector Add helper to select the link configuration iteration order for a connector during state computation and fallback selection. This keeps the connector-specific ordering policy in the link caps module. v2: Add helper to get the order for fallback selection as well. Reviewed-by: Suraj Kandpal Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-6-imre.deak@intel.com --- .../gpu/drm/i915/display/intel_dp_link_caps.c | 50 +++++++++++++++++++ .../gpu/drm/i915/display/intel_dp_link_caps.h | 5 ++ 2 files changed, 55 insertions(+) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index d46b9d505a84..36895dd9d6c8 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -160,6 +160,56 @@ static struct intel_dp_link_caps_order bw_desc_config_order(void) return order; } +static enum intel_dp_link_caps_order_key +connector_compute_order_key(bool is_mst) +{ + if (is_mst) + return INTEL_DP_LINK_CAPS_ORDER_KEY_BW; + else + return INTEL_DP_LINK_CAPS_ORDER_KEY_RATE_LANE; +} + +static enum intel_dp_link_caps_order_key +connector_fallback_order_key(bool is_mst) +{ + if (is_mst) + return INTEL_DP_LINK_CAPS_ORDER_KEY_BW; + else + return INTEL_DP_LINK_CAPS_ORDER_KEY_LANE_RATE; +} + +static enum intel_dp_link_caps_order_direction +connector_compute_order_dir(bool is_mst, bool use_max_params) +{ + if (is_mst || use_max_params) + return INTEL_DP_LINK_CAPS_ORDER_DIR_DESC; + else + return INTEL_DP_LINK_CAPS_ORDER_DIR_ASC; +} + +struct intel_dp_link_caps_order +intel_dp_link_caps_connector_compute_order(struct intel_connector *connector) +{ + struct intel_dp *intel_dp = intel_attached_dp(connector); + struct intel_dp_link_caps_order order = { + .key = connector_compute_order_key(connector->mst.dp), + .dir = connector_compute_order_dir(connector->mst.dp, intel_dp->use_max_params) + }; + + return order; +} + +struct intel_dp_link_caps_order +intel_dp_link_caps_connector_fallback_order(bool is_mst) +{ + struct intel_dp_link_caps_order order = { + .key = connector_fallback_order_key(is_mst), + .dir = INTEL_DP_LINK_CAPS_ORDER_DIR_DESC, + }; + + return order; +} + /* Get length of common rates array potentially limited by max_rate. */ int intel_dp_common_len_rate_limit(struct intel_dp_link_caps *link_caps, int max_rate) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.h b/drivers/gpu/drm/i915/display/intel_dp_link_caps.h index 5ea87f112c13..79aca74ad9ee 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.h +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.h @@ -113,6 +113,11 @@ void intel_dp_link_caps_iter_start(struct intel_dp_link_caps_iter *iter, void intel_dp_link_caps_iter_end(struct intel_dp_link_caps_iter *iter); +struct intel_dp_link_caps_order +intel_dp_link_caps_connector_compute_order(struct intel_connector *connector); +struct intel_dp_link_caps_order +intel_dp_link_caps_connector_fallback_order(bool is_mst); + int intel_dp_common_len_rate_limit(struct intel_dp_link_caps *link_caps, int max_rate); int intel_dp_common_rate(struct intel_dp_link_caps *link_caps, int index); From e5cd9e178874e8cd56247335eef10cfb19cc599a Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:35 +0300 Subject: [PATCH 18/93] drm/i915/dp_link_caps: Validate max link limits Add validation in intel_dp_link_caps_set_max_limits() to ensure that the new maximum rate and lane count leave at least one allowed configuration. The validation takes disabled configurations and active forced parameters into account. Disabled configurations are not supported yet, so that part has no effect for now. At the moment this validation is also performed by the link training fallback code, but that will be removed later, leaving only the link caps module to perform the validation added in this patch. v2: Rebase on changes using a filter object instead of a mask of configuration indices. Reviewed-by: Suraj Kandpal Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-7-imre.deak@intel.com --- .../gpu/drm/i915/display/intel_dp_link_caps.c | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index 36895dd9d6c8..5976e85bb272 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -558,6 +558,26 @@ void intel_dp_link_caps_get_max_limits(struct intel_dp_link_caps *link_caps, *max_link_limits = link_caps->max_limits; } +static bool max_link_limits_valid(struct intel_dp_link_caps *link_caps, + const struct intel_dp_link_config *max_link_limits) +{ + struct intel_dp_link_caps_filter allowed_configs; + struct intel_dp_link_config forced_params; + + if (max_link_limits->lane_count > INTEL_DP_MAX_LANE_COUNT || + !is_power_of_2(max_link_limits->lane_count)) + return false; + + /* TODO: Validate max_link_limits->rate against the source supported rates. */ + + intel_dp_link_caps_get_forced_params(link_caps, &forced_params); + /* TODO: Get filter for enabled configs. */ + allowed_configs = calc_allowed_config_filter(link_caps, INTEL_DP_LINK_CAPS_FILTER_ALL, + max_link_limits, &forced_params); + + return allowed_configs.config_mask != 0; +} + /** * intel_dp_link_caps_set_max_limits - set the current maximum link limits * @link_caps: link capabilities state @@ -566,6 +586,10 @@ void intel_dp_link_caps_get_max_limits(struct intel_dp_link_caps *link_caps, * Set the current maximum rate and lane count limits to @max_link_limits, * constraining the set of allowed configurations. * + * The new limits must leave at least one configuration allowed: the limits + * must not be below the currently active forced parameters or below all the + * configurations that remain after disabled configurations are excluded. + * * Unlike intel_dp_link_caps_get_max_limits(), the caller must serialize * this call against concurrent queries and updates to @link_caps, in line * with the rest of the API. @@ -578,9 +602,11 @@ void intel_dp_link_caps_get_max_limits(struct intel_dp_link_caps *link_caps, bool intel_dp_link_caps_set_max_limits(struct intel_dp_link_caps *link_caps, const struct intel_dp_link_config *max_link_limits) { + if (!max_link_limits_valid(link_caps, max_link_limits)) + return false; + set_max_link_limits_no_update(link_caps, max_link_limits); - /* TODO: validate max_link_limits */ return true; } From 1e5746217caee100a678a0b2c9ecf700e87316ca Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:36 +0300 Subject: [PATCH 19/93] drm/i915/dp_link_caps: Add filter for enabled link configurations Add a filter for enabled link configurations to the link capability state. This allows fallback code to disable only the configuration that failed link training, instead of constraining later modesets via maximum link rate or lane count. The code only needs to exclude the failed configuration from the allowed set; all other supported configurations remain available. Use the filter when computing the allowed configuration set and when validating maximum link limits. Follow-up changes will switch the fallback code to disable individual configurations through this filter. v2: - Rebase on changes using a filter object instead of a mask of configuration indices. - Track the enabled configurations instead of the disabled ones. Reviewed-by: Suraj Kandpal Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-8-imre.deak@intel.com --- .../gpu/drm/i915/display/intel_dp_link_caps.c | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index 5976e85bb272..03e40e8d24fb 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -130,6 +130,37 @@ struct intel_dp_link_caps { u8 rate_lane_map[INTEL_DP_MAX_LINK_CONFIGS]; u8 lane_rate_map[INTEL_DP_MAX_LINK_CONFIGS]; + /* + * Filter of configurations enabled for the current sink + * connection. + * + * Each bit in the filter's configuration mask corresponds to a + * configuration index in the intel_dp_link_caps::configs[] array. + * + * All configurations start out enabled in the filter after a + * new sink is connected. Users disable configurations afterwards + * via the link caps API. All configurations get re-enabled + * internally in the following cases: + * - when forcing a link rate or lane count + * - when intel_dp_link_caps_update(reset=true) is called after + * a new sink is connected + * - when intel_dp_link_caps_update(reset=false) with changed + * link capabilities is called + * - when intel_dp_link_caps_reset() is called after a new sink + * is connected + */ + struct intel_dp_link_caps_filter enabled_configs; + + /* + * Allowed configurations are the supported configurations defined by + * config_table.rates and config_table.max_lane_count, constrained by + * config_table.enabled_configs and the forced_params and + * max_limits values below. + * + * See get_allowed_config_filter() for the filter of these + * configurations. + */ + /* * Forced parameters requested via debugfs. Remains set across sink * disconnects. @@ -454,8 +485,7 @@ get_allowed_config_filter(struct intel_dp_link_caps *link_caps) intel_dp_link_caps_get_forced_params(link_caps, &forced_params); - /* TODO: Get filter for enabled configs. */ - return calc_allowed_config_filter(link_caps, INTEL_DP_LINK_CAPS_FILTER_ALL, + return calc_allowed_config_filter(link_caps, link_caps->enabled_configs, &link_caps->max_limits, &forced_params); } @@ -571,8 +601,7 @@ static bool max_link_limits_valid(struct intel_dp_link_caps *link_caps, /* TODO: Validate max_link_limits->rate against the source supported rates. */ intel_dp_link_caps_get_forced_params(link_caps, &forced_params); - /* TODO: Get filter for enabled configs. */ - allowed_configs = calc_allowed_config_filter(link_caps, INTEL_DP_LINK_CAPS_FILTER_ALL, + allowed_configs = calc_allowed_config_filter(link_caps, link_caps->enabled_configs, max_link_limits, &forced_params); return allowed_configs.config_mask != 0; @@ -1095,6 +1124,7 @@ struct intel_dp_link_caps *intel_dp_link_caps_init(struct intel_dp *intel_dp) return NULL; link_caps->dp = intel_dp; + link_caps->enabled_configs = INTEL_DP_LINK_CAPS_FILTER_ALL; return link_caps; } From cfc31f49d5e9f77ceb4f3defb813cf89d1c9ef86 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:37 +0300 Subject: [PATCH 20/93] drm/i915/dp_link_caps: Re-enable link configurations after a link reset Re-enable link configurations after the link is reset via a call to intel_dp_link_caps_reset(), allowing a subsequent modeset to use all the link configurations of a sink newly connected or an already connected sink changing its capabilities. Reviewed-by: Suraj Kandpal Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-9-imre.deak@intel.com --- drivers/gpu/drm/i915/display/intel_dp_link_caps.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index 03e40e8d24fb..667d2e55cea1 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -561,6 +561,12 @@ static void reset_max_link_limits_no_update(struct intel_dp_link_caps *link_caps set_max_link_limits_no_update(link_caps, &max_link_limits); } +static void reset_max_link_limits_reenable_all(struct intel_dp_link_caps *link_caps) +{ + link_caps->enabled_configs = INTEL_DP_LINK_CAPS_FILTER_ALL; + reset_max_link_limits_no_update(link_caps); +} + /** * intel_dp_link_caps_get_max_limits - get the current maximum link limits * @link_caps: link capabilities state @@ -824,7 +830,7 @@ int intel_dp_link_config_index(struct intel_dp_link_caps *link_caps, void intel_dp_link_caps_reset(struct intel_dp_link_caps *link_caps) { /* TODO: Update the maximum link information. */ - reset_max_link_limits_no_update(link_caps); + reset_max_link_limits_reenable_all(link_caps); } static int i915_dp_force_link_rate_show(struct seq_file *m, void *data) From ae40784fa03ae3170ae3264cf3fa010c2b57f0d5 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:38 +0300 Subject: [PATCH 21/93] drm/i915/dp_link_caps: Re-enable link configurations after sink caps change Re-enable link configurations after sink capabilities change or the link got reset before updating the link capabilities (due to an RX_CAP_CHANGED HPD IRQ for the currently connected sink, or a new sink getting connected). This makes resetting the link explicitly by calling intel_dp_link_caps_reset() subsequently redundant; keep the existing behavior wrt. this for now, adding only a TODO: to remove the explicit reset. While at it add documentation for intel_dp_link_caps_update(). Reviewed-by: Suraj Kandpal Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-10-imre.deak@intel.com --- drivers/gpu/drm/i915/display/intel_dp.c | 8 +++- .../gpu/drm/i915/display/intel_dp_link_caps.c | 37 +++++++++++++++++-- .../gpu/drm/i915/display/intel_dp_link_caps.h | 3 +- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index 64090a106847..6e855131d1de 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -701,7 +701,8 @@ static bool intel_dp_set_common_link_params(struct intel_dp *intel_dp) intel_dp_get_common_rates(intel_dp, common_rates, &num_common_rates); if (intel_dp_link_caps_update(intel_dp->link.caps, common_rates, num_common_rates, - intel_dp_get_max_common_lane_count(intel_dp))) + intel_dp_get_max_common_lane_count(intel_dp), + intel_dp->reset_link_params)) params_changed = true; return params_changed; @@ -3613,6 +3614,11 @@ void intel_dp_set_link_params(struct intel_dp *intel_dp, void intel_dp_reset_link_params(struct intel_dp *intel_dp) { + /* + * TODO: Remove the following reset of link capabilities, as + * this isn't needed after intel_dp_link_caps_update(reset=true) + * was called. + */ intel_dp_link_caps_reset(intel_dp->link.caps); intel_dp->link.mst_probed_lane_count = 0; intel_dp->link.mst_probed_rate = 0; diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index 667d2e55cea1..de520bd423d5 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -709,14 +709,42 @@ static int link_config_cmp_by_lane_rate(const void *a, const void *b, const void return lce_a->link_rate_idx - lce_b->link_rate_idx; } -/* Return %true if the supported link parameters have changed. */ +/** + * intel_dp_link_caps_update - rebuild the supported link configuration state + * @link_caps: link capabilities state + * @rates: supported common link rates + * @num_rates: number of entries in @rates + * @max_lane_count: supported maximum lane count + * @reset: reset limits and disabled configs + * + * Rebuild the supported link configuration state from @rates and + * @max_lane_count. + * + * If @reset is %true, reset the maximum link limits to the maximum + * supported rate and lane count, and re-enable all configurations. + * + * This function is called regularly, at least after a sink is connected, + * but it may also be called later whenever the sink capabilities may have + * changed, for example in response to HPD IRQ / RX_CAP_CHANGED signaling. + * + * In the Intel driver this function is currently called whenever the + * connector detect handler runs, after reading the sink capabilities. This + * may change if those capabilities are cached until the sink is + * disconnected, or until RX_CAP_CHANGED is signaled. In any case, this + * function should be called whenever the sink capabilities were read out + * and may have changed. + * + * Returns: + * - %true if the link capabilities have changed, %false otherwise. + */ bool intel_dp_link_caps_update(struct intel_dp_link_caps *link_caps, - const int *rates, int num_rates, int max_lane_count) + const int *rates, int num_rates, int max_lane_count, + bool reset) { struct intel_dp *intel_dp = link_caps->dp; struct intel_display *display = to_intel_display(intel_dp); struct intel_dp_link_config_entry *lce; - bool link_params_changed = false; + bool link_params_changed = reset; int num_common_lane_configs; int i; int j; @@ -778,6 +806,9 @@ bool intel_dp_link_caps_update(struct intel_dp_link_caps *link_caps, link_config_cmp_by_lane_rate, NULL, link_caps); + if (link_params_changed) + reset_max_link_limits_reenable_all(link_caps); + return link_params_changed; } diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.h b/drivers/gpu/drm/i915/display/intel_dp_link_caps.h index 79aca74ad9ee..dcbde890809b 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.h +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.h @@ -147,7 +147,8 @@ bool intel_dp_link_caps_set_max_limits(struct intel_dp_link_caps *link_caps, void intel_dp_link_caps_reset_max_limits(struct intel_dp_link_caps *link_caps); bool intel_dp_link_caps_update(struct intel_dp_link_caps *link_caps, - const int *rates, int num_rates, int max_lane_count); + const int *rates, int num_rates, int max_lane_count, + bool reset); void intel_dp_link_caps_reset(struct intel_dp_link_caps *link_caps); void intel_dp_link_caps_debugfs_add(struct intel_connector *connector); From ac24d562f490cbbca0280c2b5502e5502acdacc3 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:39 +0300 Subject: [PATCH 22/93] drm/i915/dp_link_caps: Drop noupdate postfix from max link limit set helpers There is no need to update any maximum link information when updating the max link limits, so drop the related noupdate postfix from the helpers setting the max link limits. Reviewed-by: Suraj Kandpal Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-11-imre.deak@intel.com --- drivers/gpu/drm/i915/display/intel_dp_link_caps.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index de520bd423d5..3ef678d47c71 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -545,26 +545,26 @@ bool intel_dp_link_caps_filter_add(struct intel_dp_link_caps *link_caps, return true; } -static void set_max_link_limits_no_update(struct intel_dp_link_caps *link_caps, - const struct intel_dp_link_config *max_link_limits) +static void set_max_link_limits(struct intel_dp_link_caps *link_caps, + const struct intel_dp_link_config *max_link_limits) { link_caps->max_limits = *max_link_limits; } -static void reset_max_link_limits_no_update(struct intel_dp_link_caps *link_caps) +static void reset_max_link_limits(struct intel_dp_link_caps *link_caps) { struct intel_dp_link_config max_link_limits = { .rate = intel_dp_max_common_rate(link_caps), .lane_count = intel_dp_link_caps_max_common_lane_count(link_caps), }; - set_max_link_limits_no_update(link_caps, &max_link_limits); + set_max_link_limits(link_caps, &max_link_limits); } static void reset_max_link_limits_reenable_all(struct intel_dp_link_caps *link_caps) { link_caps->enabled_configs = INTEL_DP_LINK_CAPS_FILTER_ALL; - reset_max_link_limits_no_update(link_caps); + reset_max_link_limits(link_caps); } /** @@ -640,7 +640,7 @@ bool intel_dp_link_caps_set_max_limits(struct intel_dp_link_caps *link_caps, if (!max_link_limits_valid(link_caps, max_link_limits)) return false; - set_max_link_limits_no_update(link_caps, max_link_limits); + set_max_link_limits(link_caps, max_link_limits); return true; } @@ -654,7 +654,7 @@ bool intel_dp_link_caps_set_max_limits(struct intel_dp_link_caps *link_caps, */ void intel_dp_link_caps_reset_max_limits(struct intel_dp_link_caps *link_caps) { - reset_max_link_limits_no_update(link_caps); + reset_max_link_limits(link_caps); } static int intel_dp_link_config_bw(struct intel_dp_link_caps *link_caps, @@ -860,7 +860,6 @@ int intel_dp_link_config_index(struct intel_dp_link_caps *link_caps, */ void intel_dp_link_caps_reset(struct intel_dp_link_caps *link_caps) { - /* TODO: Update the maximum link information. */ reset_max_link_limits_reenable_all(link_caps); } From cf65aa9de62a91f8dba7ce5cb08f262b3dd1b7ce Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:40 +0300 Subject: [PATCH 23/93] drm/i915/dp_link_caps: Add debugfs entry showing allowed configurations Add a debugfs entry showing the currently allowed link configurations in the connector's iteration order. Reviewed-by: Luca Coelho Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-12-imre.deak@intel.com --- .../gpu/drm/i915/display/intel_dp_link_caps.c | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index 3ef678d47c71..4482e1f9d6cd 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -1123,6 +1123,43 @@ static int i915_dp_max_lane_count_show(void *data, u64 *val) } DEFINE_DEBUGFS_ATTRIBUTE(i915_dp_max_lane_count_fops, i915_dp_max_lane_count_show, NULL, "%llu\n"); +static int intel_dp_allowed_link_configs_show(struct seq_file *m, void *data) +{ + struct intel_connector *connector = to_intel_connector(m->private); + struct intel_display *display = to_intel_display(connector); + struct intel_dp *intel_dp = intel_attached_dp(connector); + struct intel_dp_link_caps *link_caps = intel_dp->link.caps; + struct intel_dp_link_config link_config; + struct intel_dp_link_caps_iter iter; + int err; + int i; + + err = drm_modeset_lock_single_interruptible(&display->drm->mode_config.connection_mutex); + if (err) + return err; + + intel_dp_flush_connector_commits(connector); + + i = 0; + intel_dp_link_caps_iter_start(&iter, + link_caps, + intel_dp_link_caps_connector_compute_order(connector), + INTEL_DP_LINK_CAPS_FILTER_ALL); + for_each_dp_link_config(&iter, &link_config) { + seq_printf(m, "%s%dx%d", + i ? " " : "", + link_config.lane_count, link_config.rate); + i++; + } + intel_dp_link_caps_iter_end(&iter); + + drm_modeset_unlock(&display->drm->mode_config.connection_mutex); + + seq_putc(m, '\n'); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(intel_dp_allowed_link_configs); /** * intel_dp_link_caps_debugfs_add - add link caps debugfs files for a connector @@ -1149,6 +1186,9 @@ void intel_dp_link_caps_debugfs_add(struct intel_connector *connector) debugfs_create_file("i915_dp_max_lane_count", 0444, root, connector, &i915_dp_max_lane_count_fops); + + debugfs_create_file("intel_dp_allowed_link_configs", 0444, root, + connector, &intel_dp_allowed_link_configs_fops); } struct intel_dp_link_caps *intel_dp_link_caps_init(struct intel_dp *intel_dp) From 69505f3f9595ae00791a0f7191ca2877736a4df6 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:41 +0300 Subject: [PATCH 24/93] drm/i915/dp: Add link configuration filter for modeset computation Add link_config_filter to link_config_limits to track the set of valid link configurations during modeset state computation. Keep the existing min/max rate and lane count limits for now, until all users are converted to use the configuration filter. Add the helpers required to select the maximum configuration from the currently allowed configuration set. This will be used by follow-up changes as well to query the maximum link configuration without having to iterate the configurations. v2: - Rebase on changes using a filter object instead of a mask of configuration indices. - Rebase on changes using an iteration object. v3: - Add TODO: label to code comment about min/max link config limit removal. (Luca) Reviewed-by: Luca Coelho Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-13-imre.deak@intel.com --- drivers/gpu/drm/i915/display/intel_dp.c | 42 +++++++++++++++-- drivers/gpu/drm/i915/display/intel_dp.h | 11 +++++ .../gpu/drm/i915/display/intel_dp_link_caps.c | 46 +++++++++++++++++++ .../gpu/drm/i915/display/intel_dp_link_caps.h | 6 +++ 4 files changed, 102 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index 6e855131d1de..efa2cd27ac92 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -2538,6 +2538,20 @@ bool intel_dp_mode_valid_with_dsc(struct intel_connector *connector, bw_overhead_flags); } +bool +intel_dp_get_connector_max_link_config(struct intel_connector *connector, + const struct link_config_limits *limits, + struct intel_dp_link_config *max_link_config) +{ + struct intel_dp *intel_dp = intel_attached_dp(connector); + struct intel_dp_link_caps *link_caps = intel_dp->link.caps; + struct intel_dp_link_caps_order order = + intel_dp_link_caps_connector_compute_order(connector); + + return intel_dp_link_caps_get_max_config(link_caps, order.key, limits->link_config_filter, + max_link_config); +} + /* * Calculate the output link min, max bpp values in limits based on the pipe bpp * range, crtc_state and dsc mode. Return true on success. @@ -2554,6 +2568,7 @@ intel_dp_compute_config_link_bpp_limits(struct intel_connector *connector, &crtc_state->hw.adjusted_mode; const struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); const struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + struct intel_dp_link_config max_link_config; int max_link_bpp_x16; max_link_bpp_x16 = min(crtc_state->max_link_bpp_x16, @@ -2583,14 +2598,17 @@ intel_dp_compute_config_link_bpp_limits(struct intel_connector *connector, limits->link.max_bpp_x16 = max_link_bpp_x16; + if (!intel_dp_get_connector_max_link_config(connector, limits, &max_link_config)) + return false; + drm_dbg_kms(display->drm, - "[ENCODER:%d:%s][CRTC:%d:%s] DP link limits: pixel clock %d kHz DSC %s max lanes %d max rate %d max pipe_bpp %d min link_bpp " FXP_Q4_FMT " max link_bpp " FXP_Q4_FMT "\n", + "[ENCODER:%d:%s][CRTC:%d:%s] DP link limits: pixel clock %d kHz DSC %s max link %dx%d max pipe_bpp %d min link_bpp " FXP_Q4_FMT " max link_bpp " FXP_Q4_FMT "\n", encoder->base.base.id, encoder->base.name, crtc->base.base.id, crtc->base.name, adjusted_mode->crtc_clock, str_on_off(dsc), - limits->max_lane_count, - limits->max_rate, + max_link_config.lane_count, + max_link_config.rate, limits->pipe.max_bpp, FXP_Q4_ARGS(limits->link.min_bpp_x16), FXP_Q4_ARGS(limits->link.max_bpp_x16)); @@ -2641,10 +2659,15 @@ intel_dp_compute_config_limits(struct intel_dp *intel_dp, struct link_config_limits *limits) { struct intel_display *display = to_intel_display(intel_dp); + struct intel_dp_link_caps *link_caps = intel_dp->link.caps; bool is_mst = intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP_MST); struct intel_connector *connector = to_intel_connector(conn_state->connector); + /* + * TODO: Remove the following min/max rate and lane count setup, once + * all users are converted to use link_config_mask instead. + */ limits->min_rate = intel_dp_min_link_rate(intel_dp); limits->max_rate = intel_dp_max_link_rate(intel_dp); @@ -2653,6 +2676,8 @@ intel_dp_compute_config_limits(struct intel_dp *intel_dp, limits->min_lane_count = intel_dp_min_lane_count(intel_dp); limits->max_lane_count = intel_dp_max_lane_count(intel_dp); + limits->link_config_filter = INTEL_DP_LINK_CAPS_FILTER_ALL; + limits->pipe.min_bpp = intel_dp_min_bpp(crtc_state->output_format); if (is_mst) { /* @@ -2716,6 +2741,9 @@ intel_dp_compute_config_limits(struct intel_dp *intel_dp, crtc_state->pipe_bpp, limits->pipe.max_bpp); if (is_mst || intel_dp->use_max_params) { + struct intel_dp_link_caps_filter new_filter = INTEL_DP_LINK_CAPS_FILTER_NONE; + struct intel_dp_link_config max_config; + /* * For MST we always configure max link bw - the spec doesn't * seem to suggest we should do otherwise. @@ -2729,6 +2757,14 @@ intel_dp_compute_config_limits(struct intel_dp *intel_dp, */ limits->min_lane_count = limits->max_lane_count; limits->min_rate = limits->max_rate; + + if (!intel_dp_get_connector_max_link_config(connector, limits, &max_config)) + return false; + + if (!intel_dp_link_caps_filter_add(link_caps, &new_filter, &max_config)) + return false; + + limits->link_config_filter = new_filter; } intel_dp_test_compute_config(intel_dp, crtc_state, limits); diff --git a/drivers/gpu/drm/i915/display/intel_dp.h b/drivers/gpu/drm/i915/display/intel_dp.h index 02b691df6755..13872b8c4975 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.h +++ b/drivers/gpu/drm/i915/display/intel_dp.h @@ -8,6 +8,8 @@ #include +#include "intel_dp_link_caps.h" + enum intel_output_format; enum pipe; enum port; @@ -22,11 +24,17 @@ struct intel_crtc_state; struct intel_digital_port; struct intel_display; struct intel_dp; +struct intel_dp_link_config; struct intel_encoder; struct link_config_limits { + /* + * TODO: Remove the following min/max rate and lane count limits + * once all users are converted to use link_config_mask instead. + */ int min_rate, max_rate; int min_lane_count, max_lane_count; + struct intel_dp_link_caps_filter link_config_filter; struct { /* Uncompressed DSC input or link output bpp in 1 bpp units */ int min_bpp, max_bpp; @@ -144,6 +152,9 @@ int intel_dp_dsc_compute_max_bpp(const struct intel_connector *connector, u8 dsc_max_bpc); int intel_dp_compute_min_compressed_bpp_x16(struct intel_connector *connector, enum intel_output_format output_format); +bool intel_dp_get_connector_max_link_config(struct intel_connector *connector, + const struct link_config_limits *limits, + struct intel_dp_link_config *max_link_config); bool intel_dp_mode_valid_with_dsc(struct intel_connector *connector, int link_clock, int lane_count, int mode_clock, int mode_hdisplay, diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index 4482e1f9d6cd..d00bb8047de2 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -504,6 +504,52 @@ void intel_dp_link_caps_iter_end(struct intel_dp_link_caps_iter *iter) memset(iter, 0, sizeof(*iter)); } +/** + * intel_dp_link_caps_get_max_config - get the maximum config in a given order + * @link_caps: link capabilities state + * @order_key: ordering key used to rank candidate configurations + * @filter: filter for candidate configurations + * @max_config: returned maximum link configuration + * + * Find the last configuration among the currently allowed + * configurations filtered by @filter in the iteration order + * selected by @order_key, and store it in @max_config. + * + * See also: + * - &enum intel_dp_link_caps_order_key + * + * Returns: + * %true if a maximum config is returned + * %false otherwise. + */ +bool intel_dp_link_caps_get_max_config(struct intel_dp_link_caps *link_caps, + enum intel_dp_link_caps_order_key order_key, + struct intel_dp_link_caps_filter filter, + struct intel_dp_link_config *max_config) +{ + struct intel_dp_link_caps_order order = { + .key = order_key, + .dir = INTEL_DP_LINK_CAPS_ORDER_DIR_DESC + }; + struct intel_dp_link_config iter_config; + struct intel_dp_link_caps_iter iter; + bool found = false; + + intel_dp_link_caps_iter_start(&iter, link_caps, order, filter); + for_each_dp_link_config(&iter, &iter_config) { + found = true; + break; + } + intel_dp_link_caps_iter_end(&iter); + + if (!found) + return false; + + *max_config = iter_config; + + return true; +} + static int find_config_idx(struct intel_dp_link_caps *link_caps, struct intel_dp_link_caps_filter filter, const struct intel_dp_link_config *link_config) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.h b/drivers/gpu/drm/i915/display/intel_dp_link_caps.h index dcbde890809b..6dd2ce64e24e 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.h +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.h @@ -29,6 +29,7 @@ struct intel_dp_link_config; * * See also: * - &struct intel_dp_link_caps_order + * - intel_dp_link_caps_get_max_config() */ enum intel_dp_link_caps_order_key { INTEL_DP_LINK_CAPS_ORDER_KEY_BW, @@ -140,6 +141,11 @@ bool intel_dp_link_caps_filter_add(struct intel_dp_link_caps *link_caps, struct intel_dp_link_caps_filter *filter, const struct intel_dp_link_config *config); +bool intel_dp_link_caps_get_max_config(struct intel_dp_link_caps *link_caps, + enum intel_dp_link_caps_order_key order_key, + struct intel_dp_link_caps_filter filter, + struct intel_dp_link_config *max_config); + void intel_dp_link_caps_get_max_limits(struct intel_dp_link_caps *link_caps, struct intel_dp_link_config *max_link_limits); bool intel_dp_link_caps_set_max_limits(struct intel_dp_link_caps *link_caps, From 9791229bc23d4b8c9532e3e2794528297c966b44 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:42 +0300 Subject: [PATCH 25/93] drm/i915/dp_link_caps: Add helper to query max BW link configuration Add a helper to query the link configuration among the currently allowed configurations with the maximum link BW. This will be used by follow-up changes to unify the max BW link config query during mode validation and TBT BW calculation. Reviewed-by: Luca Coelho Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-14-imre.deak@intel.com --- .../gpu/drm/i915/display/intel_dp_link_caps.c | 17 +++++++++++++++++ .../gpu/drm/i915/display/intel_dp_link_caps.h | 3 +++ 2 files changed, 20 insertions(+) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index d00bb8047de2..c209c8a93523 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -550,6 +550,23 @@ bool intel_dp_link_caps_get_max_config(struct intel_dp_link_caps *link_caps, return true; } +/** + * intel_dp_link_caps_get_max_bw_config - get maximum BW link configuration + * @link_caps: link capabilities state + * @max_config: returned maximum link configuration + * + * Return the maximum BW link configuration among the currently + * allowed configurations. + */ +void intel_dp_link_caps_get_max_bw_config(struct intel_dp_link_caps *link_caps, + struct intel_dp_link_config *max_config) +{ + if (!intel_dp_link_caps_get_max_config(link_caps, + bw_desc_config_order().key, INTEL_DP_LINK_CAPS_FILTER_ALL, + max_config)) + *max_config = INTEL_DP_LINK_CONFIG_NULL; +} + static int find_config_idx(struct intel_dp_link_caps *link_caps, struct intel_dp_link_caps_filter filter, const struct intel_dp_link_config *link_config) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.h b/drivers/gpu/drm/i915/display/intel_dp_link_caps.h index 6dd2ce64e24e..667bd70b2396 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.h +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.h @@ -146,6 +146,9 @@ bool intel_dp_link_caps_get_max_config(struct intel_dp_link_caps *link_caps, struct intel_dp_link_caps_filter filter, struct intel_dp_link_config *max_config); +void intel_dp_link_caps_get_max_bw_config(struct intel_dp_link_caps *link_caps, + struct intel_dp_link_config *max_config); + void intel_dp_link_caps_get_max_limits(struct intel_dp_link_caps *link_caps, struct intel_dp_link_config *max_link_limits); bool intel_dp_link_caps_set_max_limits(struct intel_dp_link_caps *link_caps, From cae2b26d2fa2f69dc89e98afbbe3ace51065c79e Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:43 +0300 Subject: [PATCH 26/93] drm/i915/dp: Query max BW config via link_caps during mode validation Query the maximum link BW configuration during mode validation and MST link probing directly from intel_dp_link_caps_get_max_bw_config(), instead of using the intel_dp_max_link_rate() and intel_dp_max_lane_count() helpers. This makes the max BW link config query uniform across mode validation and TBT BW calculation, and allows unexporting the intel_dp_max_link_rate()/intel_dp_max_lane_count() helpers. v2: Use the max BW link configuration, instead of the max link limits. Reviewed-by: Luca Coelho Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-15-imre.deak@intel.com --- drivers/gpu/drm/i915/display/intel_dp.c | 10 ++++++---- drivers/gpu/drm/i915/display/intel_dp.h | 2 -- drivers/gpu/drm/i915/display/intel_dp_mst.c | 16 ++++++++++++---- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index efa2cd27ac92..ea4facb68da6 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -354,7 +354,7 @@ static int intel_dp_get_max_common_lane_count(struct intel_dp *intel_dp) return min3(source_max, sink_max, lane_max); } -int intel_dp_max_lane_count(struct intel_dp *intel_dp) +static int intel_dp_max_lane_count(struct intel_dp *intel_dp) { struct intel_dp_link_caps *link_caps = intel_dp->link.caps; struct intel_dp_link_config max_link_limits; @@ -1331,6 +1331,7 @@ intel_dp_mode_valid_format(struct intel_connector *connector, struct intel_dp *intel_dp = intel_attached_dp(connector); enum intel_output_format output_format; int max_rate, mode_rate, max_lanes, max_link_clock; + struct intel_dp_link_config max_bw_config; u16 dsc_max_compressed_bpp = 0; enum drm_mode_status status; bool dsc = false; @@ -1343,8 +1344,9 @@ intel_dp_mode_valid_format(struct intel_connector *connector, output_format = intel_dp_output_format(connector, sink_format); - max_link_clock = intel_dp_max_link_rate(intel_dp); - max_lanes = intel_dp_max_lane_count(intel_dp); + intel_dp_link_caps_get_max_bw_config(intel_dp->link.caps, &max_bw_config); + max_link_clock = max_bw_config.rate; + max_lanes = max_bw_config.lane_count; max_rate = intel_dp_max_link_data_rate(intel_dp, max_link_clock, max_lanes); @@ -1538,7 +1540,7 @@ static void intel_dp_print_rates(struct intel_dp *intel_dp) intel_dp_link_caps_print_common_rates(intel_dp->link.caps); } -int +static int intel_dp_max_link_rate(struct intel_dp *intel_dp) { struct intel_dp_link_caps *link_caps = intel_dp->link.caps; diff --git a/drivers/gpu/drm/i915/display/intel_dp.h b/drivers/gpu/drm/i915/display/intel_dp.h index 13872b8c4975..9564369ea485 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.h +++ b/drivers/gpu/drm/i915/display/intel_dp.h @@ -111,8 +111,6 @@ void intel_dp_mst_suspend(struct intel_display *display); void intel_dp_mst_resume(struct intel_display *display); int intel_dp_rate_limit_len(const int *rates, int len, int max_rate); int intel_dp_max_source_lane_count(struct intel_digital_port *dig_port); -int intel_dp_max_link_rate(struct intel_dp *intel_dp); -int intel_dp_max_lane_count(struct intel_dp *intel_dp); int intel_dp_config_required_rate(const struct intel_crtc_state *crtc_state); int intel_dp_rate_select(struct intel_dp *intel_dp, int rate); int intel_dp_rate_index(const int *rates, int len, int rate); diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.c b/drivers/gpu/drm/i915/display/intel_dp_mst.c index ecc90e8faee1..e113c9e60e67 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_mst.c +++ b/drivers/gpu/drm/i915/display/intel_dp_mst.c @@ -47,6 +47,7 @@ #include "intel_display_wa.h" #include "intel_dp.h" #include "intel_dp_hdcp.h" +#include "intel_dp_link_caps.h" #include "intel_dp_link_training.h" #include "intel_dp_mst.h" #include "intel_dp_test.h" @@ -1476,6 +1477,7 @@ mst_connector_mode_valid_ctx(struct drm_connector *_connector, unsigned long bw_overhead_flags = DRM_DP_BW_OVERHEAD_MST | DRM_DP_BW_OVERHEAD_SSC_REF_CLK; int min_link_bpp_x16 = fxp_q4_from_int(18); + struct intel_dp_link_config max_bw_config; static bool supports_dsc; int ret; bool dsc = false; @@ -1508,8 +1510,9 @@ mst_connector_mode_valid_ctx(struct drm_connector *_connector, min_link_bpp_x16 = intel_dp_compute_min_compressed_bpp_x16(connector, INTEL_OUTPUT_FORMAT_RGB); - max_link_clock = intel_dp_max_link_rate(intel_dp); - max_lanes = intel_dp_max_lane_count(intel_dp); + intel_dp_link_caps_get_max_bw_config(intel_dp->link.caps, &max_bw_config); + max_link_clock = max_bw_config.rate; + max_lanes = max_bw_config.lane_count; max_rate = intel_dp_max_link_data_rate(intel_dp, max_link_clock, max_lanes); @@ -2135,14 +2138,19 @@ bool intel_dp_mst_crtc_needs_modeset(struct intel_atomic_state *state, */ void intel_dp_mst_prepare_probe(struct intel_dp *intel_dp) { - int link_rate = intel_dp_max_link_rate(intel_dp); - int lane_count = intel_dp_max_lane_count(intel_dp); + struct intel_dp_link_config max_bw_config; + int link_rate; + int lane_count; u8 rate_select; u8 link_bw; if (intel_dp->link.active) return; + intel_dp_link_caps_get_max_bw_config(intel_dp->link.caps, &max_bw_config); + link_rate = max_bw_config.rate; + lane_count = max_bw_config.lane_count; + if (intel_mst_probed_link_params_valid(intel_dp, link_rate, lane_count)) return; From 9fe6cae318e44c9a34454aa3337b8124e1cfa33a Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:44 +0300 Subject: [PATCH 27/93] drm/i915/dp_tunnel: Query max BW config via link_caps for BW computation Query the maximum link BW configuration via the link caps interface to compute the available TBT bandwidth. Unlike the max common link params used so far for this, the max BW config also accounts for any forced link parameters. This makes the max BW link config query uniform across mode validation and TBT BW calculation, and allows unexporting the intel_dp_link_caps_max_common_lane_count() helper. v2: Use the max BW link configuration, instead of the max link limits. Reviewed-by: Luca Coelho Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-16-imre.deak@intel.com --- drivers/gpu/drm/i915/display/intel_dp_link_caps.c | 2 +- drivers/gpu/drm/i915/display/intel_dp_link_caps.h | 1 - drivers/gpu/drm/i915/display/intel_dp_tunnel.c | 8 +++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index c209c8a93523..8dfb7f4f703b 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -290,7 +290,7 @@ void intel_dp_link_caps_print_common_rates(struct intel_dp_link_caps *link_caps) drm_dbg_kms(display->drm, "common rates: %s\n", seq_buf_str(&s)); } -int intel_dp_link_caps_max_common_lane_count(struct intel_dp_link_caps *link_caps) +static int intel_dp_link_caps_max_common_lane_count(struct intel_dp_link_caps *link_caps) { return link_caps->max_lane_count; } diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.h b/drivers/gpu/drm/i915/display/intel_dp_link_caps.h index 667bd70b2396..07002d745d38 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.h +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.h @@ -125,7 +125,6 @@ int intel_dp_common_rate(struct intel_dp_link_caps *link_caps, int index); int intel_dp_link_caps_common_rate_idx(struct intel_dp_link_caps *link_caps, int rate); int intel_dp_max_common_rate(struct intel_dp_link_caps *link_caps); int intel_dp_link_caps_num_common_rates(struct intel_dp_link_caps *link_caps); -int intel_dp_link_caps_max_common_lane_count(struct intel_dp_link_caps *link_caps); void intel_dp_link_caps_print_common_rates(struct intel_dp_link_caps *link_caps); diff --git a/drivers/gpu/drm/i915/display/intel_dp_tunnel.c b/drivers/gpu/drm/i915/display/intel_dp_tunnel.c index 76e9753766b9..49fa4c9699b6 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_tunnel.c +++ b/drivers/gpu/drm/i915/display/intel_dp_tunnel.c @@ -58,10 +58,12 @@ static int kbytes_to_mbits(int kbytes) static int get_current_link_bw(struct intel_dp *intel_dp) { struct intel_dp_link_caps *link_caps = intel_dp->link.caps; - int rate = intel_dp_max_common_rate(link_caps); - int lane_count = intel_dp_link_caps_max_common_lane_count(link_caps); + struct intel_dp_link_config max_bw_config; - return intel_dp_max_link_data_rate(intel_dp, rate, lane_count); + intel_dp_link_caps_get_max_bw_config(link_caps, &max_bw_config); + + return intel_dp_max_link_data_rate(intel_dp, max_bw_config.rate, + max_bw_config.lane_count); } static int __update_tunnel_state(struct intel_dp *intel_dp, bool force_sink_update) From bfc7a8fff313e15ddda69d62535eaf3413f57ee4 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:45 +0300 Subject: [PATCH 28/93] drm/i915/dp_test: Use link caps for compliance link configs Use the link caps configuration mask when applying DP compliance test link parameters during state computation. Preserve the legacy behavior of falling back to all configurations with the requested lane count if the requested rate and lane count pair is not allowed. In case no valid configuration is found fail the modeset. v2: - Rebase on changes using a filter object instead of a mask of configuration indices. - Rebase on changes using an iteration object. Reviewed-by: Luca Coelho Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-17-imre.deak@intel.com --- drivers/gpu/drm/i915/display/intel_dp.c | 3 +- drivers/gpu/drm/i915/display/intel_dp_test.c | 96 +++++++++++++++++++- drivers/gpu/drm/i915/display/intel_dp_test.h | 3 +- 3 files changed, 99 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index ea4facb68da6..08f2cc038be0 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -2769,7 +2769,8 @@ intel_dp_compute_config_limits(struct intel_dp *intel_dp, limits->link_config_filter = new_filter; } - intel_dp_test_compute_config(intel_dp, crtc_state, limits); + if (!intel_dp_test_compute_config(connector, crtc_state, limits)) + return false; return intel_dp_compute_config_link_bpp_limits(connector, crtc_state, diff --git a/drivers/gpu/drm/i915/display/intel_dp_test.c b/drivers/gpu/drm/i915/display/intel_dp_test.c index da7632536dac..ec7fa690910b 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_test.c +++ b/drivers/gpu/drm/i915/display/intel_dp_test.c @@ -28,11 +28,92 @@ void intel_dp_test_reset(struct intel_dp *intel_dp) memset(&intel_dp->compliance, 0, sizeof(intel_dp->compliance)); } +static bool set_filter_for_lane_count(struct intel_connector *connector, + struct intel_dp_link_caps *link_caps, + int lane_count, + struct link_config_limits *limits) +{ + struct intel_dp_link_config link_config; + struct intel_dp_link_caps_order order = + intel_dp_link_caps_connector_compute_order(connector); + struct intel_dp_link_caps_filter new_filter = INTEL_DP_LINK_CAPS_FILTER_NONE; + struct intel_dp_link_caps_iter iter; + bool found = false; + + intel_dp_link_caps_iter_start(&iter, link_caps, order, limits->link_config_filter); + for_each_dp_link_config(&iter, &link_config) { + if (link_config.lane_count != lane_count) + continue; + + intel_dp_link_caps_filter_add(link_caps, &new_filter, &link_config); + found = true; + } + intel_dp_link_caps_iter_end(&iter); + + if (!found) + return false; + + limits->link_config_filter = new_filter; + + return true; +} + +static bool set_filter_for_link_config(struct intel_connector *connector, + struct intel_dp_link_caps *link_caps, + const struct intel_dp_link_config *link_params, + struct link_config_limits *limits) +{ + struct intel_dp_link_caps_filter new_filter = INTEL_DP_LINK_CAPS_FILTER_NONE; + + if (!intel_dp_link_caps_filter_add(link_caps, &new_filter, link_params)) + return false; + + limits->link_config_filter = new_filter; + + return true; +} + +static bool set_filter_for_link_params(struct intel_connector *connector, + int link_rate, int lane_count, + struct link_config_limits *limits) +{ + struct intel_dp *intel_dp = intel_attached_dp(connector); + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + struct intel_dp_link_caps *link_caps = intel_dp->link.caps; + struct intel_display *display = to_intel_display(intel_dp); + struct intel_dp_link_config requested_config; + + requested_config.rate = link_rate; + requested_config.lane_count = lane_count; + + if (set_filter_for_link_config(connector, link_caps, &requested_config, limits)) + return true; + + /* + * Preserve the legacy behavior: if the requested (rate, lane_count) + * combination is not an allowed config, fall back to all configs + * matching the requested lane count. + * + * TODO: Recheck whether this behavior is actually correct. + */ + if (set_filter_for_lane_count(connector, link_caps, lane_count, limits)) + return true; + + drm_dbg_kms(display->drm, + "[ENCODER:%d:%s] Invalid autotest link config parameters: %dx%d\n", + encoder->base.base.id, encoder->base.name, + requested_config.lane_count, + requested_config.rate); + + return false; +} + /* Adjust link config limits based on compliance test requests. */ -void intel_dp_test_compute_config(struct intel_dp *intel_dp, +bool intel_dp_test_compute_config(struct intel_connector *connector, struct intel_crtc_state *pipe_config, struct link_config_limits *limits) { + struct intel_dp *intel_dp = intel_attached_dp(connector); struct intel_dp_link_caps *link_caps = intel_dp->link.caps; struct intel_display *display = to_intel_display(intel_dp); @@ -51,6 +132,11 @@ void intel_dp_test_compute_config(struct intel_dp *intel_dp, if (intel_dp->compliance.test_type == DP_TEST_LINK_TRAINING) { int index; + /* + * TODO: Remove the following min/max link limit setup + * after converting to use the link configuration filter + * instead in limits. + */ /* Validate the compliance test data since max values * might have changed due to link train fallback. */ @@ -65,7 +151,15 @@ void intel_dp_test_compute_config(struct intel_dp *intel_dp, limits->min_lane_count = intel_dp->compliance.test_lane_count; limits->max_lane_count = intel_dp->compliance.test_lane_count; } + + if (!set_filter_for_link_params(connector, + intel_dp->compliance.test_link_rate, + intel_dp->compliance.test_lane_count, + limits)) + return false; } + + return true; } /* Compliance test status bits */ diff --git a/drivers/gpu/drm/i915/display/intel_dp_test.h b/drivers/gpu/drm/i915/display/intel_dp_test.h index dcc167e4c7f6..a08f37a63dc9 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_test.h +++ b/drivers/gpu/drm/i915/display/intel_dp_test.h @@ -6,6 +6,7 @@ #include +struct intel_connector; struct intel_crtc_state; struct intel_display; struct intel_dp; @@ -13,7 +14,7 @@ struct link_config_limits; void intel_dp_test_reset(struct intel_dp *intel_dp); void intel_dp_test_request(struct intel_dp *intel_dp); -void intel_dp_test_compute_config(struct intel_dp *intel_dp, +bool intel_dp_test_compute_config(struct intel_connector *connector, struct intel_crtc_state *pipe_config, struct link_config_limits *limits); bool intel_dp_test_phy(struct intel_dp *intel_dp); From 7266df62ed0a7087120e69a541905fb91656c97a Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:46 +0300 Subject: [PATCH 29/93] drm/i915/dp: Iterate configurations via link_caps for SST non-DSC Use the link caps configuration iterator for DP SST link configuration computation for non-DSC mode. This is a step towards unifying configuration selection and iteration across connector types and between compute and fallback paths. The iteration preserves the DP SST connector rate/lane ordering used by the current code. This also allows removing the now unused common rate count helper. v2: - Rebase on changes using a filter object instead of a mask of configuration indices. - Rebase on changes using an iteration object. Reviewed-by: Luca Coelho Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-18-imre.deak@intel.com --- drivers/gpu/drm/i915/display/intel_dp.c | 49 ++++++++++++------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index 08f2cc038be0..163b1fbad0de 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -1754,43 +1754,42 @@ intel_dp_compute_link_config_wide(struct intel_dp *intel_dp, const struct drm_connector_state *conn_state, const struct link_config_limits *limits) { + struct intel_connector *connector = to_intel_connector(conn_state->connector); + int bpp, clock = intel_dp_mode_clock(pipe_config, conn_state); struct intel_dp_link_caps *link_caps = intel_dp->link.caps; - int bpp, i, lane_count, clock = intel_dp_mode_clock(pipe_config, conn_state); - int link_rate, link_avail; + struct intel_dp_link_caps_order order = + intel_dp_link_caps_connector_compute_order(connector); + int link_avail; for (bpp = fxp_q4_to_int(limits->link.max_bpp_x16); bpp >= fxp_q4_to_int(limits->link.min_bpp_x16); bpp -= 2 * 3) { int link_bpp_x16 = intel_dp_output_format_link_bpp_x16(pipe_config->output_format, bpp); + struct intel_dp_link_config link_config; + struct intel_dp_link_caps_iter iter; - for (i = 0; i < intel_dp_link_caps_num_common_rates(intel_dp->link.caps); i++) { - link_rate = intel_dp_common_rate(link_caps, i); - if (link_rate < limits->min_rate || - link_rate > limits->max_rate) - continue; - - for (lane_count = limits->min_lane_count; - lane_count <= limits->max_lane_count; - lane_count <<= 1) { - const struct drm_display_mode *adjusted_mode = + intel_dp_link_caps_iter_start(&iter, link_caps, order, limits->link_config_filter); + for_each_dp_link_config(&iter, &link_config) { + const struct drm_display_mode *adjusted_mode = &pipe_config->hw.adjusted_mode; - int mode_rate = - intel_dp_link_required(link_rate, lane_count, - clock, adjusted_mode->hdisplay, - link_bpp_x16, 0); + int mode_rate; - link_avail = intel_dp_max_link_data_rate(intel_dp, - link_rate, - lane_count); + mode_rate = intel_dp_link_required(link_config.rate, + link_config.lane_count, + clock, adjusted_mode->hdisplay, + link_bpp_x16, 0); - if (mode_rate <= link_avail) { - pipe_config->lane_count = lane_count; - pipe_config->pipe_bpp = bpp; - pipe_config->port_clock = link_rate; + link_avail = intel_dp_max_link_data_rate(intel_dp, + link_config.rate, + link_config.lane_count); - return 0; - } + if (mode_rate <= link_avail) { + pipe_config->lane_count = link_config.lane_count; + pipe_config->pipe_bpp = bpp; + pipe_config->port_clock = link_config.rate; + + return 0; } } } From 4f104fc10a461684bbc72d51ffa56707749dc6fd Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:47 +0300 Subject: [PATCH 30/93] drm/i915/dp: Iterate configurations via link_caps for SST DSC Use the link caps configuration iterator for DP SST link configuration computation for DSC mode. This is a step towards unifying configuration selection and iteration across connector types and between compute and fallback paths. The iteration preserves the DP SST connector rate/lane ordering used by the current code. This also allows removing the now unused common rate count helper. v2: - Rebase on changes using a filter object instead of a mask of configuration indices. - Rebase on changes using an iteration object. Reviewed-by: Luca Coelho Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-19-imre.deak@intel.com --- drivers/gpu/drm/i915/display/intel_dp.c | 92 +++++++++---------- .../gpu/drm/i915/display/intel_dp_link_caps.c | 5 - .../gpu/drm/i915/display/intel_dp_link_caps.h | 1 - 3 files changed, 44 insertions(+), 54 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index 163b1fbad0de..d9c31be6bab7 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -1988,60 +1988,56 @@ static int dsc_compute_link_config(struct intel_dp *intel_dp, const struct link_config_limits *limits, int dsc_bpp_x16) { - struct intel_dp_link_caps *link_caps = intel_dp->link.caps; const struct drm_display_mode *adjusted_mode = &pipe_config->hw.adjusted_mode; - int link_rate, lane_count; - int i; + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct intel_dp_link_caps *link_caps = intel_dp->link.caps; + struct intel_dp_link_caps_order order = + intel_dp_link_caps_connector_compute_order(connector); + struct intel_dp_link_config link_config; + struct intel_dp_link_caps_iter iter; - for (i = 0; i < intel_dp_link_caps_num_common_rates(intel_dp->link.caps); i++) { - link_rate = intel_dp_common_rate(link_caps, i); - if (link_rate < limits->min_rate || link_rate > limits->max_rate) - continue; + intel_dp_link_caps_iter_start(&iter, link_caps, order, limits->link_config_filter); + for_each_dp_link_config(&iter, &link_config) { + /* + * FIXME: intel_dp_mtp_tu_compute_config() requires + * ->lane_count and ->port_clock set before we know + * they'll work. If we end up failing altogether, + * they'll remain in crtc state. This shouldn't matter, + * as we'd then bail out from compute config, but it's + * just ugly. + */ + pipe_config->lane_count = link_config.lane_count; + pipe_config->port_clock = link_config.rate; - for (lane_count = limits->min_lane_count; - lane_count <= limits->max_lane_count; - lane_count <<= 1) { + if (drm_dp_is_uhbr_rate(link_config.rate)) { + int ret; - /* - * FIXME: intel_dp_mtp_tu_compute_config() requires - * ->lane_count and ->port_clock set before we know - * they'll work. If we end up failing altogether, - * they'll remain in crtc state. This shouldn't matter, - * as we'd then bail out from compute config, but it's - * just ugly. - */ - pipe_config->lane_count = lane_count; - pipe_config->port_clock = link_rate; + ret = intel_dp_mtp_tu_compute_config(intel_dp, + pipe_config, + conn_state, + dsc_bpp_x16, + dsc_bpp_x16, + 0, true); + if (ret) + continue; + } else { + unsigned long bw_overhead_flags = + pipe_config->fec_enable ? DRM_DP_BW_OVERHEAD_FEC : 0; + int line_slice_count = + intel_dsc_line_slice_count(&pipe_config->dsc.slice_config); - if (drm_dp_is_uhbr_rate(link_rate)) { - int ret; - - ret = intel_dp_mtp_tu_compute_config(intel_dp, - pipe_config, - conn_state, - dsc_bpp_x16, - dsc_bpp_x16, - 0, true); - if (ret) - continue; - } else { - unsigned long bw_overhead_flags = - pipe_config->fec_enable ? DRM_DP_BW_OVERHEAD_FEC : 0; - int line_slice_count = - intel_dsc_line_slice_count(&pipe_config->dsc.slice_config); - - if (!is_bw_sufficient_for_dsc_config(intel_dp, - link_rate, lane_count, - adjusted_mode->crtc_clock, - adjusted_mode->hdisplay, - line_slice_count, - dsc_bpp_x16, - bw_overhead_flags)) - continue; - } - - return 0; + if (!is_bw_sufficient_for_dsc_config(intel_dp, + link_config.rate, + link_config.lane_count, + adjusted_mode->crtc_clock, + adjusted_mode->hdisplay, + line_slice_count, + dsc_bpp_x16, + bw_overhead_flags)) + continue; } + + return 0; } return -EINVAL; diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index 8dfb7f4f703b..4ce35b76afee 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -273,11 +273,6 @@ int intel_dp_max_common_rate(struct intel_dp_link_caps *link_caps) return intel_dp_common_rate(link_caps, link_caps->num_rates - 1); } -int intel_dp_link_caps_num_common_rates(struct intel_dp_link_caps *link_caps) -{ - return link_caps->num_rates; -} - void intel_dp_link_caps_print_common_rates(struct intel_dp_link_caps *link_caps) { struct intel_display *display = to_intel_display(link_caps->dp); diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.h b/drivers/gpu/drm/i915/display/intel_dp_link_caps.h index 07002d745d38..3c7e6c8d1ab1 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.h +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.h @@ -124,7 +124,6 @@ int intel_dp_common_len_rate_limit(struct intel_dp_link_caps *link_caps, int intel_dp_common_rate(struct intel_dp_link_caps *link_caps, int index); int intel_dp_link_caps_common_rate_idx(struct intel_dp_link_caps *link_caps, int rate); int intel_dp_max_common_rate(struct intel_dp_link_caps *link_caps); -int intel_dp_link_caps_num_common_rates(struct intel_dp_link_caps *link_caps); void intel_dp_link_caps_print_common_rates(struct intel_dp_link_caps *link_caps); From 15c441a3e8151fc27e582904fd583214aebb2937 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:48 +0300 Subject: [PATCH 31/93] drm/i915/dp: Use link caps for eDP DSC config selection Use the link caps helper to select the maximum eDP link configuration for DSC computation, instead of using the separate max rate and lane count limits, which may not form a valid configuration after individual configs are disabled by fallback. This is a step towards unifying configuration selection and iteration across connector types and between compute and fallback paths. The state computation should likely consider all allowed configurations, as noted in the code comment; for now keep the existing eDP DSC behavior of selecting the maximum configuration determined by the eDP connector rate / lane config iteration order. Reviewed-by: Luca Coelho Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-20-imre.deak@intel.com --- drivers/gpu/drm/i915/display/intel_dp.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index d9c31be6bab7..47888e682ec7 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -2209,7 +2209,7 @@ static int dsc_compute_compressed_bpp(struct intel_dp *intel_dp, int pipe_bpp) { struct intel_display *display = to_intel_display(intel_dp); - const struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct intel_connector *connector = to_intel_connector(conn_state->connector); int min_bpp_x16, max_bpp_x16, bpp_step_x16; int bpp_x16; int ret; @@ -2221,8 +2221,19 @@ static int dsc_compute_compressed_bpp(struct intel_dp *intel_dp, max_bpp_x16 = align_max_compressed_bpp_x16(connector, pipe_config->output_format, pipe_bpp, max_bpp_x16); if (intel_dp_is_edp(intel_dp)) { - pipe_config->port_clock = limits->max_rate; - pipe_config->lane_count = limits->max_lane_count; + struct intel_dp_link_config max_link_config; + + /* + * FIXME: Clarify why eDP does not use the regular SST BW + * check and instead always uses the maximum link config, + * regardless of intel_dp::use_max_params. Then unify this eDP + * path with the regular DP path. + */ + if (!intel_dp_get_connector_max_link_config(connector, limits, &max_link_config)) + return -EINVAL; + + pipe_config->port_clock = max_link_config.rate; + pipe_config->lane_count = max_link_config.lane_count; pipe_config->dsc.compressed_bpp_x16 = max_bpp_x16; From 58e57526d4299f76c38c828dbf8b91af2850ac10 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:49 +0300 Subject: [PATCH 32/93] drm/i915/dp_mst: Use link caps for non-DSC config selection Use the link caps helper to select the maximum MST link configuration for non-DSC computation, instead of using the separate max rate and lane count limits, which may not form a valid configuration after individual configs are disabled by fallback. This is a step towards unifying configuration selection and iteration across connector types and between compute and fallback paths. In some cases all configurations should be considered, as noted in the code comment; for now keep the existing behavior of selecting the maximum bandwidth configuration as determined by the MST connector's BW config iteration order. Reviewed-by: Luca Coelho Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-21-imre.deak@intel.com --- drivers/gpu/drm/i915/display/intel_dp_mst.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.c b/drivers/gpu/drm/i915/display/intel_dp_mst.c index e113c9e60e67..47b8563f85e4 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_mst.c +++ b/drivers/gpu/drm/i915/display/intel_dp_mst.c @@ -445,8 +445,20 @@ static int mst_stream_compute_link_config(struct intel_dp *intel_dp, struct drm_connector_state *conn_state, const struct link_config_limits *limits) { - crtc_state->lane_count = limits->max_lane_count; - crtc_state->port_clock = limits->max_rate; + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct intel_dp_link_config max_link_config; + + /* + * FIXME: Use a proper iteration over the link configurations, instead + * of using only the max BW config. For instance UHBR rate configs may + * have additional limitations over non-UHBR ones, due to the DSC DPT + * bpp maximum limit. + */ + if (!intel_dp_get_connector_max_link_config(connector, limits, &max_link_config)) + return -EINVAL; + + crtc_state->port_clock = max_link_config.rate; + crtc_state->lane_count = max_link_config.lane_count; /* * FIXME: allocate the BW according to link_bpp, which in the case of From b66fe0e1bc2d444789c4650111cae148207922fd Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:50 +0300 Subject: [PATCH 33/93] drm/i915/dp_mst: Use link caps for MST DSC config selection Use the link caps helper to select the maximum DP MST link configuration for DSC computation, instead of using the separate max rate and lane count limits, which may not form a valid configuration after individual configs are disabled by fallback. Also look up the maximum rate for state computation via the configuration mask when checking the DSC hblank expansion quirk. This is a step towards unifying configuration selection and iteration across connector types and between compute and fallback paths. The state computation should likely consider all allowed configurations, as noted in the code comment; for now keep the existing DP MST DSC behavior of selecting the maximum BW configuration determined by the MST connector BW config iteration order. Reviewed-by: Luca Coelho Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-22-imre.deak@intel.com --- drivers/gpu/drm/i915/display/intel_dp_mst.c | 40 +++++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.c b/drivers/gpu/drm/i915/display/intel_dp_mst.c index 47b8563f85e4..df6e54508e5d 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_mst.c +++ b/drivers/gpu/drm/i915/display/intel_dp_mst.c @@ -477,6 +477,7 @@ static int mst_stream_dsc_compute_link_config(struct intel_dp *intel_dp, { struct intel_display *display = to_intel_display(intel_dp); struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct intel_dp_link_config max_link_config; crtc_state->pipe_bpp = limits->pipe.max_bpp; @@ -484,8 +485,17 @@ static int mst_stream_dsc_compute_link_config(struct intel_dp *intel_dp, "DSC Sink supported compressed min bpp " FXP_Q4_FMT " compressed max bpp " FXP_Q4_FMT "\n", FXP_Q4_ARGS(limits->link.min_bpp_x16), FXP_Q4_ARGS(limits->link.max_bpp_x16)); - crtc_state->lane_count = limits->max_lane_count; - crtc_state->port_clock = limits->max_rate; + /* + * FIXME: Use a proper iteration over the link configurations, instead + * of using only the max BW config. For instance UHBR rate configs may + * have additional limitations over non-UHBR ones, due to the DSC DPT + * bpp maximum limit. + */ + if (!intel_dp_get_connector_max_link_config(connector, limits, &max_link_config)) + return -EINVAL; + + crtc_state->port_clock = max_link_config.rate; + crtc_state->lane_count = max_link_config.lane_count; return intel_dp_mtp_tu_compute_config(intel_dp, crtc_state, conn_state, limits->link.min_bpp_x16, @@ -501,6 +511,20 @@ static int mode_hblank_period_ns(const struct drm_display_mode *mode) mode->crtc_clock); } +static int get_connector_max_rate(const struct intel_connector *connector, + const struct link_config_limits *limits) +{ + struct intel_dp *intel_dp = intel_attached_dp((struct intel_connector *)connector); + struct intel_dp_link_caps *link_caps = intel_dp->link.caps; + struct intel_dp_link_config max_link_config; + + intel_dp_link_caps_get_max_config(link_caps, + INTEL_DP_LINK_CAPS_ORDER_KEY_RATE_LANE, + limits->link_config_filter, &max_link_config); + + return max_link_config.rate; +} + static bool hblank_expansion_quirk_needs_dsc(const struct intel_connector *connector, const struct intel_crtc_state *crtc_state, @@ -511,11 +535,13 @@ hblank_expansion_quirk_needs_dsc(const struct intel_connector *connector, bool is_uhbr_sink = connector->mst.dp && drm_dp_128b132b_supported(connector->mst.dp->dpcd); int hblank_limit = is_uhbr_sink ? 500 : 300; + int max_rate; if (!connector->dp.dsc_hblank_expansion_quirk) return false; - if (is_uhbr_sink && !drm_dp_is_uhbr_rate(limits->max_rate)) + max_rate = get_connector_max_rate(connector, limits); + if (is_uhbr_sink && !drm_dp_is_uhbr_rate(max_rate)) return false; if (mode_hblank_period_ns(adjusted_mode) > hblank_limit) @@ -537,6 +563,7 @@ adjust_limits_for_dsc_hblank_expansion_quirk(struct intel_dp *intel_dp, struct intel_display *display = to_intel_display(connector); const struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); int min_bpp_x16 = limits->link.min_bpp_x16; + int max_rate; if (!hblank_expansion_quirk_needs_dsc(connector, crtc_state, limits)) return true; @@ -563,11 +590,10 @@ adjust_limits_for_dsc_hblank_expansion_quirk(struct intel_dp *intel_dp, return true; } - drm_WARN_ON(display->drm, limits->min_rate != limits->max_rate); - - if (limits->max_rate < 540000) + max_rate = get_connector_max_rate(connector, limits); + if (max_rate < 540000) min_bpp_x16 = fxp_q4_from_int(13); - else if (limits->max_rate < 810000) + else if (max_rate < 810000) min_bpp_x16 = fxp_q4_from_int(10); if (limits->link.min_bpp_x16 >= min_bpp_x16) From 77265110e0d6fd3504f8ba22b2d5fd58fe463fe1 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:51 +0300 Subject: [PATCH 34/93] drm/i915/dp: Remove min/max link config limits Remove the min/max rate and lane count fields from struct link_config_limits after all state computation is converted to use the configuration filter. A simple min/max range cannot fully describe the valid configuration set once individual configurations are disabled (for example by fallback), as it may allow combinations that are not actually valid. The configuration filter, on the other hand, always represents a consistent set of valid configurations. Reviewed-by: Luca Coelho Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-23-imre.deak@intel.com --- drivers/gpu/drm/i915/display/intel_dp.c | 86 ------------------- drivers/gpu/drm/i915/display/intel_dp.h | 6 -- .../gpu/drm/i915/display/intel_dp_link_caps.c | 4 +- .../gpu/drm/i915/display/intel_dp_link_caps.h | 2 - drivers/gpu/drm/i915/display/intel_dp_test.c | 23 ----- 5 files changed, 2 insertions(+), 119 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index 47888e682ec7..a3c4c0973106 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -354,44 +354,6 @@ static int intel_dp_get_max_common_lane_count(struct intel_dp *intel_dp) return min3(source_max, sink_max, lane_max); } -static int intel_dp_max_lane_count(struct intel_dp *intel_dp) -{ - struct intel_dp_link_caps *link_caps = intel_dp->link.caps; - struct intel_dp_link_config max_link_limits; - struct intel_dp_link_config forced_params; - int lane_count; - - intel_dp_link_caps_get_max_limits(link_caps, &max_link_limits); - intel_dp_link_caps_get_forced_params(link_caps, &forced_params); - - if (forced_params.lane_count) - lane_count = forced_params.lane_count; - else - lane_count = max_link_limits.lane_count; - - switch (lane_count) { - case 1: - case 2: - case 4: - return lane_count; - default: - MISSING_CASE(lane_count); - return 1; - } -} - -static int intel_dp_min_lane_count(struct intel_dp *intel_dp) -{ - struct intel_dp_link_config forced_params; - - intel_dp_link_caps_get_forced_params(intel_dp->link.caps, &forced_params); - - if (forced_params.lane_count) - return forced_params.lane_count; - - return 1; -} - int intel_dp_link_bw_overhead(int link_clock, int lane_count, int hdisplay, int dsc_slice_count, int bpp_x16, unsigned long flags) { @@ -1540,39 +1502,6 @@ static void intel_dp_print_rates(struct intel_dp *intel_dp) intel_dp_link_caps_print_common_rates(intel_dp->link.caps); } -static int -intel_dp_max_link_rate(struct intel_dp *intel_dp) -{ - struct intel_dp_link_caps *link_caps = intel_dp->link.caps; - struct intel_dp_link_config max_link_limits; - struct intel_dp_link_config forced_params; - int len; - - intel_dp_link_caps_get_forced_params(link_caps, &forced_params); - - if (forced_params.rate) - return forced_params.rate; - - intel_dp_link_caps_get_max_limits(link_caps, &max_link_limits); - len = intel_dp_common_len_rate_limit(link_caps, max_link_limits.rate); - - return intel_dp_common_rate(link_caps, len - 1); -} - -static int -intel_dp_min_link_rate(struct intel_dp *intel_dp) -{ - struct intel_dp_link_caps *link_caps = intel_dp->link.caps; - struct intel_dp_link_config forced_params; - - intel_dp_link_caps_get_forced_params(intel_dp->link.caps, &forced_params); - - if (forced_params.rate) - return forced_params.rate; - - return intel_dp_common_rate(link_caps, 0); -} - int intel_dp_rate_select(struct intel_dp *intel_dp, int rate) { struct intel_display *display = to_intel_display(intel_dp); @@ -2672,18 +2601,6 @@ intel_dp_compute_config_limits(struct intel_dp *intel_dp, struct intel_connector *connector = to_intel_connector(conn_state->connector); - /* - * TODO: Remove the following min/max rate and lane count setup, once - * all users are converted to use link_config_mask instead. - */ - limits->min_rate = intel_dp_min_link_rate(intel_dp); - limits->max_rate = intel_dp_max_link_rate(intel_dp); - - limits->min_rate = min(limits->min_rate, limits->max_rate); - - limits->min_lane_count = intel_dp_min_lane_count(intel_dp); - limits->max_lane_count = intel_dp_max_lane_count(intel_dp); - limits->link_config_filter = INTEL_DP_LINK_CAPS_FILTER_ALL; limits->pipe.min_bpp = intel_dp_min_bpp(crtc_state->output_format); @@ -2763,9 +2680,6 @@ intel_dp_compute_config_limits(struct intel_dp *intel_dp, * configuration, and typically on older panels these * values correspond to the native resolution of the panel. */ - limits->min_lane_count = limits->max_lane_count; - limits->min_rate = limits->max_rate; - if (!intel_dp_get_connector_max_link_config(connector, limits, &max_config)) return false; diff --git a/drivers/gpu/drm/i915/display/intel_dp.h b/drivers/gpu/drm/i915/display/intel_dp.h index 9564369ea485..0ec519fa1236 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.h +++ b/drivers/gpu/drm/i915/display/intel_dp.h @@ -28,12 +28,6 @@ struct intel_dp_link_config; struct intel_encoder; struct link_config_limits { - /* - * TODO: Remove the following min/max rate and lane count limits - * once all users are converted to use link_config_mask instead. - */ - int min_rate, max_rate; - int min_lane_count, max_lane_count; struct intel_dp_link_caps_filter link_config_filter; struct { /* Uncompressed DSC input or link output bpp in 1 bpp units */ diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index 4ce35b76afee..766dc9a98e05 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -242,8 +242,8 @@ intel_dp_link_caps_connector_fallback_order(bool is_mst) } /* Get length of common rates array potentially limited by max_rate. */ -int intel_dp_common_len_rate_limit(struct intel_dp_link_caps *link_caps, - int max_rate) +static int intel_dp_common_len_rate_limit(struct intel_dp_link_caps *link_caps, + int max_rate) { return intel_dp_rate_limit_len(link_caps->rates, link_caps->num_rates, max_rate); diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.h b/drivers/gpu/drm/i915/display/intel_dp_link_caps.h index 3c7e6c8d1ab1..bb785c15c91f 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.h +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.h @@ -119,8 +119,6 @@ intel_dp_link_caps_connector_compute_order(struct intel_connector *connector); struct intel_dp_link_caps_order intel_dp_link_caps_connector_fallback_order(bool is_mst); -int intel_dp_common_len_rate_limit(struct intel_dp_link_caps *link_caps, - int max_rate); int intel_dp_common_rate(struct intel_dp_link_caps *link_caps, int index); int intel_dp_link_caps_common_rate_idx(struct intel_dp_link_caps *link_caps, int rate); int intel_dp_max_common_rate(struct intel_dp_link_caps *link_caps); diff --git a/drivers/gpu/drm/i915/display/intel_dp_test.c b/drivers/gpu/drm/i915/display/intel_dp_test.c index ec7fa690910b..0551a1ce60d3 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_test.c +++ b/drivers/gpu/drm/i915/display/intel_dp_test.c @@ -114,7 +114,6 @@ bool intel_dp_test_compute_config(struct intel_connector *connector, struct link_config_limits *limits) { struct intel_dp *intel_dp = intel_attached_dp(connector); - struct intel_dp_link_caps *link_caps = intel_dp->link.caps; struct intel_display *display = to_intel_display(intel_dp); /* For DP Compliance we override the computed bpp for the pipe */ @@ -130,28 +129,6 @@ bool intel_dp_test_compute_config(struct intel_connector *connector, /* Use values requested by Compliance Test Request */ if (intel_dp->compliance.test_type == DP_TEST_LINK_TRAINING) { - int index; - - /* - * TODO: Remove the following min/max link limit setup - * after converting to use the link configuration filter - * instead in limits. - */ - /* Validate the compliance test data since max values - * might have changed due to link train fallback. - */ - if (intel_dp_link_params_valid(intel_dp, intel_dp->compliance.test_link_rate, - intel_dp->compliance.test_lane_count)) { - index = intel_dp_link_caps_common_rate_idx(link_caps, - intel_dp->compliance.test_link_rate); - if (index >= 0) { - limits->min_rate = intel_dp->compliance.test_link_rate; - limits->max_rate = intel_dp->compliance.test_link_rate; - } - limits->min_lane_count = intel_dp->compliance.test_lane_count; - limits->max_lane_count = intel_dp->compliance.test_lane_count; - } - if (!set_filter_for_link_params(connector, intel_dp->compliance.test_link_rate, intel_dp->compliance.test_lane_count, From 9cee806ef5ade6fac89434bd6543e8ff366d1b6e Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:52 +0300 Subject: [PATCH 35/93] drm/i915/dp_link_training: Reset the max link limits in the fallback code The target maximum rate/lane count selected by the fallback logic may exceed the current link_caps max_limits' rate/lane count, the latter of which are used as a limit by the lookup functions when filtering allowed configurations. To ensure the fallback search finds all relevant candidates, temporarily reset the link_caps max_limits to the maximum common supported capabilities. After the fallback search completes, set the link_caps max_limits to the configuration selected by the fallback logic, as before, determining the allowed configurations for a subsequent modeset. Reviewed-by: Luca Coelho Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-24-imre.deak@intel.com --- .../drm/i915/display/intel_dp_link_training.c | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_training.c b/drivers/gpu/drm/i915/display/intel_dp_link_training.c index b521dd11b62a..7fdcc299daea 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_training.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_training.c @@ -1960,10 +1960,12 @@ static bool reduce_link_params(struct intel_dp *intel_dp, const struct intel_crt static int intel_dp_get_link_train_fallback_values(struct intel_dp *intel_dp, const struct intel_crtc_state *crtc_state) { + struct intel_display *display = to_intel_display(intel_dp); struct intel_dp_link_caps *link_caps = intel_dp->link.caps; struct intel_dp_link_config max_link_limits; int new_link_rate; int new_lane_count; + int err = -1; if (intel_dp_is_edp(intel_dp) && !intel_dp->use_max_params) { lt_dbg(intel_dp, DP_PHY_DPRX, @@ -1972,14 +1974,32 @@ static int intel_dp_get_link_train_fallback_values(struct intel_dp *intel_dp, return 0; } + /* + * Temporarily reset the max link limit before selecting the fallback + * config. + * + * After fallback, the current logic narrows the allowed configurations + * to the selected config's rate and lane count. That can make a later + * fallback candidate fall outside the current max_limit, so reset it + * before searching. + * + * TODO: Constrain the allowed configurations by only disabling individual + * configurations and remove setting maximum link parameters. + */ + intel_dp_link_caps_get_max_limits(link_caps, &max_link_limits); + intel_dp_link_caps_reset_max_limits(link_caps); + if (!reduce_link_params(intel_dp, crtc_state, &new_link_rate, &new_lane_count)) - return -1; + goto out_restore_max_limits; if (intel_dp_is_edp(intel_dp) && !intel_dp_can_link_train_fallback_for_edp(intel_dp, new_link_rate, new_lane_count)) { lt_dbg(intel_dp, DP_PHY_DPRX, "Retrying Link training for eDP with same parameters\n"); - return 0; + + err = 0; + + goto out_restore_max_limits; } lt_dbg(intel_dp, DP_PHY_DPRX, @@ -1990,10 +2010,19 @@ static int intel_dp_get_link_train_fallback_values(struct intel_dp *intel_dp, max_link_limits.rate = new_link_rate; max_link_limits.lane_count = new_lane_count; - /* TODO: handle an update failure */ - intel_dp_link_caps_set_max_limits(link_caps, &max_link_limits); + err = 0; - return 0; +out_restore_max_limits: + /* + * Shouldn't fail: setting max_limits can only fail if they drop below + * the optionally forced rate/lane-count parameters, but the reduced + * config was chosen to satisfy those constraints. + */ + if (drm_WARN_ON(display->drm, + !intel_dp_link_caps_set_max_limits(link_caps, &max_link_limits))) + err = -1; + + return err; } static bool intel_dp_schedule_fallback_link_training(struct intel_atomic_state *state, From 86e664219a06bcf780f36de5a80857b8b752d136 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:53 +0300 Subject: [PATCH 36/93] drm/i915/dp_link_training: Use config iterator for fallback Switch the fallback loop to use the link configuration iterator to select a fallback configuration. This also allows unexporting and removing from the link caps interface all the common link rate query helpers and the helpers that accept or return a link configuration index. Reviewed-by: Luca Coelho Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-25-imre.deak@intel.com --- .../gpu/drm/i915/display/intel_dp_link_caps.c | 45 +----- .../gpu/drm/i915/display/intel_dp_link_caps.h | 9 -- .../drm/i915/display/intel_dp_link_training.c | 143 +++++------------- 3 files changed, 39 insertions(+), 158 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index 766dc9a98e05..c947e6511fbc 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -249,7 +249,7 @@ static int intel_dp_common_len_rate_limit(struct intel_dp_link_caps *link_caps, link_caps->num_rates, max_rate); } -int intel_dp_common_rate(struct intel_dp_link_caps *link_caps, int index) +static int intel_dp_common_rate(struct intel_dp_link_caps *link_caps, int index) { struct intel_display *display = to_intel_display(link_caps->dp); @@ -260,15 +260,8 @@ int intel_dp_common_rate(struct intel_dp_link_caps *link_caps, int index) return link_caps->rates[index]; } -int intel_dp_link_caps_common_rate_idx(struct intel_dp_link_caps *link_caps, int rate) -{ - return intel_dp_rate_index(link_caps->rates, - link_caps->num_rates, - rate); -} - /* Theoretical max between source and sink */ -int intel_dp_max_common_rate(struct intel_dp_link_caps *link_caps) +static int intel_dp_max_common_rate(struct intel_dp_link_caps *link_caps) { return intel_dp_common_rate(link_caps, link_caps->num_rates - 1); } @@ -870,40 +863,6 @@ bool intel_dp_link_caps_update(struct intel_dp_link_caps *link_caps, return link_params_changed; } -void intel_dp_link_config_get(struct intel_dp_link_caps *link_caps, - int idx, int *link_rate, int *lane_count) -{ - struct intel_display *display = to_intel_display(link_caps->dp); - struct intel_dp_link_config config; - - if (drm_WARN_ON(display->drm, idx < 0 || idx >= link_caps->num_configs)) - idx = 0; - - to_intel_dp_link_config(link_caps, idx, &config); - - *link_rate = config.rate; - *lane_count = config.lane_count; -} - -int intel_dp_link_config_index(struct intel_dp_link_caps *link_caps, - int link_rate, int lane_count) -{ - int link_rate_idx = intel_dp_rate_index(link_caps->rates, link_caps->num_rates, - link_rate); - int lane_count_exp = ilog2(lane_count); - int i; - - for (i = 0; i < link_caps->num_configs; i++) { - const struct intel_dp_link_config_entry *lce = &link_caps->configs[i]; - - if (lce->lane_count_exp == lane_count_exp && - lce->link_rate_idx == link_rate_idx) - return i; - } - - return -1; -} - /** * intel_dp_link_caps_reset - reset link capability restrictions * @link_caps: link capabilities state diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.h b/drivers/gpu/drm/i915/display/intel_dp_link_caps.h index bb785c15c91f..5c0d66006214 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.h +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.h @@ -119,20 +119,11 @@ intel_dp_link_caps_connector_compute_order(struct intel_connector *connector); struct intel_dp_link_caps_order intel_dp_link_caps_connector_fallback_order(bool is_mst); -int intel_dp_common_rate(struct intel_dp_link_caps *link_caps, int index); -int intel_dp_link_caps_common_rate_idx(struct intel_dp_link_caps *link_caps, int rate); -int intel_dp_max_common_rate(struct intel_dp_link_caps *link_caps); - void intel_dp_link_caps_print_common_rates(struct intel_dp_link_caps *link_caps); void intel_dp_link_caps_get_forced_params(struct intel_dp_link_caps *link_caps, struct intel_dp_link_config *forced_params); -int intel_dp_link_config_index(struct intel_dp_link_caps *link_caps, - int link_rate, int lane_count); -void intel_dp_link_config_get(struct intel_dp_link_caps *link_caps, - int idx, int *link_rate, int *lane_count); - bool intel_dp_link_caps_filter_add(struct intel_dp_link_caps *link_caps, struct intel_dp_link_caps_filter *filter, const struct intel_dp_link_config *config); diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_training.c b/drivers/gpu/drm/i915/display/intel_dp_link_training.c index 7fdcc299daea..1c12503908d8 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_training.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_training.c @@ -1846,115 +1846,46 @@ static bool intel_dp_can_link_train_fallback_for_edp(struct intel_dp *intel_dp, return true; } -static bool reduce_link_params_in_bw_order(struct intel_dp *intel_dp, - const struct intel_crtc_state *crtc_state, - int *new_link_rate, int *new_lane_count) -{ - struct intel_dp_link_caps *link_caps = intel_dp->link.caps; - struct intel_dp_link_config forced_params; - int link_rate; - int lane_count; - int i; - - intel_dp_link_caps_get_forced_params(link_caps, &forced_params); - - i = intel_dp_link_config_index(intel_dp->link.caps, - crtc_state->port_clock, crtc_state->lane_count); - for (i--; i >= 0; i--) { - intel_dp_link_config_get(intel_dp->link.caps, i, &link_rate, &lane_count); - - if ((forced_params.rate && - forced_params.rate != link_rate) || - (forced_params.lane_count && - forced_params.lane_count != lane_count)) - continue; - - break; - } - - if (i < 0) - return false; - - *new_link_rate = link_rate; - *new_lane_count = lane_count; - - return true; -} - -static int reduce_link_rate(struct intel_dp *intel_dp, int current_rate) -{ - struct intel_dp_link_caps *link_caps = intel_dp->link.caps; - struct intel_dp_link_config forced_params; - int rate_index; - int new_rate; - - intel_dp_link_caps_get_forced_params(link_caps, &forced_params); - if (forced_params.rate) - return -1; - - rate_index = intel_dp_link_caps_common_rate_idx(link_caps, - current_rate); - - if (rate_index <= 0) - return -1; - - new_rate = intel_dp_common_rate(link_caps, rate_index - 1); - - /* TODO: Make switching from UHBR to non-UHBR rates work. */ - if (drm_dp_is_uhbr_rate(current_rate) != drm_dp_is_uhbr_rate(new_rate)) - return -1; - - return new_rate; -} - -static int reduce_lane_count(struct intel_dp *intel_dp, int current_lane_count) -{ - struct intel_dp_link_config forced_params; - - intel_dp_link_caps_get_forced_params(intel_dp->link.caps, &forced_params); - if (forced_params.lane_count) - return -1; - - if (current_lane_count == 1) - return -1; - - return current_lane_count >> 1; -} - -static bool reduce_link_params_in_rate_lane_order(struct intel_dp *intel_dp, - const struct intel_crtc_state *crtc_state, - int *new_link_rate, int *new_lane_count) -{ - struct intel_dp_link_caps *link_caps = intel_dp->link.caps; - int link_rate; - int lane_count; - - lane_count = crtc_state->lane_count; - link_rate = reduce_link_rate(intel_dp, crtc_state->port_clock); - if (link_rate < 0) { - lane_count = reduce_lane_count(intel_dp, crtc_state->lane_count); - link_rate = intel_dp_max_common_rate(link_caps); - } - - if (lane_count < 0) - return false; - - *new_link_rate = link_rate; - *new_lane_count = lane_count; - - return true; -} - static bool reduce_link_params(struct intel_dp *intel_dp, const struct intel_crtc_state *crtc_state, int *new_link_rate, int *new_lane_count) { - /* TODO: Use the same fallback logic on SST as on MST. */ - if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP_MST)) - return reduce_link_params_in_bw_order(intel_dp, crtc_state, - new_link_rate, new_lane_count); - else - return reduce_link_params_in_rate_lane_order(intel_dp, crtc_state, - new_link_rate, new_lane_count); + struct intel_dp_link_caps *link_caps = intel_dp->link.caps; + bool is_mst = intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP_MST); + struct intel_dp_link_caps_order order = + intel_dp_link_caps_connector_fallback_order(is_mst); + struct intel_dp_link_config old_config = { + .rate = crtc_state->port_clock, + .lane_count = crtc_state->lane_count, + }; + struct intel_dp_link_caps_iter iter; + struct intel_dp_link_config config; + bool old_found = false; + bool new_found = false; + + intel_dp_link_caps_iter_start(&iter, link_caps, order, INTEL_DP_LINK_CAPS_FILTER_ALL); + for_each_dp_link_config(&iter, &config) { + if (!old_found) { + if (config.rate == old_config.rate && + config.lane_count == old_config.lane_count) + old_found = true; + + continue; + } + + if (!is_mst && + drm_dp_is_uhbr_rate(config.rate) != + drm_dp_is_uhbr_rate(old_config.rate)) + continue; + + *new_link_rate = config.rate; + *new_lane_count = config.lane_count; + new_found = true; + + break; + } + intel_dp_link_caps_iter_end(&iter); + + return new_found; } static int intel_dp_get_link_train_fallback_values(struct intel_dp *intel_dp, From 55b131b3d00913cc809981cf1c7301956bf757ee Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:54 +0300 Subject: [PATCH 37/93] drm/i915/dp_link_training: Disable failed config during fallback Disable the link configuration that failed training when selecting fallback parameters. Fallback still selects the next configuration using the existing fallback order, but now also removes the failed configuration from the allowed set. Functionally, this only affects the case where an MST <-> SST mode switch occurs on the same root connector: previously, a configuration that failed training in one mode could be reused in the other mode due to the differing config iteration orders. The current fallback logic also sets a temporary maximum link limit across the allowed configurations to constrain subsequent modesets. This legacy behavior is preserved for now; it will be removed once the fallback logic relies solely on the individually disabled configurations to restrict the allowed set. Reviewed-by: Luca Coelho Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-26-imre.deak@intel.com --- .../gpu/drm/i915/display/intel_dp_link_caps.c | 55 +++++++++++++++++++ .../gpu/drm/i915/display/intel_dp_link_caps.h | 3 + .../drm/i915/display/intel_dp_link_training.c | 19 +++++++ 3 files changed, 77 insertions(+) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index c947e6511fbc..76b7c0fc9011 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -596,6 +596,21 @@ bool intel_dp_link_caps_filter_add(struct intel_dp_link_caps *link_caps, return true; } +static bool intel_dp_link_caps_filter_remove(struct intel_dp_link_caps *link_caps, + struct intel_dp_link_caps_filter *filter, + const struct intel_dp_link_config *config) +{ + int idx; + + idx = find_config_idx(link_caps, get_allowed_config_filter(link_caps), config); + if (idx < 0) + return false; + + filter->config_mask &= ~BIT(idx); + + return true; +} + static void set_max_link_limits(struct intel_dp_link_caps *link_caps, const struct intel_dp_link_config *max_link_limits) { @@ -618,6 +633,46 @@ static void reset_max_link_limits_reenable_all(struct intel_dp_link_caps *link_c reset_max_link_limits(link_caps); } +/** + * intel_dp_link_caps_disable_config - disable a configuration + * @link_caps: link capabilities state + * @config: configuration to disable + * + * Disable the configuration identified by @config. This removes the + * configuration from the set of allowed configurations. The disabling + * shouldn't leave the remaining configuration set empty. + * + * The configuration remains disallowed until intel_dp_link_caps() with + * reset=%true or changed sink capabilities is called, or + * intel_dp_link_caps_reset() is called. Each of these happens after a + * new sink is connected or the currently connected sink changes its + * capabilities. + * + * Return: + * - %true if @config was valid and the derived state was updated. + * - %false if @config was invalid or the remaining configuration set + * would remain empty. + */ +bool intel_dp_link_caps_disable_config(struct intel_dp_link_caps *link_caps, + const struct intel_dp_link_config *config) +{ + struct intel_dp_link_caps_filter enabled_configs = link_caps->enabled_configs; + struct intel_dp_link_config forced_params; + + if (!intel_dp_link_caps_filter_remove(link_caps, &enabled_configs, config)) + return false; + + intel_dp_link_caps_get_forced_params(link_caps, &forced_params); + + if (!calc_allowed_config_filter(link_caps, enabled_configs, + &link_caps->max_limits, &forced_params).config_mask) + return false; + + link_caps->enabled_configs = enabled_configs; + + return true; +} + /** * intel_dp_link_caps_get_max_limits - get the current maximum link limits * @link_caps: link capabilities state diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.h b/drivers/gpu/drm/i915/display/intel_dp_link_caps.h index 5c0d66006214..56c585eb5a13 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.h +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.h @@ -136,6 +136,9 @@ bool intel_dp_link_caps_get_max_config(struct intel_dp_link_caps *link_caps, void intel_dp_link_caps_get_max_bw_config(struct intel_dp_link_caps *link_caps, struct intel_dp_link_config *max_config); +bool intel_dp_link_caps_disable_config(struct intel_dp_link_caps *link_caps, + const struct intel_dp_link_config *config); + void intel_dp_link_caps_get_max_limits(struct intel_dp_link_caps *link_caps, struct intel_dp_link_config *max_link_limits); bool intel_dp_link_caps_set_max_limits(struct intel_dp_link_caps *link_caps, diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_training.c b/drivers/gpu/drm/i915/display/intel_dp_link_training.c index 1c12503908d8..a592bfab5ff0 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_training.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_training.c @@ -1894,6 +1894,10 @@ static int intel_dp_get_link_train_fallback_values(struct intel_dp *intel_dp, struct intel_display *display = to_intel_display(intel_dp); struct intel_dp_link_caps *link_caps = intel_dp->link.caps; struct intel_dp_link_config max_link_limits; + struct intel_dp_link_config current_config = { + .rate = crtc_state->port_clock, + .lane_count = crtc_state->lane_count, + }; int new_link_rate; int new_lane_count; int err = -1; @@ -1920,6 +1924,13 @@ static int intel_dp_get_link_train_fallback_values(struct intel_dp *intel_dp, intel_dp_link_caps_get_max_limits(link_caps, &max_link_limits); intel_dp_link_caps_reset_max_limits(link_caps); + /* + * TODO: Make fallback depend only on disabling the current config, + * once max_limit no longer constrains the allowed config set. Then + * disabling the current config will define the allowed configs for + * the subsequent modeset, so there will be no need to select a + * reduced config separately here. + */ if (!reduce_link_params(intel_dp, crtc_state, &new_link_rate, &new_lane_count)) goto out_restore_max_limits; @@ -1933,6 +1944,14 @@ static int intel_dp_get_link_train_fallback_values(struct intel_dp *intel_dp, goto out_restore_max_limits; } + /* + * Shouldn't fail: the current config was enabled, and reducing the + * link parameters should still leave the fallback config allowed. + */ + if (drm_WARN_ON(display->drm, + !intel_dp_link_caps_disable_config(link_caps, ¤t_config))) + return -1; + lt_dbg(intel_dp, DP_PHY_DPRX, "Reducing link parameters from %dx%d to %dx%d\n", crtc_state->lane_count, crtc_state->port_clock, From 3d5fc70709520f14da98ca54f27841dfc8fdcd2e Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Tue, 7 Jul 2026 15:48:46 +0300 Subject: [PATCH 38/93] drm/i915/kunit: Enable KUnit tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add KUnit configuration for i915 and a local .kunitconfig to run the tests. v2: Unchanged. v3: Remove kconfig options for kernel debug. Reviewed-by: Michał Grzelak # v2 Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260707124849.135319-1-imre.deak@intel.com --- drivers/gpu/drm/i915/.kunitconfig | 9 +++++++++ drivers/gpu/drm/i915/Kconfig.debug | 12 ++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 drivers/gpu/drm/i915/.kunitconfig diff --git a/drivers/gpu/drm/i915/.kunitconfig b/drivers/gpu/drm/i915/.kunitconfig new file mode 100644 index 000000000000..1b47fe62b285 --- /dev/null +++ b/drivers/gpu/drm/i915/.kunitconfig @@ -0,0 +1,9 @@ +CONFIG_EXPERT=y +CONFIG_MODULES=y +CONFIG_KUNIT=y +CONFIG_PCI=y +CONFIG_DEBUG_FS=y +CONFIG_MAGIC_SYSRQ=y +CONFIG_DRM=y +CONFIG_DRM_I915=y +CONFIG_DRM_I915_KUNIT_TEST=y diff --git a/drivers/gpu/drm/i915/Kconfig.debug b/drivers/gpu/drm/i915/Kconfig.debug index 52a3a59b4ba2..dc43dcfbadb6 100644 --- a/drivers/gpu/drm/i915/Kconfig.debug +++ b/drivers/gpu/drm/i915/Kconfig.debug @@ -220,6 +220,18 @@ config DRM_I915_SELFTEST_BROKEN If in doubt, say "N". +config DRM_I915_KUNIT_TEST + tristate "KUnit tests for the drm i915 driver" if !KUNIT_ALL_TESTS + depends on DRM_I915 && KUNIT && DEBUG_FS + default KUNIT_ALL_TESTS + help + Choose this option to allow the driver to perform selftests under + the kunit framework + + Recommended for driver developers only. + + If in doubt, say "N". + config DRM_I915_LOW_LEVEL_TRACEPOINTS bool "Enable low level request tracing events" depends on DRM_I915 From d3340e92be326c1d100f68ba7fd840a499b27b93 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:56 +0300 Subject: [PATCH 39/93] drm/i915/kunit: Add DP link test stub MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a Kunit stub test module for DP link test cases. v2: Add missing module license. Reviewed-by: Michał Grzelak Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-28-imre.deak@intel.com --- drivers/gpu/drm/i915/Makefile | 2 + drivers/gpu/drm/i915/display/tests/Makefile | 7 +++ .../i915/display/tests/intel_dp_link_test.c | 47 +++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 drivers/gpu/drm/i915/display/tests/Makefile create mode 100644 drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile index c4de717505d7..a83fa8be0aba 100644 --- a/drivers/gpu/drm/i915/Makefile +++ b/drivers/gpu/drm/i915/Makefile @@ -386,6 +386,8 @@ i915-y += \ i915-$(CONFIG_DRM_I915_DP_TUNNEL) += \ display/intel_dp_tunnel.o +obj-$(CONFIG_DRM_I915_KUNIT_TEST) += display/tests/ + i915-$(CONFIG_DRM_I915_GVT) += \ display/intel_gvt_api.o diff --git a/drivers/gpu/drm/i915/display/tests/Makefile b/drivers/gpu/drm/i915/display/tests/Makefile new file mode 100644 index 000000000000..ad250974160f --- /dev/null +++ b/drivers/gpu/drm/i915/display/tests/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 + +subdir-ccflags-y += -I$(srctree)/drivers/gpu/drm/i915/display/ + +obj-$(CONFIG_DRM_I915_KUNIT_TEST) += i915_display_test.o +i915_display_test-y = \ + intel_dp_link_test.o diff --git a/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c b/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c new file mode 100644 index 000000000000..62e1844605ac --- /dev/null +++ b/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2026 Intel Corporation + */ + +#include + +struct test_ctx { +}; + +static struct kunit_case intel_dp_link_test_cases[] = { + {} +}; + +static struct test_ctx test_ctx; + +static int intel_dp_link_test_init(struct kunit *test) +{ + test->priv = &test_ctx; + + return 0; +} + +static void intel_dp_link_test_exit(struct kunit *test) +{ +} + +static int intel_dp_link_test_suite_init(struct kunit_suite *test_suite) +{ + return 0; +} + +static struct kunit_suite intel_dp_link_test_suite = { + .name = "intel_dp_link", + .suite_init = intel_dp_link_test_suite_init, + .init = intel_dp_link_test_init, + .exit = intel_dp_link_test_exit, + .test_cases = intel_dp_link_test_cases, +}; + +kunit_test_suites(&intel_dp_link_test_suite); + +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); + +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL and additional rights"); +MODULE_DESCRIPTION("Intel DP link KUnit tests"); From 82ae0ce1ffd0bada35ca87e29d5d23fb999d4ab1 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:57 +0300 Subject: [PATCH 40/93] drm/xe/kunit: Add display test config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a separate xe KUnit config for display tests. The existing xe .kunitconfig builds xe statically, which is suitable for non-display xe tests. The display code can only be enabled for xe when xe is built as a module, so add a separate display config with DRM_XE=m and DRM_XE_DISPLAY=y. This can be folded back into the main xe KUnit config once the display code becomes a separate module. Reviewed-by: Michał Grzelak Acked-by: Rodrigo Vivi Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-29-imre.deak@intel.com --- drivers/gpu/drm/xe/.gitignore | 1 + drivers/gpu/drm/xe/.kunitconfig-display | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 drivers/gpu/drm/xe/.kunitconfig-display diff --git a/drivers/gpu/drm/xe/.gitignore b/drivers/gpu/drm/xe/.gitignore index 8778bf132674..6dad8a5a2135 100644 --- a/drivers/gpu/drm/xe/.gitignore +++ b/drivers/gpu/drm/xe/.gitignore @@ -2,3 +2,4 @@ *.hdrtest /generated /xe_gen_wa_oob +!.kunitconfig-display diff --git a/drivers/gpu/drm/xe/.kunitconfig-display b/drivers/gpu/drm/xe/.kunitconfig-display new file mode 100644 index 000000000000..17020aa4ded3 --- /dev/null +++ b/drivers/gpu/drm/xe/.kunitconfig-display @@ -0,0 +1,11 @@ +CONFIG_EXPERT=y +CONFIG_MODULES=y +CONFIG_KUNIT=y +CONFIG_PCI=y +CONFIG_DEBUG_FS=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_MAGIC_SYSRQ=y +CONFIG_DRM=m +CONFIG_DRM_XE=m +CONFIG_DRM_XE_DISPLAY=y +CONFIG_DRM_XE_KUNIT_TEST=m From d5a48b4817810a8278a880845581f8ecf1ccdd73 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Tue, 7 Jul 2026 15:48:47 +0300 Subject: [PATCH 41/93] drm/xe/kunit: Build DP link display tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hook the shared i915 display DP link KUnit tests into the xe display test build. Build the shared display test source from the i915 display test directory when xe display support is enabled. v2: Unchanged. v3: Add SPDX license header. (Michał) Reviewed-by: Michał Grzelak Acked-by: Rodrigo Vivi Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260707124849.135319-2-imre.deak@intel.com --- drivers/gpu/drm/xe/Makefile | 3 +++ drivers/gpu/drm/xe/display/tests/Makefile | 13 +++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 drivers/gpu/drm/xe/display/tests/Makefile diff --git a/drivers/gpu/drm/xe/Makefile b/drivers/gpu/drm/xe/Makefile index e5a04253e73b..3ff58186c767 100644 --- a/drivers/gpu/drm/xe/Makefile +++ b/drivers/gpu/drm/xe/Makefile @@ -373,6 +373,9 @@ xe-$(CONFIG_DRM_XE_DP_TUNNEL) += \ obj-$(CONFIG_DRM_XE) += xe.o obj-$(CONFIG_DRM_XE_KUNIT_TEST) += tests/ +ifeq ($(CONFIG_DRM_XE_DISPLAY),y) + obj-$(CONFIG_DRM_XE_KUNIT_TEST) += display/tests/ +endif # header test hdrtest_find_args := -not -path xe_rtp_helpers.h diff --git a/drivers/gpu/drm/xe/display/tests/Makefile b/drivers/gpu/drm/xe/display/tests/Makefile new file mode 100644 index 000000000000..c71686458072 --- /dev/null +++ b/drivers/gpu/drm/xe/display/tests/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 + +subdir-ccflags-$(CONFIG_DRM_XE_DISPLAY) += \ + -I$(srctree)/drivers/gpu/drm/i915/display/ + +# Rule to build display code shared with i915 +$(obj)/i915-display/tests/%.o: $(srctree)/drivers/gpu/drm/i915/display/tests/%.c FORCE + $(call cmd,force_checksrc) + $(call if_changed_rule,cc_o_c) + +obj-$(CONFIG_DRM_XE_KUNIT_TEST) += xe_display_test.o +xe_display_test-y = \ + i915-display/tests/intel_dp_link_test.o From df152476d00a46bbc28f5adf046e80ee9ad60c2a Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:59 +0300 Subject: [PATCH 42/93] drm/i915/kunit: Setup DP link test context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Initialize a reusable test context for DP link KUnit tests. Sets up minimal device, connector, encoder, and DP structures, and seeds the pseudo-random generator for deterministic test runs. Reviewed-by: Michał Grzelak Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-31-imre.deak@intel.com --- .../i915/display/tests/intel_dp_link_test.c | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c b/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c index 62e1844605ac..aa5358c94839 100644 --- a/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c +++ b/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c @@ -5,7 +5,32 @@ #include +#include +#include +#include +#include + +#include + +#include + +#include "intel_connector.h" +#include "intel_display_core.h" +#include "intel_display_types.h" + struct test_ctx { + struct { + struct intel_display display; + struct device device; + struct __intel_generic_device generic_device; + + struct intel_connector connector; + struct intel_digital_port dig_port; + + struct intel_crtc_state crtc_state; + } dev; + + struct rnd_state rnd; }; static struct kunit_case intel_dp_link_test_cases[] = { @@ -16,6 +41,29 @@ static struct test_ctx test_ctx; static int intel_dp_link_test_init(struct kunit *test) { + struct intel_digital_port *dig_port; + struct intel_encoder *encoder; + struct intel_dp *intel_dp; + + /* Reset the dev state for each test. */ + memset(&test_ctx.dev, 0, sizeof(test_ctx.dev)); + + test_ctx.dev.generic_device.drm.dev = &test_ctx.dev.device; + + test_ctx.dev.display.drm = &test_ctx.dev.generic_device.drm; + test_ctx.dev.generic_device.display = &test_ctx.dev.display; + + encoder = &test_ctx.dev.dig_port.base; + encoder->base.dev = &test_ctx.dev.generic_device.drm; + + dig_port = &test_ctx.dev.dig_port; + dig_port->base.type = INTEL_OUTPUT_DP; + + test_ctx.dev.connector.encoder = encoder; + + intel_dp = &dig_port->dp; + intel_dp->attached_connector = &test_ctx.dev.connector; + test->priv = &test_ctx; return 0; @@ -27,6 +75,8 @@ static void intel_dp_link_test_exit(struct kunit *test) static int intel_dp_link_test_suite_init(struct kunit_suite *test_suite) { + prandom_seed_state(&test_ctx.rnd, 0); + return 0; } From 2eb9982ff179e648675b6c5b345b9ed509fc3361 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:32:00 +0300 Subject: [PATCH 43/93] drm/i915/kunit: Export link training and caps funcs for testing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Export the link caps and link training helpers needed by the DP link KUnit tests. Use test ops tables instead of exporting the helpers directly, avoiding symbol name collisions between the i915 and xe builds of the shared display code. Reviewed-by: Michał Grzelak Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-32-imre.deak@intel.com --- .../gpu/drm/i915/display/intel_dp_link_caps.c | 29 +++++++++++++++ .../gpu/drm/i915/display/intel_dp_link_caps.h | 37 +++++++++++++++++++ .../drm/i915/display/intel_dp_link_training.c | 36 +++++++++++++++++- .../drm/i915/display/intel_dp_link_training.h | 31 ++++++++++++++++ .../i915/display/tests/intel_dp_link_test.c | 17 +++++++++ 5 files changed, 148 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index 76b7c0fc9011..7b6cc6055da8 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -1281,3 +1281,32 @@ void intel_dp_link_caps_cleanup(struct intel_dp_link_caps *link_caps) { kfree(link_caps); } + +#if IS_ENABLED(CONFIG_KUNIT) + +#define __INIT_MEMBER(__name, __fn) \ + .__name = __fn, + +#define INTEL_DP_LINK_CAPS_TEST_OPS_INIT \ + INTEL_DP_LINK_CAPS_TEST_OPS_MEMBERS(__INIT_MEMBER) + +#ifdef I915 + +const struct intel_dp_link_caps_test_ops i915_display_dp_link_caps_test_ops = { + INTEL_DP_LINK_CAPS_TEST_OPS_INIT +}; +EXPORT_SYMBOL(i915_display_dp_link_caps_test_ops); + +#else + +const struct intel_dp_link_caps_test_ops intel_display_dp_link_caps_test_ops = { + INTEL_DP_LINK_CAPS_TEST_OPS_INIT +}; +EXPORT_SYMBOL(intel_display_dp_link_caps_test_ops); + +#endif /* I915 */ + +#undef INTEL_DP_LINK_CAPS_TEST_OPS_INIT +#undef __INIT_MEMBER + +#endif /* CONFIG_KUNIT */ diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.h b/drivers/gpu/drm/i915/display/intel_dp_link_caps.h index 56c585eb5a13..a0a88efb9546 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.h +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.h @@ -155,4 +155,41 @@ void intel_dp_link_caps_debugfs_add(struct intel_connector *connector); struct intel_dp_link_caps *intel_dp_link_caps_init(struct intel_dp *intel_dp); void intel_dp_link_caps_cleanup(struct intel_dp_link_caps *link_caps); +#if IS_ENABLED(CONFIG_KUNIT) + +#define INTEL_DP_LINK_CAPS_TEST_OPS_MEMBERS(__X) \ + __X(connector_compute_order, intel_dp_link_caps_connector_compute_order) \ + __X(connector_fallback_order, intel_dp_link_caps_connector_fallback_order) \ + __X(iter_start, intel_dp_link_caps_iter_start) \ + __X(iter_end, intel_dp_link_caps_iter_end) \ + __X(set_max_limits, intel_dp_link_caps_set_max_limits) \ + __X(get_max_limits, intel_dp_link_caps_get_max_limits) \ + __X(get_max_bw_config, intel_dp_link_caps_get_max_bw_config) \ + __X(reset_max_limits, intel_dp_link_caps_reset_max_limits) \ + __X(disable_config, intel_dp_link_caps_disable_config) \ + __X(update, intel_dp_link_caps_update) \ + __X(init, intel_dp_link_caps_init) \ + __X(cleanup, intel_dp_link_caps_cleanup) + +#define __DECLARE_MEMBER(__name, __fn) \ + typeof(__fn) *__name; + +#define INTEL_DP_LINK_CAPS_TEST_OPS_DECLARE \ + INTEL_DP_LINK_CAPS_TEST_OPS_MEMBERS(__DECLARE_MEMBER) + +struct intel_dp_link_caps_test_ops { + INTEL_DP_LINK_CAPS_TEST_OPS_DECLARE +}; + +#undef INTEL_DP_LINK_CAPS_TEST_OPS_DECLARE +#undef __DECLARE_MEMBER + +#ifdef I915 +extern const struct intel_dp_link_caps_test_ops i915_display_dp_link_caps_test_ops; +#else +extern const struct intel_dp_link_caps_test_ops intel_display_dp_link_caps_test_ops; +#endif /* I915 */ + +#endif /* CONFIG_KUNIT */ + #endif /* __INTEL_DP_LINK_CAPS_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_training.c b/drivers/gpu/drm/i915/display/intel_dp_link_training.c index a592bfab5ff0..fa55664c9d98 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_training.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_training.c @@ -21,6 +21,8 @@ * IN THE SOFTWARE. */ +#include + #include #include @@ -1888,8 +1890,9 @@ static bool reduce_link_params(struct intel_dp *intel_dp, const struct intel_crt return new_found; } -static int intel_dp_get_link_train_fallback_values(struct intel_dp *intel_dp, - const struct intel_crtc_state *crtc_state) +VISIBLE_IF_KUNIT +int intel_dp_get_link_train_fallback_values(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) { struct intel_display *display = to_intel_display(intel_dp); struct intel_dp_link_caps *link_caps = intel_dp->link.caps; @@ -2813,3 +2816,32 @@ void intel_dp_link_training_cleanup(struct intel_dp_link_training *link_training { kfree(link_training); } + +#if IS_ENABLED(CONFIG_KUNIT) + +#define __INIT_MEMBER(__name, __fn) \ + .__name = __fn, + +#define INTEL_DP_LINK_TRAINING_TEST_OPS_INIT \ + INTEL_DP_LINK_TRAINING_TEST_OPS_MEMBERS(__INIT_MEMBER) + +#ifdef I915 + +const struct intel_dp_link_training_test_ops i915_display_dp_link_training_test_ops = { + INTEL_DP_LINK_TRAINING_TEST_OPS_INIT +}; +EXPORT_SYMBOL(i915_display_dp_link_training_test_ops); + +#else + +const struct intel_dp_link_training_test_ops intel_display_dp_link_training_test_ops = { + INTEL_DP_LINK_TRAINING_TEST_OPS_INIT +}; +EXPORT_SYMBOL(intel_display_dp_link_training_test_ops); + +#endif /* I915 */ + +#undef INTEL_DP_LINK_TRAINING_TEST_OPS_INIT +#undef __INIT_MEMBER + +#endif /* CONFIG_KUNIT */ diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_training.h b/drivers/gpu/drm/i915/display/intel_dp_link_training.h index ef16fcabd6da..581f2361fdfd 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_training.h +++ b/drivers/gpu/drm/i915/display/intel_dp_link_training.h @@ -8,6 +8,8 @@ #include +#include "intel_dp_link_caps.h" + struct intel_atomic_state; struct intel_connector; struct intel_crtc_state; @@ -71,4 +73,33 @@ void intel_dp_link_training_reset(struct intel_dp_link_training *link_training); struct intel_dp_link_training *intel_dp_link_training_init(struct intel_dp *intel_dp); void intel_dp_link_training_cleanup(struct intel_dp_link_training *link_training); +#if IS_ENABLED(CONFIG_KUNIT) + +int intel_dp_get_link_train_fallback_values(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state); + +#define INTEL_DP_LINK_TRAINING_TEST_OPS_MEMBERS(__X) \ + __X(get_fallback_values, intel_dp_get_link_train_fallback_values) + +#define __DECLARE_MEMBER(__name, __fn) \ + typeof(__fn) *__name; + +#define INTEL_DP_LINK_TRAINING_TEST_OPS_DECLARE \ + INTEL_DP_LINK_TRAINING_TEST_OPS_MEMBERS(__DECLARE_MEMBER) + +struct intel_dp_link_training_test_ops { + INTEL_DP_LINK_TRAINING_TEST_OPS_DECLARE +}; + +#undef INTEL_DP_LINK_TRAINING_TEST_OPS_DECLARE +#undef __DECLARE_MEMBER + +#ifdef I915 +extern const struct intel_dp_link_training_test_ops i915_display_dp_link_training_test_ops; +#else +extern const struct intel_dp_link_training_test_ops intel_display_dp_link_training_test_ops; +#endif /* I915 */ + +#endif /* CONFIG_KUNIT */ + #endif /* __INTEL_DP_LINK_TRAINING_H__ */ diff --git a/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c b/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c index aa5358c94839..b77472e9bbe1 100644 --- a/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c +++ b/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c @@ -17,6 +17,8 @@ #include "intel_connector.h" #include "intel_display_core.h" #include "intel_display_types.h" +#include "intel_dp_link_caps.h" +#include "intel_dp_link_training.h" struct test_ctx { struct { @@ -30,6 +32,9 @@ struct test_ctx { struct intel_crtc_state crtc_state; } dev; + const struct intel_dp_link_caps_test_ops *link_caps_ops; + const struct intel_dp_link_training_test_ops *link_training_ops; + struct rnd_state rnd; }; @@ -64,6 +69,8 @@ static int intel_dp_link_test_init(struct kunit *test) intel_dp = &dig_port->dp; intel_dp->attached_connector = &test_ctx.dev.connector; + intel_dp->link.caps = test_ctx.link_caps_ops->init(intel_dp); + test->priv = &test_ctx; return 0; @@ -71,10 +78,20 @@ static int intel_dp_link_test_init(struct kunit *test) static void intel_dp_link_test_exit(struct kunit *test) { + struct test_ctx *ctx = test->priv; + + ctx->link_caps_ops->cleanup(ctx->dev.dig_port.dp.link.caps); } static int intel_dp_link_test_suite_init(struct kunit_suite *test_suite) { +#ifdef I915 + test_ctx.link_caps_ops = &i915_display_dp_link_caps_test_ops; + test_ctx.link_training_ops = &i915_display_dp_link_training_test_ops; +#else + test_ctx.link_caps_ops = &intel_display_dp_link_caps_test_ops; + test_ctx.link_training_ops = &intel_display_dp_link_training_test_ops; +#endif prandom_seed_state(&test_ctx.rnd, 0); return 0; From ee4c8f2e2a9ac8871a14c9a17e93d6647d5ff2c8 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Tue, 7 Jul 2026 15:48:48 +0300 Subject: [PATCH 44/93] drm/i915/kunit: DP link: add baseline fixed table reference test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a simple baseline test for DP link caps iteration using a fixed standard DP configuration table. This provides a minimal validity check, independent of more complex test setups, verifying the iterator returns expected configurations in ascending and descending order. v2: Unchanged. v3: Test config iteration in lane count, rate order as well. Reviewed-by: Michał Grzelak # v2 Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260707124849.135319-3-imre.deak@intel.com --- .../i915/display/tests/intel_dp_link_test.c | 206 ++++++++++++++++++ 1 file changed, 206 insertions(+) diff --git a/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c b/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c index b77472e9bbe1..f49903433d60 100644 --- a/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c +++ b/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -20,6 +21,18 @@ #include "intel_dp_link_caps.h" #include "intel_dp_link_training.h" +#define LINK_TEST_NUM_LANE_CONFIGS(__max_lane_count) \ + (ilog2(__max_lane_count) + 1) + +#define LINK_TEST_NUM_CONFIGS(__num_rates, __max_lane_count) \ + ((__num_rates) * LINK_TEST_NUM_LANE_CONFIGS(__max_lane_count)) + +#define LINK_TEST_MAX_LANE_COUNT ((u32)4) +#define LINK_TEST_MAX_CONFIGS LINK_TEST_NUM_CONFIGS(DP_MAX_SUPPORTED_RATES, \ + LINK_TEST_MAX_LANE_COUNT) + +#define LINK_TEST_NUM_RANDOM_ITERATIONS 50 + struct test_ctx { struct { struct intel_display display; @@ -38,7 +51,200 @@ struct test_ctx { struct rnd_state rnd; }; +struct link_rate_set { + const int *entries; + int size; +}; + +struct link_config_set { + struct intel_dp_link_config entries[LINK_TEST_MAX_CONFIGS]; + int size; +}; + +static const int standard_dp_link_rates[] = { + 162000, 270000, 540000, 810000, 1000000, 1350000, 2000000 +}; + +#define LINK_TEST_NUM_STANDARD_RATES (ARRAY_SIZE(standard_dp_link_rates)) + +static const struct link_config_set standard_dp_link_configs[] = { + [INTEL_DP_LINK_CAPS_ORDER_KEY_BW] = { /* MBps PBN */ + .entries = { + { .rate = 162000, .lane_count = 1 }, /* 162.0 3.00 */ + { .rate = 270000, .lane_count = 1 }, /* 270.0 5.00 */ + { .rate = 162000, .lane_count = 2 }, /* 324.0 6.00 */ + { .rate = 270000, .lane_count = 2 }, /* 540.0 10.00 */ + { .rate = 540000, .lane_count = 1 }, /* 540.0 10.00 */ + { .rate = 162000, .lane_count = 4 }, /* 648.0 12.00 */ + { .rate = 810000, .lane_count = 1 }, /* 810.0 15.00 */ + { .rate = 270000, .lane_count = 4 }, /* 1080.0 20.00 */ + { .rate = 540000, .lane_count = 2 }, /* 1080.0 20.00 */ + { .rate = 1000000, .lane_count = 1 }, /* 1208.9 22.39 */ + { .rate = 810000, .lane_count = 2 }, /* 1620.0 30.00 */ + { .rate = 1350000, .lane_count = 1 }, /* 1632.0 30.22 */ + { .rate = 540000, .lane_count = 4 }, /* 2160.0 40.00 */ + { .rate = 1000000, .lane_count = 2 }, /* 2417.8 44.77 */ + { .rate = 2000000, .lane_count = 1 }, /* 2417.8 44.77 */ + { .rate = 810000, .lane_count = 4 }, /* 3240.0 60.00 */ + { .rate = 1350000, .lane_count = 2 }, /* 3264.0 60.44 */ + { .rate = 1000000, .lane_count = 4 }, /* 4835.6 89.55 */ + { .rate = 2000000, .lane_count = 2 }, /* 4835.6 89.55 */ + { .rate = 1350000, .lane_count = 4 }, /* 6527.9 120.89 */ + { .rate = 2000000, .lane_count = 4 }, /* 9671.1 179.09 */ + }, + .size = LINK_TEST_NUM_CONFIGS(ARRAY_SIZE(standard_dp_link_rates), + LINK_TEST_MAX_LANE_COUNT), + }, + [INTEL_DP_LINK_CAPS_ORDER_KEY_RATE_LANE] = { + .entries = { + { .rate = 162000, .lane_count = 1 }, + { .rate = 162000, .lane_count = 2 }, + { .rate = 162000, .lane_count = 4 }, + + { .rate = 270000, .lane_count = 1 }, + { .rate = 270000, .lane_count = 2 }, + { .rate = 270000, .lane_count = 4 }, + + { .rate = 540000, .lane_count = 1 }, + { .rate = 540000, .lane_count = 2 }, + { .rate = 540000, .lane_count = 4 }, + + { .rate = 810000, .lane_count = 1 }, + { .rate = 810000, .lane_count = 2 }, + { .rate = 810000, .lane_count = 4 }, + + { .rate = 1000000, .lane_count = 1 }, + { .rate = 1000000, .lane_count = 2 }, + { .rate = 1000000, .lane_count = 4 }, + + { .rate = 1350000, .lane_count = 1 }, + { .rate = 1350000, .lane_count = 2 }, + { .rate = 1350000, .lane_count = 4 }, + + { .rate = 2000000, .lane_count = 1 }, + { .rate = 2000000, .lane_count = 2 }, + { .rate = 2000000, .lane_count = 4 }, + }, + .size = LINK_TEST_NUM_CONFIGS(ARRAY_SIZE(standard_dp_link_rates), + LINK_TEST_MAX_LANE_COUNT), + }, + [INTEL_DP_LINK_CAPS_ORDER_KEY_LANE_RATE] = { + .entries = { + { .rate = 162000, .lane_count = 1 }, + { .rate = 270000, .lane_count = 1 }, + { .rate = 540000, .lane_count = 1 }, + { .rate = 810000, .lane_count = 1 }, + { .rate = 1000000, .lane_count = 1 }, + { .rate = 1350000, .lane_count = 1 }, + { .rate = 2000000, .lane_count = 1 }, + + { .rate = 162000, .lane_count = 2 }, + { .rate = 270000, .lane_count = 2 }, + { .rate = 540000, .lane_count = 2 }, + { .rate = 810000, .lane_count = 2 }, + { .rate = 1000000, .lane_count = 2 }, + { .rate = 1350000, .lane_count = 2 }, + { .rate = 2000000, .lane_count = 2 }, + + { .rate = 162000, .lane_count = 4 }, + { .rate = 270000, .lane_count = 4 }, + { .rate = 540000, .lane_count = 4 }, + { .rate = 810000, .lane_count = 4 }, + { .rate = 1000000, .lane_count = 4 }, + { .rate = 1350000, .lane_count = 4 }, + { .rate = 2000000, .lane_count = 4 }, + }, + .size = LINK_TEST_NUM_CONFIGS(ARRAY_SIZE(standard_dp_link_rates), + LINK_TEST_MAX_LANE_COUNT), + }, +}; + +static bool link_configs_match(const struct intel_dp_link_config *a, + const struct intel_dp_link_config *b) +{ + return a->rate == b->rate && a->lane_count == b->lane_count; +} + +static const struct intel_dp_link_caps_order config_orders[] = { + { + .key = INTEL_DP_LINK_CAPS_ORDER_KEY_BW, + .dir = INTEL_DP_LINK_CAPS_ORDER_DIR_ASC, + }, { + .key = INTEL_DP_LINK_CAPS_ORDER_KEY_BW, + .dir = INTEL_DP_LINK_CAPS_ORDER_DIR_DESC, + }, { + .key = INTEL_DP_LINK_CAPS_ORDER_KEY_RATE_LANE, + .dir = INTEL_DP_LINK_CAPS_ORDER_DIR_ASC, + }, { + .key = INTEL_DP_LINK_CAPS_ORDER_KEY_RATE_LANE, + .dir = INTEL_DP_LINK_CAPS_ORDER_DIR_DESC, + }, { + .key = INTEL_DP_LINK_CAPS_ORDER_KEY_LANE_RATE, + .dir = INTEL_DP_LINK_CAPS_ORDER_DIR_ASC, + }, { + .key = INTEL_DP_LINK_CAPS_ORDER_KEY_LANE_RATE, + .dir = INTEL_DP_LINK_CAPS_ORDER_DIR_DESC, + } +}; + +static const struct link_config_set * +link_caps_config_order_key_to_set(struct kunit *test, enum intel_dp_link_caps_order_key key) +{ + return &standard_dp_link_configs[key]; +} + +/* + * TEST: Baseline with fixed reference table + * ----------------------------------------- + * Verify the link_caps config iterator using fixed standard DP config tables. + */ +static void baseline_test_for_order(struct kunit *test, + struct intel_dp_link_caps *link_caps, + struct intel_dp_link_caps_order config_order) +{ + struct test_ctx *ctx = test->priv; + const struct link_config_set *config_set = + link_caps_config_order_key_to_set(test, config_order.key); + const struct intel_dp_link_caps_test_ops *ops = ctx->link_caps_ops; + struct intel_dp_link_config iter_config; + struct intel_dp_link_caps_iter iter; + int pos = 0; + + ops->iter_start(&iter, link_caps, config_order, INTEL_DP_LINK_CAPS_FILTER_ALL); + for_each_dp_link_config(&iter, &iter_config) { + int idx = pos; + + if (config_order.dir == INTEL_DP_LINK_CAPS_ORDER_DIR_DESC) + idx = config_set->size - idx - 1; + + KUNIT_EXPECT_TRUE(test, link_configs_match(&iter_config, + &config_set->entries[idx])); + + pos++; + } + ops->iter_end(&iter); +} + +static void intel_dp_link_caps_test_baseline(struct kunit *test) +{ + struct test_ctx *ctx = test->priv; + struct intel_dp_link_caps *link_caps = ctx->dev.dig_port.dp.link.caps; + const struct intel_dp_link_caps_test_ops *ops = + ctx->link_caps_ops; + int i; + + ops->update(link_caps, + standard_dp_link_rates, LINK_TEST_NUM_STANDARD_RATES, + LINK_TEST_MAX_LANE_COUNT, + true); + + for (i = 0; i < ARRAY_SIZE(config_orders); i++) + baseline_test_for_order(test, link_caps, config_orders[i]); +} + static struct kunit_case intel_dp_link_test_cases[] = { + KUNIT_CASE(intel_dp_link_caps_test_baseline), + {} }; From 3d0e60d75172265d5e4dff9b148e4adf5fd561c9 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Tue, 7 Jul 2026 15:48:49 +0300 Subject: [PATCH 45/93] drm/i915/kunit: DP link: add update config tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add KUnit tests for link_caps updates shrinking or expanding the supported rates and lane counts. The tests also cover updates with disabled configurations, including random shrink and expand sequences, to verify that disabled state, allowed configurations, ordering, and max limits stay consistent across updates. v2: Remove test cases for the now unused merge update mode. v3: - Test config iteration in lane count, rate order as well. - Keep space after comma in code comment. (Michał) Reviewed-by: Michał Grzelak # v2 Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260707124849.135319-4-imre.deak@intel.com --- .../i915/display/tests/intel_dp_link_test.c | 804 ++++++++++++++++++ 1 file changed, 804 insertions(+) diff --git a/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c b/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c index f49903433d60..18f464618d7f 100644 --- a/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c +++ b/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c @@ -61,12 +61,29 @@ struct link_config_set { int size; }; +struct test_config_table { + struct kunit *test; + + struct link_rate_set rates; + int max_lane_count; + struct link_config_set disabled_configs; +}; + static const int standard_dp_link_rates[] = { 162000, 270000, 540000, 810000, 1000000, 1350000, 2000000 }; #define LINK_TEST_NUM_STANDARD_RATES (ARRAY_SIZE(standard_dp_link_rates)) +#define INIT_STANDARD_TABLE(__test, __num_rates, __max_lane_count) { \ + .test = (__test), \ + .rates = { \ + .entries = standard_dp_link_rates, \ + .size = (__num_rates), \ + }, \ + .max_lane_count = (__max_lane_count), \ +} + static const struct link_config_set standard_dp_link_configs[] = { [INTEL_DP_LINK_CAPS_ORDER_KEY_BW] = { /* MBps PBN */ .entries = { @@ -159,12 +176,56 @@ static const struct link_config_set standard_dp_link_configs[] = { }, }; +static int lookup_rate(const struct link_rate_set *rate_set, int rate) +{ + int i; + + for (i = 0; i < rate_set->size; i++) + if (rate_set->entries[i] == rate) + return i; + + return -1; +} + +static bool has_rate(const struct link_rate_set *rate_set, int rate) +{ + return lookup_rate(rate_set, rate) >= 0; +} + static bool link_configs_match(const struct intel_dp_link_config *a, const struct intel_dp_link_config *b) { return a->rate == b->rate && a->lane_count == b->lane_count; } +static int lookup_config(const struct link_config_set *config_set, + const struct intel_dp_link_config *config) +{ + int i; + + for (i = 0; i < config_set->size; i++) + if (link_configs_match(&config_set->entries[i], config)) + return i; + + return -1; +} + +static bool has_config(const struct link_config_set *config_set, + const struct intel_dp_link_config *config) +{ + return lookup_config(config_set, config) >= 0; +} + +static void add_config(struct kunit *test, + struct link_config_set *config_set, + const struct intel_dp_link_config *config) +{ + KUNIT_ASSERT_LT(test, config_set->size, ARRAY_SIZE(config_set->entries)); + + config_set->entries[config_set->size] = *config; + config_set->size++; +} + static const struct intel_dp_link_caps_order config_orders[] = { { .key = INTEL_DP_LINK_CAPS_ORDER_KEY_BW, @@ -242,9 +303,752 @@ static void intel_dp_link_caps_test_baseline(struct kunit *test) baseline_test_for_order(test, link_caps, config_orders[i]); } +static int get_num_configs(int num_rates, int max_lane_count) +{ + return num_rates * LINK_TEST_NUM_LANE_CONFIGS(max_lane_count); +} + +static int rand_in_range(struct test_ctx *ctx, int min, int max) +{ + return min + (prandom_u32_state(&ctx->rnd) % (max - min + 1)); +} + +/* + * TEST: Update reset + * ------------------ + * Verify that resetting link_caps with the DP standard rates/lane + * counts updates the configuration table accordingly for all + * combinations. + */ +static void verify_bw_asc_config_order(struct kunit *test, + const struct intel_dp_link_config *last_config, + const struct intel_dp_link_config *config) +{ + int config_bw = drm_dp_max_dprx_data_rate(config->rate, + config->lane_count); + int last_config_bw = drm_dp_max_dprx_data_rate(last_config->rate, + last_config->lane_count); + + KUNIT_EXPECT_GE(test, config_bw, last_config_bw); + if (config_bw == last_config_bw) + KUNIT_EXPECT_GT(test, config->rate, last_config->rate); +} + +static void verify_bw_desc_config_order(struct kunit *test, + const struct intel_dp_link_config *last_config, + const struct intel_dp_link_config *config) +{ + int config_bw = drm_dp_max_dprx_data_rate(config->rate, + config->lane_count); + int last_config_bw = drm_dp_max_dprx_data_rate(last_config->rate, + last_config->lane_count); + + KUNIT_EXPECT_LE(test, config_bw, last_config_bw); + if (config_bw == last_config_bw) + KUNIT_EXPECT_LT(test, config->rate, last_config->rate); +} + +static void verify_rate_lane_asc_config_order(struct kunit *test, + const struct intel_dp_link_config *last_config, + const struct intel_dp_link_config *config) +{ + KUNIT_EXPECT_GE(test, config->rate, last_config->rate); + if (config->rate == last_config->rate) + KUNIT_EXPECT_GT(test, config->lane_count, last_config->lane_count); +} + +static void verify_rate_lane_desc_config_order(struct kunit *test, + const struct intel_dp_link_config *last_config, + const struct intel_dp_link_config *config) +{ + KUNIT_EXPECT_LE(test, config->rate, last_config->rate); + if (config->rate == last_config->rate) + KUNIT_EXPECT_LT(test, config->lane_count, last_config->lane_count); +} + +static void verify_lane_rate_asc_config_order(struct kunit *test, + const struct intel_dp_link_config *last_config, + const struct intel_dp_link_config *config) +{ + KUNIT_EXPECT_GE(test, config->lane_count, last_config->lane_count); + if (config->lane_count == last_config->lane_count) + KUNIT_EXPECT_GT(test, config->rate, last_config->rate); +} + +static void verify_lane_rate_desc_config_order(struct kunit *test, + const struct intel_dp_link_config *last_config, + const struct intel_dp_link_config *config) +{ + KUNIT_EXPECT_LE(test, config->lane_count, last_config->lane_count); + if (config->lane_count == last_config->lane_count) + KUNIT_EXPECT_LT(test, config->rate, last_config->rate); +} + +static void verify_config_order(struct kunit *test, + struct intel_dp_link_caps_order config_order, + const struct intel_dp_link_config *last_config, + const struct intel_dp_link_config *config) +{ + switch (config_order.key) { + case INTEL_DP_LINK_CAPS_ORDER_KEY_BW: + if (config_order.dir == INTEL_DP_LINK_CAPS_ORDER_DIR_ASC) + verify_bw_asc_config_order(test, last_config, config); + else + verify_bw_desc_config_order(test, last_config, config); + break; + case INTEL_DP_LINK_CAPS_ORDER_KEY_RATE_LANE: + if (config_order.dir == INTEL_DP_LINK_CAPS_ORDER_DIR_ASC) + verify_rate_lane_asc_config_order(test, last_config, config); + else + verify_rate_lane_desc_config_order(test, last_config, config); + break; + case INTEL_DP_LINK_CAPS_ORDER_KEY_LANE_RATE: + if (config_order.dir == INTEL_DP_LINK_CAPS_ORDER_DIR_ASC) + verify_lane_rate_asc_config_order(test, last_config, config); + else + verify_lane_rate_desc_config_order(test, last_config, config); + break; + default: + KUNIT_FAIL_AND_ABORT(test, "Missing order key: %d", config_order.key); + } +} + +static int expected_num_configs(const struct test_config_table *expected_table, + const struct intel_dp_link_config *max_limits) +{ + int num_configs = 0; + int lane_count; + int rate_idx; + + for (rate_idx = 0; rate_idx < expected_table->rates.size; rate_idx++) { + for (lane_count = 1; lane_count <= expected_table->max_lane_count; lane_count <<= 1) { + struct intel_dp_link_config config = { + .rate = expected_table->rates.entries[rate_idx], + .lane_count = lane_count, + }; + + if (config.rate > max_limits->rate || + config.lane_count > max_limits->lane_count) + continue; + + if (has_config(&expected_table->disabled_configs, &config)) + continue; + + num_configs++; + } + } + + return num_configs; +} + +static void +verify_link_caps_for_order(const struct test_config_table *expected_table, + struct intel_dp_link_caps *link_caps, + struct intel_dp_link_caps_order config_order, + const struct intel_dp_link_config *max_limits) +{ + struct kunit *test = expected_table->test; + struct test_ctx *ctx = test->priv; + const struct intel_dp_link_caps_test_ops *ops = + ctx->link_caps_ops; + struct intel_dp_link_config expected_max_bw_config = {}; + struct intel_dp_link_config actual_max_bw_config; + struct intel_dp_link_config last_config = {}; + struct intel_dp_link_config old_max_limits; + struct intel_dp_link_config iter_config; + struct intel_dp_link_caps_iter iter; + int num_actual_configs = 0; + int max_bw = 0; + + ops->get_max_limits(link_caps, &old_max_limits); + ops->set_max_limits(link_caps, max_limits); + + ops->iter_start(&iter, link_caps, config_order, INTEL_DP_LINK_CAPS_FILTER_ALL); + for_each_dp_link_config(&iter, &iter_config) { + int bw; + + KUNIT_EXPECT_LE(test, iter_config.rate, max_limits->rate); + KUNIT_EXPECT_LE(test, iter_config.lane_count, max_limits->lane_count); + + num_actual_configs++; + + /* + * Verify the config's rate/lane-count values and its ordering relative + * to the previous config. + */ + if (last_config.rate) + verify_config_order(test, config_order, &last_config, &iter_config); + last_config = iter_config; + + KUNIT_EXPECT_TRUE(test, has_rate(&expected_table->rates, + iter_config.rate)); + KUNIT_EXPECT_LE(test, iter_config.lane_count, + expected_table->max_lane_count); + KUNIT_EXPECT_TRUE(test, is_power_of_2(iter_config.lane_count)); + + /* Verify the config's disabled state */ + KUNIT_EXPECT_FALSE(test, has_config(&expected_table->disabled_configs, + &iter_config)); + + /* + * Update the max limits for allowed configs, verified at the + * end for the whole config table. + */ + + bw = drm_dp_max_dprx_data_rate(iter_config.rate, iter_config.lane_count); + if (bw > max_bw || + (bw == max_bw && iter_config.rate > expected_max_bw_config.rate)) { + max_bw = bw; + expected_max_bw_config = iter_config; + } + } + ops->iter_end(&iter); + + KUNIT_EXPECT_EQ(test, num_actual_configs, expected_num_configs(expected_table, max_limits)); + + ops->get_max_bw_config(link_caps, &actual_max_bw_config); + KUNIT_EXPECT_TRUE(test, link_configs_match(&expected_max_bw_config, + &actual_max_bw_config)); + + KUNIT_ASSERT_TRUE(test, ops->set_max_limits(link_caps, &old_max_limits)); +} + +static bool max_limits_valid(const struct test_config_table *expected_table, + const struct intel_dp_link_config *max_limits) +{ + int lane_count; + int rate_idx; + + for (rate_idx = 0; rate_idx < expected_table->rates.size; rate_idx++) { + for (lane_count = 1; lane_count <= expected_table->max_lane_count; lane_count <<= 1) { + struct intel_dp_link_config config = { + .rate = expected_table->rates.entries[rate_idx], + .lane_count = lane_count, + }; + + if (has_config(&expected_table->disabled_configs, &config)) + continue; + + if (config.rate <= max_limits->rate && + config.lane_count <= max_limits->lane_count) + return true; + } + } + + return false; +} + +static void get_max_limits(const struct test_config_table *expected_table, + struct intel_dp_link_config *max_limits) +{ + int lane_count; + int rate_idx; + + max_limits->rate = 0; + max_limits->lane_count = 0; + + for (rate_idx = 0; rate_idx < expected_table->rates.size; rate_idx++) { + for (lane_count = 1; lane_count <= expected_table->max_lane_count; lane_count <<= 1) { + struct intel_dp_link_config config = { + .rate = expected_table->rates.entries[rate_idx], + .lane_count = lane_count, + }; + + if (has_config(&expected_table->disabled_configs, &config)) + continue; + + max_limits->rate = max(max_limits->rate, config.rate); + max_limits->lane_count = max(max_limits->lane_count, config.lane_count); + } + } +} + +static void verify_link_caps(const struct test_config_table *expected_table, + struct intel_dp_link_caps *link_caps) +{ + struct kunit *test = expected_table->test; + struct test_ctx *ctx = test->priv; + const struct intel_dp_link_caps_test_ops *ops = ctx->link_caps_ops; + struct intel_dp_link_config max_limits; + int i; + + get_max_limits(expected_table, &max_limits); + + for (i = 0; i < ARRAY_SIZE(config_orders); i++) { + int lane_count; + int rate_idx; + + verify_link_caps_for_order(expected_table, link_caps, config_orders[i], &max_limits); + /* + * Verify iteration after setting the max limits to each + * configurations. + */ + for (rate_idx = 0; rate_idx < expected_table->rates.size; rate_idx++) { + for (lane_count = 1; lane_count <= expected_table->max_lane_count; lane_count <<= 1) { + struct intel_dp_link_config config = { + .rate = expected_table->rates.entries[rate_idx], + .lane_count = lane_count, + }; + + if (!max_limits_valid(expected_table, &config)) { + /* Verify that invalid max limits are rejected. */ + KUNIT_EXPECT_FALSE(test, ops->set_max_limits(link_caps, &config)); + + continue; + } + + verify_link_caps_for_order(expected_table, link_caps, config_orders[i], + &config); + } + } + } +} + +static void update_link_caps_and_verify(struct test_config_table *expected_table, + struct intel_dp_link_caps *link_caps, + bool reset) +{ + struct kunit *test = expected_table->test; + struct test_ctx *ctx = test->priv; + const struct intel_dp_link_caps_test_ops *ops = + ctx->link_caps_ops; + bool link_params_changed; + + link_params_changed = ops->update(link_caps, + expected_table->rates.entries, + expected_table->rates.size, + expected_table->max_lane_count, + reset); + KUNIT_EXPECT_TRUE(test, !reset || link_params_changed); + + /* + * ops->update() re-enables all configurations when called with + * reset=true, or changed link parameters. + */ + if (link_params_changed) + expected_table->disabled_configs.size = 0; + + verify_link_caps(expected_table, link_caps); +} + +static void intel_dp_link_caps_test_update_reset(struct kunit *test) +{ + struct test_ctx *ctx = test->priv; + struct intel_dp_link_caps *link_caps = ctx->dev.dig_port.dp.link.caps; + int max_lane_count; + int num_rates; + + for (max_lane_count = 1; + max_lane_count <= LINK_TEST_MAX_LANE_COUNT; + max_lane_count <<= 1) { + for (num_rates = 1; + num_rates <= LINK_TEST_NUM_STANDARD_RATES; + num_rates++) { + struct test_config_table expected_table = + INIT_STANDARD_TABLE(test, num_rates, + max_lane_count); + + update_link_caps_and_verify(&expected_table, link_caps, true); + } + } +} + +/* + * TEST: Update shrink and expand + * ------------------------------ + * Verify that removing or adding supported rates/lane counts updates + * the configuration table accordingly. + */ +static void disable_configs_and_verify(struct kunit *test, + struct intel_dp_link_caps *link_caps, + struct test_config_table *expected_table, + const struct link_config_set *config_set) +{ + struct test_ctx *ctx = test->priv; + const struct intel_dp_link_caps_test_ops *ops = + ctx->link_caps_ops; + int i; + + for (i = 0; i < config_set->size; i++) { + KUNIT_ASSERT_FALSE(test, has_config(&expected_table->disabled_configs, + &config_set->entries[i])); + add_config(test, &expected_table->disabled_configs, &config_set->entries[i]); + + KUNIT_ASSERT_TRUE(test, ops->disable_config(link_caps, &config_set->entries[i])); + + verify_link_caps(expected_table, link_caps); + } +} + +static void disable_configs_for_shrink_and_verify(struct test_config_table *expected_table, + struct intel_dp_link_caps *link_caps) +{ + struct kunit *test = expected_table->test; + struct link_config_set config_set = {}; + struct intel_dp_link_config max_config; + + /* + * When configs shrink disable the config with the + * second-highest rate, lane params, so the disabled config + * stays around after the configs got shrunk. + */ + KUNIT_ASSERT_GE(test, expected_table->rates.size, 2); + KUNIT_ASSERT_GE(test, expected_table->max_lane_count, 2); + + max_config.rate = expected_table->rates.entries[expected_table->rates.size - 2]; + max_config.lane_count = expected_table->max_lane_count >> 1; + + add_config(test, &config_set, &max_config); + disable_configs_and_verify(test, link_caps, expected_table, + &config_set); +} + +static void disable_configs_for_expand_and_verify(struct test_config_table *expected_table, + struct intel_dp_link_caps *link_caps) +{ + struct kunit *test = expected_table->test; + struct link_config_set config_set = {}; + struct intel_dp_link_config max_config; + + KUNIT_ASSERT_GE(test, expected_table->rates.size, 1); + + max_config.rate = expected_table->rates.entries[expected_table->rates.size - 1]; + max_config.lane_count = expected_table->max_lane_count; + + add_config(test, &config_set, &max_config); + disable_configs_and_verify(test, link_caps, expected_table, + &config_set); +} + +static void get_nth_rate_lane_config(const struct test_config_table *expected_table, int n, + struct intel_dp_link_config *config) +{ + int num_lane_configs = LINK_TEST_NUM_LANE_CONFIGS(expected_table->max_lane_count); + int rate_idx = n / num_lane_configs; + int lane_count_exp = n % num_lane_configs; + + config->rate = expected_table->rates.entries[rate_idx]; + config->lane_count = 1 << lane_count_exp; +} + +static void test_update_rates_shrink(struct kunit *test, bool disable_configs) +{ + struct test_ctx *ctx = test->priv; + struct intel_dp_link_caps *link_caps = ctx->dev.dig_port.dp.link.caps; + struct test_config_table expected_table = + INIT_STANDARD_TABLE(test, LINK_TEST_NUM_STANDARD_RATES, + LINK_TEST_MAX_LANE_COUNT); + + update_link_caps_and_verify(&expected_table, link_caps, true); + + while (expected_table.rates.size > 1) { + if (disable_configs) + disable_configs_for_shrink_and_verify(&expected_table, link_caps); + + expected_table.rates.size--; + + update_link_caps_and_verify(&expected_table, link_caps, false); + } +} + +static void intel_dp_link_caps_test_update_rates_shrink(struct kunit *test) +{ + test_update_rates_shrink(test, false); +} + +static void intel_dp_link_caps_test_update_rates_shrink_disable(struct kunit *test) +{ + test_update_rates_shrink(test, true); +} + +static void test_update_rates_expand(struct kunit *test, bool disable_configs) +{ + struct test_ctx *ctx = test->priv; + struct intel_dp_link_caps *link_caps = ctx->dev.dig_port.dp.link.caps; + struct test_config_table expected_table = + INIT_STANDARD_TABLE(test, 1, LINK_TEST_MAX_LANE_COUNT); + + update_link_caps_and_verify(&expected_table, link_caps, true); + + while (expected_table.rates.size < LINK_TEST_NUM_STANDARD_RATES) { + if (disable_configs) + disable_configs_for_expand_and_verify(&expected_table, link_caps); + + expected_table.rates.size++; + + update_link_caps_and_verify(&expected_table, link_caps, false); + } +} + +static void intel_dp_link_caps_test_update_rates_expand(struct kunit *test) +{ + test_update_rates_expand(test, false); +} + +static void intel_dp_link_caps_test_update_rates_expand_disable(struct kunit *test) +{ + test_update_rates_expand(test, true); +} + +static void test_update_lanes_shrink(struct kunit *test, bool disable_configs) +{ + struct test_ctx *ctx = test->priv; + struct intel_dp_link_caps *link_caps = ctx->dev.dig_port.dp.link.caps; + struct test_config_table expected_table = + INIT_STANDARD_TABLE(test, LINK_TEST_NUM_STANDARD_RATES, + LINK_TEST_MAX_LANE_COUNT); + + update_link_caps_and_verify(&expected_table, link_caps, true); + + while (expected_table.max_lane_count > 1) { + if (disable_configs) + disable_configs_for_shrink_and_verify(&expected_table, link_caps); + + expected_table.max_lane_count >>= 1; + + update_link_caps_and_verify(&expected_table, link_caps, false); + } +} + +static void intel_dp_link_caps_test_update_lanes_shrink(struct kunit *test) +{ + test_update_lanes_shrink(test, false); +} + +static void intel_dp_link_caps_test_update_lanes_shrink_disable(struct kunit *test) +{ + test_update_lanes_shrink(test, true); +} + +static void test_update_lanes_expand(struct kunit *test, bool disable_configs) +{ + struct test_ctx *ctx = test->priv; + struct intel_dp_link_caps *link_caps = ctx->dev.dig_port.dp.link.caps; + struct test_config_table expected_table = + INIT_STANDARD_TABLE(test, LINK_TEST_NUM_STANDARD_RATES, 1); + + update_link_caps_and_verify(&expected_table, link_caps, true); + + while (expected_table.max_lane_count < LINK_TEST_MAX_LANE_COUNT) { + if (disable_configs) + disable_configs_for_expand_and_verify(&expected_table, link_caps); + + expected_table.max_lane_count <<= 1; + + update_link_caps_and_verify(&expected_table, link_caps, false); + } +} + +static void intel_dp_link_caps_test_update_lanes_expand(struct kunit *test) +{ + test_update_lanes_expand(test, false); +} + +static void intel_dp_link_caps_test_update_lanes_expand_disable(struct kunit *test) +{ + test_update_lanes_expand(test, true); +} + +static void disable_random_configs_and_verify(struct test_config_table *expected_table, + struct intel_dp_link_caps *link_caps) +{ + struct kunit *test = expected_table->test; + struct test_ctx *ctx = test->priv; + struct link_config_set config_set = {}; + u32 disabled_config_mask; + int num_configs; + int i; + + num_configs = get_num_configs(expected_table->rates.size, + expected_table->max_lane_count); + disabled_config_mask = prandom_u32_state(&ctx->rnd) & + GENMASK_U32(num_configs - 1, 0); + + for (i = 0; i < num_configs; i++) { + struct intel_dp_link_config config; + + /* At least one config must remain enabled. */ + if (expected_table->disabled_configs.size + + config_set.size + 1 >= num_configs) + break; + + if (!(BIT(i) & disabled_config_mask)) + continue; + + get_nth_rate_lane_config(expected_table, i, &config); + /* Don't disable a config twice. */ + if (has_config(&expected_table->disabled_configs, &config)) + continue; + + add_config(test, &config_set, &config); + } + + disable_configs_and_verify(test, link_caps, expected_table, + &config_set); +} + +static void get_params_shrink_step(struct test_ctx *ctx, + int num_rates, int max_lane_count, + int *rates_step, int *lanes_step) +{ + int shrink_mask; + + *rates_step = 0; + *lanes_step = 0; + + if (num_rates == 1) + shrink_mask = BIT(0); /* shrink only lanes */ + else if (max_lane_count == 1) + shrink_mask = BIT(1); /* shrink only rates */ + else + shrink_mask = rand_in_range(ctx, + BIT(0), + BIT(0) | BIT(1)); /* shrink one or both params */ + + if (shrink_mask & BIT(1)) + *rates_step = rand_in_range(ctx, 1, num_rates - 1); + + if (shrink_mask & BIT(0)) + *lanes_step = rand_in_range(ctx, 1, ilog2(max_lane_count)); +} + +static void get_params_expand_step(struct test_ctx *ctx, + int max_num_rates, int num_rates, + int max_supported_lane_count, int max_lane_count, + int *rates_step, int *lanes_step) +{ + int expand_mask; + + *rates_step = 0; + *lanes_step = 0; + + if (num_rates == max_num_rates) + expand_mask = BIT(0); /* expand only lanes */ + else if (max_lane_count == max_supported_lane_count) + expand_mask = BIT(1); /* expand only rates */ + else + expand_mask = rand_in_range(ctx, + BIT(0), + BIT(0) | BIT(1)); /* expand one or both params */ + + if (expand_mask & BIT(1)) + *rates_step = rand_in_range(ctx, 1, max_num_rates - num_rates); + + if (expand_mask & BIT(0)) + *lanes_step = rand_in_range(ctx, 1, ilog2(max_supported_lane_count / + max_lane_count)); +} + +static void test_update_params_shrink_random(struct kunit *test, bool disable_configs) +{ + struct test_ctx *ctx = test->priv; + struct intel_dp_link_caps *link_caps = ctx->dev.dig_port.dp.link.caps; + struct test_config_table expected_table = + INIT_STANDARD_TABLE(test, LINK_TEST_NUM_STANDARD_RATES, + LINK_TEST_MAX_LANE_COUNT); + + update_link_caps_and_verify(&expected_table, link_caps, true); + + while (expected_table.rates.size > 1 || expected_table.max_lane_count > 1) { + int rates_step; + int lanes_step; + + if (disable_configs) + disable_random_configs_and_verify(&expected_table, link_caps); + + get_params_shrink_step(ctx, + expected_table.rates.size, + expected_table.max_lane_count, + &rates_step, &lanes_step); + + expected_table.rates.size -= rates_step; + expected_table.max_lane_count >>= lanes_step; + + update_link_caps_and_verify(&expected_table, link_caps, false); + } +} + +static void intel_dp_link_caps_test_update_params_shrink_random(struct kunit *test) +{ + int i; + + for (i = 0; i < LINK_TEST_NUM_RANDOM_ITERATIONS; i++) + test_update_params_shrink_random(test, false); +} + +static void intel_dp_link_caps_test_update_params_shrink_disable_random(struct kunit *test) +{ + int i; + + for (i = 0; i < LINK_TEST_NUM_RANDOM_ITERATIONS; i++) + test_update_params_shrink_random(test, true); +} + +static void test_update_params_expand_random(struct kunit *test, bool disable_configs) +{ + struct test_ctx *ctx = test->priv; + struct intel_dp_link_caps *link_caps = ctx->dev.dig_port.dp.link.caps; + struct test_config_table expected_table = + INIT_STANDARD_TABLE(test, 1, 1); + + update_link_caps_and_verify(&expected_table, link_caps, true); + + while (expected_table.rates.size < LINK_TEST_NUM_STANDARD_RATES || + expected_table.max_lane_count < LINK_TEST_MAX_LANE_COUNT) { + int rates_step; + int lanes_step; + + if (disable_configs) + disable_random_configs_and_verify(&expected_table, link_caps); + + get_params_expand_step(ctx, + LINK_TEST_NUM_STANDARD_RATES, + expected_table.rates.size, + LINK_TEST_MAX_LANE_COUNT, + expected_table.max_lane_count, + &rates_step, &lanes_step); + + expected_table.rates.size += rates_step; + expected_table.max_lane_count <<= lanes_step; + + update_link_caps_and_verify(&expected_table, link_caps, false); + } +} + +static void intel_dp_link_caps_test_update_params_expand_random(struct kunit *test) +{ + int i; + + for (i = 0; i < LINK_TEST_NUM_RANDOM_ITERATIONS; i++) + test_update_params_expand_random(test, false); +} + +static void intel_dp_link_caps_test_update_params_expand_disable_random(struct kunit *test) +{ + int i; + + for (i = 0; i < LINK_TEST_NUM_RANDOM_ITERATIONS; i++) + test_update_params_expand_random(test, true); +} + static struct kunit_case intel_dp_link_test_cases[] = { KUNIT_CASE(intel_dp_link_caps_test_baseline), + KUNIT_CASE(intel_dp_link_caps_test_update_reset), + + KUNIT_CASE(intel_dp_link_caps_test_update_rates_shrink), + KUNIT_CASE(intel_dp_link_caps_test_update_rates_shrink_disable), + KUNIT_CASE(intel_dp_link_caps_test_update_rates_expand), + KUNIT_CASE(intel_dp_link_caps_test_update_rates_expand_disable), + KUNIT_CASE(intel_dp_link_caps_test_update_lanes_shrink), + KUNIT_CASE(intel_dp_link_caps_test_update_lanes_shrink_disable), + KUNIT_CASE(intel_dp_link_caps_test_update_lanes_expand), + KUNIT_CASE(intel_dp_link_caps_test_update_lanes_expand_disable), + KUNIT_CASE(intel_dp_link_caps_test_update_params_shrink_random), + KUNIT_CASE(intel_dp_link_caps_test_update_params_shrink_disable_random), + KUNIT_CASE(intel_dp_link_caps_test_update_params_expand_random), + KUNIT_CASE(intel_dp_link_caps_test_update_params_expand_disable_random), + {} }; From 4fee95d4f88752c3facbc41f4d0994a074780744 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:32:03 +0300 Subject: [PATCH 46/93] drm/i915/kunit: DP link: add fallback tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add KUnit tests for DP link fallback selection across eDP, SST, and MST. Verify that the fallback logic properly selects the maximum allowed configuration, iterates through allowed configurations, and disables failed configs as expected. These tests include UHBR vs. non-UHBR conditions, MST vs. SST mode, and validate that subsequent fallback selections respect the updated allowed configuration mask. v2: - Rebase on changes using a filter object instead of a mask of configuration indices. - Rebase on changes using an iteration object. Reviewed-by: Michał Grzelak Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260701153204.4124150-35-imre.deak@intel.com --- .../i915/display/tests/intel_dp_link_test.c | 316 ++++++++++++++++++ 1 file changed, 316 insertions(+) diff --git a/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c b/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c index 18f464618d7f..e1c356ba11b5 100644 --- a/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c +++ b/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c @@ -1031,6 +1031,317 @@ static void intel_dp_link_caps_test_update_params_expand_disable_random(struct k test_update_params_expand_random(test, true); } +/* + * TEST: Fallback sequence + * ----------------------- + * Verify the eDP fallback logic to set the maximum supported configuration + * as a preference. + * + * For DP SST and MST verify fallback selection from the connector's + * maximum configuration and iteration of the resulting allowed + * configurations. + */ +static void intel_dp_link_test_fallback_for_edp(struct kunit *test) +{ + struct test_ctx *ctx = test->priv; + struct intel_dp_link_caps *link_caps = ctx->dev.dig_port.dp.link.caps; + struct test_config_table expected_table = + INIT_STANDARD_TABLE(test, LINK_TEST_NUM_STANDARD_RATES, + LINK_TEST_MAX_LANE_COUNT); + struct intel_digital_port *dig_port = &ctx->dev.dig_port; + const struct intel_dp_link_training_test_ops *lt_ops = + ctx->link_training_ops; + const struct intel_dp_link_caps_test_ops *lc_ops = + ctx->link_caps_ops; + struct intel_dp_link_config min_config = { + .rate = expected_table.rates.entries[0], + .lane_count = 1, + }; + struct intel_dp_link_config max_config = { + .rate = expected_table.rates.entries[expected_table.rates.size - 1], + .lane_count = LINK_TEST_MAX_LANE_COUNT, + }; + struct intel_dp_link_caps_order order; + struct intel_dp_link_config iter_config; + struct intel_dp_link_caps_iter iter; + int fallback_err; + + dig_port->base.type = INTEL_OUTPUT_EDP; + ctx->dev.dig_port.dp.use_max_params = false; + + update_link_caps_and_verify(&expected_table, link_caps, true); + + order = lc_ops->connector_compute_order(&ctx->dev.connector); + + lc_ops->iter_start(&iter, link_caps, order, INTEL_DP_LINK_CAPS_FILTER_ALL); + for_each_dp_link_config(&iter, &iter_config) + break; + lc_ops->iter_end(&iter); + + KUNIT_EXPECT_FALSE(test, ctx->dev.dig_port.dp.use_max_params); + KUNIT_EXPECT_TRUE(test, link_configs_match(&iter_config, &min_config)); + + ctx->dev.crtc_state.output_types = BIT(dig_port->base.type); + ctx->dev.crtc_state.port_clock = min_config.rate; + ctx->dev.crtc_state.lane_count = min_config.lane_count; + + fallback_err = lt_ops->get_fallback_values(&ctx->dev.dig_port.dp, &ctx->dev.crtc_state); + KUNIT_EXPECT_EQ(test, fallback_err, 0); + + /* The fallback should've changed the order. */ + order = lc_ops->connector_compute_order(&ctx->dev.connector); + + lc_ops->iter_start(&iter, link_caps, order, INTEL_DP_LINK_CAPS_FILTER_ALL); + for_each_dp_link_config(&iter, &iter_config) + break; + lc_ops->iter_end(&iter); + + KUNIT_EXPECT_TRUE(test, ctx->dev.dig_port.dp.use_max_params); + KUNIT_EXPECT_TRUE(test, link_configs_match(&iter_config, &max_config)); +} + +static bool test_fallback_from_target(struct test_config_table *expected_table, + enum intel_output_type output_type, int max_rate, + const struct intel_dp_link_config *expected_target_config, + const struct intel_dp_link_config *expected_fallback_config) +{ + struct kunit *test = expected_table->test; + struct test_ctx *ctx = test->priv; + struct intel_dp_link_caps *link_caps = ctx->dev.dig_port.dp.link.caps; + struct intel_dp_link_config iter_config; + const struct intel_dp_link_training_test_ops *lt_ops = + ctx->link_training_ops; + const struct intel_dp_link_caps_test_ops *lc_ops = + ctx->link_caps_ops; + /* Modify default order direction for max config lookup. */ + struct intel_dp_link_caps_order fallback_order = + lc_ops->connector_fallback_order(ctx->dev.connector.mst.dp); + struct intel_dp_link_caps_iter iter; + int expected_fallback_err = 0; + int fallback_err; + + /* Get the max connector config, optionally filtered to the max_rate limit. */ + lc_ops->iter_start(&iter, link_caps, fallback_order, INTEL_DP_LINK_CAPS_FILTER_ALL); + for_each_dp_link_config(&iter, &iter_config) + if (max_rate == 0 || iter_config.rate <= max_rate) + break; + lc_ops->iter_end(&iter); + + KUNIT_EXPECT_TRUE(test, link_configs_match(&iter_config, + expected_target_config)); + KUNIT_EXPECT_FALSE(test, link_configs_match(&iter_config, + &INTEL_DP_LINK_CONFIG_NULL)); + + ctx->dev.crtc_state.output_types = BIT(output_type); + ctx->dev.crtc_state.port_clock = expected_target_config->rate; + ctx->dev.crtc_state.lane_count = expected_target_config->lane_count; + + if (link_configs_match(expected_fallback_config, &INTEL_DP_LINK_CONFIG_NULL)) + expected_fallback_err = -1; + + fallback_err = lt_ops->get_fallback_values(&ctx->dev.dig_port.dp, &ctx->dev.crtc_state); + KUNIT_EXPECT_EQ(test, fallback_err, expected_fallback_err); + + if (!fallback_err) { + /* + * NOTE: This test does not verify any implied fallback + * target selection. + * + * The current driver behavior may still select a fallback + * configuration indirectly via max_limits, but that is an + * implementation artifact rather than part of the intended + * fallback API behavior, and is therefore not verified here. + * + * Instead, the effect of the fallback logic is verified by + * checking that the failed target configuration is disabled. + * Selecting the next target configuration from the remaining + * allowed configurations belongs to the modeset link target + * selection logic. + */ + add_config(test, &expected_table->disabled_configs, + expected_target_config); + } + + verify_link_caps(expected_table, link_caps); + + return !fallback_err; +} + +static const struct link_config_set * +get_target_configs_for_output_type(struct kunit *test, + enum intel_output_type output_type) +{ + switch (output_type) { + case INTEL_OUTPUT_DDI: + case INTEL_OUTPUT_DP: + case INTEL_OUTPUT_EDP: + return &standard_dp_link_configs[INTEL_DP_LINK_CAPS_ORDER_KEY_RATE_LANE]; + case INTEL_OUTPUT_DP_MST: + return &standard_dp_link_configs[INTEL_DP_LINK_CAPS_ORDER_KEY_BW]; + default: + KUNIT_FAIL_AND_ABORT(test, "Missing output type: %d", output_type); + } +} + +static const struct link_config_set * +get_fallback_configs_for_output_type(struct kunit *test, + enum intel_output_type output_type) +{ + switch (output_type) { + case INTEL_OUTPUT_DDI: + case INTEL_OUTPUT_DP: + case INTEL_OUTPUT_EDP: + return &standard_dp_link_configs[INTEL_DP_LINK_CAPS_ORDER_KEY_LANE_RATE]; + case INTEL_OUTPUT_DP_MST: + return &standard_dp_link_configs[INTEL_DP_LINK_CAPS_ORDER_KEY_BW]; + default: + KUNIT_FAIL_AND_ABORT(test, "Missing output type: %d", output_type); + } +} + +static bool output_type_allows_uhbr_fallback(enum intel_output_type output_type) +{ + return output_type == INTEL_OUTPUT_DP_MST; +} + +static void assert_config_is_supported(const struct test_config_table *expected_table, + const struct intel_dp_link_config *config) +{ + struct kunit *test = expected_table->test; + + KUNIT_ASSERT_TRUE(test, has_rate(&expected_table->rates, config->rate)); + KUNIT_ASSERT_LE(test, config->lane_count, expected_table->max_lane_count); +} + +static bool get_fallback_config(const struct test_config_table *expected_table, + enum intel_output_type output_type, + const struct intel_dp_link_config *target_config, + struct intel_dp_link_config *fallback_config) +{ + struct kunit *test = expected_table->test; + const struct link_config_set *config_set = + get_fallback_configs_for_output_type(test, output_type); + int i; + + i = lookup_config(config_set, target_config); + KUNIT_ASSERT_GE(test, i, 0); + + for (i--; i >= 0; i--) { + const struct intel_dp_link_config *config = + &config_set->entries[i]; + + if (output_type_allows_uhbr_fallback(output_type) || + (drm_dp_is_uhbr_rate(target_config->rate) == + drm_dp_is_uhbr_rate(config->rate))) { + assert_config_is_supported(expected_table, config); + *fallback_config = *config; + + return true; + } + } + + return false; +} + +static bool get_target_config(const struct test_config_table *expected_table, + enum intel_output_type output_type, + int max_rate, + struct intel_dp_link_config *target) +{ + struct kunit *test = expected_table->test; + const struct link_config_set *config_set = + get_target_configs_for_output_type(test, output_type); + int i; + + for (i = config_set->size - 1; i >= 0; i--) { + const struct intel_dp_link_config *config = + &config_set->entries[i]; + + if (config->rate <= max_rate) { + assert_config_is_supported(expected_table, config); + *target = *config; + + return true; + } + } + + return false; +} + +static void test_fallback_seq(struct kunit *test, + enum intel_output_type output_type, + bool uhbr) +{ + struct test_ctx *ctx = test->priv; + struct intel_dp_link_caps *link_caps = ctx->dev.dig_port.dp.link.caps; + struct test_config_table expected_table = + INIT_STANDARD_TABLE(test, LINK_TEST_NUM_STANDARD_RATES, + LINK_TEST_MAX_LANE_COUNT); + struct intel_digital_port *dig_port = &ctx->dev.dig_port; + struct intel_dp_link_config fallback_config = {}; + struct intel_dp_link_config target_config; + int fallback_count = 0; + bool target_found; + int max_rate; + + if (uhbr) + max_rate = expected_table.rates.entries[expected_table.rates.size - 1]; + else + max_rate = 810000; + + dig_port->base.type = output_type; + ctx->dev.dig_port.dp.use_max_params = false; + + update_link_caps_and_verify(&expected_table, link_caps, true); + + /* Get the initial target config. */ + target_found = get_target_config(&expected_table, output_type, + max_rate, &target_config); + KUNIT_ASSERT_TRUE(test, target_found); + + for (;;) { + /* Also test the case where no fallback is available. */ + if (!get_fallback_config(&expected_table, output_type, + &target_config, &fallback_config)) + fallback_config = INTEL_DP_LINK_CONFIG_NULL; + + if (!test_fallback_from_target(&expected_table, output_type, max_rate, + &target_config, &fallback_config)) + break; + + /* + * The fallback changed the max rate allowed for the next + * target. + */ + max_rate = fallback_config.rate; + + /* Simply select the fallback config as the next target. */ + target_config = fallback_config; + + fallback_count++; + KUNIT_ASSERT_LT(test, fallback_count, LINK_TEST_MAX_CONFIGS); + } +} + +static void intel_dp_link_test_fallback_for_sst_max_non_uhbr(struct kunit *test) +{ + test_fallback_seq(test, INTEL_OUTPUT_DP, false); +} + +static void intel_dp_link_test_fallback_for_sst_max_uhbr(struct kunit *test) +{ + test_fallback_seq(test, INTEL_OUTPUT_DP, true); +} + +static void intel_dp_link_test_fallback_for_mst(struct kunit *test) +{ + struct test_ctx *ctx = test->priv; + + ctx->dev.connector.mst.dp = &ctx->dev.dig_port.dp; + + test_fallback_seq(test, INTEL_OUTPUT_DP_MST, true); +} + static struct kunit_case intel_dp_link_test_cases[] = { KUNIT_CASE(intel_dp_link_caps_test_baseline), @@ -1049,6 +1360,11 @@ static struct kunit_case intel_dp_link_test_cases[] = { KUNIT_CASE(intel_dp_link_caps_test_update_params_expand_random), KUNIT_CASE(intel_dp_link_caps_test_update_params_expand_disable_random), + KUNIT_CASE(intel_dp_link_test_fallback_for_edp), + KUNIT_CASE(intel_dp_link_test_fallback_for_sst_max_non_uhbr), + KUNIT_CASE(intel_dp_link_test_fallback_for_sst_max_uhbr), + KUNIT_CASE(intel_dp_link_test_fallback_for_mst), + {} }; From 0c59cc78241c10e5f02d92b28d811b0435e706a7 Mon Sep 17 00:00:00 2001 From: Vidya Srinivas Date: Thu, 18 Jun 2026 23:48:37 +0530 Subject: [PATCH 47/93] drm/i915/display: Fix NV12 ceiling division for bigjoiner case Commit 16df4cc63c58 ("drm/i915/display: Use ceiling division for NV12 UV surface offset calculation") computes the UV (chroma) surface start/size as ceiling(half of Y plane start/size) directly from the U16.16 fixed-point source rectangle: x = fp_16_16_to_int_ceil(fp_16_16_div2(src.x1)); For a single pipe the source coordinates are integers, so this is correct. (UV start = ceiling(half of Y plane start)). With bigjoiner + a plane scaler the picture changes. The pipe boundary is a fixed integer destination pixel, but the plane's position and the scaler ratio are arbitrary, so drm_rect_clip_scaled() maps the seam back to a *fractional* per-pipe source. For a 1280->2407 upscaled NV12 plane crossing the seam: master src: width = 1204 * 1280/2407 = 640.265899, x1 = 0 joiner src: width = 1203 * 1280/2407 = 639.734115, x1 = 640.265884 The luma path floors this to an integer (src.x1 >> 16 = 640), but the UV path takes ceiling(640.265884 / 2) = ceil(320.13) = 321. The Y plane then starts at column 640 while the UV plane starts at 321*2 = 642, pushing the chroma read one column past the 640-wide chroma surface on the joiner secondary: [CRTC:382:pipe C] PLANE ATS fault [CRTC:382:pipe C][PLANE:267:plane 1C] fault (CTL=0x81009400, ...) The spec "Y plane start" is the integer pixel the luma surface actually programs (640), not the pre-floor fixed-point value (640.27). Convert the Y plane start/size to integer first - matching skl_check_main_surface() - and then apply the ceiling. This is a no-op for the integer (non-joiner) case and yields the correct, in-bounds chroma offset for the fractional joiner seam: before fix after fix master 1B: x=0 w=321 x=0 w=320 -> [0, 320) slave 1C: x=321 w=320 x=320 w=320 -> [320, 640) The two halves now tile the 640-wide chroma plane exactly and the ATS fault is gone. Assisted-by: GitHub-Copilot:Claude-Opus-4.8 Fixes: 16df4cc63c58 ("drm/i915/display: Use ceiling division for NV12 UV surface offset calculation") Signed-off-by: Vidya Srinivas Reviewed-by: Juha-Pekka Heikkila Signed-off-by: Uma Shankar Link: https://patch.msgid.link/20260618181837.687302-1-vidya.srinivas@intel.com --- .../drm/i915/display/skl_universal_plane.c | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.c b/drivers/gpu/drm/i915/display/skl_universal_plane.c index ad4bfff6903d..164b7d61c9a3 100644 --- a/drivers/gpu/drm/i915/display/skl_universal_plane.c +++ b/drivers/gpu/drm/i915/display/skl_universal_plane.c @@ -2126,19 +2126,6 @@ static int skl_check_main_surface(struct intel_plane_state *plane_state) return 0; } - -/* Divide a U16.16 fixed-point value by 2, staying in fixed-point domain */ -static inline u32 fp_16_16_div2(u32 fp) -{ - return fp >> 1; -} - -/* Convert a U16.16 fixed-point value to integer, rounding up */ -static inline int fp_16_16_to_int_ceil(u32 fp) -{ - return DIV_ROUND_UP(fp, 1 << 16); -} - static int skl_check_nv12_aux_surface(struct intel_plane_state *plane_state) { struct intel_display *display = to_intel_display(plane_state); @@ -2154,14 +2141,20 @@ static int skl_check_nv12_aux_surface(struct intel_plane_state *plane_state) int max_height = intel_plane_max_height(plane, fb, uv_plane, rotation); /* - * LNL+ UV surface start/size = - * ceiling(half of Y plane start/size). Use ceiling division - * unconditionally; it is a no-op for even values. + * UV (chroma) start/size = ceiling(half of the *integer* Y plane + * start/size), i.e. the value the luma surface programs (src >> 16), + * not the raw U16.16. A bigjoiner seam mapped through the scaler can + * give a fractional luma src; ceiling that directly would round the + * chroma one column too far and read past the chroma surface. */ - int x = fp_16_16_to_int_ceil(fp_16_16_div2(plane_state->uapi.src.x1)); - int y = fp_16_16_to_int_ceil(fp_16_16_div2(plane_state->uapi.src.y1)); - int w = fp_16_16_to_int_ceil(fp_16_16_div2(drm_rect_width(&plane_state->uapi.src))); - int h = fp_16_16_to_int_ceil(fp_16_16_div2(drm_rect_height(&plane_state->uapi.src))); + int luma_x = plane_state->uapi.src.x1 >> 16; + int luma_y = plane_state->uapi.src.y1 >> 16; + int luma_w = drm_rect_width(&plane_state->uapi.src) >> 16; + int luma_h = drm_rect_height(&plane_state->uapi.src) >> 16; + int x = DIV_ROUND_UP(luma_x, 2); + int y = DIV_ROUND_UP(luma_y, 2); + int w = DIV_ROUND_UP(luma_x + luma_w, 2) - x; + int h = DIV_ROUND_UP(luma_y + luma_h, 2) - y; u32 offset; /* FIXME not quite sure how/if these apply to the chroma plane */ From 466fa6902e6a6f52e9693f6fb3102179e4272cc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Tue, 12 May 2026 00:41:09 +0300 Subject: [PATCH 48/93] drm/i915: Disable the plane if initial plane config readout failed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Properly turn off the plane if it is enabled but .get_initial_plane_config() failed for whatever reason. The hardware does (or at least did) perform some kind of automagic plane disable when the pipe gets disabled, but we don't rely on that anywhere else either. Also the GGTT/actual memory may get clobbered afterwards, so leaving the plane enabled here could result in visual corruption/GTT faults/etc. Signed-off-by: Ville Syrjälä Reviewed-by: Jani Nikula Link: https://patch.msgid.link/20260511214122.8468-2-ville.syrjala@linux.intel.com Signed-off-by: Maarten Lankhorst --- drivers/gpu/drm/i915/display/intel_initial_plane.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_initial_plane.c b/drivers/gpu/drm/i915/display/intel_initial_plane.c index e414b5d1085c..7427f41e4d7e 100644 --- a/drivers/gpu/drm/i915/display/intel_initial_plane.c +++ b/drivers/gpu/drm/i915/display/intel_initial_plane.c @@ -128,11 +128,10 @@ intel_find_initial_plane_obj(struct intel_crtc *crtc, /* * TODO: - * Disable planes if get_initial_plane_config() failed. * Make sure things work if the surface base is not page aligned. */ if (!plane_config->fb) - return; + goto nofb; if (intel_alloc_initial_plane_obj(display, plane_config)) { fb = plane_config->fb; @@ -184,7 +183,8 @@ intel_find_initial_plane_obj(struct intel_crtc *crtc, * simplest solution is to just disable the primary plane now and * pretend the BIOS never had it enabled. */ - intel_plane_disable_noatomic(crtc, plane); + if (plane_state->uapi.visible) + intel_plane_disable_noatomic(crtc, plane); } static void plane_config_fini(struct intel_display *display, From b2a2c620404ecb3b9a9706bf438d788ca87a41ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Tue, 12 May 2026 00:41:10 +0300 Subject: [PATCH 49/93] drm/i915/fbdev: Extract bios_fb_ok() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pull the "is the BIOS FB OK?" checks to a helper function. We'll add other relevant checks there later. Signed-off-by: Ville Syrjälä Reviewed-by: Jani Nikula Link: https://patch.msgid.link/20260511214122.8468-3-ville.syrjala@linux.intel.com Signed-off-by: Maarten Lankhorst --- drivers/gpu/drm/i915/display/intel_fbdev.c | 26 +++++++++++++++------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_fbdev.c b/drivers/gpu/drm/i915/display/intel_fbdev.c index c8d4e3a5ce6b..bc267776106e 100644 --- a/drivers/gpu/drm/i915/display/intel_fbdev.c +++ b/drivers/gpu/drm/i915/display/intel_fbdev.c @@ -262,6 +262,23 @@ __intel_fbdev_fb_alloc(struct intel_display *display, } +static bool bios_fb_ok(const struct intel_framebuffer *fb, + const struct drm_fb_helper_surface_size *sizes) +{ + struct intel_display *display = to_intel_display(fb->base.dev); + int width = fb->base.width; + int height = fb->base.height; + + if (sizes->fb_width > width || sizes->fb_height > height) { + drm_dbg_kms(display->drm, + "BIOS fb too small (%dx%d), we require (%dx%d), releasing it\n", + width, height, sizes->fb_width, sizes->fb_height); + return false; + } + + return true; +} + int intel_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes) { @@ -279,14 +296,7 @@ int intel_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, ifbdev->fb = NULL; - if (fb && - (sizes->fb_width > fb->base.width || - sizes->fb_height > fb->base.height)) { - drm_dbg_kms(display->drm, - "BIOS fb too small (%dx%d), we require (%dx%d)," - " releasing it\n", - fb->base.width, fb->base.height, - sizes->fb_width, sizes->fb_height); + if (fb && !bios_fb_ok(fb, sizes)) { drm_framebuffer_put(&fb->base); fb = NULL; } From d6a1bc7c9b4ce71674a633242251d8a804ced82d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Tue, 12 May 2026 00:41:11 +0300 Subject: [PATCH 50/93] drm/i915: Throw away the BIOS fb if has the wrong depth/bpp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Respect the user's choice of depth/bpp for the fbdev framebuffer and throw out the fb we inherited from the BIOS if it doesn't match. Signed-off-by: Ville Syrjälä Reviewed-by: Jani Nikula Link: https://patch.msgid.link/20260511214122.8468-4-ville.syrjala@linux.intel.com Signed-off-by: Maarten Lankhorst --- drivers/gpu/drm/i915/display/intel_fbdev.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/gpu/drm/i915/display/intel_fbdev.c b/drivers/gpu/drm/i915/display/intel_fbdev.c index bc267776106e..db0e36dd8722 100644 --- a/drivers/gpu/drm/i915/display/intel_fbdev.c +++ b/drivers/gpu/drm/i915/display/intel_fbdev.c @@ -268,6 +268,8 @@ static bool bios_fb_ok(const struct intel_framebuffer *fb, struct intel_display *display = to_intel_display(fb->base.dev); int width = fb->base.width; int height = fb->base.height; + int depth = fb->base.format->depth; + int bpp = fb->base.format->cpp[0] * 8; if (sizes->fb_width > width || sizes->fb_height > height) { drm_dbg_kms(display->drm, @@ -276,6 +278,13 @@ static bool bios_fb_ok(const struct intel_framebuffer *fb, return false; } + if (sizes->surface_depth != depth || sizes->surface_bpp != bpp) { + drm_dbg_kms(display->drm, + "BIOS fb using wrong depth/bpp (%d/%d), we require (%d/%d), releasing it\n", + depth, bpp, sizes->surface_depth, sizes->surface_bpp); + return false; + } + return true; } From 3df420377411fbdf154ab5029c9f4e5d29f4207f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Tue, 12 May 2026 00:41:13 +0300 Subject: [PATCH 51/93] drm/i915: Use drm_dbg_kms() for initial FB debugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The initial FB stuff is ultimately about display stuff, so use the proper display specific debug level for it. Signed-off-by: Ville Syrjälä Reviewed-by: Jani Nikula Link: https://patch.msgid.link/20260511214122.8468-6-ville.syrjala@linux.intel.com Signed-off-by: Maarten Lankhorst --- drivers/gpu/drm/i915/i915_initial_plane.c | 4 ++-- drivers/gpu/drm/xe/display/xe_initial_plane.c | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/i915/i915_initial_plane.c b/drivers/gpu/drm/i915/i915_initial_plane.c index 4902c0748664..08d94f3a1584 100644 --- a/drivers/gpu/drm/i915/i915_initial_plane.c +++ b/drivers/gpu/drm/i915/i915_initial_plane.c @@ -69,8 +69,8 @@ initial_plane_phys(struct drm_i915_private *i915, return false; } - drm_dbg(&i915->drm, "Using dma_addr=%pa, based on initial plane programming\n", - &dma_addr); + drm_dbg_kms(&i915->drm, "Using dma_addr=%pa, based on initial plane programming\n", + &dma_addr); *out_phys_base = dma_addr - mem->region.start; *out_mem = mem; diff --git a/drivers/gpu/drm/xe/display/xe_initial_plane.c b/drivers/gpu/drm/xe/display/xe_initial_plane.c index 02b46cc3b6df..da04ee66252a 100644 --- a/drivers/gpu/drm/xe/display/xe_initial_plane.c +++ b/drivers/gpu/drm/xe/display/xe_initial_plane.c @@ -60,9 +60,9 @@ initial_plane_bo(struct xe_device *xe, return NULL; } - drm_dbg(&xe->drm, - "Using phys_base=%pa, based on initial plane programming\n", - &phys_base); + drm_dbg_kms(&xe->drm, + "Using phys_base=%pa, based on initial plane programming\n", + &phys_base); } else { struct ttm_resource_manager *stolen = ttm_manager_type(&xe->ttm, XE_PL_STOLEN); @@ -86,9 +86,9 @@ initial_plane_bo(struct xe_device *xe, bo = xe_bo_create_pin_map_at_novm(xe, tile0, size, phys_base, ttm_bo_type_kernel, flags, 0, false); if (IS_ERR(bo)) { - drm_dbg(&xe->drm, - "Failed to create bo phys_base=%pa size %u with flags %x: %li\n", - &phys_base, size, flags, PTR_ERR(bo)); + drm_dbg_kms(&xe->drm, + "Failed to create bo phys_base=%pa size %u with flags %x: %li\n", + &phys_base, size, flags, PTR_ERR(bo)); return NULL; } From 7745c857ddc866acfd141ce44f2d8d3f6535047b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Tue, 12 May 2026 00:41:14 +0300 Subject: [PATCH 52/93] drm/xe: Do the initial FB size alignment earlier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For some reason we've split the alignment of 'base' vs. 'size' to live on separate sides of the xe initial plane PTE readout. There's no reason for this split, so make things less confusing by aligning both at the same time. Signed-off-by: Ville Syrjälä Link: https://patch.msgid.link/20260511214122.8468-7-ville.syrjala@linux.intel.com Signed-off-by: Maarten Lankhorst Acked-by: Matthew Brost #teams --- drivers/gpu/drm/xe/display/xe_initial_plane.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/xe/display/xe_initial_plane.c b/drivers/gpu/drm/xe/display/xe_initial_plane.c index da04ee66252a..feb979cca12a 100644 --- a/drivers/gpu/drm/xe/display/xe_initial_plane.c +++ b/drivers/gpu/drm/xe/display/xe_initial_plane.c @@ -37,6 +37,10 @@ initial_plane_bo(struct xe_device *xe, flags = XE_BO_FLAG_FORCE_WC | XE_BO_FLAG_GGTT; base = round_down(plane_config->base, page_size); + size = round_up(plane_config->base + plane_config->size, + page_size); + size -= base; + if (IS_DGFX(xe)) { u64 pte = xe_ggtt_read_pte(tile0->mem.ggtt, base); @@ -79,10 +83,6 @@ initial_plane_bo(struct xe_device *xe, } } - size = round_up(plane_config->base + plane_config->size, - page_size); - size -= base; - bo = xe_bo_create_pin_map_at_novm(xe, tile0, size, phys_base, ttm_bo_type_kernel, flags, 0, false); if (IS_ERR(bo)) { From 1d2a6c96f6f701be117bdba9ac77d7b12949144d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Tue, 12 May 2026 00:41:16 +0300 Subject: [PATCH 53/93] drm/xe: Print a debug message if we have no stolen for the initial FB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inform the poor sop reading the logs why the initial FB was rejected if there is no stolen memory. Technically this should perhaps be an error since the plane is known to be enabled at this point, and if there is no stolen then it clearly can't be scanning out from anywhere. But maybe there are some virtualization passthrough cases and whatnot where we might not be able to get access to stolen, so keep it as debug (same as i915). Signed-off-by: Ville Syrjälä Reviewed-by: Jani Nikula Link: https://patch.msgid.link/20260511214122.8468-9-ville.syrjala@linux.intel.com Signed-off-by: Maarten Lankhorst Acked-by: Matthew Brost #teams --- drivers/gpu/drm/xe/display/xe_initial_plane.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/display/xe_initial_plane.c b/drivers/gpu/drm/xe/display/xe_initial_plane.c index feb979cca12a..a62e5394c3f7 100644 --- a/drivers/gpu/drm/xe/display/xe_initial_plane.c +++ b/drivers/gpu/drm/xe/display/xe_initial_plane.c @@ -68,10 +68,14 @@ initial_plane_bo(struct xe_device *xe, "Using phys_base=%pa, based on initial plane programming\n", &phys_base); } else { - struct ttm_resource_manager *stolen = ttm_manager_type(&xe->ttm, XE_PL_STOLEN); + struct ttm_resource_manager *stolen; - if (!stolen) + stolen = ttm_manager_type(&xe->ttm, XE_PL_STOLEN); + if (!stolen) { + drm_dbg_kms(&xe->drm, "No stolen for initial FB\n"); return NULL; + } + phys_base = base; flags |= XE_BO_FLAG_STOLEN; From 1be929d430b5127916fea059d5fa0467f1535175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Tue, 12 May 2026 00:41:17 +0300 Subject: [PATCH 54/93] drm/xe: Abstract the initial FB PTE checks a bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a few helpers that allow us to abstract the xe initial FB PTE check a bit. Still very ad-hoc compared to the nicely abstracted i915 counterpart, but whatever. Signed-off-by: Ville Syrjälä Reviewed-by: Jani Nikula Link: https://patch.msgid.link/20260511214122.8468-10-ville.syrjala@linux.intel.com Signed-off-by: Maarten Lankhorst Acked-by: Matthew Brost #teams --- drivers/gpu/drm/xe/display/xe_initial_plane.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/xe/display/xe_initial_plane.c b/drivers/gpu/drm/xe/display/xe_initial_plane.c index a62e5394c3f7..1e1962c95589 100644 --- a/drivers/gpu/drm/xe/display/xe_initial_plane.c +++ b/drivers/gpu/drm/xe/display/xe_initial_plane.c @@ -19,8 +19,19 @@ #include "xe_fb_pin.h" #include "xe_ggtt.h" #include "xe_mmio.h" +#include "xe_ttm_stolen_mgr.h" #include "xe_vram_types.h" +static bool is_pte_local(u64 pte) +{ + return pte & XE_GGTT_PTE_DM; +} + +static bool need_pte_local(struct xe_device *xe) +{ + return IS_DGFX(xe); +} + static struct xe_bo * initial_plane_bo(struct xe_device *xe, struct intel_initial_plane_config *plane_config) @@ -44,13 +55,13 @@ initial_plane_bo(struct xe_device *xe, if (IS_DGFX(xe)) { u64 pte = xe_ggtt_read_pte(tile0->mem.ggtt, base); - if (!(pte & XE_GGTT_PTE_DM)) { - drm_err(&xe->drm, - "Initial plane programming missing DM bit\n"); + if (is_pte_local(pte) != need_pte_local(xe)) { + drm_err(&xe->drm, "Initial plane PTE has bad local memory bit\n"); return NULL; } phys_base = pte & ~(page_size - 1); + flags |= XE_BO_FLAG_VRAM0; /* From 0d5d5d0c2c72f4881a116c3e29aa15e3658bb3ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Tue, 12 May 2026 00:41:18 +0300 Subject: [PATCH 55/93] drm/xe: Check the PTE local memory bit for initial FB in stolen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Do the PTE local memory bit check also for the case when the initial FB lives in stolen. We have two cases to worry about here: MTL+ with LMEMBAR, and pre-MTL with stolen being just (slightly special) physical memory. Signed-off-by: Ville Syrjälä Link: https://patch.msgid.link/20260511214122.8468-11-ville.syrjala@linux.intel.com Signed-off-by: Maarten Lankhorst Acked-by: Matthew Brost #teams --- drivers/gpu/drm/xe/display/xe_initial_plane.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/display/xe_initial_plane.c b/drivers/gpu/drm/xe/display/xe_initial_plane.c index 1e1962c95589..0f86b73036d0 100644 --- a/drivers/gpu/drm/xe/display/xe_initial_plane.c +++ b/drivers/gpu/drm/xe/display/xe_initial_plane.c @@ -27,9 +27,14 @@ static bool is_pte_local(u64 pte) return pte & XE_GGTT_PTE_DM; } +static bool has_lmembar(struct xe_device *xe) +{ + return GRAPHICS_VERx100(xe) >= 1270; +} + static bool need_pte_local(struct xe_device *xe) { - return IS_DGFX(xe); + return IS_DGFX(xe) || has_lmembar(xe); } static struct xe_bo * @@ -80,6 +85,7 @@ initial_plane_bo(struct xe_device *xe, &phys_base); } else { struct ttm_resource_manager *stolen; + u64 pte; stolen = ttm_manager_type(&xe->ttm, XE_PL_STOLEN); if (!stolen) { @@ -87,6 +93,13 @@ initial_plane_bo(struct xe_device *xe, return NULL; } + pte = xe_ggtt_read_pte(tile0->mem.ggtt, base); + + if (is_pte_local(pte) != need_pte_local(xe)) { + drm_err(&xe->drm, "Initial plane PTE has bad local memory bit\n"); + return NULL; + } + phys_base = base; flags |= XE_BO_FLAG_STOLEN; From a7cdfbb0a44f810d593e86707b8b2fbf9b031519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Tue, 12 May 2026 00:41:19 +0300 Subject: [PATCH 56/93] drm/xe: s/bar2/lmembar/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The local memory BAR has a name (LMEMBAR). Use that instead of referring to it by its BAR register index. Signed-off-by: Ville Syrjälä Reviewed-by: Jani Nikula Link: https://patch.msgid.link/20260511214122.8468-12-ville.syrjala@linux.intel.com Signed-off-by: Maarten Lankhorst Acked-by: Matthew Brost #teams --- drivers/gpu/drm/xe/xe_ttm_stolen_mgr.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_ttm_stolen_mgr.c b/drivers/gpu/drm/xe/xe_ttm_stolen_mgr.c index 5e9070739e65..60f39efb02d0 100644 --- a/drivers/gpu/drm/xe/xe_ttm_stolen_mgr.c +++ b/drivers/gpu/drm/xe/xe_ttm_stolen_mgr.c @@ -61,7 +61,7 @@ static u32 get_wopcm_size(struct xe_device *xe) return wopcm_size; } -static u64 detect_bar2_dgfx(struct xe_device *xe, struct xe_ttm_stolen_mgr *mgr) +static u64 detect_lmembar_dgfx(struct xe_device *xe, struct xe_ttm_stolen_mgr *mgr) { struct xe_vram_region *tile_vram = xe_device_get_root_tile(xe)->mem.vram; resource_size_t tile_io_start = xe_vram_region_io_start(tile_vram); @@ -102,7 +102,7 @@ static u64 detect_bar2_dgfx(struct xe_device *xe, struct xe_ttm_stolen_mgr *mgr) return ALIGN_DOWN(stolen_size, SZ_1M); } -static u32 detect_bar2_integrated(struct xe_device *xe, struct xe_ttm_stolen_mgr *mgr) +static u32 detect_lmembar_integrated(struct xe_device *xe, struct xe_ttm_stolen_mgr *mgr) { struct pci_dev *pdev = to_pci_dev(xe->drm.dev); struct xe_gt *media_gt = xe_device_get_root_tile(xe)->media_gt; @@ -212,9 +212,9 @@ int xe_ttm_stolen_mgr_init(struct xe_device *xe) if (IS_SRIOV_VF(xe)) stolen_size = 0; else if (IS_DGFX(xe)) - stolen_size = detect_bar2_dgfx(xe, mgr); + stolen_size = detect_lmembar_dgfx(xe, mgr); else if (GRAPHICS_VERx100(xe) >= 1270) - stolen_size = detect_bar2_integrated(xe, mgr); + stolen_size = detect_lmembar_integrated(xe, mgr); else stolen_size = detect_stolen(xe, mgr); @@ -262,9 +262,9 @@ u64 xe_ttm_stolen_io_offset(struct xe_bo *bo, u32 offset) return mgr->io_base + (bo->ttm.resource->start << PAGE_SHIFT) + offset; } -static int __xe_ttm_stolen_io_mem_reserve_bar2(struct xe_device *xe, - struct xe_ttm_stolen_mgr *mgr, - struct ttm_resource *mem) +static int __xe_ttm_stolen_io_mem_reserve_lmembar(struct xe_device *xe, + struct xe_ttm_stolen_mgr *mgr, + struct ttm_resource *mem) { if (!mgr->io_base) return -EIO; @@ -321,7 +321,7 @@ int xe_ttm_stolen_io_mem_reserve(struct xe_device *xe, struct ttm_resource *mem) if (xe_ttm_stolen_cpu_access_needs_ggtt(xe)) return __xe_ttm_stolen_io_mem_reserve_stolen(xe, mgr, mem); else - return __xe_ttm_stolen_io_mem_reserve_bar2(xe, mgr, mem); + return __xe_ttm_stolen_io_mem_reserve_lmembar(xe, mgr, mem); } u64 xe_ttm_stolen_gpu_offset(struct xe_device *xe) From 4a9d8c1386aab939b45aa6912f8e4799163792fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Tue, 12 May 2026 00:41:21 +0300 Subject: [PATCH 57/93] drm/i915: Fix BIOS FB memory region name debug prints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apparently we never initialize the name of the struct resource underlying the memory region. Instead we need to look at the name stored directly in the memory region itself. Signed-off-by: Ville Syrjälä Link: https://patch.msgid.link/20260511214122.8468-14-ville.syrjala@linux.intel.com Signed-off-by: Maarten Lankhorst --- drivers/gpu/drm/i915/i915_initial_plane.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/i915/i915_initial_plane.c b/drivers/gpu/drm/i915/i915_initial_plane.c index 08d94f3a1584..549fe1dc5b6e 100644 --- a/drivers/gpu/drm/i915/i915_initial_plane.c +++ b/drivers/gpu/drm/i915/i915_initial_plane.c @@ -58,14 +58,14 @@ initial_plane_phys(struct drm_i915_private *i915, if (intel_memory_type_is_local(mem->type) != is_local) { drm_err(&i915->drm, "Initial plane FB PTE unsuitable for %s\n", - mem->region.name); + mem->name); return false; } if (dma_addr < mem->region.start || dma_addr > mem->region.end) { drm_err(&i915->drm, "Initial plane programming using invalid range, dma_addr=%pa (%s [%pa-%pa])\n", - &dma_addr, mem->region.name, &mem->region.start, &mem->region.end); + &dma_addr, mem->name, &mem->region.start, &mem->region.end); return false; } @@ -120,7 +120,7 @@ initial_plane_vma(struct drm_i915_private *i915, I915_BO_PREALLOC); if (IS_ERR(obj)) { drm_dbg_kms(&i915->drm, "Failed to preallocate initial FB in %s\n", - mem->region.name); + mem->name); return NULL; } From 32fa83c255df8a40d5ab5ba777578b7537d47588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Tue, 12 May 2026 00:41:22 +0300 Subject: [PATCH 58/93] drm/i915: Print the phys_base in addition to the dma_addr for the BIOS FB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Print the dma_addr, phys_base and memory region name for the BIOS FB. Should make it a bit easier to see whether everything looks correct or not. Signed-off-by: Ville Syrjälä Link: https://patch.msgid.link/20260511214122.8468-15-ville.syrjala@linux.intel.com Signed-off-by: Maarten Lankhorst --- drivers/gpu/drm/i915/i915_initial_plane.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/i915/i915_initial_plane.c b/drivers/gpu/drm/i915/i915_initial_plane.c index 549fe1dc5b6e..98858a78e46a 100644 --- a/drivers/gpu/drm/i915/i915_initial_plane.c +++ b/drivers/gpu/drm/i915/i915_initial_plane.c @@ -69,12 +69,12 @@ initial_plane_phys(struct drm_i915_private *i915, return false; } - drm_dbg_kms(&i915->drm, "Using dma_addr=%pa, based on initial plane programming\n", - &dma_addr); - *out_phys_base = dma_addr - mem->region.start; *out_mem = mem; + drm_dbg_kms(&i915->drm, "Initial plane dma_addr=%pa phys_base=%pa mem=%s\n", + &dma_addr, out_phys_base, mem->name); + return true; } From a196406a3831291598fe8e73245914f7acffdfe0 Mon Sep 17 00:00:00 2001 From: Maarten Lankhorst Date: Tue, 30 Jun 2026 15:55:20 +0200 Subject: [PATCH 59/93] drm/xe: Fix DPT allocation paths. Remove the fallback for VRAM to system memory, I tested it and that doesn't work at all, only a black screen with pipe fault errors were observed. On systems with media GT, extra latency is added when accessing stolen memory when the GT is in MC6. Since we additionally aren't counting how much memory is used for stolen and we could in theory fill up the entire stolen area with DPT's, avoid using stolen and only use the default memory region. Using stolen may also result in random system hangs under load. Link: https://gitlab.freedesktop.org/drm/xe/kernel/-/work_items/7513 Fixes: 775d0adc01a5 ("drm/xe/fbdev: Limit the usage of stolen for LNL+") Cc: # v6.12+ Reviewed-by: Matthew Auld Link: https://patch.msgid.link/20260630135523.1775379-2-dev@lankhorst.se Signed-off-by: Maarten Lankhorst Acked-by: Matthew Brost #teams --- drivers/gpu/drm/xe/display/xe_fb_pin.c | 33 +++++++------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/drivers/gpu/drm/xe/display/xe_fb_pin.c b/drivers/gpu/drm/xe/display/xe_fb_pin.c index f93c98bec5b5..86013ef532fd 100644 --- a/drivers/gpu/drm/xe/display/xe_fb_pin.c +++ b/drivers/gpu/drm/xe/display/xe_fb_pin.c @@ -164,31 +164,14 @@ static int __xe_pin_fb_vma_dpt(struct drm_gem_object *obj, dpt_size = ALIGN(intel_rotation_info_size(&view->rotated) * 8, XE_PAGE_SIZE); - if (IS_DGFX(xe)) - dpt = xe_bo_create_pin_map_at_novm(xe, tile0, - dpt_size, ~0ull, - ttm_bo_type_kernel, - XE_BO_FLAG_VRAM0 | - XE_BO_FLAG_GGTT | - XE_BO_FLAG_PAGETABLE, - pin_params->alignment, false); - else - dpt = xe_bo_create_pin_map_at_novm(xe, tile0, - dpt_size, ~0ull, - ttm_bo_type_kernel, - XE_BO_FLAG_STOLEN | - XE_BO_FLAG_GGTT | - XE_BO_FLAG_PAGETABLE, - pin_params->alignment, false); - if (IS_ERR(dpt)) - dpt = xe_bo_create_pin_map_at_novm(xe, tile0, - dpt_size, ~0ull, - ttm_bo_type_kernel, - XE_BO_FLAG_SYSTEM | - XE_BO_FLAG_GGTT | - XE_BO_FLAG_PAGETABLE | - XE_BO_FLAG_FORCE_WC, - pin_params->alignment, false); + dpt = xe_bo_create_pin_map_at_novm(xe, tile0, + dpt_size, ~0ull, + ttm_bo_type_kernel, + XE_BO_FLAG_VRAM_IF_DGFX(tile0) | + XE_BO_FLAG_GGTT | + XE_BO_FLAG_PAGETABLE | + XE_BO_FLAG_FORCE_WC, + pin_params->alignment, false); if (IS_ERR(dpt)) return PTR_ERR(dpt); From 0687ec06f51b8037e79940e7af4374b5ff84e82d Mon Sep 17 00:00:00 2001 From: Maarten Lankhorst Date: Tue, 30 Jun 2026 15:55:22 +0200 Subject: [PATCH 60/93] drm/xe/display: Do not allocate into stolen for new framebuffers. Prefer to use system memory for global framebuffers, and reserve the space for FBC use only. Now that multiple CRTC's can use FBC's, the simple heuristic of using less than half of stolen is no longer sufficient. Additionally, there are reports of system hangs when using stolen memory, and there are also various workarounds that are avoided by using system memory instead. Link: https://gitlab.freedesktop.org/drm/xe/kernel/-/work_items/7513 Reviewed-by: Matthew Auld Link: https://patch.msgid.link/20260630135523.1775379-4-dev@lankhorst.se Signed-off-by: Maarten Lankhorst Acked-by: Matthew Brost #teams --- drivers/gpu/drm/xe/display/xe_display_bo.c | 33 ++++------------------ 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/drivers/gpu/drm/xe/display/xe_display_bo.c b/drivers/gpu/drm/xe/display/xe_display_bo.c index 7fbac223b097..a6b5b88de9cc 100644 --- a/drivers/gpu/drm/xe/display/xe_display_bo.c +++ b/drivers/gpu/drm/xe/display/xe_display_bo.c @@ -146,33 +146,12 @@ static struct drm_gem_object *xe_display_bo_fbdev_create(struct drm_device *drm, struct xe_device *xe = to_xe_device(drm); struct xe_bo *obj; - obj = ERR_PTR(-ENODEV); - - if (xe_display_bo_fbdev_prefer_stolen(xe, size)) { - obj = xe_bo_create_pin_map_novm(xe, xe_device_get_root_tile(xe), - size, - ttm_bo_type_kernel, - XE_BO_FLAG_FORCE_WC | - XE_BO_FLAG_STOLEN | - XE_BO_FLAG_GGTT, - false); - if (!IS_ERR(obj)) - drm_info(&xe->drm, "Allocated fbdev into stolen\n"); - else - drm_info(&xe->drm, "Allocated fbdev into stolen failed: %li\n", PTR_ERR(obj)); - } else { - drm_info(&xe->drm, "Allocating fbdev: Stolen memory not preferred.\n"); - } - - if (IS_ERR(obj)) { - obj = xe_bo_create_pin_map_novm(xe, xe_device_get_root_tile(xe), size, - ttm_bo_type_kernel, - XE_BO_FLAG_FORCE_WC | - XE_BO_FLAG_VRAM_IF_DGFX(xe_device_get_root_tile(xe)) | - XE_BO_FLAG_GGTT, - false); - } - + obj = xe_bo_create_pin_map_novm(xe, xe_device_get_root_tile(xe), size, + ttm_bo_type_kernel, + XE_BO_FLAG_FORCE_WC | + XE_BO_FLAG_VRAM_IF_DGFX(xe_device_get_root_tile(xe)) | + XE_BO_FLAG_GGTT, + false); if (IS_ERR(obj)) { drm_err(&xe->drm, "failed to allocate framebuffer (%pe)\n", obj); return ERR_PTR(-ENOMEM); From f7768b46b980ba263a39515a8d9e60ef258dc786 Mon Sep 17 00:00:00 2001 From: John Harrison Date: Mon, 13 Jul 2026 16:41:38 -0700 Subject: [PATCH 61/93] drm/i915/display: Fix too few bits in transcoder mask variables New transcoder enum values (for CMTG) were recently added which pushed the maximum transcoder mask beyond 8bits. The patch in question updated the info structure's u8 to u16 but not any of the functions that process transcoder masks. So fix those as well. v2: Fix more instances (found by Sashiko) Signed-off-by: John Harrison Fixes: 789dda6429e0 ("drm/i915/cmtg: Add CMTG transcoder offset in struct _device_info") Cc: Uma Shankar Cc: Animesh Manna Cc: Jani Nikula Cc: Rodrigo Vivi Cc: intel-gfx@lists.freedesktop.org Cc: intel-xe@lists.freedesktop.org Reviewed-by: Uma Shankar Link: https://patch.msgid.link/20260713234138.3861243-1-John.Harrison@Igalia.com Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/i915/display/intel_ddi.c | 6 ++--- drivers/gpu/drm/i915/display/intel_display.c | 22 +++++++++---------- .../drm/i915/display/intel_display_types.h | 2 +- drivers/gpu/drm/i915/display/intel_dp.c | 4 ++-- drivers/gpu/drm/i915/display/intel_dp_mst.c | 2 +- .../drm/i915/display/intel_modeset_setup.c | 18 +++++++-------- 6 files changed, 27 insertions(+), 27 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_ddi.c b/drivers/gpu/drm/i915/display/intel_ddi.c index 2b7eb010511b..617106c68cb4 100644 --- a/drivers/gpu/drm/i915/display/intel_ddi.c +++ b/drivers/gpu/drm/i915/display/intel_ddi.c @@ -4563,7 +4563,7 @@ static bool crtcs_port_sync_compatible(const struct intel_crtc_state *crtc_state m_n_equal(&crtc_state1->dp_m_n, &crtc_state2->dp_m_n); } -static u8 +static u16 intel_ddi_port_sync_transcoders(const struct intel_crtc_state *ref_crtc_state, int tile_group_id) { @@ -4572,7 +4572,7 @@ intel_ddi_port_sync_transcoders(const struct intel_crtc_state *ref_crtc_state, const struct drm_connector_state *conn_state; struct intel_atomic_state *state = to_intel_atomic_state(ref_crtc_state->uapi.state); - u8 transcoders = 0; + u16 transcoders = 0; int i; /* @@ -4616,7 +4616,7 @@ static int intel_ddi_compute_config_late(struct intel_atomic_state *state, { struct intel_display *display = to_intel_display(encoder); struct drm_connector *connector = conn_state->connector; - u8 port_sync_transcoders = 0; + u16 port_sync_transcoders = 0; int ret = 0; if (intel_crtc_has_dp_encoder(crtc_state)) diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c index 214454f419e9..38763a6802c5 100644 --- a/drivers/gpu/drm/i915/display/intel_display.c +++ b/drivers/gpu/drm/i915/display/intel_display.c @@ -3767,9 +3767,9 @@ static void enabled_joiner_pipes(struct intel_display *display, } } -static u8 hsw_panel_transcoders(struct intel_display *display) +static u16 hsw_panel_transcoders(struct intel_display *display) { - u8 panel_transcoder_mask = BIT(TRANSCODER_EDP); + u16 panel_transcoder_mask = BIT(TRANSCODER_EDP); if (DISPLAY_VER(display) >= 11) panel_transcoder_mask |= BIT(TRANSCODER_DSI_0) | BIT(TRANSCODER_DSI_1); @@ -3777,13 +3777,13 @@ static u8 hsw_panel_transcoders(struct intel_display *display) return panel_transcoder_mask; } -static u8 hsw_enabled_transcoders(struct intel_crtc *crtc) +static u16 hsw_enabled_transcoders(struct intel_crtc *crtc) { struct intel_display *display = to_intel_display(crtc); - u8 panel_transcoder_mask = hsw_panel_transcoders(display); + u16 panel_transcoder_mask = hsw_panel_transcoders(display); enum transcoder cpu_transcoder; u8 primary_pipe, secondary_pipes; - u8 enabled_transcoders = 0; + u16 enabled_transcoders = 0; /* * XXX: Do intel_display_power_get_if_enabled before reading this (for @@ -3844,18 +3844,18 @@ static u8 hsw_enabled_transcoders(struct intel_crtc *crtc) return enabled_transcoders; } -static bool has_edp_transcoders(u8 enabled_transcoders) +static bool has_edp_transcoders(u16 enabled_transcoders) { return enabled_transcoders & BIT(TRANSCODER_EDP); } -static bool has_dsi_transcoders(u8 enabled_transcoders) +static bool has_dsi_transcoders(u16 enabled_transcoders) { return enabled_transcoders & (BIT(TRANSCODER_DSI_0) | BIT(TRANSCODER_DSI_1)); } -static bool has_pipe_transcoders(u8 enabled_transcoders) +static bool has_pipe_transcoders(u16 enabled_transcoders) { return enabled_transcoders & ~(BIT(TRANSCODER_EDP) | BIT(TRANSCODER_DSI_0) | @@ -3863,7 +3863,7 @@ static bool has_pipe_transcoders(u8 enabled_transcoders) } static void assert_enabled_transcoders(struct intel_display *display, - u8 enabled_transcoders) + u16 enabled_transcoders) { /* Only one type of transcoder please */ drm_WARN_ON(display->drm, @@ -5880,7 +5880,7 @@ static int intel_atomic_check_crtcs(struct intel_atomic_state *state) } static bool intel_cpu_transcoders_need_modeset(struct intel_atomic_state *state, - u8 transcoders) + u16 transcoders) { const struct intel_crtc_state *new_crtc_state; struct intel_crtc *crtc; @@ -6516,7 +6516,7 @@ int intel_atomic_check(struct drm_device *dev, } if (is_trans_port_sync_mode(new_crtc_state)) { - u8 trans = new_crtc_state->sync_mode_slaves_mask; + u16 trans = new_crtc_state->sync_mode_slaves_mask; if (new_crtc_state->master_transcoder != INVALID_TRANSCODER) trans |= BIT(new_crtc_state->master_transcoder); diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h index c048da7d6fea..8bd213db5e7a 100644 --- a/drivers/gpu/drm/i915/display/intel_display_types.h +++ b/drivers/gpu/drm/i915/display/intel_display_types.h @@ -1372,7 +1372,7 @@ struct intel_crtc_state { enum transcoder master_transcoder; /* Bitmask to indicate slaves attached */ - u8 sync_mode_slaves_mask; + u16 sync_mode_slaves_mask; /* Only valid on TGL+ */ enum transcoder mst_master_transcoder; diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index a3c4c0973106..4d22ac8e0945 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -6509,7 +6509,7 @@ static int intel_modeset_tile_group(struct intel_atomic_state *state, return ret; } -static int intel_modeset_affected_transcoders(struct intel_atomic_state *state, u8 transcoders) +static int intel_modeset_affected_transcoders(struct intel_atomic_state *state, u16 transcoders) { struct intel_display *display = to_intel_display(state); struct intel_crtc *crtc; @@ -6557,7 +6557,7 @@ static int intel_modeset_synced_crtcs(struct intel_atomic_state *state, drm_atomic_get_old_connector_state(&state->base, &connector->base); const struct intel_crtc_state *old_crtc_state; struct intel_crtc *crtc; - u8 transcoders; + u16 transcoders; crtc = to_intel_crtc(old_conn_state->crtc); if (!crtc) diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.c b/drivers/gpu/drm/i915/display/intel_dp_mst.c index df6e54508e5d..b5af01159949 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_mst.c +++ b/drivers/gpu/drm/i915/display/intel_dp_mst.c @@ -815,7 +815,7 @@ intel_dp_mst_transcoder_mask(struct intel_atomic_state *state, struct intel_display *display = to_intel_display(state); const struct intel_digital_connector_state *conn_state; struct intel_connector *connector; - u8 transcoders = 0; + u16 transcoders = 0; int i; if (DISPLAY_VER(display) < 12) diff --git a/drivers/gpu/drm/i915/display/intel_modeset_setup.c b/drivers/gpu/drm/i915/display/intel_modeset_setup.c index e8730b5baf2a..e27a531e1aa2 100644 --- a/drivers/gpu/drm/i915/display/intel_modeset_setup.c +++ b/drivers/gpu/drm/i915/display/intel_modeset_setup.c @@ -186,11 +186,11 @@ static void intel_crtc_disable_noatomic_complete(struct intel_crtc *crtc) * Return all the pipes using a transcoder in @transcoder_mask. * For joiner configs return only the joiner primary. */ -static u8 get_transcoder_pipes(struct intel_display *display, - u8 transcoder_mask) +static u16 get_transcoder_pipes(struct intel_display *display, + u16 transcoder_mask) { struct intel_crtc *temp_crtc; - u8 pipes = 0; + u16 pipes = 0; for_each_intel_crtc(display, temp_crtc) { struct intel_crtc_state *temp_crtc_state = @@ -214,7 +214,7 @@ static u8 get_transcoder_pipes(struct intel_display *display, * For joiner configs return only the joiner primary pipes. */ static void get_portsync_pipes(struct intel_crtc *crtc, - u8 *master_pipe_mask, u8 *slave_pipes_mask) + u16 *master_pipe_mask, u16 *slave_pipes_mask) { struct intel_display *display = to_intel_display(crtc); struct intel_crtc_state *crtc_state = @@ -243,10 +243,10 @@ static void get_portsync_pipes(struct intel_crtc *crtc, *slave_pipes_mask = get_transcoder_pipes(display, master_crtc_state->sync_mode_slaves_mask); } -static u8 get_joiner_secondary_pipes(struct intel_display *display, u8 primary_pipes_mask) +static u16 get_joiner_secondary_pipes(struct intel_display *display, u16 primary_pipes_mask) { struct intel_crtc *primary_crtc; - u8 pipes = 0; + u16 pipes = 0; for_each_intel_crtc_in_pipe_mask(display, primary_crtc, primary_pipes_mask) { struct intel_crtc_state *primary_crtc_state = @@ -263,9 +263,9 @@ static void intel_crtc_disable_noatomic(struct intel_crtc *crtc, { struct intel_display *display = to_intel_display(crtc); struct intel_crtc *temp_crtc; - u8 portsync_master_mask; - u8 portsync_slaves_mask; - u8 joiner_secondaries_mask; + u16 portsync_master_mask; + u16 portsync_slaves_mask; + u16 joiner_secondaries_mask; /* TODO: Add support for MST */ get_portsync_pipes(crtc, &portsync_master_mask, &portsync_slaves_mask); From 94896bc5fb93d0b18832e5cf00fa5a19f1b5d749 Mon Sep 17 00:00:00 2001 From: Dibin Moolakadan Subrahmanian Date: Wed, 8 Jul 2026 14:37:12 +0530 Subject: [PATCH 62/93] drm/i915/dmc: Enable PIPEDMC_ERROR interrupt on display version 30+ Enable PIPEDMC_ERROR alongside the existing PIPEDMC_GTT_FAULT and PIPEDMC_ATS_FAULT interrupt bits for display version 30+. On PTL, DC state transitions do not trigger the spurious PIPEDMC_ERROR interrupts that were previously a concern. Enable the interrupt so pipe DMC errors are reported by intel_pipedmc_irq_handler(). v2: - Remove IGT reference from commit message (Suraj). Bspec: 70296 Suggested-by: Suraj Kandpal Signed-off-by: Dibin Moolakadan Subrahmanian Reviewed-by: Suraj Kandpal Signed-off-by: Suraj Kandpal Link: https://patch.msgid.link/20260708090712.3800170-1-dibin.moolakadan.subrahmanian@intel.com --- drivers/gpu/drm/i915/display/intel_dmc.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dmc.c b/drivers/gpu/drm/i915/display/intel_dmc.c index 11f5dbf91e68..a191eee240d9 100644 --- a/drivers/gpu/drm/i915/display/intel_dmc.c +++ b/drivers/gpu/drm/i915/display/intel_dmc.c @@ -505,14 +505,16 @@ static void pipedmc_clock_gating_wa(struct intel_display *display, bool enable) static u32 pipedmc_interrupt_mask(struct intel_display *display) { - /* - * TODO: Check if PIPEDMC_ERROR bit enabling causes errors - * on PTL, enable it if validation passes - */ if (DISPLAY_VER(display) >= 35) return PIPEDMC_FLIPQ_PROG_DONE | PIPEDMC_ERROR; + if (DISPLAY_VER(display) >= 30) + return PIPEDMC_FLIPQ_PROG_DONE | + PIPEDMC_GTT_FAULT | + PIPEDMC_ATS_FAULT | + PIPEDMC_ERROR; + /* * FIXME PIPEDMC_ERROR not enabled for now due to LNL pipe B * triggering it during the first DC state transition. Figure From 60f68a6ba298fd1e971a2d91576304bee89a16fc Mon Sep 17 00:00:00 2001 From: Vinod Govindapillai Date: Mon, 15 Jun 2026 23:33:49 +0300 Subject: [PATCH 63/93] drm/i915/wm: clear the plane ddb_y entries on plane disable The UV/Y plane DDB entriess are never cleared on sk_wm_plane_disable_noatomic() and can leave stale DDB state for NV12 planes on pre-Gen11 devices Fixes: d34b59d5ba41 ("drm/i915: Add skl_wm_plane_disable_noatomic()") Assisted-by: Copilot:claude-sonnet-4.6 Signed-off-by: Vinod Govindapillai Reviewed-by: Suraj Kandpal Link: https://patch.msgid.link/20260615203355.218578-2-vinod.govindapillai@intel.com --- drivers/gpu/drm/i915/display/skl_watermark.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/display/skl_watermark.c b/drivers/gpu/drm/i915/display/skl_watermark.c index 5a3677ea25b0..a4ce21d4c024 100644 --- a/drivers/gpu/drm/i915/display/skl_watermark.c +++ b/drivers/gpu/drm/i915/display/skl_watermark.c @@ -3856,7 +3856,7 @@ void skl_wm_plane_disable_noatomic(struct intel_crtc *crtc, return; skl_ddb_entry_init(&crtc_state->wm.skl.plane_ddb[plane->id], 0, 0); - skl_ddb_entry_init(&crtc_state->wm.skl.plane_ddb[plane->id], 0, 0); + skl_ddb_entry_init(&crtc_state->wm.skl.plane_ddb_y[plane->id], 0, 0); crtc_state->wm.skl.plane_min_ddb[plane->id] = 0; crtc_state->wm.skl.plane_interim_ddb[plane->id] = 0; From fb8a24c0799d9b1a544c26b5fba4f559e127cfcd Mon Sep 17 00:00:00 2001 From: Vinod Govindapillai Date: Mon, 15 Jun 2026 23:33:50 +0300 Subject: [PATCH 64/93] drm/i915/pm_demand: introduce HAS_PMDEMAND macro PM demand feature introduces a new way to set bw, power and performance requirements to pcode from display version 14 onwards. Use an identifiable name as a macro to distinguish the pm demand specific changes in the code. Signed-off-by: Vinod Govindapillai Reviewed-by: Mika Kahola Reviewed-by: Suraj Kandpal Link: https://patch.msgid.link/20260615203355.218578-3-vinod.govindapillai@intel.com --- drivers/gpu/drm/i915/display/intel_bw.c | 4 ++-- drivers/gpu/drm/i915/display/intel_display_device.h | 1 + drivers/gpu/drm/i915/display/intel_display_irq.c | 2 +- drivers/gpu/drm/i915/display/intel_display_power.c | 4 ++-- drivers/gpu/drm/i915/display/intel_pmdemand.c | 12 ++++++------ 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_bw.c b/drivers/gpu/drm/i915/display/intel_bw.c index 41539fdfeac5..4557de85fade 100644 --- a/drivers/gpu/drm/i915/display/intel_bw.c +++ b/drivers/gpu/drm/i915/display/intel_bw.c @@ -184,7 +184,7 @@ static int icl_pcode_restrict_qgv_points(struct intel_display *display, { int ret; - if (DISPLAY_VER(display) >= 14) + if (HAS_PMDEMAND(display)) return 0; /* bspec says to keep retrying for at least 1 ms */ @@ -1238,7 +1238,7 @@ static int intel_bw_check_qgv_points(struct intel_display *display, data_rate = DIV_ROUND_UP(data_rate, 1000); - if (DISPLAY_VER(display) >= 14) + if (HAS_PMDEMAND(display)) return mtl_find_qgv_points(display, data_rate, num_active_planes, new_bw_state); else diff --git a/drivers/gpu/drm/i915/display/intel_display_device.h b/drivers/gpu/drm/i915/display/intel_display_device.h index f77b3da2cff5..43c2d6324e1a 100644 --- a/drivers/gpu/drm/i915/display/intel_display_device.h +++ b/drivers/gpu/drm/i915/display/intel_display_device.h @@ -194,6 +194,7 @@ struct intel_display_platforms { #define HAS_OVERLAY(__display) (DISPLAY_INFO(__display)->has_overlay) #define HAS_PIPEDMC(__display) (DISPLAY_VER(__display) >= 12) #define HAS_PIXEL_NORMALIZER(__display) (DISPLAY_VER(__display) >= 35) +#define HAS_PMDEMAND(__display) (DISPLAY_VER(__display) >= 14) #define HAS_PSR(__display) (DISPLAY_INFO(__display)->has_psr) #define HAS_PSR_HW_TRACKING(__display) (DISPLAY_INFO(__display)->has_psr_hw_tracking) #define HAS_PSR2_SEL_FETCH(__display) (DISPLAY_VER(__display) >= 12) diff --git a/drivers/gpu/drm/i915/display/intel_display_irq.c b/drivers/gpu/drm/i915/display/intel_display_irq.c index bcb0ee22fb56..a59b75830bd1 100644 --- a/drivers/gpu/drm/i915/display/intel_display_irq.c +++ b/drivers/gpu/drm/i915/display/intel_display_irq.c @@ -1262,7 +1262,7 @@ gen8_de_misc_irq_handler(struct intel_display *display, u32 iir) } } - if (DISPLAY_VER(display) >= 14) { + if (HAS_PMDEMAND(display)) { if (iir & (XELPDP_PMDEMAND_RSP | XELPDP_PMDEMAND_RSPTOUT_ERR)) { if (iir & XELPDP_PMDEMAND_RSPTOUT_ERR) diff --git a/drivers/gpu/drm/i915/display/intel_display_power.c b/drivers/gpu/drm/i915/display/intel_display_power.c index 8e312f5b9f6d..0ebec6e0c240 100644 --- a/drivers/gpu/drm/i915/display/intel_display_power.c +++ b/drivers/gpu/drm/i915/display/intel_display_power.c @@ -1290,7 +1290,7 @@ static void gen9_dbuf_enable(struct intel_display *display) slices_mask = BIT(DBUF_S1) | display->dbuf.enabled_slices; - if (DISPLAY_VER(display) >= 14) + if (HAS_PMDEMAND(display)) intel_pmdemand_program_dbuf(display, slices_mask); /* @@ -1304,7 +1304,7 @@ static void gen9_dbuf_disable(struct intel_display *display) { gen9_dbuf_slices_update(display, 0); - if (DISPLAY_VER(display) >= 14) + if (HAS_PMDEMAND(display)) intel_pmdemand_program_dbuf(display, 0); } diff --git a/drivers/gpu/drm/i915/display/intel_pmdemand.c b/drivers/gpu/drm/i915/display/intel_pmdemand.c index 6d32c52269a6..f96545a5b881 100644 --- a/drivers/gpu/drm/i915/display/intel_pmdemand.c +++ b/drivers/gpu/drm/i915/display/intel_pmdemand.c @@ -152,7 +152,7 @@ intel_pmdemand_update_phys_mask(struct intel_display *display, { enum phy phy; - if (DISPLAY_VER(display) < 14) + if (!HAS_PMDEMAND(display)) return; if (!encoder) @@ -174,7 +174,7 @@ intel_pmdemand_update_port_clock(struct intel_display *display, struct intel_pmdemand_state *pmdemand_state, enum pipe pipe, int port_clock) { - if (DISPLAY_VER(display) < 14) + if (!HAS_PMDEMAND(display)) return; pmdemand_state->ddi_clocks[pipe] = port_clock; @@ -324,7 +324,7 @@ int intel_pmdemand_atomic_check(struct intel_atomic_state *state) const struct intel_dbuf_state *new_dbuf_state; struct intel_pmdemand_state *new_pmdemand_state; - if (DISPLAY_VER(display) < 14) + if (!HAS_PMDEMAND(display)) return 0; if (!intel_pmdemand_needs_update(state)) @@ -404,7 +404,7 @@ intel_pmdemand_init_pmdemand_params(struct intel_display *display, { u32 reg1, reg2; - if (DISPLAY_VER(display) < 14) + if (!HAS_PMDEMAND(display)) return; mutex_lock(&display->pmdemand.lock); @@ -637,7 +637,7 @@ void intel_pmdemand_pre_plane_update(struct intel_atomic_state *state) const struct intel_pmdemand_state *old_pmdemand_state = intel_atomic_get_old_pmdemand_state(state); - if (DISPLAY_VER(display) < 14) + if (!HAS_PMDEMAND(display)) return; if (!new_pmdemand_state || @@ -660,7 +660,7 @@ void intel_pmdemand_post_plane_update(struct intel_atomic_state *state) const struct intel_pmdemand_state *old_pmdemand_state = intel_atomic_get_old_pmdemand_state(state); - if (DISPLAY_VER(display) < 14) + if (!HAS_PMDEMAND(display)) return; if (!new_pmdemand_state || From 490de57216725dc9ae3de85d46dfd4eb73dbd233 Mon Sep 17 00:00:00 2001 From: Vinod Govindapillai Date: Mon, 15 Jun 2026 23:33:51 +0300 Subject: [PATCH 65/93] drm/i915/display: sagv pre/post plane calls to check pmdemand support For pmdemand cases, no need to even calculate the masks based on the qgv points index. Though the current logic avoids setting the registers based on the pmdemand support, some qgv point masks are compared in vain and do nothing. So leave early if pmdemand is supported. Signed-off-by: Vinod Govindapillai Reviewed-by: Mika Kahola Reviewed-by: Suraj Kandpal Link: https://patch.msgid.link/20260615203355.218578-4-vinod.govindapillai@intel.com --- drivers/gpu/drm/i915/display/skl_watermark.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/i915/display/skl_watermark.c b/drivers/gpu/drm/i915/display/skl_watermark.c index a4ce21d4c024..ec1bfe46edef 100644 --- a/drivers/gpu/drm/i915/display/skl_watermark.c +++ b/drivers/gpu/drm/i915/display/skl_watermark.c @@ -275,6 +275,9 @@ void intel_sagv_pre_plane_update(struct intel_atomic_state *state) if (!intel_has_sagv(display)) return; + if (HAS_PMDEMAND(display)) + return; + if (DISPLAY_VER(display) >= 11) icl_sagv_pre_plane_update(state); else @@ -295,6 +298,9 @@ void intel_sagv_post_plane_update(struct intel_atomic_state *state) if (!intel_has_sagv(display)) return; + if (HAS_PMDEMAND(display)) + return; + if (DISPLAY_VER(display) >= 11) icl_sagv_post_plane_update(state); else From 6d7e5cebd077b296a4a5a6aa9eb589a1b8640076 Mon Sep 17 00:00:00 2001 From: Vinod Govindapillai Date: Mon, 15 Jun 2026 23:33:52 +0300 Subject: [PATCH 66/93] drm/i915/bw: Extract icl_init_qgv_info() Simplify the initialization of QGV points info by extracting the code to initialize the QGV points info from dram info based on the memory type. This will reduce the complexity of the init QGV info routine as we will be supporting new memory types in future platforms. v2: rebase after Ville's refactoring v3: patch description updated Signed-off-by: Vinod Govindapillai Reviewed-by: Suraj Kandpal Link: https://patch.msgid.link/20260615203355.218578-5-vinod.govindapillai@intel.com --- drivers/gpu/drm/i915/display/intel_bw.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_bw.c b/drivers/gpu/drm/i915/display/intel_bw.c index 4557de85fade..6649220d81b4 100644 --- a/drivers/gpu/drm/i915/display/intel_bw.c +++ b/drivers/gpu/drm/i915/display/intel_bw.c @@ -246,12 +246,10 @@ static bool is_y_tile(struct intel_display *display) return !HAS_4TILE(display); } -static int icl_get_qgv_points(struct intel_display *display, - const struct dram_info *dram_info, - struct intel_qgv_info *qi) +static int icl_init_qgv_info(struct intel_display *display, + const struct dram_info *dram_info, + struct intel_qgv_info *qi) { - int i, ret; - qi->num_qgv_points = dram_info->num_qgv_points; qi->num_psf_points = dram_info->num_psf_gv_points; @@ -323,6 +321,18 @@ static int icl_get_qgv_points(struct intel_display *display, qi->max_numchannels = 1; } + return 0; +} + +static int icl_get_qgv_points(struct intel_display *display, + const struct dram_info *dram_info, + struct intel_qgv_info *qi) +{ + int i, ret; + + if (icl_init_qgv_info(display, dram_info, qi)) + return -EINVAL; + if (drm_WARN_ON(display->drm, qi->num_qgv_points > ARRAY_SIZE(qi->points))) qi->num_qgv_points = ARRAY_SIZE(qi->points); From f6d7d21db35386607ccceb5c8b09ab2635a9775a Mon Sep 17 00:00:00 2001 From: Vinod Govindapillai Date: Mon, 15 Jun 2026 23:33:53 +0300 Subject: [PATCH 67/93] drm/i915/bw: extract update_sagv_status() Extract the code to update the SAGV status based on the number of QGV points into a separate function and use it. v2: rebase v3: patch description updated Signed-off-by: Vinod Govindapillai Reviewed-by: Suraj Kandpal Link: https://patch.msgid.link/20260615203355.218578-6-vinod.govindapillai@intel.com --- drivers/gpu/drm/i915/display/intel_bw.c | 38 ++++++++++++------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_bw.c b/drivers/gpu/drm/i915/display/intel_bw.c index 6649220d81b4..0d89f64db848 100644 --- a/drivers/gpu/drm/i915/display/intel_bw.c +++ b/drivers/gpu/drm/i915/display/intel_bw.c @@ -521,6 +521,19 @@ static const struct intel_display_bw_params *get_display_bw_params(struct intel_ return NULL; } +static void update_sagv_status(struct intel_display *display, int qgv_points) +{ + /* + * In case if SAGV is disabled in BIOS, we always get 1 + * SAGV point, but we can't send PCode commands to restrict it + * as it will fail and pointless anyway. + */ + if (qgv_points == 1) + display->sagv.status = I915_SAGV_NOT_CONTROLLED; + else + display->sagv.status = I915_SAGV_ENABLED; +} + static int icl_get_bw_info(struct intel_display *display, const struct dram_info *dram_info, const struct intel_soc_bw_params *soc_bw_params, @@ -579,15 +592,8 @@ static int icl_get_bw_info(struct intel_display *display, i, j, bi->num_planes, bi->deratedbw[j]); } } - /* - * In case if SAGV is disabled in BIOS, we always get 1 - * SAGV point, but we can't send PCode commands to restrict it - * as it will fail and pointless anyway. - */ - if (qi.num_qgv_points == 1) - display->sagv.status = I915_SAGV_NOT_CONTROLLED; - else - display->sagv.status = I915_SAGV_ENABLED; + + update_sagv_status(display, display->bw.num_qgv_points); return 0; } @@ -699,15 +705,7 @@ static int tgl_get_bw_info(struct intel_display *display, drm_dbg_kms(display->drm, "PSF GV %d: bw=%u\n", i, display->bw.psf_bw[i]); } - /* - * In case if SAGV is disabled in BIOS, we always get 1 - * SAGV point, but we can't send PCode commands to restrict it - * as it will fail and pointless anyway. - */ - if (qi.num_qgv_points == 1) - display->sagv.status = I915_SAGV_NOT_CONTROLLED; - else - display->sagv.status = I915_SAGV_ENABLED; + update_sagv_status(display, display->bw.num_qgv_points); return 0; } @@ -729,7 +727,7 @@ static void dg2_get_bw_info(struct intel_display *display) for (i = 1; i < ARRAY_SIZE(display->bw.max); i++) display->bw.max[i] = display->bw.max[0]; - display->sagv.status = I915_SAGV_NOT_CONTROLLED; + update_sagv_status(display, display->bw.num_qgv_points); } static int xe2_hpd_get_bw_info(struct intel_display *display, @@ -777,7 +775,7 @@ static int xe2_hpd_get_bw_info(struct intel_display *display, * battery and plugged-in operation. */ drm_WARN_ON(display->drm, qi.num_qgv_points != 2); - display->sagv.status = I915_SAGV_ENABLED; + update_sagv_status(display, display->bw.num_qgv_points); return 0; } From 3c6862a39f5c760165c848713a0d0c2689e4a8ee Mon Sep 17 00:00:00 2001 From: Vinod Govindapillai Date: Mon, 15 Jun 2026 23:33:54 +0300 Subject: [PATCH 68/93] drm/i915/bw: avoid replicating the update_sagv_status() calls Now that SAGV status update is consolidated, need to update the SAGV status based on the number of QGV points only once after bw info initialization is done. v2: patch description updated. Signed-off-by: Vinod Govindapillai Reviewed-by: Suraj Kandpal Link: https://patch.msgid.link/20260615203355.218578-7-vinod.govindapillai@intel.com --- drivers/gpu/drm/i915/display/intel_bw.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_bw.c b/drivers/gpu/drm/i915/display/intel_bw.c index 0d89f64db848..db2ee9de5eec 100644 --- a/drivers/gpu/drm/i915/display/intel_bw.c +++ b/drivers/gpu/drm/i915/display/intel_bw.c @@ -593,8 +593,6 @@ static int icl_get_bw_info(struct intel_display *display, } } - update_sagv_status(display, display->bw.num_qgv_points); - return 0; } @@ -705,8 +703,6 @@ static int tgl_get_bw_info(struct intel_display *display, drm_dbg_kms(display->drm, "PSF GV %d: bw=%u\n", i, display->bw.psf_bw[i]); } - update_sagv_status(display, display->bw.num_qgv_points); - return 0; } @@ -726,8 +722,6 @@ static void dg2_get_bw_info(struct intel_display *display) /* Bandwidth does not depend on # of planes; set all groups the same */ for (i = 1; i < ARRAY_SIZE(display->bw.max); i++) display->bw.max[i] = display->bw.max[0]; - - update_sagv_status(display, display->bw.num_qgv_points); } static int xe2_hpd_get_bw_info(struct intel_display *display, @@ -775,7 +769,6 @@ static int xe2_hpd_get_bw_info(struct intel_display *display, * battery and plugged-in operation. */ drm_WARN_ON(display->drm, qi.num_qgv_points != 2); - update_sagv_status(display, display->bw.num_qgv_points); return 0; } @@ -876,6 +869,8 @@ void intel_bw_init_hw(struct intel_display *display) } else if (DISPLAY_VER(display) == 11) { icl_get_bw_info(display, dram_info, soc_bw_params, display_bw_params); } + + update_sagv_status(display, display->bw.num_qgv_points); } static unsigned int intel_bw_num_active_planes(struct intel_display *display, From 3d4d292970d3e480005181a8b78e3e8fa5eae0a8 Mon Sep 17 00:00:00 2001 From: Vinod Govindapillai Date: Mon, 15 Jun 2026 23:33:55 +0300 Subject: [PATCH 69/93] drm/i915/bw: introduce the peak bandwidth threshold On Xe3+, the SoC can lower the fabric frequency when the display needs less bandwidth than the minimum GV point. This threshold is defined as 20 GB/s. The driver can choose to request this threshold when the required data rate falls below it. Add an extra QGV entry, with both peak bw and derated bw set to 20 GB/s, to the bandwidth info when all of the following hold: 1. The platform is Xe3+. 2. There is at least one existing QGV point. 3. The number of QGV points is below 8 (the maximum). Once a plane group is found, the driver iterates over all QGV points in that group to find the best match for the required data rate. If the required data rate is below 20 GB/s, it selects the peak bw from this new QGV point (20 GB/s). v2: add the peak bandwidth threshold as an additional QGV entry v3: drm_warn switched to drm_dbg_kms (Suraj) Removed log in case of no sagv and some tweak in the log message if the system has already the maximum number of QGV points Bspec: 68880 Signed-off-by: Vinod Govindapillai Reviewed-by: Suraj Kandpal Link: https://patch.msgid.link/20260615203355.218578-8-vinod.govindapillai@intel.com --- drivers/gpu/drm/i915/display/intel_bw.c | 31 +++++++++++++++++++ .../drm/i915/display/intel_display_device.h | 1 + 2 files changed, 32 insertions(+) diff --git a/drivers/gpu/drm/i915/display/intel_bw.c b/drivers/gpu/drm/i915/display/intel_bw.c index db2ee9de5eec..546d5ccbe052 100644 --- a/drivers/gpu/drm/i915/display/intel_bw.c +++ b/drivers/gpu/drm/i915/display/intel_bw.c @@ -52,6 +52,8 @@ struct intel_qgv_point { #define DEPROGBWPCLIMIT 60 +#define PEAK_BW_THRESHOLD 20000 + struct intel_psf_gv_point { u8 clk; /* clock in multiples of 16.6666 MHz */ }; @@ -601,6 +603,32 @@ static int tgl_peakbw(int num_channels, int channel_width, int dclk) return num_channels * (channel_width / 8) * dclk; } +static void xe3_add_peakbw_threshold(struct intel_display *display) +{ + u8 qgv_points = display->bw.num_qgv_points; + + if (!HAS_PEAK_BW_THRESHOLD(display)) + return; + + if (qgv_points >= I915_NUM_QGV_POINTS) { + drm_dbg_kms(display->drm, "QGV points maxed out; skipping peak bandwidth threshold.\n"); + return; + } + + if (qgv_points <= 1) + return; + + display->bw.num_qgv_points++; + + display->bw.peakbw[qgv_points] = PEAK_BW_THRESHOLD; + + for (int i = 0; i < ARRAY_SIZE(display->bw.max); i++) + display->bw.max[i].deratedbw[qgv_points] = PEAK_BW_THRESHOLD; + + drm_dbg_kms(display->drm, "An extra QGV point %d added for Peak bw threshod of %d\n", + qgv_points, PEAK_BW_THRESHOLD); +} + static int tgl_get_bw_info(struct intel_display *display, const struct dram_info *dram_info, const struct intel_soc_bw_params *soc_bw_params, @@ -695,6 +723,9 @@ static int tgl_get_bw_info(struct intel_display *display, drm_dbg_kms(display->drm, "QGV %d: peakbw=%u\n", i, display->bw.peakbw[i]); } + /* For xe3 cases add an extra qgv point for Peak bw threshold */ + xe3_add_peakbw_threshold(display); + for (i = 0; i < qi.num_psf_points; i++) { const struct intel_psf_gv_point *sp = &qi.psf_points[i]; diff --git a/drivers/gpu/drm/i915/display/intel_display_device.h b/drivers/gpu/drm/i915/display/intel_display_device.h index 43c2d6324e1a..9aeba329b9bd 100644 --- a/drivers/gpu/drm/i915/display/intel_display_device.h +++ b/drivers/gpu/drm/i915/display/intel_display_device.h @@ -192,6 +192,7 @@ struct intel_display_platforms { #define HAS_MBUS_JOINING(__display) ((__display)->platform.alderlake_p || DISPLAY_VER(__display) >= 14) #define HAS_MSO(__display) (DISPLAY_VER(__display) >= 12) #define HAS_OVERLAY(__display) (DISPLAY_INFO(__display)->has_overlay) +#define HAS_PEAK_BW_THRESHOLD(__display) (DISPLAY_VER(__display) >= 30) #define HAS_PIPEDMC(__display) (DISPLAY_VER(__display) >= 12) #define HAS_PIXEL_NORMALIZER(__display) (DISPLAY_VER(__display) >= 35) #define HAS_PMDEMAND(__display) (DISPLAY_VER(__display) >= 14) From 67e955e073c6ff9167828444913a864a14275463 Mon Sep 17 00:00:00 2001 From: Chaitanya Kumar Borah Date: Wed, 15 Jul 2026 11:12:21 +0530 Subject: [PATCH 70/93] drm/i915/display: expose blend mode on alpha-capable planes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed"), drm_mode_config_validate() warns when a plane exposes an alpha pixel format but not the "pixel blend mode" property. The cursor (ARGB8888, all platforms) and the VLV/CHV primary and sprite planes trip this. Userspace has historically assumed premultiplied blending when the property is not attached, so it is safe to assume that planes that did not expose the property already blended with fixed pre-multiplied alpha in hardware. Therefore, expose a "pixel blend mode" property advertising only DRM_MODE_BLEND_PREMULTI to match that assumption and silence the warning. The cursor call is unconditional; the primary and sprite calls are gated to VLV/CHV, the only platforms whose format lists include alpha formats. Assisted-by: Claude:claude-opus-4-8 Cc: Ville Syrjälä Cc: Uma Shankar Cc: Leandro Ribeiro Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/16623 Signed-off-by: Chaitanya Kumar Borah Reviewed-by: Uma Shankar Signed-off-by: Ankit Nautiyal Link: https://patch.msgid.link/20260715054221.451421-1-chaitanya.kumar.borah@intel.com --- drivers/gpu/drm/i915/display/i9xx_plane.c | 4 ++++ drivers/gpu/drm/i915/display/intel_cursor.c | 3 +++ drivers/gpu/drm/i915/display/intel_sprite.c | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/drivers/gpu/drm/i915/display/i9xx_plane.c b/drivers/gpu/drm/i915/display/i9xx_plane.c index 70734d32a409..490908f59141 100644 --- a/drivers/gpu/drm/i915/display/i9xx_plane.c +++ b/drivers/gpu/drm/i915/display/i9xx_plane.c @@ -1108,6 +1108,10 @@ intel_primary_plane_create(struct intel_display *display, enum pipe pipe) DRM_MODE_ROTATE_0, supported_rotations); + if (display->platform.valleyview || display->platform.cherryview) + drm_plane_create_blend_mode_property(&plane->base, + BIT(DRM_MODE_BLEND_PREMULTI)); + zpos = 0; drm_plane_create_zpos_immutable_property(&plane->base, zpos); diff --git a/drivers/gpu/drm/i915/display/intel_cursor.c b/drivers/gpu/drm/i915/display/intel_cursor.c index 88384dea868b..0673f16f6fd0 100644 --- a/drivers/gpu/drm/i915/display/intel_cursor.c +++ b/drivers/gpu/drm/i915/display/intel_cursor.c @@ -1078,6 +1078,9 @@ intel_cursor_plane_create(struct intel_display *display, intel_cursor_add_size_hints_property(cursor); + drm_plane_create_blend_mode_property(&cursor->base, + BIT(DRM_MODE_BLEND_PREMULTI)); + zpos = DISPLAY_RUNTIME_INFO(display)->num_sprites[pipe] + 1; drm_plane_create_zpos_immutable_property(&cursor->base, zpos); diff --git a/drivers/gpu/drm/i915/display/intel_sprite.c b/drivers/gpu/drm/i915/display/intel_sprite.c index 6a65f92e8a03..3e38960bbbd8 100644 --- a/drivers/gpu/drm/i915/display/intel_sprite.c +++ b/drivers/gpu/drm/i915/display/intel_sprite.c @@ -1722,6 +1722,10 @@ intel_sprite_plane_create(struct intel_display *display, DRM_COLOR_YCBCR_BT709, DRM_COLOR_YCBCR_LIMITED_RANGE); + if (display->platform.valleyview || display->platform.cherryview) + drm_plane_create_blend_mode_property(&plane->base, + BIT(DRM_MODE_BLEND_PREMULTI)); + zpos = sprite + 1; drm_plane_create_zpos_immutable_property(&plane->base, zpos); From 04e1c59b349ec719aea836c5dc69f2ab648336eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Wed, 15 Jul 2026 15:09:23 +0300 Subject: [PATCH 71/93] drm/i915/cdclk: Use intel_cdclk_ppc() in intel_modeset_readout_hw_state() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the hand roller intel_cdclk_ppc() with the real thing in intel_modeset_readout_hw_state(). Signed-off-by: Ville Syrjälä Link: https://patch.msgid.link/20260715120926.10786-2-ville.syrjala@linux.intel.com Reviewed-by: Nemesa Garg --- drivers/gpu/drm/i915/display/intel_cdclk.c | 2 +- drivers/gpu/drm/i915/display/intel_cdclk.h | 1 + drivers/gpu/drm/i915/display/intel_modeset_setup.c | 12 ++++-------- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_cdclk.c b/drivers/gpu/drm/i915/display/intel_cdclk.c index d3c5e3438d19..1d7ff2cbaebb 100644 --- a/drivers/gpu/drm/i915/display/intel_cdclk.c +++ b/drivers/gpu/drm/i915/display/intel_cdclk.c @@ -2908,7 +2908,7 @@ intel_set_cdclk_post_plane_update(struct intel_atomic_state *state) } /* pixels per CDCLK */ -static int intel_cdclk_ppc(struct intel_display *display, bool double_wide) +int intel_cdclk_ppc(struct intel_display *display, bool double_wide) { return DISPLAY_VER(display) >= 10 || double_wide ? 2 : 1; } diff --git a/drivers/gpu/drm/i915/display/intel_cdclk.h b/drivers/gpu/drm/i915/display/intel_cdclk.h index 1ff7d078b42c..a60cbf745ee2 100644 --- a/drivers/gpu/drm/i915/display/intel_cdclk.h +++ b/drivers/gpu/drm/i915/display/intel_cdclk.h @@ -22,6 +22,7 @@ struct intel_cdclk_config { bool joined_mbus; }; +int intel_cdclk_ppc(struct intel_display *display, bool double_wide); void intel_cdclk_init_hw(struct intel_display *display); void intel_cdclk_uninit_hw(struct intel_display *display); void intel_init_cdclk_hooks(struct intel_display *display); diff --git a/drivers/gpu/drm/i915/display/intel_modeset_setup.c b/drivers/gpu/drm/i915/display/intel_modeset_setup.c index e27a531e1aa2..14d829171c7d 100644 --- a/drivers/gpu/drm/i915/display/intel_modeset_setup.c +++ b/drivers/gpu/drm/i915/display/intel_modeset_setup.c @@ -854,14 +854,10 @@ static void intel_modeset_readout_hw_state(struct intel_display *display) * FIXME don't have the fb yet, so can't * use plane->min_cdclk() :( */ - if (plane_state->uapi.visible && plane->min_cdclk) { - if (crtc_state->double_wide || DISPLAY_VER(display) >= 10) - crtc_state->plane_min_cdclk[plane->id] = - DIV_ROUND_UP(crtc_state->pixel_rate, 2); - else - crtc_state->plane_min_cdclk[plane->id] = - crtc_state->pixel_rate; - } + if (plane_state->uapi.visible && plane->min_cdclk) + crtc_state->plane_min_cdclk[plane->id] = + DIV_ROUND_UP(crtc_state->pixel_rate, + intel_cdclk_ppc(display, crtc_state->double_wide)); drm_dbg_kms(display->drm, "[PLANE:%d:%s] min_cdclk %d kHz\n", plane->base.base.id, plane->base.name, From 8e4c3b38accf7ac6607dc3ceda4235b1f6b6020f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Wed, 15 Jul 2026 15:09:24 +0300 Subject: [PATCH 72/93] drm/i915/cdclk: Introduce HAS_2PPC() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We'll need to check for the "does the platform do 2 pixels per clock?" thing in a few places. Add a feature macro for it. Signed-off-by: Ville Syrjälä Link: https://patch.msgid.link/20260715120926.10786-3-ville.syrjala@linux.intel.com Reviewed-by: Nemesa Garg --- drivers/gpu/drm/i915/display/intel_cdclk.c | 2 +- drivers/gpu/drm/i915/display/intel_display_device.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/display/intel_cdclk.c b/drivers/gpu/drm/i915/display/intel_cdclk.c index 1d7ff2cbaebb..12bb6c414683 100644 --- a/drivers/gpu/drm/i915/display/intel_cdclk.c +++ b/drivers/gpu/drm/i915/display/intel_cdclk.c @@ -2910,7 +2910,7 @@ intel_set_cdclk_post_plane_update(struct intel_atomic_state *state) /* pixels per CDCLK */ int intel_cdclk_ppc(struct intel_display *display, bool double_wide) { - return DISPLAY_VER(display) >= 10 || double_wide ? 2 : 1; + return HAS_2PPC(display) || double_wide ? 2 : 1; } /* max pixel rate as % of CDCLK (not accounting for PPC) */ diff --git a/drivers/gpu/drm/i915/display/intel_display_device.h b/drivers/gpu/drm/i915/display/intel_display_device.h index 9aeba329b9bd..7121e7cd9512 100644 --- a/drivers/gpu/drm/i915/display/intel_display_device.h +++ b/drivers/gpu/drm/i915/display/intel_display_device.h @@ -146,6 +146,7 @@ struct intel_display_platforms { func(supports_tv); #define HAS_128B_Y_TILING(__display) (!(__display)->platform.i915g && !(__display)->platform.i915gm) +#define HAS_2PPC(__display) (DISPLAY_VER(__display) >= 10) #define HAS_4TILE(__display) ((__display)->platform.dg2 || DISPLAY_VER(__display) >= 14) #define HAS_ASYNC_FLIPS(__display) (DISPLAY_VER(__display) >= 5) #define HAS_AS_SDP(__display) (DISPLAY_VER(__display) >= 13) From 776308ab96b29cf4fac71dfcdc91228811031bfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Wed, 15 Jul 2026 15:09:25 +0300 Subject: [PATCH 73/93] drm/i915/cdclk: Introduce crtc_state->pixel_rate_cdclk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We'll need to do additional adjustments to the pipe pixel rate for the purposes of min CDCLK calculations. Add a new crtc_state->pixel_rate_cdclk for that purpose. We'll leave the original crtc_state->pixel_rate for data rate related calculations since we presumably don't need those extra adjustments there. Signed-off-by: Ville Syrjälä Link: https://patch.msgid.link/20260715120926.10786-4-ville.syrjala@linux.intel.com Reviewed-by: Nemesa Garg --- drivers/gpu/drm/i915/display/hsw_ips.c | 2 +- drivers/gpu/drm/i915/display/i9xx_plane.c | 4 ++-- drivers/gpu/drm/i915/display/intel_cdclk.c | 2 +- drivers/gpu/drm/i915/display/intel_display.c | 8 ++++++-- .../gpu/drm/i915/display/intel_display_types.h | 7 +++++++ drivers/gpu/drm/i915/display/intel_fbc.c | 2 +- .../gpu/drm/i915/display/intel_modeset_setup.c | 2 +- drivers/gpu/drm/i915/display/intel_sprite.c | 18 +++++++++--------- 8 files changed, 28 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/i915/display/hsw_ips.c b/drivers/gpu/drm/i915/display/hsw_ips.c index cbaef3f13f00..f828ec962164 100644 --- a/drivers/gpu/drm/i915/display/hsw_ips.c +++ b/drivers/gpu/drm/i915/display/hsw_ips.c @@ -207,7 +207,7 @@ static int _hsw_ips_min_cdclk(const struct intel_crtc_state *crtc_state) struct intel_display *display = to_intel_display(crtc_state); if (display->platform.broadwell) - return DIV_ROUND_UP(crtc_state->pixel_rate * 100, 95); + return DIV_ROUND_UP(crtc_state->pixel_rate_cdclk * 100, 95); /* no IPS specific limits to worry about */ return 0; diff --git a/drivers/gpu/drm/i915/display/i9xx_plane.c b/drivers/gpu/drm/i915/display/i9xx_plane.c index 490908f59141..df6da1c6a359 100644 --- a/drivers/gpu/drm/i915/display/i9xx_plane.c +++ b/drivers/gpu/drm/i915/display/i9xx_plane.c @@ -418,13 +418,13 @@ static int i9xx_plane_min_cdclk(const struct intel_crtc_state *crtc_state, unsigned int num, den; /* - * Note that crtc_state->pixel_rate accounts for both + * Note that crtc_state->pixel_rate_cdclk accounts for both * horizontal and vertical panel fitter downscaling factors. * Pre-HSW bspec tells us to only consider the horizontal * downscaling factor here. We ignore that and just consider * both for simplicity. */ - pixel_rate = crtc_state->pixel_rate; + pixel_rate = crtc_state->pixel_rate_cdclk; i9xx_plane_ratio(crtc_state, plane_state, &num, &den); diff --git a/drivers/gpu/drm/i915/display/intel_cdclk.c b/drivers/gpu/drm/i915/display/intel_cdclk.c index 12bb6c414683..a53d88727177 100644 --- a/drivers/gpu/drm/i915/display/intel_cdclk.c +++ b/drivers/gpu/drm/i915/display/intel_cdclk.c @@ -2936,7 +2936,7 @@ static int _intel_pixel_rate_to_cdclk(const struct intel_crtc_state *crtc_state, static int intel_pixel_rate_to_cdclk(const struct intel_crtc_state *crtc_state) { - return _intel_pixel_rate_to_cdclk(crtc_state, crtc_state->pixel_rate); + return _intel_pixel_rate_to_cdclk(crtc_state, crtc_state->pixel_rate_cdclk); } static int intel_planes_min_cdclk(const struct intel_crtc_state *crtc_state) diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c index 38763a6802c5..df43be51b3ba 100644 --- a/drivers/gpu/drm/i915/display/intel_display.c +++ b/drivers/gpu/drm/i915/display/intel_display.c @@ -2259,13 +2259,16 @@ static void intel_crtc_compute_pixel_rate(struct intel_crtc_state *crtc_state) { struct intel_display *display = to_intel_display(crtc_state); - if (HAS_GMCH(display)) + if (HAS_GMCH(display)) { /* FIXME calculate proper pipe pixel rate for GMCH pfit */ crtc_state->pixel_rate = crtc_state->hw.pipe_mode.crtc_clock; - else + crtc_state->pixel_rate_cdclk = crtc_state->pixel_rate; + } else { crtc_state->pixel_rate = ilk_pipe_pixel_rate(crtc_state); + crtc_state->pixel_rate_cdclk = crtc_state->pixel_rate; + } } static void intel_joiner_adjust_timings(const struct intel_crtc_state *crtc_state, @@ -5382,6 +5385,7 @@ intel_pipe_config_compare(const struct intel_crtc_state *current_config, PIPE_CONF_CHECK_I(pch_pfit.casf.strength); PIPE_CONF_CHECK_I(scaler_state.scaler_id); + PIPE_CONF_CHECK_I(pixel_rate_cdclk); PIPE_CONF_CHECK_I(pixel_rate); PIPE_CONF_CHECK_X(gamma_mode); diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h index 8bd213db5e7a..17336ad6d5e9 100644 --- a/drivers/gpu/drm/i915/display/intel_display_types.h +++ b/drivers/gpu/drm/i915/display/intel_display_types.h @@ -1083,6 +1083,13 @@ struct intel_crtc_state { */ unsigned int pixel_rate; + /* + * Pipe pixel rate for CDCLK, adjusted for + * panel fitter/pipe scaler downscaling. + * CDCLK use cases need further adjustment. + */ + unsigned int pixel_rate_cdclk; + /* Whether to set up the PCH/FDI. Note that we never allow sharing * between pch encoders and cpu encoders. */ bool has_pch_encoder; diff --git a/drivers/gpu/drm/i915/display/intel_fbc.c b/drivers/gpu/drm/i915/display/intel_fbc.c index a7d02dd6ca96..cd889f7429bb 100644 --- a/drivers/gpu/drm/i915/display/intel_fbc.c +++ b/drivers/gpu/drm/i915/display/intel_fbc.c @@ -1567,7 +1567,7 @@ static int _intel_fbc_min_cdclk(const struct intel_crtc_state *crtc_state) /* WaFbcExceedCdClockThreshold:hsw,bdw */ if (display->platform.haswell || display->platform.broadwell) - return DIV_ROUND_UP(crtc_state->pixel_rate * 100, 95); + return DIV_ROUND_UP(crtc_state->pixel_rate_cdclk * 100, 95); /* no FBC specific limits to worry about */ return 0; diff --git a/drivers/gpu/drm/i915/display/intel_modeset_setup.c b/drivers/gpu/drm/i915/display/intel_modeset_setup.c index 14d829171c7d..6aed88173770 100644 --- a/drivers/gpu/drm/i915/display/intel_modeset_setup.c +++ b/drivers/gpu/drm/i915/display/intel_modeset_setup.c @@ -856,7 +856,7 @@ static void intel_modeset_readout_hw_state(struct intel_display *display) */ if (plane_state->uapi.visible && plane->min_cdclk) crtc_state->plane_min_cdclk[plane->id] = - DIV_ROUND_UP(crtc_state->pixel_rate, + DIV_ROUND_UP(crtc_state->pixel_rate_cdclk, intel_cdclk_ppc(display, crtc_state->double_wide)); drm_dbg_kms(display->drm, "[PLANE:%d:%s] min_cdclk %d kHz\n", diff --git a/drivers/gpu/drm/i915/display/intel_sprite.c b/drivers/gpu/drm/i915/display/intel_sprite.c index 3e38960bbbd8..2321bfbb8630 100644 --- a/drivers/gpu/drm/i915/display/intel_sprite.c +++ b/drivers/gpu/drm/i915/display/intel_sprite.c @@ -241,13 +241,13 @@ int vlv_plane_min_cdclk(const struct intel_crtc_state *crtc_state, unsigned int num, den; /* - * Note that crtc_state->pixel_rate accounts for both + * Note that crtc_state->pixel_rate_cdclk accounts for both * horizontal and vertical panel fitter downscaling factors. * Pre-HSW bspec tells us to only consider the horizontal * downscaling factor here. We ignore that and just consider * both for simplicity. */ - pixel_rate = crtc_state->pixel_rate; + pixel_rate = crtc_state->pixel_rate_cdclk; vlv_plane_ratio(crtc_state, plane_state, &num, &den); @@ -550,13 +550,13 @@ int ivb_plane_min_cdclk(const struct intel_crtc_state *crtc_state, unsigned int num, den; /* - * Note that crtc_state->pixel_rate accounts for both + * Note that crtc_state->pixel_rate_cdclk accounts for both * horizontal and vertical panel fitter downscaling factors. * Pre-HSW bspec tells us to only consider the horizontal * downscaling factor here. We ignore that and just consider * both for simplicity. */ - pixel_rate = crtc_state->pixel_rate; + pixel_rate = crtc_state->pixel_rate_cdclk; ivb_plane_ratio(crtc_state, plane_state, &num, &den); @@ -570,13 +570,13 @@ static int ivb_sprite_min_cdclk(const struct intel_crtc_state *crtc_state, unsigned int num, den; /* - * Note that crtc_state->pixel_rate accounts for both + * Note that crtc_state->pixel_rate_cdclk accounts for both * horizontal and vertical panel fitter downscaling factors. * Pre-HSW bspec tells us to only consider the horizontal * downscaling factor here. We ignore that and just consider * both for simplicity. */ - pixel_rate = crtc_state->pixel_rate; + pixel_rate = crtc_state->pixel_rate_cdclk; src_w = drm_rect_width(&plane_state->uapi.src) >> 16; dst_w = drm_rect_width(&plane_state->uapi.dst); @@ -629,7 +629,7 @@ static void hsw_plane_ratio(const struct intel_crtc_state *crtc_state, int hsw_plane_min_cdclk(const struct intel_crtc_state *crtc_state, const struct intel_plane_state *plane_state) { - unsigned int pixel_rate = crtc_state->pixel_rate; + unsigned int pixel_rate = crtc_state->pixel_rate_cdclk; unsigned int num, den; hsw_plane_ratio(crtc_state, plane_state, &num, &den); @@ -918,13 +918,13 @@ static int g4x_sprite_min_cdclk(const struct intel_crtc_state *crtc_state, unsigned int limit, decimate; /* - * Note that crtc_state->pixel_rate accounts for both + * Note that crtc_state->pixel_rate_cdclk accounts for both * horizontal and vertical panel fitter downscaling factors. * Pre-HSW bspec tells us to only consider the horizontal * downscaling factor here. We ignore that and just consider * both for simplicity. */ - pixel_rate = crtc_state->pixel_rate; + pixel_rate = crtc_state->pixel_rate_cdclk; /* Horizontal downscaling limits the maximum pixel rate */ hscale = drm_rect_calc_hscale(&plane_state->uapi.src, From f69946f6d891aca1614233573741543b3bab4e6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Wed, 15 Jul 2026 15:09:26 +0300 Subject: [PATCH 74/93] drm/i915/cdclk: Deal with 2 PPC hscale issues when calculating min CDCLK MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Double the fractional part of the horizontal scale factor for the purposes of min_cdck calculation. This bumps the min CDCLK sufficiently to overcome some kind of 2 PPC granularity issue. Without this CDCLK may end up being too low and we get underruns with certain horizontal downscale factors. The current Bspec formula calls for doubling only the fractional part below 0.5, and rounding it down to a unit fraction. But that formula does not result in a sufficient CDCLK bump in a lot of cases. Empirical evidence supports doubling the entire fractional part, so let's just do that while we wait for further analysis from the hardware team. Also note that the position of the scaler output window also seems to matter. If the output is near the left edge of the screen then lower CDCLK is sufficient, but moving the output window further to the right causes underruns unless CDCLK is also bumped. Some prefill happening during hblank already? Signed-off-by: Ville Syrjälä Link: https://patch.msgid.link/20260715120926.10786-5-ville.syrjala@linux.intel.com Reviewed-by: Nemesa Garg Tested-by: Vidya Srinivas --- drivers/gpu/drm/i915/display/intel_display.c | 26 ++++++++- drivers/gpu/drm/i915/display/intel_plane.c | 55 +++++++++++++++++++ drivers/gpu/drm/i915/display/intel_plane.h | 5 ++ .../drm/i915/display/skl_universal_plane.c | 6 +- 4 files changed, 88 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c index df43be51b3ba..2c55a4818ad7 100644 --- a/drivers/gpu/drm/i915/display/intel_display.c +++ b/drivers/gpu/drm/i915/display/intel_display.c @@ -2234,6 +2234,29 @@ static u32 ilk_pipe_pixel_rate(const struct intel_crtc_state *crtc_state) pixel_rate); } +static u32 ilk_pipe_pixel_rate_cdclk(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + u32 pixel_rate = crtc_state->hw.pipe_mode.crtc_clock; + unsigned int ppc = HAS_2PPC(display) ? 2 : 1; + struct drm_rect src; + + /* + * We only use IF-ID interlacing. If we ever use + * PF-ID we'll need to adjust the pixel_rate here. + */ + + if (!crtc_state->pch_pfit.enabled) + return pixel_rate; + + drm_rect_init(&src, 0, 0, + drm_rect_width(&crtc_state->pipe_src) << 16, + drm_rect_height(&crtc_state->pipe_src) << 16); + + return intel_adjusted_rate_cdclk(&src, &crtc_state->pch_pfit.dst, + pixel_rate, ppc); +} + static void intel_mode_from_crtc_timings(struct drm_display_mode *mode, const struct drm_display_mode *timings) { @@ -2267,7 +2290,8 @@ static void intel_crtc_compute_pixel_rate(struct intel_crtc_state *crtc_state) } else { crtc_state->pixel_rate = ilk_pipe_pixel_rate(crtc_state); - crtc_state->pixel_rate_cdclk = crtc_state->pixel_rate; + crtc_state->pixel_rate_cdclk = + ilk_pipe_pixel_rate_cdclk(crtc_state); } } diff --git a/drivers/gpu/drm/i915/display/intel_plane.c b/drivers/gpu/drm/i915/display/intel_plane.c index 667343bed5eb..c87885a32917 100644 --- a/drivers/gpu/drm/i915/display/intel_plane.c +++ b/drivers/gpu/drm/i915/display/intel_plane.c @@ -264,6 +264,50 @@ unsigned int intel_adjusted_rate(const struct drm_rect *src, dst_w * dst_h); } +static unsigned int hscale_cdclk(const struct drm_rect *src, + const struct drm_rect *dst, + unsigned int ppc) +{ + unsigned int hscale; + + hscale = drm_rect_calc_hscale(src, dst, 0, INT_MAX); + hscale = max(hscale, 0x10000); + + /* + * Double the fractional part due to some 2 PPC granularity issue + * + * FIXME: BSpec calls for doubling only the <0.5 fractional part, + * and rounding it down to a unit fraction. In practice that is + * not sufficient, and we need a more aggressive CDCLK bump in + * many cases. The updated formula was derived empirically. + * This may need to be updated once we have better undestading + * of what's happening in the hardware... + */ + return (hscale & ~0xffff) + ppc * (hscale & 0xffff); +} + +static unsigned int vscale_cdclk(const struct drm_rect *src, + const struct drm_rect *dst) +{ + unsigned int vscale; + + vscale = drm_rect_calc_vscale(src, dst, 0, INT_MAX); + vscale = max(vscale, 0x10000); + + return vscale; +} + +unsigned int intel_adjusted_rate_cdclk(const struct drm_rect *src, + const struct drm_rect *dst, + unsigned int rate, + unsigned int ppc) +{ + unsigned int hscale = hscale_cdclk(src, dst, ppc); + unsigned int vscale = vscale_cdclk(src, dst); + + return DIV64_U64_ROUND_UP((u64)rate * hscale * vscale, 1ull << 32); +} + unsigned int intel_plane_pixel_rate(const struct intel_crtc_state *crtc_state, const struct intel_plane_state *plane_state) { @@ -284,6 +328,17 @@ unsigned int intel_plane_pixel_rate(const struct intel_crtc_state *crtc_state, crtc_state->pixel_rate); } +unsigned int intel_plane_pixel_rate_cdclk(const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + unsigned int ppc = HAS_2PPC(display) ? 2 : 1; + + return intel_adjusted_rate_cdclk(&plane_state->uapi.src, + &plane_state->uapi.dst, + crtc_state->pixel_rate_cdclk, ppc); +} + unsigned int intel_plane_data_rate(const struct intel_crtc_state *crtc_state, const struct intel_plane_state *plane_state, int color_plane) diff --git a/drivers/gpu/drm/i915/display/intel_plane.h b/drivers/gpu/drm/i915/display/intel_plane.h index 31a6229aea73..dba2be24aae2 100644 --- a/drivers/gpu/drm/i915/display/intel_plane.h +++ b/drivers/gpu/drm/i915/display/intel_plane.h @@ -29,8 +29,13 @@ bool intel_plane_can_async_flip(struct intel_plane *plane, unsigned int intel_adjusted_rate(const struct drm_rect *src, const struct drm_rect *dst, unsigned int rate); +unsigned int intel_adjusted_rate_cdclk(const struct drm_rect *src, + const struct drm_rect *dst, + unsigned int rate, unsigned int ppc); unsigned int intel_plane_pixel_rate(const struct intel_crtc_state *crtc_state, const struct intel_plane_state *plane_state); +unsigned int intel_plane_pixel_rate_cdclk(const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state); unsigned int intel_plane_data_rate(const struct intel_crtc_state *crtc_state, const struct intel_plane_state *plane_state, diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.c b/drivers/gpu/drm/i915/display/skl_universal_plane.c index 164b7d61c9a3..b246fc48558b 100644 --- a/drivers/gpu/drm/i915/display/skl_universal_plane.c +++ b/drivers/gpu/drm/i915/display/skl_universal_plane.c @@ -266,7 +266,7 @@ bool icl_is_hdr_plane(struct intel_display *display, enum plane_id plane_id) static int icl_plane_min_cdclk(const struct intel_crtc_state *crtc_state, const struct intel_plane_state *plane_state) { - unsigned int pixel_rate = intel_plane_pixel_rate(crtc_state, plane_state); + unsigned int pixel_rate = intel_plane_pixel_rate_cdclk(crtc_state, plane_state); /* two pixels per clock */ return DIV_ROUND_UP(pixel_rate, 2); @@ -290,7 +290,7 @@ glk_plane_ratio(const struct intel_plane_state *plane_state, static int glk_plane_min_cdclk(const struct intel_crtc_state *crtc_state, const struct intel_plane_state *plane_state) { - unsigned int pixel_rate = intel_plane_pixel_rate(crtc_state, plane_state); + unsigned int pixel_rate = intel_plane_pixel_rate_cdclk(crtc_state, plane_state); unsigned int num, den; glk_plane_ratio(plane_state, &num, &den); @@ -317,7 +317,7 @@ skl_plane_ratio(const struct intel_plane_state *plane_state, static int skl_plane_min_cdclk(const struct intel_crtc_state *crtc_state, const struct intel_plane_state *plane_state) { - unsigned int pixel_rate = intel_plane_pixel_rate(crtc_state, plane_state); + unsigned int pixel_rate = intel_plane_pixel_rate_cdclk(crtc_state, plane_state); unsigned int num, den; skl_plane_ratio(plane_state, &num, &den); From 7d594b24c915afb4b0c5fb8875403253daef5b24 Mon Sep 17 00:00:00 2001 From: Suraj Kandpal Date: Thu, 16 Jul 2026 08:39:59 +0530 Subject: [PATCH 75/93] drm/i915/backlight: Remove DP_EDP_BACKLIGHT_AUX_ENABLE_CAP check for DPCD backlight MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turns out some panels allow only AUX based backlight by just setting the DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP and not setting the DP_EDP_BACKLIGHT_AUX_ENABLE_CAP. If we make DP_EDP_BACKLIGHT_AUX_ENABLE_CAP a necessity for AUX based DPCD backlight these panels loose the ability to manipulate backlight via AUX, especially ones with no PWM controller. Remove this check from function so that panels who do not advertise DP_EDP_BACKLIGHT_AUX_ENABLE_CAP but advertise DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP are able to manipulate backlight again. Fixes: ed8be780bdbc ("drm/i915/backlight: Fix VESA backlight possible check condition") Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/16507 Signed-off-by: Suraj Kandpal Reviewed-by: Michał Grzelak Link: https://patch.msgid.link/20260716030959.436430-1-suraj.kandpal@intel.com --- drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c b/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c index 7a6c07f6aaeb..266e042e0023 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c +++ b/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c @@ -615,12 +615,7 @@ check_if_vesa_backlight_possible(struct intel_dp *intel_dp) int ret; u8 bit_min, bit_max; - /* - * Since we only support Fully AUX Based VESA Backlight interface make sure - * backlight enable is possible via AUX along with backlight adjustment - */ - if (!(intel_dp->edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP && - intel_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP)) + if (!(intel_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP)) return false; ret = drm_dp_dpcd_read_byte(&intel_dp->aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, &bit_min); From b0b6d44646dc7750df4f052d17688fb7e7569a74 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Tue, 14 Jul 2026 18:26:55 +0300 Subject: [PATCH 76/93] drm/dp_tunnel: Add UHBR tunneling support Add the DPCD registers and detection required to support UHBR link rates over Thunderbolt tunnels. Cc: dri-devel@lists.freedesktop.org Reviewed-by: Suraj Kandpal Acked-by: Maarten Lankhorst Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260714152700.555527-2-imre.deak@intel.com --- drivers/gpu/drm/display/drm_dp_tunnel.c | 116 +++++++++++++++++++++++- include/drm/display/drm_dp.h | 12 +++ include/drm/display/drm_dp_tunnel.h | 21 +++++ 3 files changed, 148 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/display/drm_dp_tunnel.c b/drivers/gpu/drm/display/drm_dp_tunnel.c index 76c6bc84a806..d29ad4116e64 100644 --- a/drivers/gpu/drm/display/drm_dp_tunnel.c +++ b/drivers/gpu/drm/display/drm_dp_tunnel.c @@ -111,6 +111,8 @@ DPTUN_REG(DP_ALLOCATED_BW) | \ DPTUN_REG(DP_TUNNELING_MAX_LINK_RATE) | \ DPTUN_REG(DP_TUNNELING_MAX_LANE_COUNT) | \ + DPTUN_REG(DP_TUNNELING_MAIN_LINK_CHANNEL_CODING) | \ + DPTUN_REG(DP_TUNNELING_128B132B_LINK_RATE) | \ DPTUN_REG(DP_DPTX_BW_ALLOCATION_MODE_CONTROL)) static const DECLARE_BITMAP(dptun_info_regs, 64) = { @@ -140,11 +142,14 @@ struct drm_dp_tunnel { int estimated_bw; int allocated_bw; + u8 dprx_128b132b_rates; int max_dprx_rate; u8 max_dprx_lane_count; u8 adapter_id; + bool dprx_128b132b_support:1; + bool dprx_128b132b_lane0_mapping_support:1; bool bw_alloc_supported:1; bool bw_alloc_enabled:1; bool has_io_error:1; @@ -260,9 +265,46 @@ static int tunnel_reg_bw_granularity(const struct drm_dp_tunnel_regs *regs) return (250000 << gr) / 8; } +static bool tunnel_reg_dprx_128b132b_support(const struct drm_dp_tunnel_regs *regs) +{ + return tunnel_reg(regs, DP_TUNNELING_MAIN_LINK_CHANNEL_CODING) & DP_128B132B_DP_SUPPORTED; +} + +static bool tunnel_reg_dprx_128b132b_lane0_mapping_support(const struct drm_dp_tunnel_regs *regs) +{ + return tunnel_reg(regs, DP_TUNNELING_128B132B_LINK_RATE) & + DP_TUNNELING_128B132B_LL_LANE0_MAPPING_SUPPORT; +} + +static u8 tunnel_reg_dprx_128b132b_rates(const struct drm_dp_tunnel_regs *regs) +{ + if (!tunnel_reg_dprx_128b132b_support(regs)) + return 0; + + return tunnel_reg(regs, DP_TUNNELING_128B132B_LINK_RATE) & + DP_TUNNELING_128B132B_LINK_RATE_MASK; +} + +static u8 max_128b132b_rate(u8 rates) +{ + if (rates & DP_TUNNELING_20GBPS_PER_LANE_SUPPORT) + return DP_TUNNELING_20GBPS_PER_LANE_SUPPORT; + else if (rates & DP_TUNNELING_13_5GBPS_PER_LANE_SUPPORT) + return DP_TUNNELING_13_5GBPS_PER_LANE_SUPPORT; + else if (rates & DP_TUNNELING_10GBPS_PER_LANE_SUPPORT) + return DP_TUNNELING_10GBPS_PER_LANE_SUPPORT; + + WARN_ON(rates); + + return 0; +} + static int tunnel_reg_max_dprx_rate(const struct drm_dp_tunnel_regs *regs) { - u8 bw_code = tunnel_reg(regs, DP_TUNNELING_MAX_LINK_RATE); + u8 bw_code = max_128b132b_rate(tunnel_reg_dprx_128b132b_rates(regs)); + + if (!bw_code) + bw_code = tunnel_reg(regs, DP_TUNNELING_MAX_LINK_RATE); return drm_dp_bw_code_to_link_rate(bw_code); } @@ -706,6 +748,23 @@ static bool update_dprx_caps(struct drm_dp_tunnel *tunnel, const struct drm_dp_t { bool changed = false; + if (tunnel_reg_dprx_128b132b_support(regs) != tunnel->dprx_128b132b_support) { + tunnel->dprx_128b132b_support = tunnel_reg_dprx_128b132b_support(regs); + changed = true; + } + + if (tunnel_reg_dprx_128b132b_lane0_mapping_support(regs) != + tunnel->dprx_128b132b_lane0_mapping_support) { + tunnel->dprx_128b132b_lane0_mapping_support = + tunnel_reg_dprx_128b132b_lane0_mapping_support(regs); + changed = true; + } + + if (tunnel_reg_dprx_128b132b_rates(regs) != tunnel->dprx_128b132b_rates) { + tunnel->dprx_128b132b_rates = tunnel_reg_dprx_128b132b_rates(regs); + changed = true; + } + if (tunnel_reg_max_dprx_rate(regs) != tunnel->max_dprx_rate) { tunnel->max_dprx_rate = tunnel_reg_max_dprx_rate(regs); changed = true; @@ -1331,6 +1390,61 @@ int drm_dp_tunnel_handle_irq(struct drm_dp_tunnel_mgr *mgr, struct drm_dp_aux *a } EXPORT_SYMBOL(drm_dp_tunnel_handle_irq); +/** + * drm_dp_tunnel_128b132b_supported - Query if 128b132b is supported by the tunnel's DPRX + * @tunnel: Tunnel object + * + * The function is used to query if 128b132b is supported by the DPRX connected + * to @tunnel. + * + * Returns %true if 128b132b is supported by the DPRX. + */ +bool drm_dp_tunnel_128b132b_supported(const struct drm_dp_tunnel *tunnel) +{ + return tunnel->dprx_128b132b_support; +} +EXPORT_SYMBOL(drm_dp_tunnel_128b132b_supported); + +/** + * drm_dp_tunnel_128b132b_lane0_mapping_supported - Check 128b/132b lane 0 mapping support + * @tunnel: Tunnel object + * + * Check whether the DP-out adapter always maps lane 0 as expected by the + * DPRX on a tunneled 128b/132b link. If the function returns %true, one- and + * two-lane configurations with UHBR link rates can always be used. If it + * returns %false, using one or two lanes with UHBR link rates may cause a + * lane-count conversion failure in the DPRX, requiring corrective action by + * the source during link training. See DP Standard v2.1b, section + * 3.5.2.16.3, 128b/132b DPRX Lane Count Conversion Failure Indication and + * Corrective Action. + * + * A four-lane configuration can always be used, provided that the DPRX + * supports it, regardless of the function's return value. + * + * Returns %true if the DP-out adapter supports the 128b/132b lane 0 mapping. + */ +bool drm_dp_tunnel_128b132b_lane0_mapping_supported(const struct drm_dp_tunnel *tunnel) +{ + return tunnel->dprx_128b132b_lane0_mapping_support; +} +EXPORT_SYMBOL(drm_dp_tunnel_128b132b_lane0_mapping_supported); + +/** + * drm_dp_tunnel_128b132b_dprx_rates - Query the supported 128b132b rates of the tunnel's DPRX + * @tunnel: Tunnel object + * + * The function is used to query the supported 128b132b rates of the DPRX connected + * to @tunnel. Note that the related DP_128B132B_SUPPROTED_LINK_RATES DPCD + * register will indicate no supported 128B132B rates for a tunneled DPRX. + * + * Returns the mask of supported 128b132b rates. + */ +u8 drm_dp_tunnel_128b132b_dprx_rates(const struct drm_dp_tunnel *tunnel) +{ + return tunnel->dprx_128b132b_rates; +} +EXPORT_SYMBOL(drm_dp_tunnel_128b132b_dprx_rates); + /** * drm_dp_tunnel_max_dprx_rate - Query the maximum rate of the tunnel's DPRX * @tunnel: Tunnel object diff --git a/include/drm/display/drm_dp.h b/include/drm/display/drm_dp.h index 829e4d98d61c..8a827ed0b77c 100644 --- a/include/drm/display/drm_dp.h +++ b/include/drm/display/drm_dp.h @@ -1497,6 +1497,18 @@ #define DP_TUNNELING_MAX_LANE_COUNT 0xe0029 #define DP_TUNNELING_MAX_LANE_COUNT_MASK 0x1f +#define DP_TUNNELING_MAIN_LINK_CHANNEL_CODING 0xe002b +#define DP_128B132B_DP_SUPPORTED (1 << 0) + +#define DP_TUNNELING_128B132B_LINK_RATE 0xe002c +#define DP_TUNNELING_13_5GBPS_PER_LANE_SUPPORT (1 << 2) +#define DP_TUNNELING_20GBPS_PER_LANE_SUPPORT (1 << 1) +#define DP_TUNNELING_10GBPS_PER_LANE_SUPPORT (1 << 0) +#define DP_TUNNELING_128B132B_LINK_RATE_MASK (DP_TUNNELING_10GBPS_PER_LANE_SUPPORT | \ + DP_TUNNELING_13_5GBPS_PER_LANE_SUPPORT | \ + DP_TUNNELING_20GBPS_PER_LANE_SUPPORT) +#define DP_TUNNELING_128B132B_LL_LANE0_MAPPING_SUPPORT (1 << 7) + #define DP_DPTX_BW_ALLOCATION_MODE_CONTROL 0xe0030 #define DP_DISPLAY_DRIVER_BW_ALLOCATION_MODE_ENABLE (1 << 7) #define DP_UNMASK_BW_ALLOCATION_IRQ (1 << 6) diff --git a/include/drm/display/drm_dp_tunnel.h b/include/drm/display/drm_dp_tunnel.h index 57f5e90ba8fd..b5e665560a47 100644 --- a/include/drm/display/drm_dp_tunnel.h +++ b/include/drm/display/drm_dp_tunnel.h @@ -63,6 +63,9 @@ void drm_dp_tunnel_set_io_error(struct drm_dp_tunnel *tunnel); int drm_dp_tunnel_handle_irq(struct drm_dp_tunnel_mgr *mgr, struct drm_dp_aux *aux); +bool drm_dp_tunnel_128b132b_supported(const struct drm_dp_tunnel *tunnel); +bool drm_dp_tunnel_128b132b_lane0_mapping_supported(const struct drm_dp_tunnel *tunnel); +u8 drm_dp_tunnel_128b132b_dprx_rates(const struct drm_dp_tunnel *tunnel); int drm_dp_tunnel_max_dprx_rate(const struct drm_dp_tunnel *tunnel); int drm_dp_tunnel_max_dprx_lane_count(const struct drm_dp_tunnel *tunnel); int drm_dp_tunnel_available_bw(const struct drm_dp_tunnel *tunnel); @@ -173,6 +176,24 @@ drm_dp_tunnel_handle_irq(struct drm_dp_tunnel_mgr *mgr, return -EOPNOTSUPP; } +static inline bool +drm_dp_tunnel_128b132b_supported(const struct drm_dp_tunnel *tunnel) +{ + return false; +} + +static inline bool +drm_dp_tunnel_128b132b_lane0_mapping_supported(const struct drm_dp_tunnel *tunnel) +{ + return false; +} + +static inline u8 +drm_dp_tunnel_128b132b_dprx_rates(const struct drm_dp_tunnel *tunnel) +{ + return 0; +} + static inline int drm_dp_tunnel_max_dprx_rate(const struct drm_dp_tunnel *tunnel) { From eb23a5abd1d37c25fa906b5116f0814a68f4ee45 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Tue, 14 Jul 2026 18:26:56 +0300 Subject: [PATCH 77/93] drm/i915/dp: End link configuration loops properly Call intel_dp_link_caps_iter_end() after the link configuration loops. At the moment this call only clears the iteration object, so the lack of call didn't cause an actual issue. Cc: Luca Coelho Fixes: 7266df62ed0a7 ("drm/i915/dp: Iterate configurations via link_caps for SST non-DSC") Fixes: 4f104fc10a461 ("drm/i915/dp: Iterate configurations via link_caps for SST DSC") Reviewed-by: Suraj Kandpal Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260714152700.555527-3-imre.deak@intel.com --- drivers/gpu/drm/i915/display/intel_dp.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index 4d22ac8e0945..61b18e056c2a 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -1688,6 +1688,7 @@ intel_dp_compute_link_config_wide(struct intel_dp *intel_dp, struct intel_dp_link_caps *link_caps = intel_dp->link.caps; struct intel_dp_link_caps_order order = intel_dp_link_caps_connector_compute_order(connector); + int err = -EINVAL; int link_avail; for (bpp = fxp_q4_to_int(limits->link.max_bpp_x16); @@ -1718,12 +1719,18 @@ intel_dp_compute_link_config_wide(struct intel_dp *intel_dp, pipe_config->pipe_bpp = bpp; pipe_config->port_clock = link_config.rate; - return 0; + err = 0; + + break; } } + intel_dp_link_caps_iter_end(&iter); + + if (!err) + break; } - return -EINVAL; + return err; } int intel_dp_dsc_max_src_input_bpc(struct intel_display *display) @@ -1924,6 +1931,7 @@ static int dsc_compute_link_config(struct intel_dp *intel_dp, intel_dp_link_caps_connector_compute_order(connector); struct intel_dp_link_config link_config; struct intel_dp_link_caps_iter iter; + int err = -EINVAL; intel_dp_link_caps_iter_start(&iter, link_caps, order, limits->link_config_filter); for_each_dp_link_config(&iter, &link_config) { @@ -1966,10 +1974,13 @@ static int dsc_compute_link_config(struct intel_dp *intel_dp, continue; } - return 0; - } + err = 0; - return -EINVAL; + break; + } + intel_dp_link_caps_iter_end(&iter); + + return err; } static From b03e0f8651ca8d041cb2602be8bb701b5af83dbe Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Tue, 14 Jul 2026 18:26:57 +0300 Subject: [PATCH 78/93] drm/i915/dp: Enable SST fallback between UHBR and non-UHBR rates Enable link training fallback between UHBR and non-UHBR link rates on DP SST links. This was disabled so far to preserve the fallback behavior. There isn't a known issue related to such a fallback and DP MST has been using this for a while already. Also, enabling UHBR rates over Thunderbolt tunnels in a follow-up change, which at least on some links supports only 4 lanes and not 1 or 2 lanes on UHBR, makes a UHBR->non-UHBR fallback scenario more likely. At the same time align the corresponding link training fallback kunit test, allowing a UHBR <-> non-UHBR fallback there as well. Reviewed-by: Suraj Kandpal Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260714152700.555527-4-imre.deak@intel.com --- .../gpu/drm/i915/display/intel_dp_link_training.c | 5 ----- .../drm/i915/display/tests/intel_dp_link_test.c | 15 +++------------ 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_training.c b/drivers/gpu/drm/i915/display/intel_dp_link_training.c index fa55664c9d98..cb92cff90614 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_training.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_training.c @@ -1874,11 +1874,6 @@ static bool reduce_link_params(struct intel_dp *intel_dp, const struct intel_crt continue; } - if (!is_mst && - drm_dp_is_uhbr_rate(config.rate) != - drm_dp_is_uhbr_rate(old_config.rate)) - continue; - *new_link_rate = config.rate; *new_lane_count = config.lane_count; new_found = true; diff --git a/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c b/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c index e1c356ba11b5..776d93f33385 100644 --- a/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c +++ b/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c @@ -1199,11 +1199,6 @@ get_fallback_configs_for_output_type(struct kunit *test, } } -static bool output_type_allows_uhbr_fallback(enum intel_output_type output_type) -{ - return output_type == INTEL_OUTPUT_DP_MST; -} - static void assert_config_is_supported(const struct test_config_table *expected_table, const struct intel_dp_link_config *config) { @@ -1230,14 +1225,10 @@ static bool get_fallback_config(const struct test_config_table *expected_table, const struct intel_dp_link_config *config = &config_set->entries[i]; - if (output_type_allows_uhbr_fallback(output_type) || - (drm_dp_is_uhbr_rate(target_config->rate) == - drm_dp_is_uhbr_rate(config->rate))) { - assert_config_is_supported(expected_table, config); - *fallback_config = *config; + assert_config_is_supported(expected_table, config); + *fallback_config = *config; - return true; - } + return true; } return false; From 0f0950ca8eb20053022ce956cc3cd112279dd7bb Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Tue, 14 Jul 2026 18:26:58 +0300 Subject: [PATCH 79/93] drm/i915/dp: Remove UHBR dependency from SST fallback kunit test After fallback between UHBR and non-UHBR link rates got enabled in a previous change, there is no need to test fallback sequences for UHBR and non-UHBR rates separately. Make the test simply start from the maximum (UHBR) rate. Reviewed-by: Suraj Kandpal Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260714152700.555527-5-imre.deak@intel.com --- .../i915/display/tests/intel_dp_link_test.c | 47 +++++-------------- 1 file changed, 12 insertions(+), 35 deletions(-) diff --git a/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c b/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c index 776d93f33385..67c6fe9f1812 100644 --- a/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c +++ b/drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c @@ -1101,7 +1101,7 @@ static void intel_dp_link_test_fallback_for_edp(struct kunit *test) } static bool test_fallback_from_target(struct test_config_table *expected_table, - enum intel_output_type output_type, int max_rate, + enum intel_output_type output_type, const struct intel_dp_link_config *expected_target_config, const struct intel_dp_link_config *expected_fallback_config) { @@ -1123,8 +1123,7 @@ static bool test_fallback_from_target(struct test_config_table *expected_table, /* Get the max connector config, optionally filtered to the max_rate limit. */ lc_ops->iter_start(&iter, link_caps, fallback_order, INTEL_DP_LINK_CAPS_FILTER_ALL); for_each_dp_link_config(&iter, &iter_config) - if (max_rate == 0 || iter_config.rate <= max_rate) - break; + break; lc_ops->iter_end(&iter); KUNIT_EXPECT_TRUE(test, link_configs_match(&iter_config, @@ -1236,7 +1235,6 @@ static bool get_fallback_config(const struct test_config_table *expected_table, static bool get_target_config(const struct test_config_table *expected_table, enum intel_output_type output_type, - int max_rate, struct intel_dp_link_config *target) { struct kunit *test = expected_table->test; @@ -1248,20 +1246,17 @@ static bool get_target_config(const struct test_config_table *expected_table, const struct intel_dp_link_config *config = &config_set->entries[i]; - if (config->rate <= max_rate) { - assert_config_is_supported(expected_table, config); - *target = *config; + assert_config_is_supported(expected_table, config); + *target = *config; - return true; - } + return true; } return false; } static void test_fallback_seq(struct kunit *test, - enum intel_output_type output_type, - bool uhbr) + enum intel_output_type output_type) { struct test_ctx *ctx = test->priv; struct intel_dp_link_caps *link_caps = ctx->dev.dig_port.dp.link.caps; @@ -1273,12 +1268,6 @@ static void test_fallback_seq(struct kunit *test, struct intel_dp_link_config target_config; int fallback_count = 0; bool target_found; - int max_rate; - - if (uhbr) - max_rate = expected_table.rates.entries[expected_table.rates.size - 1]; - else - max_rate = 810000; dig_port->base.type = output_type; ctx->dev.dig_port.dp.use_max_params = false; @@ -1287,7 +1276,7 @@ static void test_fallback_seq(struct kunit *test, /* Get the initial target config. */ target_found = get_target_config(&expected_table, output_type, - max_rate, &target_config); + &target_config); KUNIT_ASSERT_TRUE(test, target_found); for (;;) { @@ -1296,16 +1285,10 @@ static void test_fallback_seq(struct kunit *test, &target_config, &fallback_config)) fallback_config = INTEL_DP_LINK_CONFIG_NULL; - if (!test_fallback_from_target(&expected_table, output_type, max_rate, + if (!test_fallback_from_target(&expected_table, output_type, &target_config, &fallback_config)) break; - /* - * The fallback changed the max rate allowed for the next - * target. - */ - max_rate = fallback_config.rate; - /* Simply select the fallback config as the next target. */ target_config = fallback_config; @@ -1314,14 +1297,9 @@ static void test_fallback_seq(struct kunit *test, } } -static void intel_dp_link_test_fallback_for_sst_max_non_uhbr(struct kunit *test) +static void intel_dp_link_test_fallback_for_sst(struct kunit *test) { - test_fallback_seq(test, INTEL_OUTPUT_DP, false); -} - -static void intel_dp_link_test_fallback_for_sst_max_uhbr(struct kunit *test) -{ - test_fallback_seq(test, INTEL_OUTPUT_DP, true); + test_fallback_seq(test, INTEL_OUTPUT_DP); } static void intel_dp_link_test_fallback_for_mst(struct kunit *test) @@ -1330,7 +1308,7 @@ static void intel_dp_link_test_fallback_for_mst(struct kunit *test) ctx->dev.connector.mst.dp = &ctx->dev.dig_port.dp; - test_fallback_seq(test, INTEL_OUTPUT_DP_MST, true); + test_fallback_seq(test, INTEL_OUTPUT_DP_MST); } static struct kunit_case intel_dp_link_test_cases[] = { @@ -1352,8 +1330,7 @@ static struct kunit_case intel_dp_link_test_cases[] = { KUNIT_CASE(intel_dp_link_caps_test_update_params_expand_disable_random), KUNIT_CASE(intel_dp_link_test_fallback_for_edp), - KUNIT_CASE(intel_dp_link_test_fallback_for_sst_max_non_uhbr), - KUNIT_CASE(intel_dp_link_test_fallback_for_sst_max_uhbr), + KUNIT_CASE(intel_dp_link_test_fallback_for_sst), KUNIT_CASE(intel_dp_link_test_fallback_for_mst), {} From 47fff2b1749e9ba5dfab7528960555cc865360b5 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Tue, 14 Jul 2026 18:26:59 +0300 Subject: [PATCH 80/93] drm/i915/dp: Disable UHBR link configs with 1/2 lanes Disable 1 and 2 lane link configurations on UHBR tunneled links, according to DP Standard v2.1b "3.5.2.16.3 128b/132b DPRX Lane Count Conversion Failure Indication and Corrective Action". A tunnel can indicate if it's not affected by this limitation, check for that and skip disabling the unsupported lanes for a well-behaving tunnel. The Standard in the same section also describes a workaround for 2 lanes which requires assisstance from the sink, where the sink indicates at the end of the link training sequence if the link training must be retried. This mechanism also requires quirking out some sinks - based on the sink's DPCD OUI and EDID identifications - which doesn't implement this link training feedback indication properly. This patch leaves the implementation of this workaround for a follow-up, but prepares for it already by detecting the supported number of lane counts at a place where both DPCD OUI and EDID is available for the quirk detection. Reviewed-by: Suraj Kandpal Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260714152700.555527-6-imre.deak@intel.com --- .../drm/i915/display/intel_display_types.h | 1 + drivers/gpu/drm/i915/display/intel_dp.c | 7 +++ drivers/gpu/drm/i915/display/intel_dp_mst.c | 5 +++ .../gpu/drm/i915/display/intel_dp_tunnel.c | 45 +++++++++++++++++++ .../gpu/drm/i915/display/intel_dp_tunnel.h | 17 +++++++ 5 files changed, 75 insertions(+) diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h index 17336ad6d5e9..b7cc361fd955 100644 --- a/drivers/gpu/drm/i915/display/intel_display_types.h +++ b/drivers/gpu/drm/i915/display/intel_display_types.h @@ -1878,6 +1878,7 @@ struct intel_dp { struct drm_dp_tunnel *tunnel; bool tunnel_suspended:1; + u8 disabled_uhbr_lane_mask; struct { struct intel_dp_mst_encoder *stream_encoders[I915_MAX_PIPES]; diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index 61b18e056c2a..6bcaba434788 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -3590,6 +3590,8 @@ void intel_dp_reset_link_params(struct intel_dp *intel_dp) * was called. */ intel_dp_link_caps_reset(intel_dp->link.caps); + intel_dp_tunnel_uhbr_lanes_wa_apply(intel_dp); + intel_dp->link.mst_probed_lane_count = 0; intel_dp->link.mst_probed_rate = 0; intel_dp_link_training_reset(intel_dp->link.training); @@ -6242,6 +6244,8 @@ intel_dp_detect(struct drm_connector *_connector, intel_dp_tunnel_disconnect(intel_dp); + intel_dp_tunnel_uhbr_lanes_wa_reset(intel_dp); + goto out_unset_edid; } @@ -6306,6 +6310,9 @@ intel_dp_detect(struct drm_connector *_connector, if (intel_dp_is_edp(intel_dp) || connector->detect_edid) status = connector_status_connected; + if (intel_dp_tunnel_uhbr_lanes_wa_setup(intel_dp)) + intel_dp_tunnel_uhbr_lanes_wa_apply(intel_dp); + out_unset_edid: if (status != connector_status_connected && !intel_dp->is_mst) intel_dp_unset_edid(intel_dp); diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.c b/drivers/gpu/drm/i915/display/intel_dp_mst.c index b5af01159949..3be1643f8d03 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_mst.c +++ b/drivers/gpu/drm/i915/display/intel_dp_mst.c @@ -1453,6 +1453,11 @@ static int mst_connector_get_ddc_modes(struct drm_connector *_connector) drm_edid_free(drm_edid); + if (intel_dp_tunnel_uhbr_lanes_wa_setup(intel_dp)) { + intel_dp_flush_connector_commits(connector); + intel_dp_tunnel_uhbr_lanes_wa_apply(intel_dp); + } + return ret; } diff --git a/drivers/gpu/drm/i915/display/intel_dp_tunnel.c b/drivers/gpu/drm/i915/display/intel_dp_tunnel.c index 49fa4c9699b6..219939634564 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_tunnel.c +++ b/drivers/gpu/drm/i915/display/intel_dp_tunnel.c @@ -831,6 +831,51 @@ void intel_dp_tunnel_atomic_alloc_bw(struct intel_atomic_state *state) atomic_increase_bw(state); } +static u8 lane_count_mask(int lane_count) +{ + return BIT(ilog2(lane_count)); +} + +void intel_dp_tunnel_uhbr_lanes_wa_apply(struct intel_dp *intel_dp) +{ + struct intel_connector *connector = intel_dp->attached_connector; + struct intel_dp_link_caps_order order = + intel_dp_link_caps_connector_compute_order(connector); + struct intel_dp_link_caps *link_caps = intel_dp->link.caps; + struct intel_dp_link_config link_config; + struct intel_dp_link_caps_iter iter; + + if (!intel_dp->disabled_uhbr_lane_mask) + return; + + intel_dp_link_caps_iter_start(&iter, link_caps, order, INTEL_DP_LINK_CAPS_FILTER_ALL); + for_each_dp_link_config(&iter, &link_config) { + if (drm_dp_is_uhbr_rate(link_config.rate) && + lane_count_mask(link_config.lane_count) & intel_dp->disabled_uhbr_lane_mask) + intel_dp_link_caps_disable_config(link_caps, &link_config); + } + intel_dp_link_caps_iter_end(&iter); +} + +bool intel_dp_tunnel_uhbr_lanes_wa_setup(struct intel_dp *intel_dp) +{ + u8 old_mask = intel_dp->disabled_uhbr_lane_mask; + + if (!intel_dp_tunnel_bw_alloc_is_enabled(intel_dp) || + drm_dp_tunnel_128b132b_lane0_mapping_supported(intel_dp->tunnel)) + intel_dp->disabled_uhbr_lane_mask = 0; + else + /* TODO: Add support for keeping 2 lanes enabled as well. */ + intel_dp->disabled_uhbr_lane_mask = lane_count_mask(1) | lane_count_mask(2); + + return intel_dp->disabled_uhbr_lane_mask != old_mask; +} + +void intel_dp_tunnel_uhbr_lanes_wa_reset(struct intel_dp *intel_dp) +{ + intel_dp->disabled_uhbr_lane_mask = 0; +} + /** * intel_dp_tunnel_mgr_init - Initialize the DP tunnel manager * @display: display device diff --git a/drivers/gpu/drm/i915/display/intel_dp_tunnel.h b/drivers/gpu/drm/i915/display/intel_dp_tunnel.h index 8273e681a512..13fc8fbb6e30 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_tunnel.h +++ b/drivers/gpu/drm/i915/display/intel_dp_tunnel.h @@ -54,6 +54,10 @@ int intel_dp_tunnel_atomic_check_state(struct intel_atomic_state *state, void intel_dp_tunnel_atomic_alloc_bw(struct intel_atomic_state *state); +void intel_dp_tunnel_uhbr_lanes_wa_apply(struct intel_dp *intel_dp); +bool intel_dp_tunnel_uhbr_lanes_wa_setup(struct intel_dp *intel_dp); +void intel_dp_tunnel_uhbr_lanes_wa_reset(struct intel_dp *intel_dp); + int intel_dp_tunnel_mgr_init(struct intel_display *display); void intel_dp_tunnel_mgr_cleanup(struct intel_display *display); @@ -129,6 +133,19 @@ intel_dp_tunnel_atomic_alloc_bw(struct intel_atomic_state *state) return 0; } +static inline void intel_dp_tunnel_uhbr_lanes_wa_apply(struct intel_dp *intel_dp) +{ +} + +static inline bool intel_dp_tunnel_uhbr_lanes_wa_setup(struct intel_dp *intel_dp) +{ + return false; +} + +static inline void intel_dp_tunnel_uhbr_lanes_wa_reset(struct intel_dp *intel_dp) +{ +} + static inline int intel_dp_tunnel_mgr_init(struct intel_display *display) { From 1fd440de4e02efab97874bbda0a4daf59d228fa3 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Tue, 14 Jul 2026 18:27:00 +0300 Subject: [PATCH 81/93] drm/i915/dp_tunnel: Add UHBR tunneling support Add support for UHBR link rates on Thunderbolt tunneled links. Reviewed-by: Suraj Kandpal Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260714152700.555527-7-imre.deak@intel.com --- drivers/gpu/drm/i915/display/intel_dp.c | 37 +++++++++++++++++++++---- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index 6bcaba434788..ea557af82662 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -191,6 +191,29 @@ static void intel_dp_set_default_sink_rates(struct intel_dp *intel_dp) intel_dp->num_sink_rates = 1; } +static bool dprx_supports_128b132b(struct intel_dp *intel_dp) +{ + if (intel_dp_tunnel_bw_alloc_is_enabled(intel_dp)) + return drm_dp_tunnel_128b132b_supported(intel_dp->tunnel); + else + return drm_dp_128b132b_supported(intel_dp->dpcd); +} + +static u8 dprx_128b132b_link_rates(struct intel_dp *intel_dp, u8 no_bwa_rates) +{ + u8 ret; + + if (!intel_dp_tunnel_bw_alloc_is_enabled(intel_dp)) + return no_bwa_rates; + + ret = drm_dp_tunnel_128b132b_dprx_rates(intel_dp->tunnel); + static_assert(DP_TUNNELING_10GBPS_PER_LANE_SUPPORT == DP_UHBR10 && + DP_TUNNELING_13_5GBPS_PER_LANE_SUPPORT == DP_UHBR13_5 && + DP_TUNNELING_20GBPS_PER_LANE_SUPPORT == DP_UHBR20); + + return ret; +} + /* update sink rates from dpcd */ static void intel_dp_set_dpcd_sink_rates(struct intel_dp *intel_dp) { @@ -199,6 +222,7 @@ static void intel_dp_set_dpcd_sink_rates(struct intel_dp *intel_dp) }; int i, max_rate; int max_lttpr_rate; + u8 uhbr_rates = 0; if (drm_dp_has_quirk(&intel_dp->desc, DP_DPCD_QUIRK_CAN_DO_MAX_LINK_RATE_3_24_GBPS)) { /* Needed, e.g., for Apple MBP 2017, 15 inch eDP Retina panel */ @@ -224,17 +248,20 @@ static void intel_dp_set_dpcd_sink_rates(struct intel_dp *intel_dp) intel_dp->sink_rates[i] = dp_rates[i]; } + /* + * The following register must be read unconditionally for the later + * DP tunnel 128b132b detection to work, see DP Standard v2.1 5.14.3 . + */ + drm_dp_dpcd_read_byte(&intel_dp->aux, DP_128B132B_SUPPORTED_LINK_RATES, &uhbr_rates); + /* * Sink rates for 128b/132b. If set, sink should support all 8b/10b * rates and 10 Gbps. */ - if (drm_dp_128b132b_supported(intel_dp->dpcd)) { - u8 uhbr_rates = 0; - + if (dprx_supports_128b132b(intel_dp)) { BUILD_BUG_ON(ARRAY_SIZE(intel_dp->sink_rates) < ARRAY_SIZE(dp_rates) + 3); - drm_dp_dpcd_readb(&intel_dp->aux, - DP_128B132B_SUPPORTED_LINK_RATES, &uhbr_rates); + uhbr_rates = dprx_128b132b_link_rates(intel_dp, uhbr_rates); if (drm_dp_lttpr_count(intel_dp->lttpr_common_caps)) { /* We have a repeater */ From 8b9bc7786adef4e2df3a23293c0682efac6dcc83 Mon Sep 17 00:00:00 2001 From: Uma Shankar Date: Wed, 15 Jul 2026 20:02:42 +0530 Subject: [PATCH 82/93] drm/i915/dram: Interpret 0xF populated-channel count as 16 Wa_16030862157: Interpret 0xF populated-channel count as 16 The register MEM_SS_INFO_GLOBAL [Number of populated channels] field definition is updated with an encoding for 16 channels. For 16-channel configuration, program 1111b. A programmed value of 1111b must be interpreted as 16 channels for memory bandwidth calculations. The MEM_SS_INFO_GLOBAL populated-channel field is only 4 bits and cannot encode 16, so on Xe3p the BIOS programs the saturated field value (0xf) to indicate the fully-populated 16-channel config (4 memory controllers x 4 channels). Interpret it as 16 and let the bandwidth math handle the larger channel count. v2: Limit the WA only till NVL (Suraj) Logical separation of WA (Vinod) WA: 16030862157, 16030875223 Bspec: 69131, 79482 Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Uma Shankar Reviewed-by: Suraj Kandpal Reviewed-by: Vinod Govindapillai Link: https://patch.msgid.link/20260715143243.4141208-2-uma.shankar@intel.com --- drivers/gpu/drm/i915/display/intel_display_wa.c | 2 ++ drivers/gpu/drm/i915/display/intel_display_wa.h | 1 + drivers/gpu/drm/i915/display/intel_dram.c | 14 +++++++++++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/display/intel_display_wa.c b/drivers/gpu/drm/i915/display/intel_display_wa.c index b4c49816f7eb..3662e0f17c69 100644 --- a/drivers/gpu/drm/i915/display/intel_display_wa.c +++ b/drivers/gpu/drm/i915/display/intel_display_wa.c @@ -140,6 +140,8 @@ bool __intel_display_wa(struct intel_display *display, enum intel_display_wa wa, STEP_A0, STEP_B0); case INTEL_DISPLAY_WA_16029024088: return DISPLAY_VER(display) >= 35; + case INTEL_DISPLAY_WA_16030862157: + return DISPLAY_VER(display) == 35; case INTEL_DISPLAY_WA_18034343758: return DISPLAY_VER(display) == 20 || (display->platform.pantherlake && diff --git a/drivers/gpu/drm/i915/display/intel_display_wa.h b/drivers/gpu/drm/i915/display/intel_display_wa.h index 92b3980bea84..338b32e4162d 100644 --- a/drivers/gpu/drm/i915/display/intel_display_wa.h +++ b/drivers/gpu/drm/i915/display/intel_display_wa.h @@ -54,6 +54,7 @@ enum intel_display_wa { INTEL_DISPLAY_WA_16025573575, INTEL_DISPLAY_WA_16025596647, INTEL_DISPLAY_WA_16029024088, + INTEL_DISPLAY_WA_16030862157, INTEL_DISPLAY_WA_18034343758, INTEL_DISPLAY_WA_22010178259, INTEL_DISPLAY_WA_22010947358, diff --git a/drivers/gpu/drm/i915/display/intel_dram.c b/drivers/gpu/drm/i915/display/intel_dram.c index f103f7cba018..0763d0bfd98e 100644 --- a/drivers/gpu/drm/i915/display/intel_dram.c +++ b/drivers/gpu/drm/i915/display/intel_dram.c @@ -13,6 +13,7 @@ #include "intel_display_core.h" #include "intel_display_utils.h" #include "intel_display_regs.h" +#include "intel_display_wa.h" #include "intel_dram.h" #include "intel_mchbar.h" #include "intel_parent.h" @@ -794,7 +795,18 @@ static int xelpdp_get_dram_info(struct intel_display *display, struct dram_info dram_info->num_channels = REG_FIELD_GET(MTL_N_OF_POPULATED_CH_MASK, val); dram_info->num_qgv_points = REG_FIELD_GET(MTL_N_OF_ENABLED_QGV_POINTS_MASK, val); - /* PSF GV points not supported in D14+ */ + + /* + * Wa_16030862157 + * MEM_SS_INFO_GLOBAL populated-channel field is only 4 bits and + * cannot encode 16, so on Xe3p the BIOS programs the saturated field + * value (0xf) to indicate the fully-populated 16-channel config (4 + * memory controllers x 4 channels). Interpret it as 16. + */ + + if (intel_display_wa(display, INTEL_DISPLAY_WA_16030862157) && + dram_info->num_channels == REG_FIELD_MAX(MTL_N_OF_POPULATED_CH_MASK)) + dram_info->num_channels = 16; if (DISPLAY_VER(display) >= 35) dram_info->ecc_impacting_de_bw = REG_FIELD_GET(XE3P_ECC_IMPACTING_DE, val); From 7a4c8d8830c5bd58e4e26be92339984705c1d641 Mon Sep 17 00:00:00 2001 From: Uma Shankar Date: Wed, 15 Jul 2026 20:02:43 +0530 Subject: [PATCH 83/93] drm/i915/bw: Update bw calculation to account for 16 channels Wa_16030862157: Update Bandwidth Calculation to account for 16channel memory config. v2: Logical separation of changes (Suraj, Vinod) WA: 16030862157, 16030875223 Bspec: 69131, 68859 Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Uma Shankar Reviewed-by: Suraj Kandpal Reviewed-by: Vinod Govindapillai Link: https://patch.msgid.link/20260715143243.4141208-3-uma.shankar@intel.com --- drivers/gpu/drm/i915/display/intel_bw.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_bw.c b/drivers/gpu/drm/i915/display/intel_bw.c index 546d5ccbe052..f4ae6db9f567 100644 --- a/drivers/gpu/drm/i915/display/intel_bw.c +++ b/drivers/gpu/drm/i915/display/intel_bw.c @@ -14,6 +14,7 @@ #include "intel_display_regs.h" #include "intel_display_types.h" #include "intel_display_utils.h" +#include "intel_display_wa.h" #include "intel_dram.h" #include "intel_mchbar.h" #include "intel_parent.h" @@ -272,7 +273,14 @@ static int icl_init_qgv_info(struct intel_display *display, case INTEL_DRAM_LPDDR4: case INTEL_DRAM_LPDDR5: qi->t_bl = 16; - qi->max_numchannels = 8; + /* + * Wa_16030862157 + * Xe3p supports a fully-populated 16-channel LPDDR + * config (4 memory controllers x 4 channels); earlier + * D14+ platforms top out at 8. + */ + qi->max_numchannels = + intel_display_wa(display, INTEL_DISPLAY_WA_16030862157) ? 16 : 8; qi->channel_width = 16; qi->deinterleave = 4; break; @@ -666,10 +674,16 @@ static int tgl_get_bw_info(struct intel_display *display, ipqdepth = min(ipqdepthpch, display_bw_params->displayrtids / num_channels); /* + * Wa_16030862157 * clperchgroup = 4kpagespermempage * clperchperblock, - * clperchperblock = 8 / num_channels * interleave + * clperchperblock = max(8 / num_channels, 1) * interleave + * + * The 8 / num_channels truncating divide collapses to 0 for + * >8-channel configs (16-channel: 8 / 16 = 0); the max(..., 1) floor + * keeps clperchperblock >= 1 there while preserving the literal + * truncating divide for <=8-channel configs. */ - clperchgroup = 4 * (8 / num_channels) * qi.deinterleave; + clperchgroup = 4 * max(8 / num_channels, 1) * qi.deinterleave; display->bw.num_qgv_points = qi.num_qgv_points; display->bw.num_psf_gv_points = qi.num_psf_points; From 662560bad9be29c3e1a2668806392d2ed9a743d6 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Mon, 20 Jul 2026 17:16:30 +0100 Subject: [PATCH 84/93] drm/i915/bw: Fix spelling mistake "threshod" -> "threshold" There is a spelling mistake in a drm_dbg_kms message. Fix it. Signed-off-by: Colin Ian King Link: https://patch.msgid.link/20260720161630.326016-1-colin.i.king@gmail.com Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/i915/display/intel_bw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/display/intel_bw.c b/drivers/gpu/drm/i915/display/intel_bw.c index f4ae6db9f567..99fd84a5ac13 100644 --- a/drivers/gpu/drm/i915/display/intel_bw.c +++ b/drivers/gpu/drm/i915/display/intel_bw.c @@ -633,7 +633,7 @@ static void xe3_add_peakbw_threshold(struct intel_display *display) for (int i = 0; i < ARRAY_SIZE(display->bw.max); i++) display->bw.max[i].deratedbw[qgv_points] = PEAK_BW_THRESHOLD; - drm_dbg_kms(display->drm, "An extra QGV point %d added for Peak bw threshod of %d\n", + drm_dbg_kms(display->drm, "An extra QGV point %d added for Peak bw threshold of %d\n", qgv_points, PEAK_BW_THRESHOLD); } From d87992672c342e70e250c57f05a60cd70baff298 Mon Sep 17 00:00:00 2001 From: Suraj Kandpal Date: Fri, 17 Jul 2026 09:17:34 +0530 Subject: [PATCH 85/93] drm/i915/hdcp: Make hdcp2_encrypted and hdcp_encrypted mutually exclusive With introduction of force_hdcp14 we can now for HDCP 1.4 by passing HDCP 2.2 authentication but In MST, _intel_hdcp_disable() and _intel_hdcp2_disable() can return early when there are still other streams active on the port (num_streams > 0), leaving the per-connector hdcp_encrypted / hdcp2_encrypted flag stale. If the same connector is later re-enabled with the other HDCP version (e.g. HDCP 1.4 via force_hdcp14 after a prior HDCP 2.2 session on that connector) the stale flag from the previous protocol survives. intel_hdcp_disable() then picks the wrong branch on the next disable causing the following splat. xe 0000:00:02.0: [drm] drm_WARN_ON(!(intel_de_read(display, ((((&(display)->info.__runtime_info)->ip.ver) >= 12) ? ((const i915_reg_t){ .reg = (((0x664B4) + (cpu_transcoder) * ((0x665B4) - (0x664B4)))) }) : ((const i915_reg_t){ .reg = ((((const u32 []){ 0x66800, 0x66500, 0x66600, 0x66700, 0x66A00, 0x66900 })[(port)]) + (0xB4)) }))) & ((u32)(((int)sizeof(struct {_Static_assert(!(__builtin_choose_expr((sizeof(int) == sizeof(*(8 ? ((void *)((long)((20) >= (sizeof(u32) * 8)) * 0l)) : (int *)8))), (20) >= (sizeof(u32) * 8), false)), "const_true((20) >= BITS_PER_TYPE(u32))" " is true");})) + ((((1ULL))) << (20)))))) WARNING: drivers/gpu/drm/i915/display/intel_hdcp.c:1969 at _intel_hdcp2_disable+0x4bf/0x520 [xe], CPU#2: kms_content_pro/101318 Modules linked in: xe vfio_pci_core vfio_iommu_type1 vfio iommufd xt_multiport snd_hda_codec_intelhdmi snd_hda_codec_hdmi drm_gpuvm drm_gpusvm_helper drm_buddy gpu_sched drm_ttm_helper ttm drm_suballoc_helper drm_exec drm_display_helper cec rc_core drm_kunit_helpers kunit i2c_algo_bit xt_conntrack xt_MASQUERADE bridge stp llc nft_chain_nat nf_nat nf_conntrack nf_defrag_ipv6 nf_defrag_ipv4 xt_addrtype nft_compat x_tables nf_tables xfrm_user xfrm_algo overlay cpuid cmdlinepart spi_nor mei_gsc_proxy mtd intel_rapl_msr wmi_bmof intel_uncore_frequency intel_uncore_frequency_common x86_pkg_temp_thermal intel_powerclamp coretemp snd_intel_dspcfg aesni_intel snd_hda_codec gf128mul snd_hda_core snd_hwdep r8169 snd_pcm intel_cstate snd_timer i2c_i801 i2c_mux snd spi_intel_pci processor_thermal_device_pci realtek video e1000e spi_intel i2c_smbus soundcore phy_package processor_thermal_device processor_thermal_wt_hint mei_me platform_temperature_control processor_thermal_soc_slider idma64 mei processor_thermal_rfim processor_thermal_rapl intel_ish_ipc intel_rapl_common intel_ishtp thunderbolt processor_thermal_wt_req processor_thermal_power_floor intel_vpu processor_thermal_mbox igen6_edac wmi sunrpc kvm_intel binfmt_misc kvm irqbypass int3403_thermal int340x_thermal_zone intel_skl_int3472_tps68470 tps68470_regulator intel_pmc_core clk_tps68470 acpi_tad int3400_thermal intel_hid pmt_telemetry acpi_thermal_rel sparse_keymap pmt_discovery pinctrl_intel_platform pmt_class intel_pmc_ssram_telemetry intel_pmc_pwrm_telemetry intel_skl_int3472_discrete intel_skl_int3472_common acpi_pad intel_vsec nls_iso8859_1 dm_multipath msr fuse efi_pstore autofs4 [last unloaded: xe_live_test] CPU: 2 UID: 0 PID: 101318 Comm: kms_content_pro Kdump: loaded Tainted: G U L N 7.2.0-rc2-lgci-xe-xe-5389-67aa8ad5c4c59cd1a-debug+ #1 PREEMPT(lazy) Tainted: [U]=USER, [L]=SOFTLOCKUP, [N]=TEST Hardware name: Intel Corporation Panther Lake Client Platform/PTL-UH LP5 T3 RVP1, BIOS PTLPFWI1.R00.3514.D01.2512291130 12/29/2025 RIP: 0010:_intel_hdcp2_disable+0x4c9/0x520 [xe] Code: 85 ff 74 04 48 8b 7f 08 4c 8b 7f 50 4d 85 ff 74 23 e8 5b 80 df e0 48 89 c6 48 8d 3d b1 f7 ba ff 48 c7 c1 a0 27 6a a1 4c 89 fa <67> 48 0f b9 3a e9 c9 fc ff ff 4c 8b 3f eb d8 e8 33 45 4e e1 4c 89 RSP: 0018:ffffc900027bf738 EFLAGS: 00010246 RAX: ffffffffa16bd4f1 RBX: ffff8881a1e69000 RCX: ffffffffa16a27a0 RDX: ffff8881c7b1b190 RSI: ffffffffa16bd4f1 RDI: ffffffffa100de30 RBP: ffffc900027bf7b0 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000000 R12: ffff88844397a000 R13: ffff88844397a000 R14: ffff88817f7d2000 R15: ffff8881c7b1b190 FS: 0000774e9299dbc0(0000) GS:ffff8884eb36c000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000774e94bc7220 CR3: 00000002166c1002 CR4: 0000000108f72ef0 PKRU: 55555554 Call Trace: ? intel_hdcp_update_value+0x7d/0x260 [xe] intel_hdcp_disable+0x118/0x130 [xe] intel_hdcp_update_pipe+0x175/0x1b0 [xe] ? intel_initial_watermarks+0x20/0x60 [xe] intel_ddi_update_pipe+0x44/0xb0 [xe] intel_pre_update_crtc+0x3bc/0x410 [xe] skl_commit_modeset_enables+0x1a7/0x890 [xe] ? icl_sagv_pre_plane_update+0x6c/0xf0 [xe] intel_atomic_commit_tail+0x114b/0x20a0 [xe] ? lock_release+0xd0/0x2a0 intel_atomic_commit+0x2ff/0x350 [xe] ? intel_atomic_commit+0x2ff/0x350 [xe] drm_atomic_commit+0xaf/0xf0 ? __pfx___drm_printfn_info+0x10/0x10 drm_mode_atomic_ioctl+0xcb5/0xf60 ? drm_mode_atomic_ioctl+0x8a8/0xf60 ? drm_mode_atomic_ioctl+0x604/0xf60 ? __pfx_drm_mode_atomic_ioctl+0x10/0x10 drm_ioctl_kernel+0xb3/0x120 drm_ioctl+0x2db/0x5a0 ? __pfx_drm_mode_atomic_ioctl+0x10/0x10 ? _raw_spin_unlock_irqrestore+0x51/0x80 ? __pm_runtime_resume+0x5b/0x90 ? xe_pm_runtime_get_ioctl+0x61/0x150 [xe] xe_drm_ioctl+0x64/0xb0 [xe] __x64_sys_ioctl+0xa5/0x100 x64_sys_call+0x1250/0x26e0 do_syscall_64+0x103/0x6d0 ? __task_pid_nr_ns+0xc0/0x290 ? trace_hardirqs_on_prepare+0xcb/0xf0 ? do_syscall_64+0x34/0x6d0 ? fred_entry_from_user+0x92/0x140 ? trace_hardirqs_on+0x22/0xe0 ? do_syscall_64+0xb8/0x6d0 fred_entry_from_user+0x92/0x140 asm_fred_entrypoint_user+0x41/ Ensure the two flags are mutually exclusive at every successful enable: after intel_hdcp_auth() succeeds. Signed-off-by: Suraj Kandpal Tested-by: Santhosh Reddy Guddati Reviewed-by: Santhosh Reddy Guddati Link: https://patch.msgid.link/20260717034734.477591-1-suraj.kandpal@intel.com --- drivers/gpu/drm/i915/display/intel_hdcp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/i915/display/intel_hdcp.c b/drivers/gpu/drm/i915/display/intel_hdcp.c index 0a076d2ed70a..e56df337dc6d 100644 --- a/drivers/gpu/drm/i915/display/intel_hdcp.c +++ b/drivers/gpu/drm/i915/display/intel_hdcp.c @@ -1075,6 +1075,7 @@ static int intel_hdcp1_enable(struct intel_connector *connector) ret = intel_hdcp_auth(connector); if (!ret) { hdcp->hdcp_encrypted = true; + hdcp->hdcp2_encrypted = false; return 0; } @@ -2109,6 +2110,7 @@ static int _intel_hdcp2_enable(struct intel_atomic_state *state, hdcp->content_type); hdcp->hdcp2_encrypted = true; + hdcp->hdcp_encrypted = false; return 0; } From b201029ca6956e18a6762fc955d1f497030ff912 Mon Sep 17 00:00:00 2001 From: Ankit Nautiyal Date: Wed, 22 Jul 2026 11:54:49 +0530 Subject: [PATCH 86/93] drm/i915/display: Ensure a non-zero safe window from PTL onwards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From PTL onwards, the set context latency can be in the vactive region, letting the safe window start some lines before the vblank start. The DSB waits on this safe window instead of vblank (bits [15:14] select the safe window signal from the DPT unit to start the DMA engine). With modes that have a smaller vblank region, the computed guardband is clamped to the vblank length, making the undelayed and delayed vblank coincide. If the SCL is also 0, the 'safe window' becomes effectively 0, and the DSB configured to wait for it gets stalled, since the hardware never signals the safe window, leading to: *ERROR* [CRTC:159:pipe A] flip_done timed out *ERROR* [CRTC:159:pipe A] DSB 0 timed out waiting for idle Keep the set context latency at a minimum of 1 to avoid this. v2: Fix the bit numbers and correct the commit message. (Chaitanya) Fixes: 4a68c7516c57 ("drm/i915/dsb: Use safe window path when VRR TG is used") Cc: Ville Syrjälä Signed-off-by: Ankit Nautiyal Assisted-by: GitHub-Copilot:Claude-Opus-4.8 Reviewed-by: Chaitanya Kumar Borah Link: https://patch.msgid.link/20260722062449.1166411-1-ankit.k.nautiyal@intel.com --- drivers/gpu/drm/i915/display/intel_display.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c index 2c55a4818ad7..9dfd095f325f 100644 --- a/drivers/gpu/drm/i915/display/intel_display.c +++ b/drivers/gpu/drm/i915/display/intel_display.c @@ -2486,6 +2486,19 @@ static int intel_crtc_set_context_latency(struct intel_crtc_state *crtc_state) set_context_latency = max(set_context_latency, intel_psr_min_set_context_latency(crtc_state)); + /* + * From PTL onwards, the set context latency can be in the vactive + * region, letting the safe window start some lines before the vblank + * start. With modes that have a smaller vblank region, the computed + * guardband is clamped to the vblank length, making the undelayed and + * delayed vblank coincide. If the SCL is also 0, the 'safe window' + * becomes effectively 0, and the DSB configured to wait for it gets + * stalled, since the hardware never signals the safe window. Keep the + * set context latency at a minimum of 1 to avoid this. + */ + if (DISPLAY_VER(display) >= 30) + set_context_latency = max(1, set_context_latency); + return set_context_latency; } From a0e65490319c468f4585e84994a7a4085b5da341 Mon Sep 17 00:00:00 2001 From: Chaitanya Kumar Borah Date: Thu, 9 Jul 2026 17:09:50 +0530 Subject: [PATCH 87/93] drm/i915/dp: s/intel_dp_needs_vsc_sdp/intel_dp_needs_vsc_colorimetry The function answers whether a VSC SDP with colorimetry and pixel encoding payload is required by the content being displayed. Rename it to reflect that. No functional change intended. Assisted-by: GitHub_Copilot:claude-sonnet-4.6 Signed-off-by: Chaitanya Kumar Borah Reviewed-by: Pranay Samala Link: https://patch.msgid.link/20260709113951.3557968-2-chaitanya.kumar.borah@intel.com --- drivers/gpu/drm/i915/display/intel_ddi.c | 2 +- drivers/gpu/drm/i915/display/intel_dp.c | 8 ++++---- drivers/gpu/drm/i915/display/intel_dp.h | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_ddi.c b/drivers/gpu/drm/i915/display/intel_ddi.c index 617106c68cb4..ec7ebb889c27 100644 --- a/drivers/gpu/drm/i915/display/intel_ddi.c +++ b/drivers/gpu/drm/i915/display/intel_ddi.c @@ -470,7 +470,7 @@ void intel_ddi_set_dp_msa(const struct intel_crtc_state *crtc_state, * YCBCR 420, HDR BT.2020 signals we should program MSA MISC1 fields * which indicate VSC SDP for the Pixel Encoding/Colorimetry Format. */ - if (intel_dp_needs_vsc_sdp(crtc_state, conn_state)) + if (intel_dp_needs_vsc_colorimetry(crtc_state, conn_state)) temp |= DP_MSA_MISC_COLOR_VSC_SDP; intel_de_write(display, TRANS_MSA_MISC(display, cpu_transcoder), diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index ea557af82662..429fdfa6f153 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -3123,7 +3123,7 @@ static void intel_dp_compute_vsc_sdp(struct intel_dp *intel_dp, struct drm_dp_vsc_sdp *vsc; if ((!intel_dp->colorimetry_support || - !intel_dp_needs_vsc_sdp(crtc_state, conn_state)) && + !intel_dp_needs_vsc_colorimetry(crtc_state, conn_state)) && !crtc_state->has_psr) return; @@ -3133,7 +3133,7 @@ static void intel_dp_compute_vsc_sdp(struct intel_dp *intel_dp, vsc->sdp_type = DP_SDP_VSC; /* Needs colorimetry */ - if (intel_dp_needs_vsc_sdp(crtc_state, conn_state)) { + if (intel_dp_needs_vsc_colorimetry(crtc_state, conn_state)) { intel_dp_compute_vsc_colorimetry(crtc_state, conn_state, vsc); } else if (crtc_state->has_panel_replay) { @@ -5066,8 +5066,8 @@ static bool intel_dp_get_and_ack_sink_irq_esi_sst(struct intel_dp *intel_dp, u8 } bool -intel_dp_needs_vsc_sdp(const struct intel_crtc_state *crtc_state, - const struct drm_connector_state *conn_state) +intel_dp_needs_vsc_colorimetry(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) { /* * As per DP 1.4a spec section 2.2.4.3 [MSA Field for Indication diff --git a/drivers/gpu/drm/i915/display/intel_dp.h b/drivers/gpu/drm/i915/display/intel_dp.h index 0ec519fa1236..04fae203bb69 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.h +++ b/drivers/gpu/drm/i915/display/intel_dp.h @@ -128,8 +128,8 @@ int intel_dp_max_link_data_rate(struct intel_dp *intel_dp, bool intel_dp_joiner_needs_dsc(struct intel_display *display, int num_joined_pipes); bool intel_dp_has_joiner(struct intel_dp *intel_dp); -bool intel_dp_needs_vsc_sdp(const struct intel_crtc_state *crtc_state, - const struct drm_connector_state *conn_state); +bool intel_dp_needs_vsc_colorimetry(const 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); From 6d7efb5fbb812d7cd7899dd1aacacb02a36a5886 Mon Sep 17 00:00:00 2001 From: Chaitanya Kumar Borah Date: Thu, 9 Jul 2026 17:09:51 +0530 Subject: [PATCH 88/93] drm/i915/dp: set VSC_SDP MSA delegation only for capable sinks Per DP 1.4a section 2.2.4.3, the MSA MISC1 VSC_SDP bit signals that the sink should defer colorimetry to the VSC SDP. It should only be set when the sink advertises DP_VSC_SDP_EXT_FOR_COLORIMETRY_SUPPORTED and a VSC SDP with pixel encoding/colorimetry payload will actually be sent. Fold the colorimetry_support check into intel_dp_needs_vsc_colorimetry() so both intel_ddi_set_dp_msa() and intel_dp_compute_vsc_sdp() share a single consistent gate. Assisted-by: GitHub_Copilot:claude-sonnet-4.6 Signed-off-by: Chaitanya Kumar Borah Reviewed-by: Pranay Samala Link: https://patch.msgid.link/20260709113951.3557968-3-chaitanya.kumar.borah@intel.com --- drivers/gpu/drm/i915/display/intel_ddi.c | 2 ++ drivers/gpu/drm/i915/display/intel_dp.c | 14 ++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_ddi.c b/drivers/gpu/drm/i915/display/intel_ddi.c index ec7ebb889c27..b8985e1e0a81 100644 --- a/drivers/gpu/drm/i915/display/intel_ddi.c +++ b/drivers/gpu/drm/i915/display/intel_ddi.c @@ -469,6 +469,8 @@ void intel_ddi_set_dp_msa(const struct intel_crtc_state *crtc_state, * of Color Encoding Format and Content Color Gamut] while sending * YCBCR 420, HDR BT.2020 signals we should program MSA MISC1 fields * which indicate VSC SDP for the Pixel Encoding/Colorimetry Format. + * Only set the delegation bit when the content needs it and + * the sink advertises support. */ if (intel_dp_needs_vsc_colorimetry(crtc_state, conn_state)) temp |= DP_MSA_MISC_COLOR_VSC_SDP; diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index 429fdfa6f153..83748305b4a7 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -3122,8 +3122,7 @@ static void intel_dp_compute_vsc_sdp(struct intel_dp *intel_dp, { struct drm_dp_vsc_sdp *vsc; - if ((!intel_dp->colorimetry_support || - !intel_dp_needs_vsc_colorimetry(crtc_state, conn_state)) && + if (!intel_dp_needs_vsc_colorimetry(crtc_state, conn_state) && !crtc_state->has_psr) return; @@ -3132,7 +3131,6 @@ static void intel_dp_compute_vsc_sdp(struct intel_dp *intel_dp, crtc_state->infoframes.enable |= intel_hdmi_infoframe_enable(DP_SDP_VSC); vsc->sdp_type = DP_SDP_VSC; - /* Needs colorimetry */ if (intel_dp_needs_vsc_colorimetry(crtc_state, conn_state)) { intel_dp_compute_vsc_colorimetry(crtc_state, conn_state, vsc); @@ -5069,11 +5067,19 @@ bool intel_dp_needs_vsc_colorimetry(const struct intel_crtc_state *crtc_state, const struct drm_connector_state *conn_state) { + struct intel_dp *intel_dp = + enc_to_intel_dp(to_intel_encoder(conn_state->best_encoder)); + /* * As per DP 1.4a spec section 2.2.4.3 [MSA Field for Indication * of Color Encoding Format and Content Color Gamut], in order to - * sending YCBCR 420 or HDR BT.2020 signals we should use DP VSC SDP. + * send YCBCR 420 or HDR BT.2020 signals we should use DP VSC SDP. + * Only signal this when the sink advertises VSC SDP colorimetry + * support. */ + if (!intel_dp->colorimetry_support) + return false; + if (crtc_state->output_format == INTEL_OUTPUT_FORMAT_YCBCR420) return true; From 22931a311193368ac44170716267028dc4325982 Mon Sep 17 00:00:00 2001 From: Alexander Kaplan Date: Sat, 18 Jul 2026 12:52:06 +0200 Subject: [PATCH 89/93] drm/i915/dp: Prefer DSC over 6 bpc uncompressed output for HDMI sinks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For modes which fit through the link uncompressed only with a 6 bpc pipe BPP, the link config currently selects 6 bpc with dithering even if the sink supports DSC. For HDMI sinks behind a DP to HDMI protocol converter this is the wrong preference: HDMI knows no 6 bpc transport format, so the converter has to expand the dithered 6 bpc stream back to 8 bpc for the HDMI link anyway, and DSC with an at least 8 bpc input provides a better output quality than that. Prefer DSC, following the pattern commit ba49a4643cf5 ("drm/i915/dp: Set min_bpp limit to 30 in HDR mode") uses for HDR: keep the uncompressed minimum pipe BPP at 8 bpc if the sink supports DSC, making the uncompressed link config fail for such modes and the mode fall back to DSC. As there, if the DSC computation fails, the mode falls back to YCbCr 4:2:0 where supported, or gets rejected. Besides the output quality, some converters can't even display a 6 bpc stream at high pixel clocks. Synaptics VMM PCON based DP to HDMI 2.1 adapters from two device families (branch device IDs SYNAq and SYNAa) output corrupted FRL timings for an uncompressed RGB 6 bpc 4k120 (1188 MHz) stream, resulting in a black screen, while the same mode works with DSC (12 bpc input) and 6 bpc works at lower pixel clocks. Windows and macOS drive 4k120 on these devices only via DSC. A lower bpc limit explicitly requested via the max bpc connector property is still honored. This keeps the current uAPI behavior (exercised by IGT kms_dither) and provides an escape hatch for sinks with a broken DSC implementation. DP and eDP sinks, and HDMI sinks without DSC support, are not affected and keep falling back to 6 bpc. Tested on PTL (xe) with the above PCONs and an LG OLED G4. Cc: Imre Deak Cc: Chaitanya Kumar Borah Cc: Ankit Nautiyal Cc: Ville Syrjälä Signed-off-by: Alexander Kaplan Reviewed-by: Ankit Nautiyal Signed-off-by: Ankit Nautiyal Link: https://patch.msgid.link/20260718105207.5565-2-alexander.kaplan@sms-medipool.de --- drivers/gpu/drm/i915/display/intel_dp.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index 83748305b4a7..fa4a02b00d77 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -2670,6 +2670,20 @@ intel_dp_compute_config_limits(struct intel_dp *intel_dp, crtc_state))); } + /* + * HDMI knows no 6 bpc transport format, and DSC with an at least + * 8 bpc input provides a better output quality than a dithered + * 6 bpc output. Prefer it for HDMI sinks, by failing the + * uncompressed link config for modes which would fit only with a + * 6 bpc pipe BPP. Honor a lower limit set via the max bpc + * connector property. + */ + if (!dsc && intel_dp_has_hdmi_sink(intel_dp) && + intel_dp_supports_dsc(intel_dp, connector, crtc_state) && + limits->pipe.max_bpp >= 24 && + crtc_state->pipe_bpp >= 24) + limits->pipe.min_bpp = max(limits->pipe.min_bpp, 24); + if (limits->pipe.min_bpp <= 0 || limits->pipe.min_bpp > limits->pipe.max_bpp) { drm_dbg_kms(display->drm, "[CONNECTOR:%d:%s] Invalid pipe bpp range: %d-%d\n", From 71b57dd92f94569dca4bdf883fbd8ca5d4ed4bae Mon Sep 17 00:00:00 2001 From: Alexander Kaplan Date: Sat, 18 Jul 2026 12:52:07 +0200 Subject: [PATCH 90/93] drm/i915/dp: Ignore the sink's DSC max FRL rate without a PCON DSC encoder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit intel_dp_hdmi_sink_max_frl() limits the sink's max FRL rate by its DSC max FRL rate whenever the sink supports DSC 1.2. However, the DSC max FRL rate (HF-VSDB DSC_Max_FRL_Rate) only applies to compressed video transport, which requires a DSC 1.2 encoder in the PCON (configured via intel_dp_pcon_dsc_configure()). Without such an encoder the HDMI link always carries uncompressed video, for which the regular Max_FRL_Rate is the correct limit. Applying the DSC limit unconditionally trains the FRL link at a lower rate than both the PCON and the sink support. E.g. an LG OLED G4 (Max_FRL_Rate 48 Gbps, DSC_Max_FRL_Rate 24 Gbps) behind a Synaptics VMM7100 PCON (PCON max FRL bw 48 Gbps, no DSC encoder): Sink max rate from EDID = 24 Gbps FRL trained with : 24 Gbps while Windows/macOS train the same hardware at 40/48 Gbps. The too low FRL rate needlessly constrains the formats available to the sink. Only apply the sink's DSC max FRL rate if the PCON has a DSC 1.2 encoder, matching the gate in intel_dp_pcon_dsc_configure(). PCONs with a DSC encoder keep the current conservative behavior, since the link is trained once and compressed transport may be used for any subsequent mode. With this the setup above trains at 48 Gbps. Tested on PTL (xe) with the above PCON/sink combo. Fixes: 10fec80b48c5 ("drm/i915/display: Configure PCON for DSC1.1 to DSC1.2 encoding") Cc: Ankit Nautiyal Cc: Ville Syrjälä Reviewed-by: Ankit Nautiyal Signed-off-by: Alexander Kaplan Signed-off-by: Ankit Nautiyal Link: https://patch.msgid.link/20260718105207.5565-3-alexander.kaplan@sms-medipool.de --- drivers/gpu/drm/i915/display/intel_dp.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index fa4a02b00d77..0b84f6fb257d 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -4099,7 +4099,14 @@ static int intel_dp_hdmi_sink_max_frl(struct intel_dp *intel_dp) rate_per_lane = info->hdmi.max_frl_rate_per_lane; max_frl_rate = max_lanes * rate_per_lane; - if (info->hdmi.dsc_cap.v_1p2) { + /* + * The sink's DSC max FRL rate only applies to compressed video + * transport, which requires a DSC 1.2 encoder in the PCON. Without + * one the HDMI link always carries uncompressed video, for which + * the regular max FRL rate is the limit. + */ + if (drm_dp_pcon_enc_is_dsc_1_2(intel_dp->pcon_dsc_dpcd) && + info->hdmi.dsc_cap.v_1p2) { max_dsc_lanes = info->hdmi.dsc_cap.max_lanes; dsc_rate_per_lane = info->hdmi.dsc_cap.max_frl_rate_per_lane; if (max_dsc_lanes && dsc_rate_per_lane) From 6a86e215baf60e8daf7bcd92553ed4023cb70e5d Mon Sep 17 00:00:00 2001 From: Vinod Govindapillai Date: Wed, 22 Jul 2026 16:24:46 +0300 Subject: [PATCH 91/93] drm/xe/display: Expose plane max width/height helpers to global scope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enabling features like FBC on platforms may depend on a plane's maximum supported resolutions or some other arbitrary constant values because of hw restrictions. Currently the helpers that report a plane's max width and height are private to skl_plane / skl_universal_plane. Change the scope to golbal so that this can be queried from other areas as well. v2: function parameter alignment fixes Signed-off-by: Vinod Govindapillai Reviewed-by: Jouni Högander Link: https://patch.msgid.link/20260722132448.318112-1-vinod.govindapillai@intel.com --- .../gpu/drm/i915/display/skl_universal_plane.c | 16 ++++++++-------- .../gpu/drm/i915/display/skl_universal_plane.h | 9 +++++++++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.c b/drivers/gpu/drm/i915/display/skl_universal_plane.c index b246fc48558b..07a683293352 100644 --- a/drivers/gpu/drm/i915/display/skl_universal_plane.c +++ b/drivers/gpu/drm/i915/display/skl_universal_plane.c @@ -1932,10 +1932,10 @@ static int intel_plane_min_height(struct intel_plane *plane, return 1; } -static int intel_plane_max_width(struct intel_plane *plane, - const struct drm_framebuffer *fb, - int color_plane, - unsigned int rotation) +int intel_plane_max_width(struct intel_plane *plane, + const struct drm_framebuffer *fb, + int color_plane, + unsigned int rotation) { if (plane->max_width) return plane->max_width(fb, color_plane, rotation); @@ -1943,10 +1943,10 @@ static int intel_plane_max_width(struct intel_plane *plane, return INT_MAX; } -static int intel_plane_max_height(struct intel_plane *plane, - const struct drm_framebuffer *fb, - int color_plane, - unsigned int rotation) +int intel_plane_max_height(struct intel_plane *plane, + const struct drm_framebuffer *fb, + int color_plane, + unsigned int rotation) { if (plane->max_height) return plane->max_height(fb, color_plane, rotation); diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.h b/drivers/gpu/drm/i915/display/skl_universal_plane.h index 5e2451c21eeb..ad71105cc55a 100644 --- a/drivers/gpu/drm/i915/display/skl_universal_plane.h +++ b/drivers/gpu/drm/i915/display/skl_universal_plane.h @@ -8,6 +8,7 @@ #include +struct drm_framebuffer; struct intel_crtc; struct intel_display; struct intel_initial_plane_config; @@ -42,5 +43,13 @@ bool icl_is_hdr_plane(struct intel_display *display, enum plane_id plane_id); u32 skl_plane_aux_dist(const struct intel_plane_state *plane_state, int color_plane); +int intel_plane_max_width(struct intel_plane *plane, + const struct drm_framebuffer *fb, + int color_plane, + unsigned int rotation); +int intel_plane_max_height(struct intel_plane *plane, + const struct drm_framebuffer *fb, + int color_plane, + unsigned int rotation); #endif From 8e58bdd59508465dd58cf65a3facf8571cf3c63f Mon Sep 17 00:00:00 2001 From: Vinod Govindapillai Date: Wed, 22 Jul 2026 16:24:47 +0300 Subject: [PATCH 92/93] drm/i915/fbc: No need of any hardcoded plane width limit from LNL+ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In xe2_lpd+ platforms, there are no restrictions to enable FBC on planes with respect to the plane width - width can be as per the platform's maximum supported resolution. v2: patches squashed and changes in patch descriptions Bspec: 69560 Signed-off-by: Vinod Govindapillai Reviewed-by: Jouni Högander Link: https://patch.msgid.link/20260722132448.318112-2-vinod.govindapillai@intel.com --- drivers/gpu/drm/i915/display/intel_fbc.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_fbc.c b/drivers/gpu/drm/i915/display/intel_fbc.c index cd889f7429bb..1cb5439f0d42 100644 --- a/drivers/gpu/drm/i915/display/intel_fbc.c +++ b/drivers/gpu/drm/i915/display/intel_fbc.c @@ -59,6 +59,7 @@ #include "intel_fbc_regs.h" #include "intel_frontbuffer.h" #include "intel_parent.h" +#include "skl_universal_plane.h" #define for_each_fbc_id(__display, __fbc_id) \ for ((__fbc_id) = INTEL_FBC_A; (__fbc_id) < I915_MAX_FBCS; (__fbc_id)++) \ @@ -1315,10 +1316,18 @@ static bool intel_fbc_surface_size_ok(const struct intel_plane_state *plane_stat return effective_w <= max_w && effective_h <= max_h; } -static void intel_fbc_max_plane_size(struct intel_display *display, +static void intel_fbc_max_plane_size(const struct intel_plane_state *plane_state, unsigned int *w, unsigned int *h) { - if (DISPLAY_VER(display) >= 10) { + struct intel_display *display = to_intel_display(plane_state); + struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane); + const struct drm_framebuffer *fb = plane_state->hw.fb; + unsigned int rotation = plane_state->hw.rotation; + + if (DISPLAY_VER(display) >= 20) { + *w = intel_plane_max_width(plane, fb, 0, rotation); + *h = 4096; + } else if (DISPLAY_VER(display) >= 10) { *w = 5120; *h = 4096; } else if (DISPLAY_VER(display) >= 8 || display->platform.haswell) { @@ -1335,10 +1344,9 @@ static void intel_fbc_max_plane_size(struct intel_display *display, static bool intel_fbc_plane_size_valid(const struct intel_plane_state *plane_state) { - struct intel_display *display = to_intel_display(plane_state); unsigned int w, h, max_w, max_h; - intel_fbc_max_plane_size(display, &max_w, &max_h); + intel_fbc_max_plane_size(plane_state, &max_w, &max_h); w = drm_rect_width(&plane_state->uapi.src) >> 16; h = drm_rect_height(&plane_state->uapi.src) >> 16; From f0cabcc880aba3f05ca59d735f6d166b7fceca80 Mon Sep 17 00:00:00 2001 From: Vinod Govindapillai Date: Wed, 22 Jul 2026 16:24:48 +0300 Subject: [PATCH 93/93] drm/i915/fbc: No surface size restrictions from LNL onwards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From xe2_lpd+ platforms, enabling FBC do not depend on the surface size. So remove the check. v2: changes to patch description Bspec: 69560 Signed-off-by: Vinod Govindapillai Reviewed-by: Jouni Högander Link: https://patch.msgid.link/20260722132448.318112-3-vinod.govindapillai@intel.com --- drivers/gpu/drm/i915/display/intel_fbc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/i915/display/intel_fbc.c b/drivers/gpu/drm/i915/display/intel_fbc.c index 1cb5439f0d42..f61b4a218d6e 100644 --- a/drivers/gpu/drm/i915/display/intel_fbc.c +++ b/drivers/gpu/drm/i915/display/intel_fbc.c @@ -1306,6 +1306,9 @@ static bool intel_fbc_surface_size_ok(const struct intel_plane_state *plane_stat struct intel_display *display = to_intel_display(plane_state); unsigned int effective_w, effective_h, max_w, max_h; + if (DISPLAY_VER(display) >= 20) + return true; + intel_fbc_max_surface_size(display, &max_w, &max_h); effective_w = plane_state->view.color_plane[0].x +